The Source for Java Technology Collaboration
User: Password:



Sergey Groznyh's Blog

Swing Archives


Generating Print Preview of Swing Text Components

Posted by g_s_m on September 05, 2007 at 04:42 AM | Permalink | Comments (6)

This question, “how to generate print preview of Java Swing components,” is asked often on various discussion forums, because currently (as of version 6) the Java SE doesn't have standard facilities for building print previews. While working on the Swing Tutorial, I tried to address this issue under the topic “Using JTextComponent.getPrintable method.” This will hopefully appear in the updated version of the Swing Tutorial for JDK6. In the meantime, I'll put the relevant information here.

There exist several resources on the Internet related to generating print previews in Java, but in most cases they are either commercial, or use an outdated API, or attempt to build some all-in-one custom dialog solution without paying much attention to details. In this issue I'll try to focus on how to create a print preview—what necessary parts are involved and how they do communicate.

What to Print

The JTextComponent class defines the getPrintable method which returns an instance of the Printable interface containing a single method, print. Assuming the textComponent is a JTextComponent object we are going to generate print preview for, the following code shows how to obtain a Printable instance out of it.

Printable p = textComponent.getPrintable();

Page Format

Next thing we need to know is the parameters of the output media, most important ones being the physical page dimensions. In real-life situation the available page dimensions should be obtained by querying some PrintService known to your Java environment. For the purposes of this example we'll just use the default page format.

PageFormat f = new PageFormat();
int pageWidth = f.getWidth();
int pageHeight = f.getHeight();

Where to Print

Then we need a print destination that will receive the actual drawing commands. The destination should be an instance of the Graphics class. A simple way of obtaining this is using the BufferedImage class as physical backing store. This class defines the getGraphics method which returns a Graphics instance.

