Browser integration in Java GUIs

Working around the Adobe PDF in 64 bits

Publicado en CodingMarkers el 12 de noviembre del 2017

Browser integration in Java GUIs

One of the challenges I had to face a couple of years ago was to migrate a Java desktop application from 32 bits to 64 bits architecture, and what may have been as simple as using the right JDK, switching to the proper external libraries versions and getting rid of the deprecated code actually had an extra issue. The application embeded a 32 bits PDF reader on a panel, and that limitation had been stopping this migration for a while.

One of the challenges I had to face a couple of years ago was to migrate a Java desktop application from 32 bits to 64 bits architecture, and what may have been as simple as using the right JDK, switching to the proper external libraries versions and getting rid of the deprecated code actually had an extra issue. The application embeded a 32 bits PDF reader on a panel, and that limitation had been stopping this migration for a while.

Going into the code itself, it was using the XULRunner, one of the Firefox engine components, so what it actually did was “using a 32 bits Firefox browser embedded”. It also used Adobe Acrobat generated interactive forms, which actually required Adobe Acrobat Reader itself to be rendered properly and this took Apache PDFBox out of the picture to do this task.

When I looked into the philosophy of running an embed browser I tried to take what happened in my laptop: when I was surfing the net and opening a PDF file my Firefox 32 bits browser, I didn’t use some browser code: I delegated this in the Adobe Acrobat Reader X Plugin for the browser. So, why should not we take this idea one step further, and use the same plugin through an integration? Adobe itself provides this system, so there would be no incompatibility issue, I just had to setup this through the operating system configuration to get a workaround and get the proper version for the machine architecture in which we are currently running the program.

So let’s meet DJNativeSwing, a library based in SWT (Standard Widget Toolkit) which allows us to have our very own embedded browser in our code. SWT is highly recommended library due to its portability, as it accesses to the Native Operating System GUIs, and that is exactly what we require for this kind of issue. It is also the next wrapping level to Java Swing, and actually lighter and faster, and I had already used it back in the day to avoid problems when I had to deal with Macromedia Flash plugin integrations.

As an example of how to configure this, we are going to follow 2 basic steps:

  • Set up a browser tab on a Java swing component.
  • Be able to open a PDF on that said browser tab.

Previous set up: the Maven dependecies required

Let’s start with the basics: first of all we will get the maven dependencies.

❕This was the stable version when the post was originally written.

<dependecy>
  <group>chrriis.dj.nativeswing</group>
  <artifactId>DJNativeSwing</artifactId>
  <version>1.0.2</version>
</dependecy>

 

Building a basic Swing interface

Right after that, we will define the basic window frame, with a button the select the file (via FileChooser) and a panel to show the results.

❕Setting up all the text strings as constants makes them easier to spot them in order to replace them if needed. It’s not really necessary, but doing it improves reusability.

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.Map;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;

import nativeSwing.BrowserPanel;
import pdfHandler.PdfReader;
import xmlHandler.XMLHandler;
import chrriis.common.UIUtils;
import chrriis.dj.nativeswing.swtimpl.NativeInterface;

public class DemoPDFRenderLauncher {

  private static final String TITLE = "PDF Renderer demo";
  private static final String NO_OUTPUT_MESSAGE = "No output available";
  private static final String NO_DATA_MESSAGE = "There is no data available from form";
  private static final int LENGTH = 800;
  private static final int WIDTH = 600;
  private static final String FILTER_FILES "PDF files";
  private static final String FILE_EXTENSION "pdf";

  /**
   * The main app window
   */
  private JFrame window;
  /**
   * The path of the file we will open
   */
  private String path;
  /**
   * Button for open file function
   */
  private JButton buttonOpen;
  /**
   * A browser panel
   */
  private BrowserPanel browserPanel;

  /**
   * Constructor method, creates the GUI
   */
  public Launcher() {
    window = new JFrame(TITLE);
    window.getContentPane().setLayout(new BorderLayout());
    window.setSize(LENGTH, WIDTH);
    window.add(createButtonsPanel(), BorderLayout.NORTH);
    window.add(createContentPanel(), BorderLayout.CENTER);
    window.setVisible(true);
    window.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
NativeInterface.close();
        System.exit(0);
      }
    });
  }

  /**
   * Creates a button panel with the action button: open a file
   * @return the buttons panel
   */
  private Component createButtonsPanel() {
    JPanel panel = new JPanel();
    panel.setLayout(new FlowLayout());
    buttonOpen = new JButton("Open file");
    buttonOpen.addActionListener(new ButtonOpenController());
    panel.add(buttonOpen);
    return panel;
  }

  /**
   * Creates a panel to render the content
   *
   * @return the buttons panel
   */
  private Component createContentPanel() {
    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
    JScrollPane scrollPaneText = new JScrollPane(textPanel);
    panel.add(scrollPaneText);
    //here we will insert the DJNativeSwing panle
    browserPanel = new BrowserPanel();
    JScrollPane scrollPaneBrowser = new JScrollPane(browserPanel);
    panel.add(scrollPaneBrowser);
    return panel;
  }

  /**
   * Load button controller, which launches the FileChooser.
   */
  private class ButtonOpenController implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent arg0) {
      launchOpenSelectFile();
    }
  }

  /**
   * Launches the FileChooser window, and invokes the pdf opener window.
   */
  private void launchOpenSelectFile() {
    JFileChooser fileChooser = new JFileChooser();
    fileChooser.setAcceptAllFileFilterUsed(false);
    FileNameExtensionFilter filter = new FileNameExtensionFilter(
FILTER_FILES, FILE_EXTENSION);
    fileChooser.addChoosableFileFilter(filter);
    if (fileChooser.showOpenDialog(window) == JFileChooser.APPROVE_OPTION) {
      path = fileChooser.getSelectedFile().getAbsolutePath();
browserPanel.navigate(path);
    }
  }

  @SuppressWarnings("unused")
  public static void main(String[] args) {
    UIUtils.setPreferredLookAndFeel();
    NativeInterface.open();
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        Launcher demo = new Launcher();
      }
    });
  }
}

Embeding a web browser

Then let’s follow up with browser tab: if we just wanted to create a browser panel it would be as easy as writing the following lines:

import java.awt.BorderLayout;

import javax.swing.BorderFactory;
import javax.swing.JPanel;

import chrriis.dj.nativeswing.swtimpl.components.JWebBrowser;

public class BasicBrowserPanel extends JPanel {

  private static final String TITLE = "";

  /**
   * The browser will be handled in this specific component
   */
  private JWebBrowser webBrowser;

  /**
   * Constructor
   */
  public BrowserPanel() {
    super(new BorderLayout());
    JPanel webBrowserPanel = new JPanel(new BorderLayout());
webBrowserPanel.setBorder(BorderFactory.createTitledBorder(TITLE));
    webBrowser = new JWebBrowser();
    webBrowser.setBarsVisible(false);
    webBrowser.setStatusBarVisible(false);
    webBrowserPanel.add(webBrowser, BorderLayout.CENTER);
    add(webBrowserPanel, BorderLayout.CENTER);
  }

  /**
   * Initializes the browser and sets a value in the URL storage
   * @param path the URL value o file path to open
   */
  public void navigate(String path) {
    webBrowser.setVisible(true);
    webBrowser.navigate(path);
  }

  /**
   * Makes the browser retrieve and render the content from the path previously stored
   */
  public String getAddress(){
    return webBrowser.getHTMLContent();
  }

  /**
   * Hides the browser controls (forward, back, home buttons...)
   */
  public void hideContent() {
    webBrowser.setVisible(false);
  }
}

This way we are getting a fully functional web browser, very similar to the browser in the Eclipse IDE, but with too many unnecessary functions for what we are trying to do here. Since we are only doing the rendering process by delegating it on Adobe, we can remove the GUI extra elements from this whole custom browsing system and leave the bare panel.

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import chrriis.dj.nativeswing.swtimpl.NativeInterface;
import chrriis.dj.nativeswing.swtimpl.components.JWebBrowser;

/**
 * Allows to launch a JFrame containing an embedded browser.
 */
public class BrowserFrameLauncher {

  /**
   * Renders the file content on a browser via DJ Native Swing
   *
   * @param path
   * the file url if we pass as a parameter any webpage URL, the system would try to render it
   * @return a JPanel with a native browser, to render the file content
   */
  private Component createBrowserPanel(String path) {
    JWebBrowser.useXULRunnerRuntime();
    JPanel fileBrowserPanel = new JPanel(new BorderLayout());
    final JWebBrowser fileBrowser = new JWebBrowser();
    fileBrowser.setBarsVisible(false);
    fileBrowser.setStatusBarVisible(false);
    fileBrowser.navigate(path);
    fileBrowserPanel.add(fileBrowser, BorderLayout.CENTER);
    return fileBrowserPanel;
  }
}

Setting the last piece of the puzzle: getting the PDF itself

Finally let’s make the final touches to open a PDF. What we are actually doing is getting the PDF file path into the browser, so in the end we have a new layer over our old friend XULRunner, but this provides us a way to integrate the plugins via the “right architecture version” SWT library. So as a conclussion we are able to connect to the “right architecture version” plugin, fixing our rendering problem and making us independent from the 32 bits plaform once and for all.

// excerpt from BrowserFrameLauncherPDF.java

  private static final String CLOSING_MESSAGE = "Do you really want to close the file?";
  private static final String RENDERED_TITLE = "PDF Renderer demo - Embed Browser";

  /**
   * Opens a file and shows it content in a JFrame.
   *
   * @param path
   * the url of the file to open in a JFrame
   */
  public static void openPDF(final String path) {
    NativeInterface.open();
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        final JFrame frame = new JFrame(RENDERED_TITLE);
frame.setLocation(0, 0);
        //we may set up a default size for this test
        //frame.setSize(800, 600);
        frame.setVisible(true);
        frame.add(createBrowserPanel(path));

        //a window listener would allow us to control the closing actions
        frame.addWindowListener(new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
            int i = JOptionPane.showConfirmDialog(frame,CLOSING_MESSAGE);
            if (i == 0) {
              NativeInterface.close();
            }
          }
        });
      }
    });
  }

❗️ Please notice the NativeInterface.open() line to make sure about getting the components correctly booted, and the threading of this component in order to avoid other processes interfering with the rendering.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.