How do I handle pagination when printing text? - java

I am attempting to send some potentially long text to a printer for... well, printing. Here is a minimal example that demonstrates how I am currently printing text:
#FXML
private void print() {
Text printText = new Text(textArea.getText());
TextFlow printArea = new TextFlow(printText);
printArea.setTextAlignment(TextAlignment.LEFT);
printArea.setMaxHeight(Region.USE_COMPUTED_SIZE);
PrinterJob printerJob = PrinterJob.createPrinterJob();
if (printerJob != null && printerJob.showPrintDialog(textArea.getScene().getWindow())) {
PageLayout pageLayout = printerJob.getJobSettings().getPageLayout();
printArea.setMaxWidth(pageLayout.getPrintableWidth());
if (printerJob.printPage(pageLayout, printArea)) {
printerJob.endJob();
// done printing
} else {
System.err.println("Printing failed!");
}
} else {
System.err.println("Unable to create printer job or printer dialog cancelled by user");
}
}
The code above prints text as expected, except that no matter how much text is being printed, it always prints just a single page. In the print dialog that is shown, under the section for "Print range", I have been selecting the "All" option. I have tried choosing the "Pages" option (which for some reason has been defaulting from 1 to 9999 - this is a little odd since the text should be at most two pages long), but have not had any success in printing more than a single page. I have also tried manually setting the page range on the JobSettings object, but that didn't seem to do anything either.
How can I use the PrinterJob and its related classes to get a long piece of text to properly print on multiple pages?

As far as I know you need to supply multiple Nodes, printAreas in your case, to the PrinterJob, having divided them up beforehand in your code (unfortunately...) Placing your TextFlow in a styled Scene will give you the total size, you can divide that up based on the PageLayout.getPrintableWidth and getPrintableHeight, with the proviso that the user can potentially alter these in the print settings dialog.
job.getJobSettings().setPageRanges(new PageRange(1, numPages));
Tells the job how many pages you can supply. You generally set this before showing the print dialog so that the user can choose how many pages to print, what range etc.
After showing the dialog the JobSettings will be updated with the selected PageRanges the user has chosen, which you then loop over and print individually.
if (job.showPrintDialog(null)) {
JobSettings js = job.getJobSettings();
for (PageRange pr : js.getPageRanges()) {
for (int p = pr.getStartPage(); p <= pr.getEndPage(); p++) {
boolean ok = job.printPage(...code to get your node for the page...);
...take action on success/failure etc.
}
}
}

Related

how to print the selected pane inside FXML

i want to print the fxml contain pane but don't know how to do it.
i try two code inside button action .
first
void doPrint(Node printPane) {
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null && job.showPageSetupDialog(printPane.getScene().getWindow())) {
job.printPage(printPane);
job.endJob();
}
}
}
second
boolean doPrint(Node printPane) {
PrinterJob job = PrinterJob.createPrinterJob();
if (job == null) {
return false;
}
if (!job.printPage(printPane)) {
return false;
}
return job.endJob();
}
This will print any node you pass into it.
private void printImage(Node node) {
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
boolean success = job.printPage(node);
if (success) {
System.out.println("PRINTING FINISHED");
job.endJob();
}
}
}
Try this on action event
#FXML
private void PrintAction(ActionEvent event) {
Printer printer = Printer.getDefaultPrinter(); //get the default printer
javafx.print.PageLayout pageLayout = printer.createPageLayout(Paper.NA_LETTER, PageOrientation.PORTRAIT, Printer.MarginType.DEFAULT); //create a pagelayout. I used Paper.NA_LETTER for a standard 8.5 x 11 in page.
PrinterJob job = PrinterJob.createPrinterJob();//create a printer job
if(job.showPrintDialog(tab_doctor_list.getScene().getWindow()))// this is very useful it allows you to save the file as a pdf instead using all of your printer's paper. A dialog box pops up, allowing you to change the "name" option from your default printer to Adobe pdf.
{
double pagePrintableWidth = pageLayout.getPrintableWidth(); //this should be 8.5 inches for this page layout.
double pagePrintableHeight = pageLayout.getPrintableHeight();// this should be 11 inches for this page layout.
tab_doctor_list.prefHeightProperty().bind(Bindings.size(tab_doctor_list.getItems()).multiply(35));// If your cells' rows are variable size you add the .multiply and play with the input value until your output is close to what you want. If your cells' rows are the same height, I think you can use .multiply(1). This changes the height of your tableView to show all rows in the table.
tab_doctor_list.minHeightProperty().bind(tab_doctor_list.prefHeightProperty());//You can probably play with this to see if it's really needed. Comment it out to find out.
tab_doctor_list.maxHeightProperty().bind(tab_doctor_list.prefHeightProperty());//You can probably play with this to see if it' really needed. Comment it out to find out.
double scaleX = pagePrintableWidth / tab_doctor_list.getBoundsInParent().getWidth();//scaling down so that the printing width fits within the paper's width bound.
double scaleY = scaleX; //scaling the height using the same scale as the width. This allows the writing and the images to maintain their scale, or not look skewed.
double localScale = scaleX; //not really needed since everything is scaled down at the same ratio. scaleX is used thoughout the program to scale the print out.
double numberOfPages = Math.ceil((tab_doctor_list.getPrefHeight() * localScale) / pagePrintableHeight);//used to figure out the number of pages that will be printed.
}
}
Instead of tab_doctor_list use your fx:id of pane.

JavaFX PrintAPI wrong PaperSource

I'm using the JavaFx Print-Dialog to customize the print job. All properties will be stored in the PrinterJob#JobSettings variable, but when I receive the paper source from the jobSetting the paper source is always the default.
How can I get the paper source that I set?
Here is a short example:
public class PrinterPaperSourceTest extends Application {
public static void main(String[] args) {
launch( args );
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Printer");
Button btn = new Button();
btn.setText("Show Printer Settings ");
btn.setOnAction( new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
PrinterJob job = PrinterJob.createPrinterJob(Printer.getDefaultPrinter());
job.showPageSetupDialog(null);
Alert alert = new Alert(AlertType.INFORMATION);
PaperSource paperSource = job.getJobSettings().getPaperSource();
alert.setContentText("PaperSource: " + paperSource.getName());
alert.show();
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}
Printing API appeared in fx8.0. And it can print nodes. You can create printer job with javafx.print.PrinterJob class. But it prints only region that fits to a printed page, and not the one that you on a screen. So you need to make your node fit page(scale, translate, etc) by hands.
You can use this code. Hope it will help you.
/**
* Prints the current page displayed within the internal browser, not necessarily the {#link #presentationProperty()}.
* If no printers are installed on the system, an awareness is displayed.
*/
public final void print() {
final PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
if (job.showPrintDialog(null)) {
if(this.getPresentation().getArchive() != null) {
final String extension = ".".concat(this.getPresentation().getArchiveExtension());
final int indexOfExtension = this.getPresentation().getArchive().getName().indexOf(extension);
final String jobName = this.getPresentation().getArchive().getName().substring(0, indexOfExtension);
job.getJobSettings().setJobName(jobName);
}
job.getJobSettings().setPrintQuality(PrintQuality.HIGH);
job.getJobSettings().setPageLayout(job.getPrinter().createPageLayout(Paper.A4, PageOrientation.LANDSCAPE, 0, 0, 0, 0));
this.internalBrowser.getEngine().print(job);
job.endJob();
} else {
job.cancelJob();
}
} else {
DialogHelper.showError("No printer", "There is no printer installed on your system.");
}
}
Resource Link:
javafx.print.PrinterJob examples
Introduction by Example: JavaFX 8 Printing
I don’t have an answer but I will try to explain why it happens and why it will not be easy to fix. This behavior seems to be influenced by Internet Printing Protocol (IPP) specifications and is caused by the way IPP is implemented by Java Print Service API (to which JavaFX print jobs delegate). Below is a fragment from Oracle’s tech note that explains limitations of manually setting the paper source (https://docs.oracle.com/javase/8/docs/technotes/guides/jps/spec/attributes.fm5.html):
Media is the IPP attribute that identifies the medium on which to print. The Media attribute is an important attribute to understand, but is relatively complex.
The Java Print Service API defines three subclasses of the abstract class Media to reflect the overloaded Media attribute in the IPP specification: MediaSizeName, MediaName and MediaTray. All the Media subclasses have the Media category, for which each subclass defines different standard attribute values. […]
The value of the Media attribute is always a String, but because the attribute is overloaded, its value determines the type of media to which the attribute refers. For example, the IPP pre-defined set of attribute values include the values "a4" and "top-tray". If Media is set to the value "a4" then the Media attribute refers to the size of paper, but if Media is set to "top-tray" then the Media attribute refers to the paper source. […]
In most cases, applications will use either MediaSizeName or MediaTray. The MediaSizeName class enumerates the media by size. The MediaTray class enumerates the paper trays on a printer, which usually include a main tray and a manual feed tray. The IPP 1.1 specification does not provide for specifying both the media size and the media tray at the same time, which means, for example, that an application cannot request size A4 paper from the manual tray. A future revision of the IPP specification might provide for a way to request more than one type of media at a time, in which case the JPS API will most likely be enhanced to implement this change.
So, MediaTray (or paper source) is not an independent parameter and cannot be set if the Media attribute is already defined by one of the other two ways (MediaSizeName or MediaName). This is exactly what happens with page setup dialogs.
J2DPrinterJob class (from com.sun.prism.j2d.print package) contains the dialog code and updates print job settings (I found this by debugging your application). Below is the method from this class that updates the paper source setting from the dialog.
private void updatePaperSource() {
Media m = (Media)printReqAttrSet.get(Media.class);
if (m instanceof MediaTray) {
PaperSource s = j2dPrinter.getPaperSource((MediaTray)m);
if (s != null) {
settings.setPaperSource(s);
}
}
}
I tested different scenarios and the result was the same: by the time updatePaperSource() starts execution the Media attribute is already defined as MediaSizeName type. So the statements in the if branches are never executed and that’s why the paper source is not updated.
I suspect that paper type or paper size have priority over paper source and because the page setup dialog always defines paper type (there is no ‘Automatic’ option), it overloads the selection of paper source to avoid attribute conflict. This essentially makes this option useless.
It may be a bug in JDK or an intentional design decision. In any case, I don’t see an easy way to solve this problem staying within JavaFX considering that it comes from private methods in Java’s internal API.
After a lot of searching I have found a way to print to a different tray with javafx and this was the first place I looked so I figured here would be the best spot to post my solution it may vary with different tray names mine was Tray 2 it will also print out all trays that are available
private void printImage(Node node) {
PrinterJob job = PrinterJob.createPrinterJob();
if (job != null) {
JobSettings js = job.getJobSettings();
PaperSource papersource = js.getPaperSource();
System.out.println("PaperSource=" + papersource);
PrinterAttributes pa = printer.getPrinterAttributes();
Set<PaperSource> s = pa.getSupportedPaperSources();
System.out.println("# of papersources=" + s.size());
if (s != null) {
for (PaperSource newPaperSource : s) {
System.out.println("newpapersource= " + newPaperSource);
//Here is where you would put the tray name that is appropriate
//in the contains section
if(newPaperSource.toString().contains("Tray 2"))
js.setPaperSource(newPaperSource);
}
}
job.getJobSettings().setJobName("Whatever You want");
ObjectProperty<PaperSource> sources = job.getJobSettings().paperSourceProperty();
System.out.println(sources.toString());
boolean success = job.printPage(node);
if (success) {
System.out.println("PRINTING FINISHED");
job.endJob();
//Stage mainStage = (Stage) root.getScene().getWindow();
//mainStage.close();
}
}
}
Here's My output:
PaperSource=Paper source : Automatic
# of papersources=6
newpapersource= Paper source :
newpapersource= Paper source : Manual Feed in Tray 1
newpapersource= Paper source : Printer auto select
newpapersource= Paper source : Tray 1
newpapersource= Paper source : Tray 2
newpapersource= Paper source : Form-Source
ObjectProperty [bean: Collation = UNCOLLATED
Copies = 1
Sides = ONE_SIDED
JobName = Whatever
Page ranges = null
Print color = COLOR
Print quality = NORMAL
Print resolution = Feed res=600dpi. Cross Feed res=600dpi.
Paper source = Paper source : Tray 2
Page layout = Paper=Paper: Letter size=8.5x11.0 INCH Orient=PORTRAIT leftMargin=54.0 rightMargin=54.0 topMargin=54.0 bottomMargin=54.0, name: paperSource, value: Paper source : Tray 2]
PRINTING FINISHED

How to get an active window title between two browser tabs/windows using Selenium Java

I can switch between two tabs/windows but my requirement is to know or get active window between them.
In my project, on a click of a webElement of a page a random pop(tab/window) gets opened and I would like to know whether that(new) window has focus or my original page.
I tried to use JNA Api to get the active window and its title but my web page is
remotely located.
Perfect solution is greatly appreciated.
Thanks
driver.getTitle() will give you the title of the page that you can use to determine which page you are on or if you are on the page where you want to be and then use the logic to switch window if required. getTitle() returns a String and you can use one of the string methods to compare the title, for example:
String title = getDriver().getTitle();
if(!title.equals("Expected Title")) {
//may be you would like to switch window here
}
String title = driver.getTitle()
This will give you the title of the page which you can refer to using Selenium to figure out which page the driver is currently on.
I wrote my own method to switch to a window if the window title is known, maybe some of this would be helpful. I used Selenide (Java) methods for this, but if you've got Vanilla WebDriver, you can achieve the same thing
/** Switches user to window of user's choice */
public static void switchToWindow(String windowTitle) {
WebDriver driver = getWebDriver();
// Get list of all open tabs - note behaviour may be different between FireFox and Chrome.
ArrayList<String> tabs = new ArrayList<>(driver.getWindowHandles());
// iterate through open tabs. If the title of the page is contained in the tab, switch to it.
for (String windowHandle : driver.getWindowHandles()) {
String title = getWebDriver().getTitle();
driver.switchTo().window(windowHandle);
if (title.equalsIgnoreCase(windowTitle)) {
break;
}
}
}
This method might not be lightening fast, but it will iterate through current open windows and check the title matches the one you've specified.
If you want to assert the title, you could use the xpath selector:
String pageTitle = driver.findElement(By.xpath("//title[text() = 'Title you looking for']"));
This is a dumb example with you can surround with try/catch, implement assertions or other technique to have the result you need.
In JavaScript, for me this worked. After clicking on the first link of bing search results in edge, my link opened in a new tab. I explicitly mentioned to stay in the same tab.
async function switchTab() {
await driver.getAllWindowHandles().then(async function (handles) {
await driver.switchTo().window(handles[1]);
});
}
//get initial window handles
Set<String> prevWindowHandles = driver.getWindowHandles();
while(true){
//get current window handles
Set<String> currWindowHandles = driver.getWindowHandles();
//if one of the current window handles not equals to
//any of the previous window handles,switch to this window
//and prevWindowHandles = currWindowHandles
for(String prevHandle : prevWindowHandles){
int noEqualNum = 0;
for(String currHandle : currWindowHandles){
if(!currHandle.equals(prevHandle))
noEqualNum++
}
if(noEqualNum == currWindowHandles.size()){
driver.switchTo().window(currWindow);
prevWindowHandles = currWindowHandles;
break;
}
}
}

GWT - Google visualization problems using layout panels?

I'm having troubles in my GWT app with a Google Visualization chart not showing up until after the user has some sort of interaction with the window (e.g. moves the mouse across the screen or presses a button). This would be fine except that the chart is suppose to show up as the first thing the user sees and since it's meant to be seen on a mobile device, it's likely they will not see the chart because their first interaction will be clicking a button that hides the chart to show other information.
Using the "Getting started tutorial" over at the Visualization code's page, the chart loads immediately fine (once some slight changes are made the fix the problems from the slightly out of date tutorial). After some trial and error to find where the difference between my code and the example code that was causing the problem, I found that it's happening because my code is using the newer layout panels instead of just regular panels in GWT.
The below code is the working tutorial code changed so that it uses a RootLayoutPanel.get() instead of a RootPanel.get(). With this, the chart doesn't load until you click to reload the page, then you can see the chart for an instant before the page reloads. This should be easily tested with the below code. To get the chart to show up for the entire time, simply change RootLayoutPanel.get() to RootPanel.get().
Something in my app is allowing the chart to load after user interaction (I'm not sure what). However, the layout panel is certainly the problem as if I change it to a regular panel it works fine. Unfortunately, my entire app is built using layout panels.
What's going on and how might I be able to make the chart show up from the start using layout panels? Thank you much!
package com.test.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.visualization.client.AbstractDataTable;
import com.google.gwt.visualization.client.VisualizationUtils;
import com.google.gwt.visualization.client.DataTable;
import com.google.gwt.visualization.client.Selection;
import com.google.gwt.visualization.client.AbstractDataTable.ColumnType;
import com.google.gwt.visualization.client.events.SelectHandler;
import com.google.gwt.visualization.client.visualizations.corechart.PieChart;
import com.google.gwt.visualization.client.visualizations.corechart.Options;
public class SimpleViz implements EntryPoint {
public void onModuleLoad() {
// Create a callback to be called when the visualization API
// has been loaded.
Runnable onLoadCallback = new Runnable() {
public void run() {
LayoutPanel panel = RootLayoutPanel.get();
// Create a pie chart visualization.
PieChart pie = new PieChart(createTable(), createOptions());
pie.addSelectHandler(createSelectHandler(pie));
panel.add(pie);
}
};
// Load the visualization api, passing the onLoadCallback to be called
// when loading is done.
VisualizationUtils.loadVisualizationApi(onLoadCallback, PieChart.PACKAGE);
}
private Options createOptions() {
Options options = Options.create();
options.setWidth(400);
options.setHeight(240);
options.setTitle("My Daily Activities");
return options;
}
private SelectHandler createSelectHandler(final PieChart chart) {
return new SelectHandler() {
#Override
public void onSelect(SelectEvent event) {
String message = "";
// May be multiple selections.
JsArray<Selection> selections = chart.getSelections();
for (int i = 0; i < selections.length(); i++) {
// add a new line for each selection
message += i == 0 ? "" : "\n";
Selection selection = selections.get(i);
if (selection.isCell()) {
// isCell() returns true if a cell has been selected.
// getRow() returns the row number of the selected cell.
int row = selection.getRow();
// getColumn() returns the column number of the selected cell.
int column = selection.getColumn();
message += "cell " + row + ":" + column + " selected";
} else if (selection.isRow()) {
// isRow() returns true if an entire row has been selected.
// getRow() returns the row number of the selected row.
int row = selection.getRow();
message += "row " + row + " selected";
} else {
// unreachable
message += "Pie chart selections should be either row selections or cell selections.";
message += " Other visualizations support column selections as well.";
}
}
Window.alert(message);
}
};
}
private AbstractDataTable createTable() {
DataTable data = DataTable.create();
data.addColumn(ColumnType.STRING, "Task");
data.addColumn(ColumnType.NUMBER, "Hours per Day");
data.addRows(2);
data.setValue(0, 0, "Work");
data.setValue(0, 1, 14);
data.setValue(1, 0, "Sleep");
data.setValue(1, 1, 10);
return data;
}
}
With the Layout panels the sizing of the widgets is done in JavaScript. When the initial page is loaded the initial sizing is done after everything else is finished. However in your case the pie is added when the library is loaded and that runs after the initial sizing. Therefor your widget isn't sized and won't show up. You need to call panel.forceLayout(); explicitly as the last method in you run method.
The google chart tools definately work with LayoutPanels. I am using it myself.
It's really difficult to say what's wrong but here are a couple of suggestions:
Check with Chrome Dev Tools (Console) if an exception is thrown.
Do you have standard mode enabled. That's important with LayoutPanels (make sure you have <!DOCTYPE html> in your HTML host page
You could try a 3rd party wrapper (supports automatic resizes)

Why does my Java Swing JScrollPane keep scrolling to the top?

I'm developing a small calculator widget that keeps a running log of calculations. It's supposed to scroll to the bottom of the log every time a new entry is added. This part seems to be working fine.
The problem is, when I press a calculator button that does not add to the log, the log pane always scrolls back to the top, and the scrollbar disappears. How can I keep it from doing this?
The code that adds to the log is:
private JTextPane logArea; //This is placed inside a JScrollPane
private void log(String m, SimpleAttributeSet a) {
int len = logArea.getDocument().getLength();
logArea.setEditable(true);
logArea.setCaretPosition(len);
logArea.setCharacterAttributes(a, false);
logArea.replaceSelection(m);
logArea.scrollRectToVisible(new Rectangle(0,logArea.getBounds(null).height,1,1));
logArea.setEditable(false);
}
The code that seems to be messing with the scroll is:
private void addDigit(char digit) {
if (clearDisplayBeforeDigit) {
clearNumDisplay();
}
if (numInDisplay.getText().length() < maxNumDigits) {
if (digit == '.') { //Point
if (!hasPoint) { //Only one point allowed
hasPoint = true;
String newText = numInDisplay.getText() + ".";
numInDisplay.setText(newText);
}
} else { //New digit
String newText = numInDisplay.getText() + digit;
numInDisplay.setText(newText);
}
}
}
The code you think is causing the problem doesn't even reference the logArea, so why would you think this causes the problem?
You don't need to use the scrollRectToVisible(...) method. The setCaretPosition(...) should do the trick. Although you should get the length of the document and invoke that method AFTER you update the document.
Check out Text Area Scrolling for more information.
Edit:
I also don't see any reason for changing the editability of the text area.

Categories