Image page = new BufferedImage(
        pageWidth, pageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics g = page.getGraphics();

How to Print

Now we are ready to generate page images. We need to repeatedly invoke the print method on the Printable instance, specifying the page number to print (0 is the first page) and the Graphic and PageFormat objects. The print method returns some status code. The value of PAGE_EXISTS indicates the success of the operation.

for (int n = 0; p.print(g, f, n) == Printable.PAGE_EXISTS; n++) {
    // N-th page printed successfully
}

How to Scale

The generated page image is usually too large for on-screen display, so we need to shrink the image for viewing. The following code creates an image with size reduced by two times on both dimensions.

double scaleFactor = 0.5;
int previewWidth = (int) (pageWidth * scaleFactor);
int previewHeight = (int) (pageHeight * scaleFactor);
Image preview = page.getScaledInstance(
        previewWidth, previewHeight, SCALE_SMOOTH);

Back to Swing

Until now we dealt mostly with 2D and AWT parts of the Client Java API. Now we need to place the generated preview image into some Swing component. For this we'll create a subclass of the JPanel, implementing the custom paintComponent method and overriding the getPreferredSize, getMinimumSize and getMaximumSize methods so our preview component is laid out correctly.

JPanel previewPane = new JPanel() {
    public void paintComponent(Graphics g) {
        g.drawImage(preview, 0, 0, previewWidth, previewHeight, null);
    }

    public Dimension getPreferredSize() {
        return new Dimension(previewWidth, previewHeight);
    }

    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

    public Dimension getMaximumSize() {
        return getPreferredSize();
    }
}

The resulting print preview pane can then be added to Swing container, just like any other Swing component.

Below are links to the example containing the complete print preview implementation as described in this issue. Using this implementation, the print preview functionality could be added to any text component in a couple lines of code.

Please note that in order to run this demo, you'll need Java runtime version 1.6 (aka 6.0) or higher.

The demo displays a chapter from “Alice in Wonderland” in the JEditorPane component. Print preview window could be activated from menu or by using a keyboard shortcut. Standard Java Print Dialog could be invoked from inside the print preview.



Removing elements from Swing HTML in JDK6

Posted by g_s_m on August 01, 2007 at 10:12 AM | Permalink | Comments (0)

After I published the entry on removing elements from Swing HTMLDocument in JDK7, I got a question from a reader: but how to actually remove elements in JDK6? There's no easy way, but what is the hard way?

Well, there exist a bunch of methods, to various degrees of ugliness, let's look at one of them. It's neither pretty nor effective, but has the advantage of being simple enough.

The method is to serialize all the sibling elements using HTMLWriter class, concatenate the serialized strings, and re-parse the resulting text using setInnerHTML method with the parent element as the target.

The following code illustrates this approach.


void removeElement(HTMLDocument d, Element e) throws Exception {
    Element p = e.getParentElement();
    int n = p.getElementCount();
    String s = "";
    for (int i = 0; i < n; i++) {
        Element c = p.getElement(i);
        if (c != e) {
            s += getElementText(d, c);
        }
    }
    d.setInnerHTML(p, s);
}

The tricky thing is, how to serialize not the complete document but a single element only. There exist a constructor in the HTMLWriter class that allows to specify only a fragment of the document to serialize, and we could use element's start and end offsets as the fragment boundaries. But by default the writer will output all ancestors of the element up to the root to make serialized form the complete HTML document.

To overcome this, we need to override the getElementIterator method which returns the root of the element hierarchy to serialize. In the HTMLWriter implementation it returns the document root element. We'll implement it to return just the element to serialize.


String getElementText(HTMLDocument d, final Element e) throws Exception {
    StringWriter sw = new StringWriter();
    int p0 = e.getStartOffset();
    int p1 = e.getEndOffset();
    new HTMLWriter(sw, d, p0, p1 - p0) {
        protected ElementIterator getElementIterator() {
            return new ElementIterator(e);
        }
    }.write();
    return sw.toString();
}

Note that if we try to remove the sole child element, the behavior of this method differs from removeElement in JDK7. In JDK7 the parent element is removed as well, recursively. In the above example nothing is removed (to make the example simple). To get the JDK7 behavior, we need to traverse to the nearest parent element that is not a sole child and remove it instead of the original element.



Removing elements from Swing HTMLDocument

Posted by g_s_m on June 18, 2007 at 08:36 AM | Permalink | Comments (2)

Removing elements from Swing HTMLDocument

Suppose you have an HTML document, like this:


<ul>
  <li id="LI1">Item 1</li>
  <li id="LI2">Item 2</li>
  <li id="LI3">Item 3</li>
</ul>
          

And you want to programmatically remove one of the list items (say, the second one).

For this, you load the document into Swing’s JEditorPane component:


JEditorPane p = new JEditorPane();
p.setContentType("text/html");
p.setText("HTML text from the above example");
    

As you know the HTML element’s ID, you can easily obtain a reference to the corresponding Element object:


HTMLDocument d = (HTMLDocument) p.getDocument();
Element e = d.getElement("LI2");
    

But how to remove this element from the document? A quick glance through the HTMLDocument API reveals the natural candidate for the task: the setOuterHTML method which allows to replace an arbitrary element with some new content. So you think, “if I specify an empty string as the new content, the element will be just removed from the document as there’s no replacement.” Pretty simple and elegant solution, really.

Except that it doesn’t work.

The setOuterHTML method is implemented so that if there’s no content in the replacement string, the target document won’t be altered at all. The method call is just a no-op in this case.

So how to actually remove an element from the document? Quite surprisingly, until recently there was no easy way of doing this.

Starting with JDK7 build 10, the Swing Text subsystem provides the new public method in the DefaultStyledDocument class: the removeElement method. It takes an element to remove as the sole parameter and removes the element from the document tree, as well as the corresponding text from the document content. So in order to remove the element, you just invoke this method:


d.removeElement(e);
    

Due to specifics of the default Element and Content implementations, there are some caveats:

  • Empty branch elements are not allowed in the default implementation; so if you remove the last child of some element, the element itself will be removed as well, recursively (this means that if you need to replace the sole child, you should add the new child first and then remove the old one, not vice-versa).
  • Element-less documents are not allowed in the default implementation; so you’ll get an exception if you try to remove the last leaf element in the document (because this will eventually require removal of the document root element).
  • The default Content implementation requires the presence of the trailing newline character in the document; so if you remove the leaf element containing the trailing newline, it will be added to the preceding leaf element (unless that element already ends with newline).




Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds