/*
 * Copyright (c) 1997-2024 IDRsolutions (https://www.idrsolutions.com)
 */
package org.jpedal.examples.viewer.gui;

import com.idrsolutions.image.JDeli;
import org.jpedal.FileAccess;
import org.jpedal.FileAccessHelper;
import org.jpedal.PdfDecoder;
import org.jpedal.PdfDecoderInt;
import org.jpedal.constants.JPedalSettings;
import org.jpedal.display.Display;
import org.jpedal.display.GUIDisplay;
import org.jpedal.display.PageOffsets;
import org.jpedal.display.swing.MultiDisplay;
import org.jpedal.display.swing.SingleDisplay;
import org.jpedal.examples.viewer.Commands;
import org.jpedal.examples.viewer.RecentDocuments;
import org.jpedal.examples.viewer.Values;
import org.jpedal.examples.viewer.ViewerCommands;
import org.jpedal.examples.viewer.commands.OpenFile;
import org.jpedal.examples.viewer.commands.PageNavigator;
import org.jpedal.examples.viewer.gui.popups.PrintPanel;
import org.jpedal.examples.viewer.gui.swing.ColorListCellRenderer;
import org.jpedal.examples.viewer.gui.swing.FrameCloser;
import org.jpedal.examples.viewer.gui.swing.SearchList;
import org.jpedal.examples.viewer.gui.swing.SwingAnnotationPanel;
import org.jpedal.examples.viewer.gui.swing.SwingAttachmentsPanel;
import org.jpedal.examples.viewer.gui.swing.SwingCombo;
import org.jpedal.examples.viewer.gui.swing.SwingCommandListener;
import org.jpedal.examples.viewer.gui.swing.SwingLayersPanel;
import org.jpedal.examples.viewer.gui.swing.SwingMenuItems;
import org.jpedal.examples.viewer.gui.swing.SwingMouseListener;
import org.jpedal.examples.viewer.gui.swing.SwingMouseUtils;
import org.jpedal.examples.viewer.gui.swing.SwingPageChanger;
import org.jpedal.examples.viewer.gui.swing.SwingPortfolioDetail;
import org.jpedal.examples.viewer.gui.swing.SwingPortfolioTile;
import org.jpedal.examples.viewer.gui.swing.SwingScrollListener;
import org.jpedal.examples.viewer.gui.swing.SwingSearchWindow;
import org.jpedal.examples.viewer.gui.swing.SwingSignaturesPanel;
import org.jpedal.examples.viewer.gui.swing.SwingThumbnailPanel;
import org.jpedal.examples.viewer.paper.PaperSizes;
import org.jpedal.examples.viewer.utils.LimitDecodeTrackerNoSwing;
import org.jpedal.examples.viewer.utils.PropertiesFile;
import org.jpedal.exception.PdfException;
import org.jpedal.external.AnnotationHandler;
import org.jpedal.external.CustomMessageHandler;
import org.jpedal.external.ExternalHandlers;
import org.jpedal.external.ImageNameCollector;
import org.jpedal.external.OffsetOptions;
import org.jpedal.external.Options;
import org.jpedal.external.RenderChangeListener;
import org.jpedal.fonts.tt.TTGlyph;
import org.jpedal.gui.CommandWindow;
import org.jpedal.io.LimitDecodeTracker;
import org.jpedal.io.PageLabels;
import org.jpedal.io.PdfObjectReader;
import org.jpedal.io.StatusBar;
import org.jpedal.linear.LinearThread;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.acroforms.AcroRenderer;
import org.jpedal.objects.acroforms.actions.ActionHandler;
import org.jpedal.objects.acroforms.creation.FormFactory;
import org.jpedal.objects.layers.PdfLayerList;
import org.jpedal.objects.raw.FormObject;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.parser.CommandParser;
import org.jpedal.parser.DecodeStatus;
import org.jpedal.parser.DecoderOptions;
import org.jpedal.parser.text.Tj;
import org.jpedal.render.BaseDisplay;
import org.jpedal.render.DynamicVectorRenderer;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.Messages;
import org.jpedal.utils.StringUtils;
import org.w3c.dom.Node;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.RootPaneContainer;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.border.AbstractBorder;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.NumberFormatter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.StringSelection;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimerTask;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 <br>Description: Swing GUI functions in Viewer
 */
public class SwingGUI {

    public static final int BUTTONBAR = 0;
    public static final int NAVBAR = 1;
    public static final int PAGES = 2;
    public static final String version;
    public static final int CURSOR = 1;
    /**
     * grabbing cursor
     */
    public static final int GRAB_CURSOR = 1;
    public static final int GRABBING_CURSOR = 2;
    public static final int DEFAULT_CURSOR = 3;
    public static final int PAN_CURSOR = 4;
    public static final int PAN_CURSORL = 5;
    public static final int PAN_CURSORTL = 6;
    public static final int PAN_CURSORT = 7;
    public static final int PAN_CURSORTR = 8;
    public static final int PAN_CURSORR = 9;
    public static final int PAN_CURSORBR = 10;
    public static final int PAN_CURSORB = 11;
    public static final int PAN_CURSORBL = 12;
    /**
     * Constants for glowing border
     */
    private static final int glowThickness = 11;
    /**
     * minimum screen width to ensure menu buttons are visible
     */
    private static final int minimumScreenWidth = 700;
    public static boolean deletePropertiesOnExit;
    public static boolean alwaysShowMouse;
    /**
     * control if messages appear
     */
    public static boolean showMessages = true;
    static int expandedSize = 190;
    private static int collapsedSize = 30;
    /**
     * padding so that the pdf is not right at the edge
     */
    private static int inset = 25;
    /**
     * default scaling on the combobox scalingValues
     */
    private static int defaultSelection;
    private static String windowTitle;

    static {

        final Properties props = new Properties();
        try (InputStream is = SwingGUI.class.getResourceAsStream("/version.num")) {
            if (is != null) {
                try {
                    props.load(is);
                catch (final IOException ex) {
                    LogWriter.writeLog("Exception: " + ex.getMessage());
                }
            }
        catch (final IOException e) {
            LogWriter.writeLog("Exception: " + e.getMessage());
        }

        final String versionSet = props.getProperty("release");

        if (versionSet != null) {
            version = versionSet;
        else {
            version = "@VERSION@";
        }
    }

    private final Color glowOuterColor = new Color(0.0f0.0f0.0f0.0f);
    private final Color glowInnerColor = new Color(0.8f0.75f0.45f0.8f);
    private final Values commonValues;
    private final SwingThumbnailPanel thumbnails;
    private final PropertiesFile properties;
    private RecentDocuments recent;
    /**
     * listener on buttons, menus, combboxes to execute options (one instance on all objects)
     */
    private SwingCommandListener currentCommandListener;
    private Commands currentCommands;
    //allow user to control messages in Viewer
    private CustomMessageHandler customMessageHandler;
    //setup in init so we can pass in some objects
    private SwingMenuItems menuItems;
    //layers tab
    private PdfLayerList layersObject;
    private boolean finishedDecoding;
    private Font textFont = new Font("Serif", Font.PLAIN, 12);
    //flag if generated so we setup once for each file
    private boolean bookmarksGenerated;
    private SwingSearchWindow searchFrame;
    private String pageTitle;
    private String bookmarksTitle;
    private String signaturesTitle;
    private String layersTitle;
    private String annotationTitle;
    private String attachmentsTitle;
    /**
     * handle for internal use
     */
    private PdfDecoderInt decode_pdf;
    /**
     * XML structure of bookmarks
     */
    private SwingOutline tree;
    /**
     * scaling values as floats to save conversion
     */
    private float[] scalingFloatValues = {1.0f1.0f1.0f.25f.5f.75f1.0f1.25f1.5f2.0f2.5f5.0f7.5f10.0f};
    /**
     * page scaling to use 1=100%
     */
    private float scaling = 1;
    /**
     * store page rotation
     */
    private int rotation;
    /**
     * scaling factors on the page
     */
    private SwingCombo rotationBox;
    /**
     * scaling factors on the page
     */
    private SwingCombo scalingBox;
    private RefreshLayout viewListener;

    private final Buttons swButtons = new Buttons();

    //GUICursor object that holds everything to do with Cursor for SwingGUI
    private final SwingCursor guiCursor = new SwingCursor();

    //all mouse actions
    private SwingMouseListener mouseHandler;

    private Timer memoryMonitor;

    private SwingScrollListener scrollListener;

    //This scroll bar allows for page navigation via the scroll bar/wheel when the page
    //fits within the dimensions of the viewable area
    private JScrollBar thumbscroll;

    //Creates the top toolbar
    private final JPanel top;

    private boolean sideTabBarOpenByDefault;
    private String startSelectedTab = "";

    //use new GUI layout
    private boolean hasListener;
    private boolean isSetup;
    private int lastTabSelected = -1;
    private boolean tabsExpanded;

    private PaperSizes paperSizes;

    //Multibox for new GUI Layout to contain memory, cursor and loading bars
    private final JPanel multibox;

    //Track whether both pages are properly displayed
    private boolean pageTurnScalingAppropriate = true;

    //holds back/forward buttons at bottom of page
    private JToolBar navButtons;

    //hold the thumbnails for display panes
    private final JPanel containerForThumbnails;

    // visual display of current cursor co-ords on page
    private JLabel coords;

    //root element to hold display
    private Container frame;

    //element to add portfolio display to
    private Container portfolioParent;

    //displayed on left to hold thumbnails, bookmarks
    private JTabbedPane navOptionsPanel;

    //split display between PDF and thumbnails/bookmarks
    private JSplitPane displayPane;


    //Scrollpane for pdf panel
    private JScrollPane scrollPane;

    //Interactive display object - needs to be added to PdfDecoder
    private StatusBar statusBar;
    private StatusBar downloadBar;

    private JLabel pageCounter1;

    private JTextField pageCounter2;

    private JLabel pageCounter3;

    private SwingSignaturesPanel signaturesTree;

    private SwingLayersPanel layersPanel;

    private SwingAnnotationPanel annotationsPanel;

    private SwingPortfolioDetail detailView;

    private SwingPortfolioTile tileView;

    private SwingAttachmentsPanel attachmentsPanel;

    //stop user forcing open tab before any pages loaded
    private boolean tabsNotInitialised = true;
    private JToolBar navToolBar;
    private JToolBar pagesToolBar;

    //Progress bar on nav bar
    private final JProgressBar memoryBar;

    //Component to display cursor position on page
    private final JToolBar cursor;

    private PrintPanel printPanel;

    private JPanel glassPane;

    private boolean searchInMenu;
    private JTextField searchText;

    private JToggleButton options;
    private JPopupMenu menu;

    private boolean cursorOverPage;

    private final JLayeredPane lpane;

    private Map<FormObject, String> objs;
    private long start = System.currentTimeMillis();
    /**
     * stops autoscrolling at screen edge
     */
    private boolean allowScrolling = true;
    /**
     * confirms exit when closing the window
     */
    private boolean confirmClose;
    private boolean debugMode;
    private ImageNameCollector imageNameCollector = new ImageNameCollector() {};

    /**
     * Create the user interface for the Viewer example.
     *
     @param decode_pdf the object that decodes the pdf
     @param commonValues all common values
     @param thumbnails the panel displaying the thumbnails
     @param properties all properties of the viewer
     */
    public SwingGUI(final PdfDecoderInt decode_pdf, final Values commonValues, final SwingThumbnailPanel thumbnails, final PropertiesFile properties) {

        this.decode_pdf = decode_pdf;
        this.commonValues = commonValues;
        this.thumbnails = thumbnails;
        this.properties = properties;

        statusBar = new StatusBar(new Color(2351540));
        downloadBar = new StatusBar(new Color(1852090));

        //Initialise the java components
        top = new JPanel();
        multibox = new JPanel();
        navButtons = new JToolBar();
        containerForThumbnails = new JPanel();
        coords = new JLabel();
        frame = new JFrame();
        portfolioParent = ((RootPaneContainerframe).getContentPane();
        navOptionsPanel = new JTabbedPane();
        pageCounter2 = new JTextField(4);
        pageCounter3 = new JLabel();
        memoryBar = new JProgressBar();
        cursor = new JToolBar();
        navToolBar = new JToolBar();
        pagesToolBar = new JToolBar();
        signaturesTree = new SwingSignaturesPanel();
        layersPanel = new SwingLayersPanel();
        scrollPane = new JScrollPane();
        tree = new SwingOutline();
        lpane = new JLayeredPane() {
            @Override
            public void doLayout() {
                synchronized (getTreeLock()) {
                    final int w = getWidth();
                    final int h = getHeight();
                    for (final Component c : getComponents()) {
                        c.setBounds(00, w, h);
                    }
                }
            }
        };
        setupDisplay();
    }

    public static int getPDFDisplayInset() {
        return inset;
    }

    private static void setHHighlightComposite(final String propValue) {
        float value = Float.parseFloat(propValue);
        if (value > 1) {
            value = 1;
        }
        if (value < 0) {
            value = 0;
        }

        DecoderOptions.highlightComposite = value;
    }

    private static void getFlattenedTreeNodes(final TreeNode theNode, final List<TreeNode> items) {
        // add the item
        items.add(theNode);

        // recursion
        final int count = theNode.getChildCount();
        for (int i = 0; i < count; i++) {
            getFlattenedTreeNodes(theNode.getChildAt(i), items);
        }
    }

    /**
     * setup display
     */
    private void setupDisplay() {
        if (SwingUtilities.isEventDispatchThread()) {

            setDisplayView(Display.SINGLE_PAGE, Display.DISPLAY_CENTERED);

        else {
            final Runnable doPaintComponent = () -> setDisplayView(Display.SINGLE_PAGE, Display.DISPLAY_CENTERED);
            try {
                SwingUtilities.invokeAndWait(doPaintComponent);
            catch (InterruptedException | InvocationTargetException e) {
                LogWriter.writeLog("Exception: " + e.getMessage());
            }
        }

        //pass in SwingGUI so we can call via callback
        decode_pdf.addExternalHandler(this, Options.GUIContainer);

        //pass properties into Swing Menu items
        menuItems = new SwingMenuItems(properties);
    }

    /**
     * Returns the split pane used in the display area of the viewer.
     *
     @return JSplitPane used to hold the page display and the side tab bar
     */
    public JSplitPane getDisplayPane() {
        return displayPane;
    }

    /**
     * Returns the scroll bar used in single page mode that shows page thumbnails as you scroll
     *
     @return JScrollBar used for the thumbnail scroll in single page mode
     */
    public JScrollBar getThumbnailScrollBar() {
        return thumbscroll;
    }

    /**
     * Control thumbnail scroll bars visibility
     *
     @param isVisible boolean value to control visibility
     */
    public void setThumbnailScrollBarVisibility(final boolean isVisible) {
        thumbscroll.setVisible(isVisible);
    }

    /**
     * Set the position of the scroll bar based on desired page number
     *
     @param pageNum int value for the page
     */
    public void setThumbnailScrollBarValue(final int pageNum) {
        thumbscroll.setValue(pageNum);
    }

    /**
     * Gets the file path for the properties file used in the viewer.
     *
     @return String containing the properties file name
     */
    public String getPropertiesFileLocation() {
        return properties.getConfigFile();
    }

    /**
     * Gets the page number of the given bookmark
     *
     @param bookmark String value of the bookmark
     @return String value of the page number
     */
    public String getBookmark(final String bookmark) {
        return tree.getPage(bookmark);
    }

    /**
     * Set the side tab bar state and set any values required for that state.
     *
     @param showVisible true to expand the side tab bar, false to collapse
     */
    public void reinitialiseTabs(final boolean showVisible) {

        if ("true".equalsIgnoreCase(properties.getValue("ShowSidetabbar"))) {

            if (!showVisible && !"true".equalsIgnoreCase(properties.getValue("consistentTabBar"))) {
                if (sideTabBarOpenByDefault) {
                    displayPane.setDividerLocation(expandedSize);
                    tabsExpanded = true;
                else {
                    displayPane.setDividerLocation(collapsedSize);
                    tabsExpanded = false;
                    navOptionsPanel.setSelectedIndex(-1);
                }
            }
            lastTabSelected = -1;

            if (commonValues.isPDF()) {

                setupPdfDependantTabs();
            }

            if (tabsNotInitialised) {
                navOptionsPanel.setSelectedIndex(-1);
                for (int i = 0; i != navOptionsPanel.getTabCount(); i++) {
                    if (DecoderOptions.isRunningOnMac) {
                        if (navOptionsPanel.getTitleAt(i).equals(startSelectedTab)) {
                            navOptionsPanel.setSelectedIndex(i);
                            break;
                        }
                    else {
                        if (navOptionsPanel.getIconAt(i).toString().equals(startSelectedTab)) {
                            navOptionsPanel.setSelectedIndex(i);
                            break;
                        }
                    }
                }
            }
        }
    }

    private void setupPdfDependantTabs() {
        //add/remove optional tabs
        if (!decode_pdf.hasOutline()) {

            final int outlineTab = getTabIndex(bookmarksTitle);

            if (outlineTab != -1) {
                navOptionsPanel.remove(outlineTab);
            }

        else if ("true".equalsIgnoreCase(properties.getValue("Bookmarkstab"))) {
            final int outlineTab = getTabIndex(bookmarksTitle);
            if (outlineTab == -1) {
                if (DecoderOptions.isRunningOnMac && UIManager.getLookAndFeel().isNativeLookAndFeel()) {
                    navOptionsPanel.addTab(bookmarksTitle, tree);
                else {
                    final VTextIcon textIcon2 = new VTextIcon(navOptionsPanel, bookmarksTitle);
                    navOptionsPanel.addTab(null, textIcon2, tree);
                }
            }
        }

        //handle signatures pane
        final AcroRenderer currentFormRenderer = decode_pdf.getFormRenderer();

        Iterator<FormObject> signatureObjects = null;

        if (currentFormRenderer != null) {
            signatureObjects = currentFormRenderer.getSignatureObjects();
        }

        if (signatureObjects != null) {
            signaturesTree.reinitialise(decode_pdf, signatureObjects);
            checkTabShown(signaturesTitle);
        else {
            removeTab(signaturesTitle);
        }

                 //add a control Panel to enable/disable layers
        layersObject = (PdfLayerListdecode_pdf.getJPedalObject(PdfDictionary.Layer);

        if (layersObject != null && layersObject.getLayersCount() 0) { //some files have empty Layers objects

            checkTabShown(layersTitle);
            layersPanel.reinitialise(layersObject, decode_pdf, scrollPane, commonValues.getCurrentPage());

        else {
            removeTab(layersTitle);
        }


        final PdfObjectReader reader = decode_pdf.getIO();
        Object[] embeddedFiles = null;
        if (reader != null) {
            if (reader.getNamesLookup() != null) {
                embeddedFiles = reader.getNamesLookup().getEmbeddedFiles();
            }
            if (attachmentsPanel == null) {
                attachmentsPanel = new SwingAttachmentsPanel(this);
            }

            //Always load values even when null as this method also loads values stored in annotations
            if (attachmentsPanel.loadEmbeddedFileData(decode_pdf, embeddedFiles)) {
                checkTabShown(attachmentsTitle);
            else {
                removeTab(attachmentsTitle);
            }

        }

        if (decode_pdf.getDisplayView() == Display.SINGLE_PAGE && !decode_pdf.isEncrypted()) {
            checkTabShown(annotationTitle);
        else {
            removeTab(annotationTitle);
        }

        setBookmarks(false);
    }

    private int getTabIndex(final String title) {
        int tabIndex = -1;
        if (DecoderOptions.isRunningOnMac) {
            //see if there is an outlines tab
            for (int jj = 0; jj < navOptionsPanel.getTabCount(); jj++) {
                if (navOptionsPanel.getTitleAt(jj).equals(title)) {
                    tabIndex = jj;
                }
            }
        else {
            //see if there is an outlines tab
            for (int jj = 0; jj < navOptionsPanel.getTabCount(); jj++) {
                if (navOptionsPanel.getIconAt(jj).toString().equals(title)) {
                    tabIndex = jj;
                }
            }
        }
        return tabIndex;
    }

    private void checkTabShown(final String title) {
        final int outlineTab = getTabIndex(title);

        if (outlineTab == -1) {
            if (DecoderOptions.isRunningOnMac && UIManager.getLookAndFeel().isNativeLookAndFeel()) {

                if (title.equals(signaturesTitle&& "true".equalsIgnoreCase(properties.getValue("Signaturestab"))) {
                    navOptionsPanel.addTab(signaturesTitle, signaturesTree);
                    navOptionsPanel.setTitleAt(navOptionsPanel.getTabCount() 1, signaturesTitle);

                else if (title.equals(layersTitle&& "true".equalsIgnoreCase(properties.getValue("Layerstab"))) {

                    final JScrollPane layerScrollPane = new JScrollPane();
                    layerScrollPane.getViewport().add(layersPanel);
                    layerScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
                    layerScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

                    navOptionsPanel.addTab(layersTitle, layerScrollPane);
                    navOptionsPanel.setTitleAt(navOptionsPanel.getTabCount() 1, layersTitle);

                else if (title.equals(annotationTitle&& "true".equalsIgnoreCase(properties.getValue("AnnotationTab")) && SwingAnnotationPanel.addPanel()) {
                    navOptionsPanel.addTab(annotationTitle, annotationsPanel.getDisplayPanel());
                    navOptionsPanel.setTitleAt(navOptionsPanel.getTabCount() 1, annotationTitle);

                else if (title.equals(attachmentsTitle&& "true".equalsIgnoreCase(properties.getValue("AttachmentsTab"))) {
                    navOptionsPanel.addTab(attachmentsTitle, attachmentsPanel);
                    navOptionsPanel.setTitleAt(navOptionsPanel.getTabCount() 1, attachmentsTitle);

                }


            else {

                if (title.equals(signaturesTitle&& "true".equalsIgnoreCase(properties.getValue("Signaturestab"))) {  //stop spurious display of Sig tab
                    final VTextIcon textIcon2 = new VTextIcon(navOptionsPanel, signaturesTitle);
                    navOptionsPanel.addTab(null, textIcon2, signaturesTree);
                else if (title.equals(layersTitle&& "true".equalsIgnoreCase(properties.getValue("Layerstab"))) {
                    final VTextIcon textIcon = new VTextIcon(navOptionsPanel, layersTitle);

                    final JScrollPane scrollPane = new JScrollPane();
                    scrollPane.getViewport().add(layersPanel);
                    scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
                    scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

                    navOptionsPanel.addTab(null, textIcon, scrollPane);
                else if (title.equals(annotationTitle&& "true".equalsIgnoreCase(properties.getValue("AnnotationTab")) && SwingAnnotationPanel.addPanel()) {

                    final VTextIcon textIcon = new VTextIcon(navOptionsPanel, annotationTitle);
                    navOptionsPanel.addTab(null, textIcon, annotationsPanel.getDisplayPanel());

                else if (title.equals(attachmentsTitle&& "true".equalsIgnoreCase(properties.getValue("AttachmentsTab"))) {

                    final VTextIcon textIcon = new VTextIcon(navOptionsPanel, attachmentsTitle);
                    navOptionsPanel.addTab(null, textIcon, attachmentsPanel);

                }

            }
        }
    }

    private void removeTab(final String title) {

        final int outlineTab = getTabIndex(title);

        if (outlineTab != -1) {
            navOptionsPanel.remove(outlineTab);
        }

    }

    /**
     * Stop thumbnails from being generated for the side tab bar
     */
    public void stopThumbnails() {

        if (thumbnails.isShownOnscreen()) {
            //if running terminate first
            thumbnails.terminateDrawing();

            thumbnails.removeAllListeners();

        }
    }

    /**
     * Flag the thumbnails tab as requires reinitialisation
     */
    public void reinitThumbnails() {

        isSetup = false;

    }

    /**
     * Reset the nav bar and collapse side tab bar
     */
    public void resetNavBar() {

        if (!"true".equalsIgnoreCase(properties.getValue("consistentTabBar"))) {
            displayPane.setDividerLocation(collapsedSize);
            navOptionsPanel.setSelectedIndex(-1);
            tabsNotInitialised = true;
        }

        //also reset layers
        layersPanel.resetLayers();

        //disable page view buttons until we know we have multiple pages
        swButtons.setPageLayoutButtonsEnabled(false);

    }

    /*
     * Called when new file opened so we set flags here
     */
    public void setNoPagesDecoded() {


        bookmarksGenerated = false;

        resetNavBar();

        //Ensure preview from last file doesn't appear
        if (scrollListener != null) {
            scrollListener.lastImage = null;
        }

    }

    /**
     * Gets the thumbnail panel.
     *
     @return SwingThumbnailPanel instance
     */
    public SwingThumbnailPanel getThumbnailPanel() {
        return thumbnails;
    }

    /**
     * Gets the SwingOutline used for the outline panel
     *
     @return SwingOutline
     */
    public SwingOutline getOutlinePanel() {
        return tree;
    }

    /**
     * Gets the current vertical scroll bar. This will be the thumbnail scroll bar
     * if it is currently present, otherwise it will be the scrollpanes scroll bar.
     *
     @return a JScrollBar representing the scroll bar
     */
    public JScrollBar getVerticalScrollBar() {
        if (scrollPane.getVerticalScrollBar().isVisible()) {
            return scrollPane.getVerticalScrollBar();
        else {
            return thumbscroll;
        }
    }

    /**
     * Sets the scroll bar policy for the display areas scroll pane
     *
     @param pol ScrollPolicy to be applied to the scroll pane
     */
    public void setScrollBarPolicy(final ScrollPolicy pol) {
        switch (pol) {
            case VERTICAL_NEVER:
                scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
                break;
            case VERTICAL_AS_NEEDED:
                scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
                break;
            case HORIZONTAL_NEVER:
                scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
                break;
            case HORIZONTAL_AS_NEEDED:
                scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
                break;

        }
    }

    /**
     * Specify a Container for the viewers interface to be added to.
     *
     @param rawValue an Object that extends container to add the user interface to.
     */
    public void setRootContainer(final Object rawValue) {

        if (rawValue == null) {
            throw new RuntimeException("Null containers not allowed.");
        }

        final Container rootContainer = (ContainerrawValue;

        Container c = rootContainer;

        if ((rootContainer instanceof JTabbedPane)) {
            final JPanel temp = new JPanel(new BorderLayout());
            rootContainer.add(temp);
            c = temp;
        else if (rootContainer instanceof JScrollPane) {
            final JPanel temp = new JPanel(new BorderLayout());
            ((JScrollPanerootContainer).getViewport().add(temp);
            c = temp;

        else if (rootContainer instanceof JSplitPane) {
            throw new RuntimeException("To add the viewer to a split pane please pass through either JSplitPane.getLeftComponent() or JSplitPane.getRightComponent()");
        }

        if (rootContainer instanceof JFrame) {
            portfolioParent = ((RootPaneContainerrootContainer).getContentPane();
        else {
            c.setLayout(new BorderLayout());
            portfolioParent = c;
        }

        //Load width and height from properties file
        int width = Integer.parseInt(properties.getValue("startViewerWidth"));
        int height = Integer.parseInt(properties.getValue("startViewerHeight"));

        //Used to prevent infinite scroll issue as a preferred size has been set
        final Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        if (width < 0) {
            width = d.width / 2;
            if (width < 700) {
                width = 700;
            }
            properties.setValue("startViewerWidth", String.valueOf(width));
        }

        if (height < 0) {
            height = d.height / 2;
            properties.setValue("startViewerHeight", String.valueOf(height));
        }

        //allow user to alter size
        final String customWindowSize = System.getProperty("org.jpedal.startWindowSize");
        if (customWindowSize != null) {

            final StringTokenizer values = new StringTokenizer(customWindowSize, "x");

            if (values.countTokens() != 2) {
                throw new RuntimeException("Unable to use value for org.jpedal.startWindowSize=" + customWindowSize + "\nValue should be in format org.jpedal.startWindowSize=200x300");
            }

            try {
                width = Integer.parseInt(values.nextToken().trim());
                height = Integer.parseInt(values.nextToken().trim());

            catch (final NumberFormatException ee) {
                throw new RuntimeException("Unable to use value for org.jpedal.startWindowSize=" + customWindowSize + "\nValue should be in format org.jpedal.startWindowSize=200x300 " + ee);
            }
        }

        c.setPreferredSize(new Dimension(width, height));

        frame = c;

    }

    public void resetRotationBox() {

        final PdfPageData currentPageData = decode_pdf.getPdfPageData();

        if (decode_pdf.getDisplayView() == Display.SINGLE_PAGE) {
            rotation = currentPageData.getRotation(commonValues.getCurrentPage());
        }
        if (getSelectedComboIndex(Commands.ROTATION!= (rotation / 90)) {
            setSelectedComboIndex(Commands.ROTATION, (rotation / 90));
        else if (!Values.isProcessing()) {
            decode_pdf.repaint();
        }
    }

    public PropertiesFile getProperties() {
        return properties;
    }

    /**
     * Set Search Bar to be in the Left hand Tabbed pane
     *
     @param searchFrame the search window that will be add to the side tab bar
     */
    public void searchInTab(final SwingSearchWindow searchFrame) {
        this.searchFrame = searchFrame;

        this.searchFrame.init(decode_pdf, commonValues);

        if (DecoderOptions.isRunningOnMac && UIManager.getLookAndFeel().isNativeLookAndFeel()) {
            if (thumbnails.isShownOnscreen()) {
                navOptionsPanel.addTab("Search", searchFrame.getContentPane());
            }
        else {
            final VTextIcon textIcon2 = new VTextIcon(navOptionsPanel, "Search");
            navOptionsPanel.addTab(null, textIcon2, searchFrame.getContentPane());
        }

        //Create menu bar text field to trigger side tab bar
        final JTextField searchTextLocal = new JTextField();
        searchTextLocal.setColumns(searchText.getColumns());
        searchTextLocal.setText(searchText.getText());

        //Get last listener as all listeners added to the end of the array
        searchTextLocal.addKeyListener(searchText.getKeyListeners()[searchText.getKeyListeners().length - 1]);
        searchTextLocal.addFocusListener(searchText.getFocusListeners()[searchText.getFocusListeners().length - 1]);
        searchTextLocal.addActionListener(e -> {
            navOptionsPanel.setSelectedComponent(searchFrame.getContentPane());
            if (!tabsExpanded) {
                displayPane.setDividerLocation(expandedSize);
                tabsExpanded = true;
                thumbnails.setIsDisplayedOnscreen(false);
            }
            searchText.setText(searchTextLocal.getText());

        });
        searchText.addActionListener(e -> searchTextLocal.setText(searchText.getText()));
        swButtons.getTopButtons().add(searchTextLocal);
    }

    private JToggleButton createMenuBarSearchOptions() {
        if (options == null) {
            options = new JToggleButton(new ImageIcon(guiCursor.getURLForImage("menuSearchOptions.png")));
            menu = new JPopupMenu();
            options.setBorder(null);
            options.setName("SEARCHOPTIONS");
            options.addItemListener(e -> {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    menu.show(((Componente.getSource())0((Componente.getSource()).getHeight());
                }
            });
            options.setFocusable(false);
            options.setToolTipText(Messages.getMessage("PdfViewerSearch.Options"));

            final JCheckBoxMenuItem wholeWords = new JCheckBoxMenuItem(Messages.getMessage("PdfViewerSearch.WholeWords"));
            wholeWords.setToolTipText(Messages.getMessage("PdfViewerSearch.WholeWordsTooltip"));
            wholeWords.addActionListener(e -> {
                searchFrame.setWholeWords(((AbstractButtone.getSource()).isSelected());
                enableSearchItems(true);
            });

            final JCheckBoxMenuItem caseSense = new JCheckBoxMenuItem(Messages.getMessage("PdfViewerSearch.CaseSense"));
            caseSense.setToolTipText(Messages.getMessage("PdfViewerSearch.CaseSenseTooltip"));
            caseSense.addActionListener(e -> {
                searchFrame.setCaseSensitive(((AbstractButtone.getSource()).isSelected());
                enableSearchItems(true);
            });

            final JCheckBoxMenuItem multiLine = new JCheckBoxMenuItem(Messages.getMessage("PdfViewerSearch.MultiLine"));
            multiLine.setToolTipText(Messages.getMessage("PdfViewerSearch.MultiLineTooltip"));
            multiLine.addActionListener(e -> {
                searchFrame.setMultiLine(((AbstractButtone.getSource()).isSelected());
                enableSearchItems(true);
            });

            final JCheckBoxMenuItem ignoreSpaces = new JCheckBoxMenuItem(Messages.getMessage("PdfViewerSearch.IgnoreSpaces"));
            ignoreSpaces.setToolTipText(Messages.getMessage("PdfViewerSearch.IgnoreSpacesTooltip"));
            ignoreSpaces.addActionListener(e -> {
                searchFrame.setIgnoreSpaces(((AbstractButtone.getSource()).isSelected());
                enableSearchItems(true);
            });

            menu.add(wholeWords);
            menu.add(caseSense);
            menu.add(multiLine);
            menu.add(ignoreSpaces);

            menu.addPopupMenuListener(new PopupMenuListener() {
                @Override
                public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
                    //We do not need to perform an action before making popup visible
                }

                @Override
                public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
                    options.setSelected(false);
                }

                @Override
                public void popupMenuCanceled(final PopupMenuEvent e) {
                    options.setSelected(false);
                }
            });
        }

        return options;
    }

    /*
     * Set Search Bar to be in the Top Button Bar
     */
    private void searchInMenu(final SwingSearchWindow searchFrame) {
        this.searchFrame = searchFrame;
        searchInMenu = true;
        searchFrame.find(decode_pdf, commonValues);
        swButtons.getTopButtons().add(searchText);
        swButtons.getTopButtons().add(createMenuBarSearchOptions());
        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerSearch.Previous")"search_previous.gif", Commands.PREVIOUSRESULT, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);
        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerSearch.Next")"search_next.gif", Commands.NEXTRESULT, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.getButton(Commands.NEXTRESULT).setEnabled(false);
        swButtons.getButton(Commands.PREVIOUSRESULT).setEnabled(false);

        swButtons.getButton(Commands.NEXTRESULT).setVisible(true);
        swButtons.getButton(Commands.PREVIOUSRESULT).setVisible(true);
    }

    /**
     * Sets the properties file to be used
     *
     @param file String file name for the properties file to be used
     */
    public void setPropertiesFileLocation(final String file) {
        properties.loadProperties(file);
    }

    /**
     * Gets the Commands object used by the user interface
     *
     @return Commands object for executing commands
     */
    public Commands getCommand() {
        return currentCommands;
    }

    /**
     * Gets the annotation tabs panel if one is present
     *
     @return an implementation of SwingAnnotationPanel
     */
    public SwingAnnotationPanel getAnnotationPanel() {
        return annotationsPanel;
    }

    @SuppressWarnings({"OverlyLongMethod""java:S138"})
    public void init(final Commands currentCommands) {

        //single listener to execute all commands
        currentCommandListener = new SwingCommandListener(currentCommands);

        //setup custom message and switch off error messages if used
        customMessageHandler = (CustomMessageHandler) (decode_pdf.getExternalHandler(Options.CustomMessageOutput));
        if (customMessageHandler != null) {
            DecoderOptions.showErrorMessages = false;
            showMessages = false;
        }

        this.currentCommands = currentCommands;

        initProperties();

        annotationsPanel = new SwingAnnotationPanel(this);

        /*
         * arrange insets
         */
        decode_pdf.setInset(inset, inset);

        setViewerTitle();
        setViewerIcon();

        setDefaultPdfDecoderParameters();

        //Add Background color to the panel to help break up view.
        setupCenterPanelBackground();

        //setup combo boxes.
        setupComboBoxes();

        if (LogWriter.isRunningFromIDE) {
            nameElements();
        }

        //add the pdf display to show page.
        setupPDFDisplayPane();

        //add the pdf display left and right panes.
        setupBorderPanes();

        //Initialise the Swing Buttons
        swButtons.init(true);

        //create a menu bar and add to display
        createTopMenuBar();


        //set colours on display boxes and add listener to page number
        setupBottomToolBarItems();

        //create other tool bars and add to display
        createOtherToolBars();

        initButtonsAndMenu();

        //navigation toolbar for moving between pages
        createNavbar();

        //add cursor location
        initCoordBox();

        if (searchFrame != null && searchFrame.getViewStyle() == SwingSearchWindow.SEARCH_MENU_BAR) {
            searchInMenu(searchFrame);
        }

        //status object on toolbar showing 0 -100 % completion
        initStatus();

        //Ensure all gui sections are displayed correctly
        //Issues found when removing some sections
        frame.invalidate();
        frame.validate();
        frame.repaint();

        //Load properties
        GUIModifier.load(properties, this);

        initViewerWindow();

        redrawDocumentOnResize();

        setupPDFBorder();

        //Set side tab bar state at start up
        if (sideTabBarOpenByDefault) {
            displayPane.setDividerLocation(expandedSize);
            tabsExpanded = true;
        else {
            displayPane.setDividerLocation(collapsedSize);
            tabsExpanded = false;
            navOptionsPanel.setSelectedIndex(-1);
        }

        mouseHandler = new SwingMouseListener((PdfDecoderdecode_pdf, this, commonValues, currentCommands);
        mouseHandler.setupMouse();

        final long eventMask = AWTEvent.MOUSE_MOTION_EVENT_MASK;

        //Add universal listener to catch coords updating regardless of form component
        Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
            //Required to prevent recursive calling of listener
            boolean listenerCalled;

            @Override
            public void eventDispatched(final AWTEvent e) {

                if (decode_pdf != null && !listenerCalled && e.getID() == MouseEvent.MOUSE_MOVED) {
                    listenerCalled = true;

                    Point mousePosition = ((Componentdecode_pdf).getMousePosition();

                    if ((annotationsPanel != null && SwingAnnotationPanel.addPanel()) && glassPane != null) {
                        mousePosition = glassPane.getMousePosition();
                    }

                    if (mousePosition != null) {
                        mouseHandler.allowMouseCoordsUpdate();
                        ((Componentdecode_pdf).dispatchEvent(new MouseEvent(((Componentdecode_pdf),
                                e.getID(),
                                System.currentTimeMillis(),
                                0,
                                mousePosition.x,
                                mousePosition.y,
                                0,
                                false));
                    }

                    listenerCalled = false;
                }
            }
        }, eventMask);

        if (LogWriter.isRunningFromIDE && !debugMode) {
            final LimitDecodeTracker decodeLimiter = new LimitDecodeTracker(menuItems, (PdfDecoderdecode_pdf);
            decode_pdf.addExternalHandler(decodeLimiter, Options.ErrorTracker);
        }
    }

    private void setDefaultPdfDecoderParameters() {
        final boolean separateCoverOn = "true".equalsIgnoreCase(properties.getValue("separateCoverOn"));
        decode_pdf.getPages().setBoolean(Display.BoolValue.SEPARATE_COVER, separateCoverOn);

        final boolean showFormFieldHighlights = "true".equalsIgnoreCase(properties.getValue("highlightFormFields"));
        final int formFieldHighlightColor = Integer.parseInt(properties.getValue("formFieldsHighlightColor"));
        final boolean useOffScreenRendering = "true".equalsIgnoreCase(properties.getValue("useOffScreenRendering"));
        final boolean fullOffScreenRendering = "true".equalsIgnoreCase(properties.getValue("fullOffScreenRendering"));
        final Map<Integer, Object> map = new HashMap<>();
        map.put(JPedalSettings.HIGHLIGHT_FORM_FIELDS, showFormFieldHighlights);
        map.put(JPedalSettings.HIGHLIGHT_FORM_FIELDS_COLOR, formFieldHighlightColor);
        map.put(JPedalSettings.OFF_SCREEN_RENDERING, useOffScreenRendering);
        map.put(JPedalSettings.FULL_OFF_SCREEN_RENDERING, fullOffScreenRendering);
        try {
            decode_pdf.modifyNonstaticJPedalParameters(map);
        catch (final PdfException e) {
            LogWriter.writeLog("Error setting values from properties " + e);
        }
    }

    private void initViewerWindow() {

        //Load width and height from properties file
        int width = Integer.parseInt(properties.getValue("startViewerWidth"));
        int height = Integer.parseInt(properties.getValue("startViewerHeight"));

        //Used to prevent infinite scroll issue as a preferred size has been set
        final Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        if (width < 0) {
            width = d.width / 2;
            if (width < minimumScreenWidth) {
                width = minimumScreenWidth;
            }
            properties.setValue("startViewerWidth", String.valueOf(width));
        }

        if (height < 0) {
            height = d.height / 2;
            properties.setValue("startViewerHeight", String.valueOf(height));
        }

        //allow user to alter size
        final String customWindowSize = System.getProperty("org.jpedal.startWindowSize");
        if (customWindowSize != null) {

            final StringTokenizer values = new StringTokenizer(customWindowSize, "x");

            if (values.countTokens() != 2) {
                throw new RuntimeException("Unable to use value for org.jpedal.startWindowSize=" + customWindowSize + "\nValue should be in format org.jpedal.startWindowSize=200x300");
            }

            try {
                width = Integer.parseInt(values.nextToken().trim());
                height = Integer.parseInt(values.nextToken().trim());

            catch (final NumberFormatException ee) {
                throw new RuntimeException("Unable to use value for org.jpedal.startWindowSize=" + customWindowSize + "\nValue should be in format org.jpedal.startWindowSize=200x300 " + ee);
            }
        }

        createMainViewerWindow(width, height);
    }

    private void initButtonsAndMenu() {

        //Menu bar for using the majority of functions
        menuItems.createMainMenu(true, currentCommandListener,  currentCommands, swButtons);

        //sets up all the toolbar items
        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.openFile")"open.gif", Commands.OPENFILE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.saveFile")"save.gif", Commands.SAVE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.print")"print.gif", Commands.PRINT, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        if (searchFrame != null && (searchFrame.getViewStyle() == SwingSearchWindow.SEARCH_EXTERNAL_WINDOW)) {
            searchFrame.setViewStyle(SwingSearchWindow.SEARCH_EXTERNAL_WINDOW);
            swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.search")"find.gif", Commands.FIND, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);
        }

        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.properties")"properties.gif", Commands.DOCINFO, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        //snapshot screen function
        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.snapshot")"snapshot.gif", Commands.SNAPSHOT, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.minimise")"minimise.gif", Commands.ZOOMOUT, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);
        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.zoom")"zoom.gif", Commands.ZOOMIN, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        final JSeparator sep = new JSeparator(SwingConstants.VERTICAL);
        sep.setPreferredSize(new Dimension(532));
        swButtons.getTopButtons().add(sep);

        //combo boxes on toolbar
        addCombo(Messages.getMessage("PdfViewerToolbarTooltip.zoomin"), Commands.SCALING);

        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.rotateLeft")"rotateLeft.gif", Commands.ROTATELEFT, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);
        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.rotateRight")"rotateRight.gif", Commands.ROTATERIGHT, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        addCombo(Messages.getMessage("PdfViewerToolbarTooltip.rotation"), Commands.ROTATION);

        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.mouseMode")"mouse_select.png", Commands.MOUSEMODE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        final JSeparator sep2 = new JSeparator(SwingConstants.VERTICAL);
        sep2.setPreferredSize(new Dimension(532));
        swButtons.getTopButtons().add(sep2);

        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.help")"help.png", Commands.HELP, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(BUTTONBAR, Messages.getMessage("PdfViewerToolbarTooltip.openSystemDefault")"openSystem.png", Commands.OPENINSYSTEMDEFAULT, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

    }

    @SuppressWarnings({"OverlyLongMethod""java:S138"})
    private void initProperties() {

        String propValue = properties.getValue("pageInsets");
        if (!propValue.isEmpty()) {
            inset = Integer.parseInt(propValue);
        }

        propValue = properties.getValue("changeTextAndLineart");
        if (!propValue.isEmpty()
                && "true".equalsIgnoreCase(propValue)) {
            currentCommands.executeCommand(ViewerCommands.CHANGELINEART, new Object[]{Boolean.parseBoolean(propValue)});
        }

        propValue = properties.getValue("windowTitle");
        if (!propValue.isEmpty()) {
            windowTitle = propValue;
        else {
            windowTitle = Messages.getMessage("PdfViewer.titlebar"' ' + version;
        }

        propValue = properties.getValue("vbgColor");
        if (!propValue.isEmpty()) {
            currentCommands.executeCommand(ViewerCommands.SETPAGECOLOR, new Object[]{Integer.parseInt(propValue)});
        }

        propValue = properties.getValue("replaceDocumentTextColors");
        if (!propValue.isEmpty()
                && "true".equalsIgnoreCase(propValue)) {

            propValue = properties.getValue("vfgColor");
            if (!propValue.isEmpty()) {
                currentCommands.executeCommand(ViewerCommands.SETTEXTCOLOR, new Object[]{Integer.parseInt(propValue)});
            }

        }

        propValue = properties.getValue("TextColorThreshold");
        if (!propValue.isEmpty()) {
            currentCommands.executeCommand(ViewerCommands.SETREPLACEMENTCOLORTHRESHOLD, new Object[]{Integer.parseInt(propValue)});
        }

        propValue = properties.getValue("enhanceFractionalLines");
        if (!propValue.isEmpty()) {
            currentCommands.executeCommand(ViewerCommands.SETENHANCEFRACTIONALLINES, new Object[]{Boolean.parseBoolean(propValue)});
        }

        propValue = properties.getValue("hideLineWeights");
        if (!propValue.isEmpty()) {
            currentCommands.executeCommand(ViewerCommands.SETHIDELINEWEIGHTS, new Object[]{Boolean.parseBoolean(propValue)});
        }

        //Set autoScroll default and add to properties file
        propValue = properties.getValue("autoScroll");
        if (!propValue.isEmpty()) {
            allowScrolling = Boolean.parseBoolean(propValue);
        }

        //set confirmClose
        propValue = properties.getValue("confirmClose");
        if (!propValue.isEmpty()) {
            confirmClose = "true".equals(propValue);
        }

        //Dpi is taken into effect when zoom is called
        propValue = properties.getValue("resolution");
        if (!propValue.isEmpty()) {
            setDpi(Integer.parseInt(propValue));
        }

        //Ensure valid value if not recognised
        propValue = properties.getValue("startView");

        if (!propValue.isEmpty()) {
            int pageMode = Integer.parseInt(propValue);
            if (pageMode < Display.SINGLE_PAGE || pageMode > Display.PAGEFLOW) {
                pageMode = Display.SINGLE_PAGE;
            }
            //Default Page Layout
            decode_pdf.setPageMode(pageMode);
        }

        final String val = properties.getValue("highlightBoxColor")//empty string to old users
        if (!val.isEmpty()) {
            DecoderOptions.highlightColor = new Color(Integer.parseInt(val));
        }

        propValue = properties.getValue("useSmoothImage");
        if (!propValue.isEmpty()) {
            final HashMap<Integer, Object> map = new HashMap<>();
            map.put(JPedalSettings.SMOOTH_IMAGES, Boolean.valueOf(propValue));
            try {
                decode_pdf.modifyNonstaticJPedalParameters(map);
            catch (final PdfException e) {
                LogWriter.writeLog(e);
            }
        }

        propValue = properties.getValue("highlightTextColor");
        if (!propValue.isEmpty()) {
            DecoderOptions.backgroundColor = new Color(Integer.parseInt(propValue));
        }

        propValue = properties.getValue("showMouseSelectionBox");
        if (!propValue.isEmpty()) {
            DecoderOptions.showMouseBox = Boolean.parseBoolean(propValue);
        }

        propValue = properties.getValue("enhancedViewerMode");
        if (!propValue.isEmpty()) {
            decode_pdf.useNewGraphicsMode(Boolean.parseBoolean(propValue));
        }

        propValue = properties.getValue("highlightComposite");
        if (!propValue.isEmpty()) {
            setHHighlightComposite(propValue);
        }

        //Set border config value and repaint
        propValue = properties.getValue("borderType");
        if (!propValue.isEmpty()) {
            decode_pdf.getPages().setBorderPresent(Integer.parseInt(propValue== 1);
        }

        //Allow cursor to change
        propValue = properties.getValue("allowCursorToChange");
        if (!propValue.isEmpty()) {
            GUIDisplay.allowChangeCursor = "true".equalsIgnoreCase(propValue);
        }

        propValue = properties.getValue("invertHighlights");
        if (!propValue.isEmpty()) {
            BaseDisplay.invertHighlight = Boolean.parseBoolean(propValue);
        }

        propValue = properties.getValue("enhancedFacingMode");
        if (!propValue.isEmpty()) {
            GUIDisplay.default_turnoverOn = Boolean.parseBoolean(propValue);
        }

        //Set whether to use hinting
        propValue = properties.getValue("useHinting");
        final String propValue2 = System.getProperty("org.jpedal.useTTFontHinting");

        //check JVM flag first
        if (propValue2 != null) {
            //check if properties file conflicts
            if (!propValue.isEmpty() && !propValue2.equalsIgnoreCase(propValue)) {
                JOptionPane.showMessageDialog(null, Messages.getMessage("PdfCustomGui.hintingFlagFileConflict"));
            }

            TTGlyph.useHinting = "true".equalsIgnoreCase(propValue2);

            //check properties file
        else {
            TTGlyph.useHinting = !propValue.isEmpty() && "true".equalsIgnoreCase(propValue);
        }

        //Set icon location
        propValue = properties.getValue("iconLocation");
        if (!propValue.isEmpty()) {
            guiCursor.setIconLocation(propValue);
        }
    }

    private void nameElements() {
        menuItems.getCurrentMenu().setName("menuBar");
        frame.setName("frame");
        pageCounter2.setName("pageCounter");

        rotationBox.setName("rotationBox");
        scalingBox.setName("scalingBox");
    }

    /**
     * method being called from within init to create other tool bars to add to
     * display
     */
    private void createOtherToolBars() {

        //setup top main menu toolbar and create options
        top.add(menuItems.getCurrentMenu(), BorderLayout.NORTH);

        //This is where we add the Buttons for the top ToolBar
        top.add(swButtons.getTopButtons(), BorderLayout.CENTER);


        //This is where we add the Buttons for the bottom ToolBar
        if (frame instanceof JFrame) {
            ((RootPaneContainerframe).getContentPane().add(navButtons, BorderLayout.SOUTH);
        else {
            frame.add(navButtons, BorderLayout.SOUTH);
        }

        //This is where we add the display pane to the center of the viewer
        if (frame instanceof JFrame) {
            ((RootPaneContainerframe).getContentPane().add(displayPane, BorderLayout.CENTER);
        else {
            frame.add(displayPane, BorderLayout.CENTER);
        }

    }

    private void handleTabbedPanes() {

        if (tabsNotInitialised) {
            return;
        }

        //expand size if not already at size
        final int tabSelected = navOptionsPanel.getSelectedIndex();

        if (tabSelected == -1) {
            return;
        }

        if (!tabsExpanded) {
            //workout selected tab
            final String tabName;
            if (DecoderOptions.isRunningOnMac) {
                tabName = navOptionsPanel.getTitleAt(tabSelected);
            else {
                tabName = navOptionsPanel.getIconAt(tabSelected).toString();
            }

            thumbnails.setIsDisplayedOnscreen(tabName.equals(pageTitle));

            if (tabName.equals(annotationTitle)) {
                if (decode_pdf.isEncrypted()) {
                    showMessageDialog((Messages.getMessage("PdfViewerMessage.NewAnnotInEncryptedFile")), Messages.getMessage("PdfViewerGeneral.Warning"), JOptionPane.WARNING_MESSAGE);
                }
                annotationsPanel.populateList(decode_pdf);
            }

            displayPane.setDividerLocation(expandedSize);
            tabsExpanded = true;
        else if (lastTabSelected == tabSelected) {
            displayPane.setDividerLocation(collapsedSize);
            tabsExpanded = false;
            thumbnails.setIsDisplayedOnscreen(false);
            navOptionsPanel.setSelectedIndex(-1);
        }
        lastTabSelected = tabSelected;
    }

    /**
     * Not part of API - used internally
     <p>
     * Set the state and value for the multi box at the bottom left of the user interface.
     *
     @param flags an int[] used to control multi box display
     */
    public void setMultibox(final int[] flags) {

        //debug code for formclicktests
        if (alwaysShowMouse) {
            multibox.removeAll();
            multibox.add(coords, BorderLayout.CENTER);

            multibox.repaint();
            return;
        }

        //deal with flags
        if (flags.length > && flags[0== CURSOR) {
            //if no change, return
            if (cursorOverPage != (flags[1== 1)) {
                cursorOverPage = flags[1== 1;
            else {
                return;
            }
        }

        //LOAD_PROGRESS:
        if (statusBar.isEnabled() && statusBar.isVisible() && !statusBar.isDone()) {
            multibox.removeAll();
            statusBar.getStatusObject().setSize(multibox.getSize());
            multibox.add(statusBar.getStatusObject(), BorderLayout.CENTER);

            multibox.repaint();
            return;
        }

        //CURSOR:
        if (cursor.isEnabled() && cursor.isVisible() && cursorOverPage && decode_pdf.isOpen()) {
            multibox.removeAll();
            multibox.add(coords, BorderLayout.CENTER);

            multibox.repaint();
            return;
        }

        //DOWNLOAD_PROGRESS:
        if (downloadBar.isEnabled() && downloadBar.isVisible() && !downloadBar.isDone() && (decode_pdf.isLoadingLinearizedPDF() || !decode_pdf.isOpen())) {
            multibox.removeAll();
            downloadBar.getStatusObject().setSize(multibox.getSize());
            multibox.add(downloadBar.getStatusObject(), BorderLayout.CENTER);

            multibox.repaint();
            return;
        }

        //MEMORY:
        if (memoryBar.isEnabled() && memoryBar.isVisible()) {
            multibox.removeAll();
            memoryBar.setSize(multibox.getSize());
            memoryBar.setForeground(new Color(125145255));
            multibox.add(memoryBar, BorderLayout.CENTER);

            multibox.repaint();
        }

    }

    /**
     * Overrides method from GUI.java, see GUI.java for DOCS.
     */
    private void setTitle(final String title) {
        if (frame instanceof JFrame) {
            ((Frameframe).setTitle(title);
        }
    }

    public void resetComboBoxes(final boolean value) {

        scalingBox.setEnabled(value);
        rotationBox.setEnabled(value);

    }

    public SwingCombo getCombo(final int ID) {

        switch (ID) {
            case Commands.SCALING:
                return scalingBox;
            case Commands.ROTATION:
                return rotationBox;

        }

        return null;

    }

    /**
     * All scaling and rotation should go through this.
     */
    public void scaleAndRotate() {

        if (decode_pdf != null) {
            if (decode_pdf.getDisplayView() == Display.PAGEFLOW) {
                decode_pdf.setPageParameters(scaling, commonValues.getCurrentPage(), rotation);
                return;
            }

            //ignore if called too early
            if (!decode_pdf.isOpen() && OpenFile.isPDf) {
                return;
            }
        }

        // [AWI]: Get the values of all of the border insets around the scrollpane
        Insets borderInsets = new Insets(0000);
        if (scrollPane.getBorder() != null && scrollPane.getBorder().getBorderInsets(scrollPane!= null) {
            borderInsets = scrollPane.getBorder().getBorderInsets(scrollPane);
        }
        float width = scrollPane.getWidth() - inset - inset - borderInsets.left - borderInsets.right;
        final float height = scrollPane.getHeight() - inset - inset - borderInsets.top - borderInsets.bottom;


        if (decode_pdf != null) {

            int index = getSelectedComboIndex(Commands.SCALING);

            if (decode_pdf.getDisplayView() == Display.PAGEFLOW) {

                //Ensure we only display in window mode
                setSelectedComboIndex(Commands.SCALING, 0);
                index = 0;

                //Disable scaling option
                scalingBox.setEnabled(false);
            else if (decode_pdf.getDisplayView() != Display.PAGEFLOW) {

                //No long pageFlow. enable scaling option
                scalingBox.setEnabled(true);
            }

            if (index == -1) {
                final float zoom = getScalingFromString((StringgetSelectedComboItem(Commands.SCALING));

                //if nothing off either attempt, use window value
                if (zoom == -1) {
                    //its not set so use To window value
                    index = defaultSelection;
                    setSelectedComboIndex(Commands.SCALING, index);
                else {
                    scaling = decode_pdf.getDPIFactory().adjustScaling(zoom / 100);

                    setSelectedComboItem(Commands.SCALING, String.valueOf(zoom));
                }
            }

            int page = commonValues.getCurrentPage();

            //Multipage tiff should be treated as a single page
            if (commonValues.isMultiTiff()) {
                page = 1;
            }

            //always check in facing mode with turnover on
            if (index != -|| decode_pdf.getDisplayView() == Display.SINGLE_PAGE || (decode_pdf.getDisplayView() == Display.FACING && decode_pdf.getPages().getBoolean(Display.BoolValue.TURNOVER_ON))) {

                final Dimension contentSize = getContentDimensions(page);

                if (displayPane != null) {
                    width -= displayPane.getDividerSize();
                }

                setupScalingValue(index, width, height, contentSize.width, contentSize.height);
            }

            //allow for clicking on it before page opened
            decode_pdf.setPageParameters(scaling, page, rotation);

            //Ensure the page is displayed in the correct rotation
            setRotation();

        }

        if (LogWriter.isRunningFromIDE) {
            System.out.println("Memory usage after zoom=" (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) 1_000);
        }

        //Update annotation selection position if required
        annotationsPanel.getAnnotationListener().updateSelectionPosition();

    }

    private Dimension getContentDimensions(final int page) {
        final Dimension dimensions = new Dimension();
        final PdfPageData pageData = decode_pdf.getPdfPageData();
        final int raw_rotation;

        if (decode_pdf.getDisplayView() == Display.FACING) {
            raw_rotation = pageData.getRotation(page);
        else {
            raw_rotation = 0;
        }

        final boolean isRotated = (rotation + raw_rotation180 == 90;

        final PageOffsets offsets = (PageOffsetsdecode_pdf.getExternalHandler(Options.CurrentOffset);
        switch (decode_pdf.getDisplayView()) {
            case Display.CONTINUOUS_FACING:
                handleContinuousModes(dimensions, isRotated, offsets, offsets.getMaxH() 2, offsets.getMaxW() 2);
                break;
            case Display.CONTINUOUS:
                handleContinuousModes(dimensions, isRotated, offsets, offsets.getMaxH(), offsets.getMaxW());
                break;
            case Display.FACING:
                handleFacing(page, dimensions, pageData, isRotated, decode_pdf, commonValues);
                break;
            default:
                if (isRotated) {
                    dimensions.width = pageData.getCropBoxHeight(page);
                    dimensions.height = pageData.getCropBoxWidth(page);
                else {
                    dimensions.width = pageData.getCropBoxWidth(page);
                    dimensions.height = pageData.getCropBoxHeight(page);
                }
        }

        //Check if image with no size data stored and get values direct from image
        if (dimensions.width == && dimensions.height == && commonValues.getBufferedImg() != null) {
            if (isRotated) {
                dimensions.width = commonValues.getBufferedImg().getHeight();
                dimensions.height = commonValues.getBufferedImg().getWidth();
            else {
                dimensions.width = commonValues.getBufferedImg().getWidth();
                dimensions.height = commonValues.getBufferedImg().getHeight();
            }
        }

        return dimensions;
    }

    private static void handleFacing(final int page, final Dimension dimensions, final PdfPageData pageData,
                                     final boolean isRotated, final PdfDecoderInt decode_pdf, final Values commonValues) {
        int leftPage;

        if (decode_pdf.getPages().getBoolean(Display.BoolValue.SEPARATE_COVER)) {
            leftPage = (page / 22;
            if (commonValues.getPageCount() == 2) {
                leftPage = 1;
            }
        else {
            leftPage = (page);
            if ((leftPage & 1== 0) {
                leftPage--;
            }
        }

        if (leftPage == 0) {
            leftPage++;
        }

        if (isRotated) {
            dimensions.width = pageData.getCropBoxHeight(leftPage);

            //if first or last page double the width, otherwise add other page width
            if (leftPage + > commonValues.getPageCount() || leftPage == 1) {
                dimensions.width *= 2;
            else {
                dimensions.width += pageData.getCropBoxHeight(leftPage + 1);
            }

            dimensions.height = pageData.getCropBoxWidth(leftPage);
            if (leftPage + <= commonValues.getPageCount() && dimensions.height < pageData.getCropBoxWidth(leftPage + 1)) {
                dimensions.height = pageData.getCropBoxWidth(leftPage + 1);
            }
        else {
            dimensions.width = pageData.getCropBoxWidth(leftPage);

            //if first or last page double the width, otherwise add other page width
            if (leftPage + > commonValues.getPageCount()) {
                dimensions.width *= 2;
            else {
                dimensions.width += pageData.getCropBoxWidth(leftPage + 1);
            }

            dimensions.height = pageData.getCropBoxHeight(leftPage);
            if (leftPage + <= commonValues.getPageCount() && dimensions.height < pageData.getCropBoxHeight(leftPage + 1)) {
                dimensions.height = pageData.getCropBoxHeight(leftPage + 1);
            }
        }
    }

    private static void handleContinuousModes(final Dimension dimensions, final boolean isRotated,
                                              final PageOffsets offsets, final int i, final int i2) {
        if (isRotated) {
            dimensions.width = i;
            dimensions.height = offsets.getMaxW();
        else {
            dimensions.width = i2;
            dimensions.height = offsets.getMaxH();
        }
    }

    private static float getScalingFromString(String numberValue) {
        float zoom = -1;
        if ((numberValue != null&& (!numberValue.isEmpty())) {

            //Remove the % that may be present when setting value manually
            if (numberValue.endsWith("%")) {
                numberValue = numberValue.substring(0, numberValue.length() 1);
            }

            try {
                zoom = Float.parseFloat(numberValue);
            catch (final NumberFormatException e) {

                LogWriter.writeLog("Exception in getting zoom " + e);

                //its got characters in it so get first valid number string
                final int length = numberValue.length();
                int ii = 0;
                while (ii < length) {
                    final char c = numberValue.charAt(ii);
                    if ((c >= '0' && c <= '9'|| c == '.') {
                        ii++;
                    else {
                        break;
                    }
                }

                if (ii > 0) {
                    numberValue = numberValue.substring(0, ii);
                }

                //try again if we reset above
                try {
                    zoom = Float.parseFloat(numberValue);
                catch (final NumberFormatException e1) {
                    LogWriter.writeLog("Exception in getting zoom " + e1);

                    zoom = -1;
                }
            }
            if (zoom > 1_000) {
                zoom = 1_000;
            }
        }

        return zoom;
    }

    private void setupScalingValue(final int index, float viewWidth, float viewHeight, final float contentWidth, final float contentHeight) {

        // [AWI]: Get the default width of scrollbars from the UI Manager
        int scrollbarWidth = 0;
        final Object scrollbarWidthObj = UIManager.get("ScrollBar.width");
        if (scrollbarWidthObj instanceof Integer) {
            scrollbarWidth = ((IntegerscrollbarWidthObj);
            if (scrollbarWidth < 0) {
                scrollbarWidth = 0;
            }
        }

        float x_factor, y_factor, window_factor = 1.0f;

        // [AWI]: Iterate over the scaling algorithm to try and take the scrollbar size into account.
        // This logic was added to address an issue with 'Fit Width' and 'Fit Height' calculations where the
        // scrollbar appeared as a result of the scaling, but the scrollbar was not taken into account in the
        // scaling algorithm. When this occurred, both scrollbars could appear, even though only one should.
        //
        // The loopCount is configured such that the scaling routine should be run at least once, but not more
        // than enough to make room for the scrollbar, to prevent an infinite loop.
        float scaledWidth, scaledHeight;
        int loopCount = 0;
        while (loopCount++ <= (scrollbarWidth)) {
            x_factor = viewWidth / contentWidth;
            y_factor = viewHeight / contentHeight;

            window_factor = Math.min(x_factor, y_factor);

            if (index != -1) {
                if (index < 3) { //handle scroll to width/height/window
                    switch (index) {
                        case 0:
                            scaling = window_factor;
                            break;
                        case 1:
                            scaling = y_factor;
                            break;
                        case 2:
                            scaling = x_factor;
                            break;
                    }

                    // [AWI]: Determine if the horizontal scrollbar will appear when set to 'Fit Height'
                    // or if the vertical scrollbar will appear when set to 'Fit Width' and if so, add the
                    // scrollbar size into the scaling algorithm and recalculate once.
                    scaledWidth = scaling * contentWidth;
                    scaledHeight = scaling * contentHeight;

                    if (index == && scaledWidth > viewWidth) { // height
                        viewHeight--;
                    else if (index == && scaledHeight > viewHeight) { // width
                        viewWidth--;
                    else {
                        // Break out to prevent multiple passes since scrollbars should not be displayed
                        break;
                    }
                else {
                    scaling = decode_pdf.getDPIFactory().adjustScaling(scalingFloatValues[index]);
                    // Break out to prevent multiple passes since scrollbars should not be displayed
                    break;
                }
            }
        }
        if (decode_pdf.getDisplayView() == Display.FACING) { // Enable turnover if both pages properly displayed
            pageTurnScalingAppropriate = scaling <= window_factor;
        }

        if (thumbscroll != null) {
            if (decode_pdf.getDisplayView() == Display.SINGLE_PAGE && scaling <= window_factor) {

                scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
                scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

                thumbscroll.setVisible(commonValues.getFileType() == Values.FileType.PDF);
            else {

                scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
                scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

                thumbscroll.setVisible(false);
            }
        }
    }

    /**
     * Set scaling to nearest of the viewer default values.
     *
     @param newScaling The scaling value to snap to viewer default values
     */
    public void snapScalingToDefaults(float newScaling) {
        newScaling = decode_pdf.getDPIFactory().adjustScaling(newScaling / 100);

        float width = scrollPane.getViewport().getWidth() - inset - inset;
        final float height = scrollPane.getViewport().getHeight() - inset - inset;

        final Dimension contentSize = getContentDimensions(commonValues.getCurrentPage());

        if (displayPane != null) {
            width -= displayPane.getDividerSize();
        }

        float x_factor;
        float y_factor;
        final float window_factor;
        x_factor = width / contentSize.width;
        y_factor = height / contentSize.height;

        if (x_factor < y_factor) {
            window_factor = x_factor;
            x_factor = -1;
        else {
            window_factor = y_factor;
            y_factor = -1;
        }

        if (getSelectedComboIndex(Commands.SCALING!= 0
                && ((newScaling < window_factor * 1.1 && newScaling > window_factor * 0.91)
                || ((window_factor > scaling && window_factor < newScaling|| (window_factor < scaling && window_factor > newScaling)))) {
            setSelectedComboIndex(Commands.SCALING, 0);
            scaling = window_factor;
        else if (y_factor != -1
                && getSelectedComboIndex(Commands.SCALING!= 1
                && ((newScaling < y_factor * 1.1 && newScaling > y_factor * 0.91)
                || ((y_factor > scaling && y_factor < newScaling|| (y_factor < scaling && y_factor > newScaling)))) {
            setSelectedComboIndex(Commands.SCALING, 1);
            scaling = y_factor;
        else if (x_factor != -1
                && getSelectedComboIndex(Commands.SCALING!= 2
                && ((newScaling < x_factor * 1.1 && newScaling > x_factor * 0.91)
                || ((x_factor > scaling && x_factor < newScaling|| (x_factor < scaling && x_factor > newScaling)))) {
            setSelectedComboIndex(Commands.SCALING, 2);
            scaling = x_factor;
        else {
            setSelectedComboItem(Commands.SCALING, String.valueOf((intdecode_pdf.getDPIFactory().removeScaling(newScaling * 100)));
            scaling = newScaling;
        }
    }

    public void rotate() {
        rotation = Integer.parseInt((StringgetSelectedComboItem(Commands.ROTATION));
        scaleAndRotate();
        ((JComponentdecode_pdf).updateUI();

    }

    /**
     * Scrolls the view to the specified page.
     *
     @param page Page to scroll to.
     */
    private void scrollToPage(final int page) {

        commonValues.setCurrentPage(page);

        if (commonValues.getCurrentPage() 0) {

            int yCord = 0;

            if (decode_pdf.getDisplayView() != Display.SINGLE_PAGE) {
                yCord = decode_pdf.getPages().getYCordForPage(page);
            }

            if ((decode_pdf.getDisplayView() == Display.CONTINUOUS_FACING || decode_pdf.getDisplayView() == Display.FACING)
                    && page == 1) {
                scrollPane.getHorizontalScrollBar().setValue((int) (decode_pdf.getPdfPageData().getCropBoxWidth(commonValues.getCurrentPage()) * scaling));
            else {
                scrollPane.getHorizontalScrollBar().setValue(0);
            }

            scrollPane.getVerticalScrollBar().setValue(yCord);
        }

        if (decode_pdf.getPageCount() 1) {
            swButtons.setPageLayoutButtonsEnabled(true);
        }

    }

    /**
     * Set the thumbnail panel ready for display.
     */
    private void setupThumbnailPanel() {

        if (isSetup) {
            return;
        }

        isSetup = true;

        if (thumbnails.isShownOnscreen()) {

            final int pages = decode_pdf.getPageCount();

            //setup and add to display
            thumbnails.setupThumbnails(pages, textFont, decode_pdf.getPdfPageData());

            //add listener so clicking on button changes to page - has to be in Viewer so it can update it
            final Object[] buttons = thumbnails.getButtons();
            for (int i = 0; i < pages; i++) {
                ((AbstractButtonbuttons[i]).addActionListener(new SwingPageChanger(this, commonValues, i));
            }

            //add global listener
            thumbnails.addComponentListener();

        }
    }

    /**
     * Set the bookmarks for the outline panel
     *
     @param alwaysGenerate true we will generate the bookmarks, false we only generate bookmarks if not already generated
     */
    public void setBookmarks(final boolean alwaysGenerate) {

        //Ignore if done already
        if ((bookmarksGenerated && !alwaysGenerate|| decode_pdf.isLoadingLinearizedPDF()) {
            return;
        }

        bookmarksGenerated = true;

        final org.w3c.dom.Document doc = decode_pdf.getOutlineAsXML();

        Node rootNode = null;
        if (doc != null) {
            rootNode = doc.getFirstChild();
        }

        if (rootNode != null) {

            tree.reset(rootNode);

            /*
             * Use a mouse listener instead of a TreeSelectionListener for 2 reasons
             * 1. Tree selection only works on new selections, Mouse clicks work on
             *    every click of the mouse.
             * 2. Tree nodes are highlighted before the mouse event, the mouse event
             *    can check the node that was highlighted by the tree and trigger it
             *    allowing highlighted bookmarks to be triggered again.
             *    We can also check if the mouse is within the node so it doesn't trigger
             *    if clicked in an empty area.
             */
            tree.getTree().addMouseListener(new MouseAdapter() {

                @Override
                public void mousePressed(final MouseEvent e) {
                    final DefaultMutableTreeNode node = tree.getLastSelectedPathComponent();

                    final int[] selected = tree.getTree().getSelectionRows();
                    if (selected != null && selected.length != 0) {
                        final Rectangle r = tree.getTree().getRowBounds(selected[0]);

                        if (node == null || !r.contains(e.getX(), e.getY())) {
                            return;
                        }
                    }
                    triggerBookmark(node);
                }
            });

            /*
             * Navigate bookmarks using arrow keys,
             * left and right go to parent / child nodes,
             * enter key will trigger the currently selected node
             */
            tree.getTree().addKeyListener(new KeyAdapter() {

                @Override
                public void keyReleased(final KeyEvent e) {
                    if (e.getKeyCode() == '\n') {

                        final DefaultMutableTreeNode node = tree.getLastSelectedPathComponent();
                        if (node != null) {
                            triggerBookmark(node);
                        }
                    }
                }
            });

        else {
            tree.reset(null);
        }
    }

    private void triggerBookmark(final DefaultMutableTreeNode node) {

        final JTree jtree = tree.getTree();

        final DefaultTreeModel treeModel = (DefaultTreeModeljtree.getModel();

        final java.util.List<TreeNode> flattenedTree = new ArrayList<>();

        //flatten out the tree so we can find the index of the selected node
        getFlattenedTreeNodes((TreeNodetreeModel.getRoot(), flattenedTree);
        flattenedTree.remove(0)// remove the root node as we don't account for this

        final int index = flattenedTree.indexOf(node);

        final String ref = tree.convertNodeIDToRef(index);

        final PdfObject Aobj = decode_pdf.getOutlineData().getAobj(ref);

        //handle in our handler code
        if (Aobj != null) {
            if (SwingUtilities.isEventDispatchThread()) {
                decode_pdf.getFormRenderer().getActionHandler().gotoDest(Aobj, ActionHandler.MOUSECLICKED, PdfDictionary.Dest);
            else {
                try {
                    SwingUtilities.invokeAndWait(() -> decode_pdf.getFormRenderer().getActionHandler().gotoDest(Aobj, ActionHandler.MOUSECLICKED, PdfDictionary.Dest));
                catch (InterruptedException | InvocationTargetException e) {
                    LogWriter.writeLog("Exception: " + e.getMessage());
                }
            }
        }
    }

    private void initStatus() {
        decode_pdf.setStatusBarObject(statusBar);

        //and initialise the display
        setMultibox(new int[]{});

    }

    public void setCoordText(final String string) {
        coords.setText(string);
    }

    private void initCoordBox() {

        coords.setOpaque(true);

        coords.setBorder(BorderFactory.createEtchedBorder());
        coords.setPreferredSize(multibox.getPreferredSize());
        //Need to ensure the coords appear correctly in facing mode
        //incase coords have not been displayed before entering facing mode
        coords.setSize(multibox.getPreferredSize());

        coords.setText("  X: " " Y: " ' ' ' ');

    }

    public void setPageNumber() {

        if (SwingUtilities.isEventDispatchThread()) {
            setPageNumberWorker();
            if (isSetup) {
                //Page changed so save this page as last viewed
                setThumbnails();
            }
        else {
            final Runnable r = () -> {
                setPageNumberWorker();
                if (isSetup) {
                    //Page changed so save this page as last viewed
                    setThumbnails();
                }
            };
            try {
                SwingUtilities.invokeAndWait(r);
            catch (InterruptedException | InvocationTargetException e) {
                LogWriter.writeLog("Exception: " + e.getMessage());
            }
        }
    }

    private void setPageNumberWorker() {

        if (pageCounter2 == null) {
            return;
        }

        if (!decode_pdf.isOpen() && !commonValues.isMultiTiff()) {
            pageCounter2.setText("0");
            pageCounter3.setText(Messages.getMessage("PdfViewerOfLabel.text"" 0");
        else {

            if (thumbscroll != null) {
                thumbscroll.setMaximum(decode_pdf.getPageCount());
                thumbscroll.setValue(commonValues.getCurrentPage() 1);
            }

            final int currentPage = commonValues.getCurrentPage();
            if (decode_pdf.getDisplayView() == Display.FACING || decode_pdf.getDisplayView() == Display.CONTINUOUS_FACING) {
                if (decode_pdf.getPageCount() == 2) {
                    pageCounter2.setText(getPageLabel(1'/' + getPageLabel(2));
                else if (decode_pdf.getPages().getBoolean(Display.BoolValue.SEPARATE_COVER)) {
                    final int base = currentPage & -2;
                    if (base != decode_pdf.getPageCount() && base != 0) {
                        pageCounter2.setText(getPageLabel(base'/' + getPageLabel(base + 1));
                    else {
                        pageCounter2.setText(getPageLabel(currentPage));
                    }
                else {
                    final int base = currentPage - ((currentPage & 1));
                    if (base != decode_pdf.getPageCount()) {
                        pageCounter2.setText(getPageLabel(base'/' + getPageLabel(base + 1));
                    else {
                        pageCounter2.setText(getPageLabel(currentPage));
                    }
                }

            else {
                pageCounter2.setText(getPageLabel(currentPage));
            }

            if (pageLabelDiffers(currentPage)) {
                pageCounter3.setText("(" + currentPage + ' ' + Messages.getMessage("PdfViewerOfLabel.text"' ' + commonValues.getPageCount() ')')//$NON-NLS-1$
            else {
                pageCounter3.setText(Messages.getMessage("PdfViewerOfLabel.text"' ' + commonValues.getPageCount())//$NON-NLS-1$
            }

            swButtons.hideRedundentNavButtons(this);
        }
    }

    /**
     * note - to plugin put all on single line so addButton values over-ridden
     */
    private void createNavbar() {

        if (memoryMonitor == null) { //ensure only 1 instance
            memoryMonitor = new Timer(500, event -> {
                final int free = (int) (Runtime.getRuntime().freeMemory() (1_024 * 1_024));
                final int total = (int) (Runtime.getRuntime().totalMemory() (1_024 * 1_024));

                //this broke the image saving when it was run every time
                if (finishedDecoding) {
                    finishedDecoding = false;
                }

                memoryBar.setMaximum(total);
                memoryBar.setValue(total - free);
                memoryBar.setStringPainted(true);
                memoryBar.setString((total - free"M of " + total + 'M');
            });
            memoryMonitor.start();
        }

        multibox.setLayout(new BorderLayout());

        navButtons.add(multibox, BorderLayout.WEST);

        navButtons.add(Box.createHorizontalGlue());

        navToolBar.add(Box.createHorizontalGlue());

        swButtons.addButton(NAVBAR, Messages.getMessage("PdfViewerNavBar.RewindToStart")"start.gif", Commands.FIRSTPAGE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(NAVBAR, Messages.getMessage("PdfViewerNavBar.Rewind10")"fback.gif", Commands.FBACKPAGE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(NAVBAR, Messages.getMessage("PdfViewerNavBar.Rewind1")"back.gif", Commands.BACKPAGE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        pageCounter1 = new JLabel(Messages.getMessage("PdfViewerPageLabel.text"));
        pageCounter1.setOpaque(false);
        navToolBar.add(pageCounter1);
        navToolBar.add(pageCounter2);
        navToolBar.add(pageCounter3);

        swButtons.addButton(NAVBAR, Messages.getMessage("PdfViewerNavBar.Forward1")"forward.gif", Commands.FORWARDPAGE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(NAVBAR, Messages.getMessage("PdfViewerNavBar.Forward10")"fforward.gif", Commands.FFORWARDPAGE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(NAVBAR, Messages.getMessage("PdfViewerNavBar.ForwardLast")"end.gif", Commands.LASTPAGE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        navToolBar.add(Box.createHorizontalGlue());

        if (debugMode) {
            addLimitDecodeSlider();
        }

        swButtons.addButton(PAGES, Messages.getMessage("PageLayoutButton.SinglePage")"single.gif", Commands.SINGLE, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(PAGES, Messages.getMessage("PageLayoutButton.Continuous")"continuous.gif", Commands.CONTINUOUS, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(PAGES, Messages.getMessage("PageLayoutButton.ContinousFacing")"continuous_facing.gif", Commands.CONTINUOUS_FACING, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(PAGES, Messages.getMessage("PageLayoutButton.Facing")"facing.gif", Commands.FACING, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        swButtons.addButton(PAGES, Messages.getMessage("PageLayoutButton.PageFlow")"pageflow.gif", Commands.PAGEFLOW, menuItems, this, currentCommandListener, pagesToolBar, navToolBar);

        final Dimension size;

        navButtons.add(pagesToolBar, BorderLayout.EAST);


        size = pagesToolBar.getPreferredSize();

        multibox.setPreferredSize(size);

        navButtons.add(navToolBar, BorderLayout.CENTER);

    }

    @SuppressWarnings({"OverlyLongMethod""java:S138"})
    private void addLimitDecodeSlider() {
        final JToolBar decoderPanel = new JToolBar();
        decoderPanel.setFloatable(false);
        decoderPanel.setLayout(new GridBagLayout());
        final GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridy = 0;

        final JSlider slider = new JSlider();
        slider.setMinimum(0);
        slider.setMinorTickSpacing(1);
        slider.setMinorTickSpacing(10);
        slider.setValue(0);

        c.gridx = 0;
        c.weightx = 0;
        final NumberFormat format = NumberFormat.getInstance();
        final NumberFormatter formatter = new NumberFormatter(format);
        formatter.setValueClass(Integer.class);
        formatter.setMinimum(0);
        formatter.setAllowsInvalid(true);
        formatter.setCommitsOnValidEdit(false);
        final JFormattedTextField field = new JFormattedTextField(formatter);

        final CommandWindow commandWindow = new CommandWindow();
        CommandParser.commandWindow = commandWindow;
        Tj.commandWindow = commandWindow;

        final DefaultListModel<String> listModel = new DefaultListModel<>();
        commandWindow.setListModel(listModel);

        final HashSet<String> commands = new HashSet<>();
        commandWindow.setHashSet(commands);

        final JList<String> list = new JList<>(listModel);

        final JScrollPane scrollPane = new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        final JScrollBar v = scrollPane.getVerticalScrollBar();

        final TreeSet<String> breaks =
                new TreeSet<>(Comparator.comparingInt(cmd -> Integer.parseInt(cmd.substring(0, cmd.indexOf(':')))));
        list.setCellRenderer(new ColorListCellRenderer(commands, breaks, 300));

        final LimitDecodeTrackerNoSwing errorTracker =
                new LimitDecodeTrackerNoSwing((PdfDecoderdecode_pdf, formatter, field, slider, breaks, list);


        final KeyAdapter keyAdapter = new KeyAdapter() {
            @Override
            public void keyReleased(final KeyEvent e) {
                final int value = (Integerfield.getValue();
                if (e != null && (e.getKeyChar() == '\n' || e.getKeyChar() == '\r')) {
                    field.setValue(value);
                }
                slider.setValue(value);
            }
        };

        field.addKeyListener(keyAdapter);

        slider.addChangeListener(e -> {
            if (slider.getValueIsAdjusting()) {
                return;  // Redecoding each time the slider moves is very slow
            }
            final int value = slider.getValue();
            field.setValue(value);
            errorTracker.repaint(value);
            v.setValue(v.getMaximum());
        });

        decode_pdf.addExternalHandler(errorTracker, Options.ErrorTracker);

        c.gridx = 1;
        swButtons.addButtonToToolBarWithConstraints("Start""start.gif", -1, menuItems, this, e -> {
            field.setValue(0);
            keyAdapter.keyReleased(null);
        }, decoderPanel, c);
        c.gridx = 2;
        swButtons.addButtonToToolBarWithConstraints("Retreat by 1""back.gif", -1, menuItems, this, e -> {
            if ((Integerfield.getValue() <= 0) {
                field.setValue(0);
            else {
                field.setValue(((Integerfield.getValue()) 1);
            }
            keyAdapter.keyReleased(null);
        }, decoderPanel, c);

        c.gridx = 3;
        c.weightx = 1;
        decoderPanel.add(field, c);
        c.weightx = 0;

        c.gridx = 4;
        swButtons.addButtonToToolBarWithConstraints("Advance by 1""forward.gif", -1, menuItems, this, e -> {
            if ((intfield.getValue() >= (Integerformatter.getMaximum()) {
                field.setValue(formatter.getMaximum());
            else {
                field.setValue(((Integerfield.getValue()) 1);
            }
            keyAdapter.keyReleased(null);
        }, decoderPanel, c);
        c.gridx = 5;
        swButtons.addButtonToToolBarWithConstraints("End""end.gif", -1, menuItems, this, e -> {
            field.setValue(formatter.getMaximum());
            keyAdapter.keyReleased(null);
        }, decoderPanel, c);

        c.gridx = 6;
        c.fill = GridBagConstraints.VERTICAL;
        final JSeparator separator = new JSeparator(SwingConstants.VERTICAL);
        decoderPanel.add(separator, c);
        c.fill = GridBagConstraints.HORIZONTAL;

        c.gridx = 7;
        swButtons.addButtonToToolBarWithConstraints("Next breakpoint""resume.gif", -1, menuItems, this, e -> {
            // If we are at the end, the resume button restarts the decoding
            if ((intfield.getValue() == (Integerformatter.getMaximum()) {
                try {
                    final String firstBreak = breaks.first();
                    field.setValue(Integer.parseInt(firstBreak.substring(0, firstBreak.indexOf(':'))));
                    keyAdapter.keyReleased(null);
                    return;
                catch (final NoSuchElementException ignored) {
                    //NOSONAR
                }
            }

            // If we are not at the end, attempt to find the next breakpoint
            String nextBreakpoint = null;
            int nextBreakpointCommand = -1;
            if (!breaks.isEmpty()) {
                for (final String breakPoint : breaks) {
                    nextBreakpointCommand = Integer.parseInt(breakPoint.substring(0, breakPoint.indexOf(':')));
                    if (nextBreakpointCommand > (Integerfield.getValue()) {
                        nextBreakpoint = breakPoint;
                        break;
                    }
                }
            }

            if (nextBreakpoint == null) {
                field.setValue(formatter.getMaximum());
            else {
                field.setValue(nextBreakpointCommand);
            }

            keyAdapter.keyReleased(null);
        }, decoderPanel, c);

        c.gridx = 8;
        swButtons.addButtonToToolBarWithConstraints("Clear breakpoints""clearb.gif", -1, menuItems, this, e -> {
            breaks.clear();
            list.repaint();
        }, decoderPanel, c);

        final ListSelectionListener listSelectionListener = e -> {
            if (list.getValueIsAdjusting()) {
                return;
            }
            final String command = list.getSelectedValue();
            if (commands.contains(command)) { // Only allow commands to be breakpoints
                if (breaks.contains(command)) {
                    breaks.remove(command);
                else {
                    breaks.add(command);
                }
            }
            list.clearSelection();
        };

        final MouseAdapter listMouseListener = new MouseAdapter() {
            @Override
            public void mousePressed(final MouseEvent e) {
                showMenu(e);
            }

            @Override
            public void mouseReleased(final MouseEvent e) {
                showMenu(e);
            }

            private void showMenu(final MouseEvent e) {
                if (!e.isPopupTrigger()) {
                    return;
                }

                final int clicked = list.locationToIndex(e.getPoint());
                if (clicked != -&& list.getCellBounds(clicked, clicked).contains(e.getPoint())) {
                    final JPopupMenu menu = new JPopupMenu();

                    final JMenuItem breakItem = new JMenuItem("Set breakpoint");
                    breakItem.addActionListener(e1 -> {
                        final String command = listModel.get(clicked);
                        if (commands.contains(command)) { // Only allow commands to be breakpoints
                            if (breaks.contains(command)) {
                                breaks.remove(command);
                            else {
                                breaks.add(command);
                            }
                            list.repaint();
                        }
                    });
                    menu.add(breakItem);

                    final String command = listModel.get(clicked);
                    final Pattern doPattern = Pattern.compile("^\\d+?:\\s+?DO$", Pattern.CASE_INSENSITIVE);
                    final Matcher doMatcher = doPattern.matcher(command);
                    if (doMatcher.matches()) {
                        final JMenuItem imageItem = new JMenuItem("View Image");
                        imageItem.addActionListener(e1 -> {
                            final String imageCacheName = imageNameCollector.getCacheName(listModel.get(clicked - 1).substring(1));
                            if (imageCacheName == null) {
                                LogWriter.writeLog("[Warning] Cannot find image name in collector");
                                return;
                            }
                            final JDialog imageDialog = new JDialog((Framenull, doMatcher.group()false);
                            imageDialog.add(new JLabel(new ImageIcon(decode_pdf.getObjectStore().loadStoredImage(imageCacheName))));
                            imageDialog.pack();
                            imageDialog.setLocationRelativeTo(frame);
                            imageDialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
                            imageDialog.setVisible(true);
                        });
                        menu.add(imageItem);
                    else {
                        final Pattern idPattern = Pattern.compile("^(\\d+?):\\s+?ID$", Pattern.CASE_INSENSITIVE);
                        final Matcher idMatcher = idPattern.matcher(command);
                        if (idMatcher.matches()) {
                            final JMenuItem imageItem = new JMenuItem("View Inline Image");
                            imageItem.addActionListener(e1 -> {
                                final String imageCacheName = imageNameCollector.getCacheNameFromIDToken(Integer.parseInt(idMatcher.group(1)));
                                if (imageCacheName == null) {
                                    LogWriter.writeLog("[Warning] Cannot find image name in collector");
                                    return;
                                }
                                final JDialog imageDialog = new JDialog((Framenull, idMatcher.group()false);
                                imageDialog.add(new JLabel(new ImageIcon(decode_pdf.getObjectStore().loadStoredImage(imageCacheName))));
                                imageDialog.pack();
                                imageDialog.setLocationRelativeTo(frame);
                                imageDialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
                                imageDialog.setVisible(true);
                            });
                            menu.add(imageItem);
                        }
                    }

                    menu.add(new JSeparator(SwingConstants.HORIZONTAL));

                    final JMenuItem copyItem = new JMenuItem("Copy Text");
                    copyItem.addActionListener(e1 -> {
                        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(
                                new StringSelection(listModel.get(clicked))null);
                    });
                    menu.add(copyItem);
                    menu.show(list, e.getX(), e.getY());
                }
            }
        };

        list.addListSelectionListener(listSelectionListener);
        list.addMouseListener(listMouseListener);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));

        final GridBagConstraints gridBagConstraints = new GridBagConstraints();
        final JPanel panel = new JPanel(new GridBagLayout());

        panel.setPreferredSize(new Dimension(400400));

        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.weightx = 0;
        gridBagConstraints.weighty = 0;
        gridBagConstraints.anchor = GridBagConstraints.NORTH;
        panel.add(slider, gridBagConstraints);

        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.weightx = 1;
        gridBagConstraints.weighty = 1;
        gridBagConstraints.anchor = GridBagConstraints.CENTER;
        panel.add(scrollPane, gridBagConstraints);

        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.weightx = 0;
        gridBagConstraints.weighty = 0;
        gridBagConstraints.anchor = GridBagConstraints.SOUTH;
        panel.add(decoderPanel, gridBagConstraints);

        ((RootPaneContainerframe).getContentPane().add(panel, BorderLayout.EAST);
    }

    /**
     * Set the interfaces page counter to the page number
     * and alter any other interface options to match.
     *
     @param page page number as an int
     */
    public void setPage(int page) {

        if (decode_pdf.getDisplayView() == Display.CONTINUOUS_FACING
                && (page & 1== && page != 1) {
            page--;
        }

        if (decode_pdf.getDisplayView() == Display.FACING) {
            final boolean isSeparacteCover = decode_pdf.getPages().getBoolean(Display.BoolValue.SEPARATE_COVER);
            if (isSeparacteCover ? (page & 1== && page != (page & 1== 0) {
                page--;
            }
        }

        commonValues.setCurrentPage(page);
        setPageNumber();

    }

    /**
     * Method to set the viewers rotation from external sources.
     *
     @param rot The rotation to use in the Viewer
     */
    public void setRotationFromExternal(final int rot) {
        rotation = rot;
        rotationBox.setSelectedIndex(rotation / 90);
        if (!Values.isProcessing()) {
            decode_pdf.repaint();
        }
    }

    /**
     * Method to set the viewers scaling from external sources.
     *
     @param scale The scaling to use in the Viewer as a String
     */
    public void setScalingFromExternal(final String scale) {

        if (StringUtils.isNumber(scale)) {
            scalingBox.setSelectedItem(scale + '%');
        else {
            scalingBox.setSelectedItem(scale);
        }
    }

    public Container getFrame() {
        return frame;
    }

    public void showMessageDialog(final String message1) {

        final boolean showMessage = customMessageHandler == null || customMessageHandler.showMessage(message1);

        //check user has not setup message and if we still show message

        if (showMessage) {
            javax.swing.SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame, message1));
        }
    }

    public void showMessageDialog(final Object message, final String title, final int type) {

        final boolean showMessage = customMessageHandler == null || customMessageHandler.showMessage(message);

        //check user has not setup message and if we still show message

        if (showMessage) {
            javax.swing.SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame, message, title, type));
        }
    }

    public String showInputDialog(final Object message, final String title, final int type) {

        String returnMessage = null;

        //check user has not setup message and if we still show message
        if (customMessageHandler != null) {
            returnMessage = customMessageHandler.requestInput(new Object[]{message, title, title});
        }

        if (returnMessage == null) {
            return JOptionPane.showInputDialog(frame, message, title, type);
        else {
            return returnMessage;
        }
    }

    public String showInputDialog(final String message) {

        String returnMessage = null;

        //check user has not setup message and if we still show message
        if (customMessageHandler != null) {
            returnMessage = customMessageHandler.requestInput(new String[]{message});
        }

        if (returnMessage == null) {
            return JOptionPane.showInputDialog(frame, message);
        else {
            return returnMessage;
        }
    }

    public int showConfirmDialog(final String message, final String message2, final int option) {

        int returnMessage = -1;

        //check user has not setup message and if we still show message
        if (customMessageHandler != null) {
            returnMessage = customMessageHandler.requestConfirm(new Object[]{message, message2, String.valueOf(option)});
        }

        if (returnMessage == -1) {
            return JOptionPane.showConfirmDialog(frame, message, message2, option);
        else {
            return returnMessage;
        }
    }

    public int showOverwriteDialog(final String file, final boolean yesToAllPresent) {

        final int n;

        int returnMessage = -1;

        //check user has not setup message and if we still show message
        if (customMessageHandler != null) {
            returnMessage = customMessageHandler.requestConfirm(new Object[]{file, String.valueOf(yesToAllPresent)});
        }

        if (returnMessage != -1) {
            return returnMessage;
        }

        if (yesToAllPresent) {

            final Object[] buttonRowObjects = {
                    Messages.getMessage("PdfViewerConfirmButton.Yes"),
                    Messages.getMessage("PdfViewerConfirmButton.YesToAll"),
                    Messages.getMessage("PdfViewerConfirmButton.No"),
                    Messages.getMessage("PdfViewerConfirmButton.Cancel")
            };

            n = JOptionPane.showOptionDialog(frame,
                    file + '\n' + Messages.getMessage("PdfViewerMessage.FileAlreadyExists")
                            '\n' + Messages.getMessage("PdfViewerMessage.ConfirmResave"),
                    Messages.getMessage("PdfViewerMessage.Overwrite"),
                    JOptionPane.DEFAULT_OPTION,
                    JOptionPane.QUESTION_MESSAGE,
                    null,
                    buttonRowObjects,
                    buttonRowObjects[0]);

        else {
            n = JOptionPane.showOptionDialog(frame,
                    file + '\n' + Messages.getMessage("PdfViewerMessage.FileAlreadyExists")
                            '\n' + Messages.getMessage("PdfViewerMessage.ConfirmResave"),
                    Messages.getMessage("PdfViewerMessage.Overwrite"),
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE,
                    null, null, null);
        }

        return n;
    }

    public void showMessageDialog(final Object info) {

        final boolean showMessage = customMessageHandler == null || customMessageHandler.showMessage(info);

        //check user has not setup message and if we still show message

        if (showMessage) {
            javax.swing.SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame, info));
        }
    }

    public void showFirstTimePopup() {

        //allow user to disable
        final boolean showMessage = customMessageHandler == null || customMessageHandler.showMessage("first time popup");

        if (!showMessage) {
            return;
        }

        try {
            final JPanel a = new JPanel();
            a.setLayout(new BoxLayout(a, BoxLayout.Y_AXIS));

            final MouseAdapter supportListener = new MouseAdapter() {
                @Override
                public void mouseEntered(final MouseEvent e) {
                    if (GUIDisplay.allowChangeCursor) {
                        a.setCursor(new Cursor(Cursor.HAND_CURSOR));
                    }
                }

                @Override
                public void mouseExited(final MouseEvent e) {
                    if (GUIDisplay.allowChangeCursor) {
                        a.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
                    }
                }

                @Override
                public void mouseClicked(final MouseEvent e) {
                    try {
                        java.awt.Desktop.getDesktop().browse(new URI((Messages.getMessage("PdfViewer.SupportLink.Link"))));
                    catch (final Exception e1) {
                        showMessageDialog(e1.getMessage() ' ' + Messages.getMessage("PdfViewer.ErrorWebsite"));
                    }
                }
            };

            final JLabel img = new JLabel(new ImageIcon(getClass().getResource("/org/jpedal/examples/viewer/res/supportScreenshot.png")));
            img.setBorder(BorderFactory.createRaisedBevelBorder());
            img.setAlignmentX(JLabel.CENTER_ALIGNMENT);
            img.addMouseListener(supportListener);
            a.add(img);

            final JLabel supportLink = new JLabel("<html><center><u>" + Messages.getMessage("PdfViewer.SupportLink.Text1"' ' + Messages.getMessage("PdfViewer.SupportLink.Text2""</u></html>");
            supportLink.setMaximumSize(new Dimension(24560));
            supportLink.setForeground(Color.BLUE);
            supportLink.addMouseListener(supportListener);
            supportLink.setAlignmentX(JLabel.CENTER_ALIGNMENT);
            a.add(supportLink);
            a.add(Box.createRigidArea(new Dimension(1010)));

            JOptionPane.showMessageDialog(
                    frame,
                    a,
                    Messages.getMessage("PdfViewerTitle.RunningFirstTime"),
                    JOptionPane.PLAIN_MESSAGE);
        catch (final Exception | Error e) {
            System.err.println(Messages.getMessage("PdfViewerFirstRunDialog.Error"' ' + e);
        }
    }

    public int showConfirmDialog(final Object message, final String title, final int optionType, final int messageType) {

        int returnMessage = -1;

        //check user has not setup message and if we still show message
        if (customMessageHandler != null) {
            returnMessage = customMessageHandler.requestConfirm(new Object[]{message, title, String.valueOf(optionType), String.valueOf(messageType)});
        }

        if (returnMessage == -1) {
            return JOptionPane.showConfirmDialog(frame, message, title, optionType, messageType);
        else {
            return returnMessage;
        }
    }

    public void setDownloadProgress(final String message, final int percentage) {
        downloadBar.setProgress(message, percentage);

        setMultibox(new int[]{});
    }

    public void updateStatusMessage(final String message) {
        statusBar.updateStatus(message, 0);
    }

    public void resetStatusMessage(final String message) {
        statusBar.resetStatus(message);

    }

    public void setStatusProgress(final int size) {
        statusBar.setProgress(size);

        setMultibox(new int[]{});
    }

    /**
     * Gets the current divider location for the split pane
     *
     @return int values representing the current divider location
     */
    public int getSplitDividerLocation() {
        return displayPane.getDividerLocation();
    }

    /**
     * Get the print dialog.
     *
     @param printersList   String array of all available printers
     @param defaultPrinter String representing the default printer
     @return a print dialog.
     */
    public PrintPanel printDialog(final String[] printersList, final String defaultPrinter) {

        final JDialog printDialog = new JDialog((Framenull, Messages.getMessage("PdfViewerLabel.Printer")true);
        printDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        //get default resolution
        String propValue = properties.getValue("defaultDPI");
        int defaultDPI = -1;
        if (propValue != null && !propValue.isEmpty()) {
            try {
                propValue = propValue.replaceAll("[^0-9]""");
                defaultDPI = Integer.parseInt(propValue);
            catch (final NumberFormatException e) {
                LogWriter.writeLog("Caught an Exception " + e);
            }
        }

        if (printPanel == null) {
            printPanel = new PrintPanel(printersList, defaultPrinter, getPaperSizes(), defaultDPI, commonValues.getCurrentPage(), decode_pdf);
        else {
            printPanel.resetDefaults(printersList, defaultPrinter, commonValues.getPageCount(), commonValues.getCurrentPage());
        }

        printDialog.getContentPane().add(printPanel);

        printDialog.setSize(670415);
        printDialog.setResizable(false);
        printDialog.setLocationRelativeTo(frame);
        printDialog.setName("printDialog");
        printDialog.setVisible(true);

        printDialog.remove(printPanel);

        return printPanel;
    }

    /**
     * Gets the paper sizes for use by the print dialog
     *
     @return a PaperSizes object holding all paper sizes
     */
    public PaperSizes getPaperSizes() {
        if (paperSizes == null) {
            paperSizes = new PaperSizes(properties.getValue("defaultPagesize"));
        }
        return paperSizes;
    }

    private void setThumbnails() {
        final org.jpedal.utils.SwingWorker worker = new org.jpedal.utils.SwingWorker() {
            @Override
            public Object construct() {

                if (thumbnails.isShownOnscreen()) {
                    setupThumbnailPanel();

                    thumbnails.generateOtherVisibleThumbnails(commonValues.getCurrentPage());
                }

                return null;
            }
        };
        worker.start();
    }

    /**
     * Set the search text field to be used for the menu bar search input
     *
     @param searchText A JTextField to be used for search input
     */
    public void setSearchText(final Object searchText) {
        this.searchText = (JTextFieldsearchText;
    }

    /**
     * Set the search list to be used to hold search results in menu bar search mode
     *
     @param results an implementation of SearchList
     */
    public void setResults(final SearchList results) {

        if (searchInMenu && results.getResultCount() == 0) {
            showMessageDialog(Messages.getMessage("PdfViewerFileMenuFind.noResultText"" \"" + results.getSearchTerm() '"', Messages.getMessage("PdfViewerFileMenuFind.noResultTitle"), JOptionPane.INFORMATION_MESSAGE);
        }
    }

    /**
     * Get the JTabbedPane, as an Object, used for the side tab bar
     *
     @return JTabbedPane used for the side tab bar
     */
    public JTabbedPane getSideTabBar() {
        return navOptionsPanel;
    }

    private void enableSearchItems(final boolean enabled) {
        if (searchInMenu) { //Menu Bar search
            searchText.setEnabled(enabled);
            options.setEnabled(enabled);
            swButtons.getButton(Commands.NEXTRESULT).setEnabled(false);
            swButtons.getButton(Commands.PREVIOUSRESULT).setEnabled(false);
        else if (swButtons.getButton(Commands.FIND!= null) { //External Window search
            swButtons.getButton(Commands.FIND).setEnabled(enabled);
        }
    }

    /**
     * Removes the search window from view with option to clear search results
     *
     */
    private void removeSearchWindow() {
        searchFrame.removeSearchWindow(false);
    }

    /**
     * Alter property using properties files keys and values to alter the user interface
     *
     @param value String representation of the property key
     @param set   true to turn on option, false to turn off
     */
    public void alterProperty(final String value, final boolean set) {
        GUIModifier.alterProperty(value, set, this);
    }

    /**
     * Method to dispose of objects used by the interface before closing.
     */
    @SuppressWarnings({"OverlyLongMethod""java:S138"})
    public void dispose() {

        for (final AWTEventListener listener : Toolkit.getDefaultToolkit().getAWTEventListeners()) {
            Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
        }

        for (final ComponentListener listener : frame.getComponentListeners()) {
            frame.removeComponentListener(listener);
        }

        if (options != null) {
            for (final ItemListener listener : options.getItemListeners()) {
                options.removeItemListener(listener);
            }
        }
        if (menu != null) {
            for (final PopupMenuListener listener : menu.getPopupMenuListeners()) {
                menu.removePopupMenuListener(listener);
            }
            menu.removeAll();
        }

        thumbnails.removeAllListeners();
        thumbnails.dispose();

        if (scrollListener != null) {
            scrollListener.dispose();
        }

        mouseHandler.dispose();

        if (navButtons != null) {
            navButtons.removeAll();
        }

        menuItems.dispose();

        if (coords != null) {
            coords.removeAll();
        }

        if (navOptionsPanel != null) {
            disposeNavOptions(navOptionsPanel);
        }

        if (thumbscroll != null) {
            disposeThumbnailScroll(thumbscroll);
        }

        if (scrollPane != null) {
            disposeScrollPane(scrollPane);
        }

        if (signaturesTree != null) {
            signaturesTree.setCellRenderer(null);
            signaturesTree.removeAll();
        }

        if (layersPanel != null) {
            layersPanel.removeAll();
        }

        if (annotationsPanel != null) {
            annotationsPanel.dispose();
        }


        searchFrame.dispose();

        if (navToolBar != null) {
            disposeNavToolBar(navToolBar);
        }

        if (pagesToolBar != null) {
            disposePageToolBar(pagesToolBar);
        }

        //release memory at end
        if (memoryMonitor != null) {
            disposeMemoryMonitor();
        }

        //Ensure Timer is closed as does not use daemon thread
        if (viewListener != null) {
            viewListener.dispose();
        }

        //Below line causes a NullPointerException
        removeSearchWindow();

        swButtons.dispose();

        //display pane cleanup
        if (displayPane != null) {
            disposeDisplayPane();
        }

        if (attachmentsPanel != null) {
            attachmentsPanel.dispose();
            attachmentsPanel = null;
        }

        final JTree disposeTree = tree.getTree();
        if (disposeTree != null) {
            disposeTree(disposeTree);
        }
        removePageListener();

        removeGUIListeners((Componentdecode_pdf);

        if (glassPane != null) {
            removeGUIListeners(glassPane);
            glassPane.removeAll();
        }

        disposeFrame();

        if (lpane != null) {
            for (final PropertyChangeListener listener : lpane.getPropertyChangeListeners()) {
                lpane.removePropertyChangeListener(listener);
            }
            for (final ComponentListener listener : lpane.getComponentListeners()) {
                lpane.removeComponentListener(listener);
            }
            lpane.removeAll();
        }

        if (containerForThumbnails != null) {
            for (final PropertyChangeListener listener : containerForThumbnails.getPropertyChangeListeners()) {
                containerForThumbnails.removePropertyChangeListener(listener);
            }
            containerForThumbnails.removeAll();
        }

        if (frame != null) {
            frame.removeAll();
        }

        decode_pdf.closePdfFile();
        decode_pdf.dispose();
        ((Containerdecode_pdf).removeAll();

        setGlobalVariablesToNull();

        tree = null;
        scalingFloatValues = null;
        rotationBox = null;
        scalingBox = null;
    }

    private void setGlobalVariablesToNull() {
        options = null;
        menu = null;
        scrollListener = null;
        mouseHandler = null;
        pageTitle = null;
        bookmarksTitle = null;
        signaturesTitle = null;
        layersTitle = null;
        annotationTitle = null;
        attachmentsTitle = null;
        currentCommandListener = null;
        navButtons = null;
        coords = null;
        navOptionsPanel = null;
        thumbscroll = null;
        scrollPane = null;
        textFont = null;
        statusBar = null;
        downloadBar = null;
        pageCounter2 = null;
        pageCounter3 = null;
        signaturesTree = null;
        layersPanel = null;
        annotationsPanel = null;
        searchFrame = null;
        navToolBar = null;
        pagesToolBar = null;
        layersObject = null;
        viewListener = null;
        glassPane = null;
        frame = null;
        portfolioParent = null;
        decode_pdf = null;
    }

    private static void disposePageToolBar(final JToolBar toolBar) {
        for (final MouseListener listener : toolBar.getMouseListeners()) {
            toolBar.removeMouseListener(listener);
        }
        for (final MouseMotionListener listener : toolBar.getMouseMotionListeners()) {
            toolBar.removeMouseMotionListener(listener);
        }
        toolBar.removeAll();
    }

    private static void disposeNavToolBar(final JToolBar navBar) {
        final Component[] comps = navBar.getComponents();
        for (final Component comp : comps) {
            for (final PropertyChangeListener listener : comp.getPropertyChangeListeners()) {
                comp.removePropertyChangeListener(listener);
            }
            for (final ComponentListener listener : comp.getComponentListeners()) {
                comp.removeComponentListener(listener);
            }
        }
        navBar.removeAll();
    }

    private static void disposeScrollPane(final JScrollPane scrollPane) {
        for (final PropertyChangeListener listener : scrollPane.getPropertyChangeListeners()) {
            scrollPane.removePropertyChangeListener(listener);
        }
        for (final MouseWheelListener listener : scrollPane.getMouseWheelListeners()) {
            scrollPane.removeMouseWheelListener(listener);
        }
        for (final MouseMotionListener listener : scrollPane.getMouseMotionListeners()) {
            scrollPane.removeMouseMotionListener(listener);
        }
        for (final FocusListener listener : scrollPane.getFocusListeners()) {
            scrollPane.removeFocusListener(listener);
        }
        scrollPane.removeAll();
    }

    private static void disposeNavOptions(final JTabbedPane navOptions) {
        for (final MouseListener listener : navOptions.getMouseListeners()) {
            navOptions.removeMouseListener(listener);
        }
        for (final ChangeListener listener : navOptions.getChangeListeners()) {
            navOptions.removeChangeListener(listener);
        }
        navOptions.removeAll();
    }

    private static void disposeThumbnailScroll(final JScrollBar thumbnailScroll) {
        for (final MouseListener listener : thumbnailScroll.getMouseListeners()) {
            thumbnailScroll.removeMouseListener(listener);
        }
        for (final PropertyChangeListener listener : thumbnailScroll.getPropertyChangeListeners()) {
            thumbnailScroll.removePropertyChangeListener(listener);
        }
        thumbnailScroll.removeAll();
    }

    private void disposeMemoryMonitor() {
        memoryMonitor.stop();
        final ActionListener[] al = memoryMonitor.getActionListeners();
        for (int i = 0; i != al.length; i++) {
            memoryMonitor.removeActionListener(al[i]);
        }
    }

    private void disposeFrame() {
        if (frame instanceof JFrame) {
            final JFrame disposeFrame = (JFrameframe;
            for (final ComponentListener listener : disposeFrame.getComponentListeners()) {
                disposeFrame.removeComponentListener(listener);
            }
        else {
            final Component disposeComp = frame;
            for (final ComponentListener listener : disposeComp.getComponentListeners()) {
                disposeComp.removeComponentListener(listener);
            }
        }
    }

    private static void removeGUIListeners(final Component comp) {
        for (final KeyListener listener : comp.getKeyListeners()) {
            comp.removeKeyListener(listener);
        }

        for (final MouseListener listener : comp.getMouseListeners()) {
            comp.removeMouseListener(listener);
        }

        for (final MouseWheelListener listener : comp.getMouseWheelListeners()) {
            comp.removeMouseWheelListener(listener);
        }

        for (final MouseMotionListener listener : comp.getMouseMotionListeners()) {
            comp.removeMouseMotionListener(listener);
        }
    }

    private static void disposeTree(final JTree disposeTree) {
        for (final MouseListener listener : disposeTree.getMouseListeners()) {
            disposeTree.removeMouseListener(listener);
        }
        for (final KeyListener listener : disposeTree.getKeyListeners()) {
            disposeTree.removeKeyListener(listener);
        }
        disposeTree.removeAll();
    }

    private void disposeDisplayPane() {
        for (final PropertyChangeListener listener : displayPane.getPropertyChangeListeners()) {
            displayPane.removePropertyChangeListener(listener);
        }
        for (final MouseListener listener : displayPane.getMouseListeners()) {
            displayPane.removeMouseListener(listener);
        }
        for (final ComponentListener listener : displayPane.getComponentListeners()) {
            displayPane.removeComponentListener(listener);
        }
        displayPane.removeAll();
    }

    /**
     * Not part of API - used internally
     *
     @return true if it is page turn scaling is appropriate
     */
    public boolean getPageTurnScalingAppropriate() {
        return pageTurnScalingAppropriate;
    }

    /**
     * Not part of API - used internally
     *
     @return the gui cursor object
     */
    public SwingCursor getGUICursor() {
        return guiCursor;
    }

    /**
     * Checks if title matches the title of any of the side tab bar tabs
     *
     @param title String title to check for.
     @return String title of tab found.
     */
    public String getTitles(final String title) {
        if (title.equals(pageTitle)) {
            return pageTitle;
        else if (title.equals(bookmarksTitle)) {
            return bookmarksTitle;
        else if (title.equals(signaturesTitle)) {
            return signaturesTitle;
        else if (title.equals(layersTitle)) {
            return layersTitle;
        else if (title.equals(annotationTitle)) {
            return annotationTitle;
        else if (title.equals(attachmentsTitle)) {
            return attachmentsTitle;
        }
        return null;
    }

    /**
     * Get the GUIButtons object that holds all buttons used by the user interface
     *
     @return GUIButtons implementation used by the user interface
     */
    public Buttons getButtons() {
        return swButtons;
    }

    /**
     * Overrides method from GUI.java, see GUI.java for DOCS.
     */
    private void setViewerIcon() {

        if (frame instanceof JFrame) {

            //Check if file location provided
            final URL path = guiCursor.getURLForImage("icon.png");
            if (path != null) {
                try {
                    final BufferedImage fontIcon = JDeli.read(path.openStream());
                    ((Windowframe).setIconImage(fontIcon);
                catch (final Exception e) {
                    LogWriter.writeLog("Exception attempting to set icon " + e);
                }
            }
        }

    }

    /**
     * Get the SwingMenuItems object that holds all the menu items used by the user interface.
     *
     @return SwingMenuItems implementation used by the user interface
     */
    public SwingMenuItems getMenuItems() {
        return menuItems;
    }

    /**
     * Key method that calls decodePage from GUI.java
     */
    public void decodePage() {

        if (SwingAnnotationPanel.addPanel()) {
            annotationsPanel.getAnnotationListener().setFormMode(SwingAnnotationPanel.FORMMODE.CREATION);
        }

        //Ensure thumbnail scroll bar is updated when page changed
        if (thumbscroll != null) {
            setThumbnailScrollBarValue(commonValues.getCurrentPage() 1);
        }
        decodeGUIPage();

    }

    /**
     * Get the PdfDecoder object used to display the PDF in the user interface
     *
     @return PdfDecoder object used for display
     */
    public PdfDecoder getPdfDecoder() {
        return ((PdfDecoderdecode_pdf);
    }

    /**
     * Overrides method from GUI.java, see GUI.java for DOCS.
     */
    private void addComboListenerAndLabel(final SwingCombo combo) {
        //add listener
        combo.addActionListener(currentCommandListener);
    }

    /**
     * Overrides method from GUI.java, see GUI.java for DOCS.
     */
    private void addGUIComboBoxes(final SwingCombo combo) {
        swButtons.getTopButtons().add(combo);
    }

    /**
     * Overrides method from GUI.java, see GUI.java for DOCS.
     */
    private void setupCenterPanelBackground() {
        String propValue = properties.getValue("replacePdfDisplayBackground");
        Color background = new Color(190190190);
        if (!propValue.isEmpty()
                && "true".equalsIgnoreCase(propValue)) {
            propValue = properties.getValue("pdfDisplayBackground");
            if (!propValue.isEmpty()) {
                currentCommands.executeCommand(ViewerCommands.SETDISPLAYBACKGROUND, new Object[]{Integer.parseInt(propValue)});
            }
            background = new Color(Integer.parseInt(propValue));

        else {
            if (decode_pdf.getDecoderOptions().getDisplayBackgroundColor() != null) {
                background = decode_pdf.getDecoderOptions().getDisplayBackgroundColor();
            else if (decode_pdf.useNewGraphicsMode()) {
                background = new Color(555565);
            }
        }

        ((Componentdecode_pdf).setBackground(background);
        frame.setBackground(background);
    }

    /**
     * Overrides method from GUI.java, see GUI.java for DOCS.
     */
    private void setupComboBoxes() {

        final String[] rotationValues = {"0""90""180""270"};

        final String[] scalingValues = {Messages.getMessage("PdfViewerScaleWindow.text"), Messages.getMessage("PdfViewerScaleHeight.text"),
                Messages.getMessage("PdfViewerScaleWidth.text"),
                "25%""50%""75%""100%""125%""150%""200%""250%""500%""750%""1000%"};

        //set new default if appropriate
        String choosenScaling = System.getProperty("org.jpedal.defaultViewerScaling");

        //Only use value from properties is VM arguement not set
        if (choosenScaling == null) {
            choosenScaling = properties.getValue("startScaling");
        }

        if (choosenScaling != null) {
            final int total = scalingValues.length;
            for (int aa = 0; aa < total; aa++) {
                if (scalingValues[aa].equals(choosenScaling)) {
                    defaultSelection = aa;
                    aa = total;
                }
            }
        }

        scalingBox = new SwingCombo(scalingValues);
        scalingBox.setEditable(true);
        scalingBox.setSelectedIndex(defaultSelection)//set default before we add a listener

        //if you enable, remember to change rotation and quality Comboboxes
        rotationBox = new SwingCombo(rotationValues);
        rotationBox.setSelectedIndex(0)//set default before we add a listener
    }

    /**
     * Overrides method from GUI.java, see GUI.java for DOCS.
     */
    private void setupKeyboardControl() {
        ((Componentdecode_pdf).addKeyListener(new KeyAdapter() {
            int count;
            int pageChange;
            java.util.Timer t2;

            @Override
            public void keyPressed(final KeyEvent e) {
                final JScrollBar scroll = scrollPane.getVerticalScrollBar();

                final int keyPressed = e.getKeyCode();

                if (keyPressed == KeyEvent.VK_LEFT || keyPressed == KeyEvent.VK_RIGHT) {

                    //Only use left and right arrows to change pages if page is smaller than display area
                    if (scrollPane.getWidth() ((Componentdecode_pdf).getWidth()) {
                        if (keyPressed == KeyEvent.VK_LEFT) { //change page
                            pageChange--;
                        else //change page
                            pageChange++;
                        }

                    }

                else if ((keyPressed == KeyEvent.VK_UP || keyPressed == KeyEvent.VK_DOWN&& (count == 0)) {
                    if (keyPressed == KeyEvent.VK_UP
                            && scroll.getValue() == scroll.getMinimum()
                            && commonValues.getCurrentPage() 1) {

                        //change page
                        pageChange--;

                    else if (keyPressed == KeyEvent.VK_DOWN
                            && (scroll.getValue() == scroll.getMaximum() - scroll.getHeight() || scroll.getHeight() == 0)
                            && commonValues.getCurrentPage() < decode_pdf.getPageCount()) {

                        //change page
                        pageChange++;

                    }
                }

                count++;

                if (pageChange != 0) {
                    if (t2 != null) {
                        t2.cancel();
                    }

                    final TimerTask t = new TimerTask() {

                        @Override
                        public void run() {

                            int p = (commonValues.getCurrentPage() + pageChange);

                            if (p < 1) {
                                p = 1;
                            else if (p > decode_pdf.getPageCount()) {
                                p = decode_pdf.getPageCount();
                            }

                            if (p != commonValues.getCurrentPage()) {
                                final String page = String.valueOf(p);

                                SwingUtilities.invokeLater(() -> {
                                    currentCommands.executeCommand(ViewerCommands.GOTO, new Object[]{page});

                                    if (scroll.getValue() == scroll.getMinimum()
                                            && commonValues.getCurrentPage() 1) {

                                        scroll.setValue(scroll.getMaximum());

                                    else if ((scroll.getValue() == scroll.getMaximum() - scroll.getHeight() || scroll.getHeight() == 0)
                                            && commonValues.getCurrentPage() < decode_pdf.getPageCount()) {

                                        scroll.setValue(scroll.getMinimum());
                                    }
                                });

                            }
                            pageChange = 0;

                            //Add cancel for the timer at the end of this task.
                            //This should ensure the timer is not kept active
                            if (t2 != null) {
                                t2.cancel();
                            }
                        }
                    };

                    //restart - if its not stopped it will trigger page update
                    t2 = new java.util.Timer();
                    t2.schedule(t, 500);
                }
            }

            @Override
            public void keyReleased(final KeyEvent e) {
                count = 0;
            }
        });
    }

    /**
     * Overrides method from GUI.java, see GUI.java for DOCS.
     */
    private void setupPDFDisplayPane() {

        if (SwingAnnotationPanel.addPanel()) {
            glassPane = new JPanel();
            glassPane.setLayout(null);
            glassPane.setOpaque(false);

            lpane.add(scrollPane, 00);
            lpane.add(glassPane, 11);
        }

        thumbscroll = new JScrollBar(JScrollBar.VERTICAL, 0101);
        thumbscroll.setName("ThumbnailScroll");

        if (scrollListener == null) {
            scrollListener = new SwingScrollListener(this, "true".equalsIgnoreCase(properties.getValue("previewOnSingleScroll")));
        }
        thumbscroll.addAdjustmentListener(scrollListener);
        thumbscroll.addMouseListener(scrollListener);

        containerForThumbnails.setLayout(new BorderLayout());
        containerForThumbnails.add(thumbscroll, BorderLayout.EAST);
        scrollPane.getViewport().add(((Componentdecode_pdf));
        if (SwingAnnotationPanel.addPanel()) {
            containerForThumbnails.add(lpane, BorderLayout.CENTER);
        else {
            containerForThumbnails.add(scrollPane, BorderLayout.CENTER);
        }

        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        scrollPane.getVerticalScrollBar().setUnitIncrement(80);
        scrollPane.getHorizontalScrollBar().setUnitIncrement(80);

        //Keyboard control of next/previous page
        setupKeyboardControl();

    }

    /**
     * Overrides method from GUI.java, see GUI.java for DOCS.
     */
    private void setupBorderPanes() {

        //Create a left-right split pane with tabs and add to main display
        navOptionsPanel.setTabPlacement(JTabbedPane.LEFT);
        navOptionsPanel.setOpaque(true);
        //Use start size as min width to keep divider from covering tabs
        navOptionsPanel.setMinimumSize(new Dimension(collapsedSize, 100));
        navOptionsPanel.setName("NavPanel");
        navOptionsPanel.setFocusable(false);

        setupSidebarTitles();

        displayPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, navOptionsPanel, containerForThumbnails);

        displayPane.setOneTouchExpandable(false);

        //update scaling when divider moved
        displayPane.addPropertyChangeListener("dividerLocation", e -> {

            //hack to get it to use current values instead of old values
            scrollPane.getViewport().setSize((scrollPane.getViewport().getWidth() (Integere.getOldValue() (Integere.getNewValue()),
                    scrollPane.getViewport().getHeight());

            //Keep track of the current divider position
            final int dividerPos = (Integere.getNewValue();

            if (tabsExpanded && (dividerPos > collapsedSize)) {
                expandedSize = dividerPos;
            }

            scaleAndRotate();

        });

        if (DecoderOptions.isRunningOnMac && UIManager.getLookAndFeel().isNativeLookAndFeel()) {
            navOptionsPanel.addTab(pageTitle, thumbnails);
            navOptionsPanel.setTitleAt(navOptionsPanel.getTabCount() 1, pageTitle);

            if (thumbnails.isShownOnscreen()) {
                navOptionsPanel.addTab(bookmarksTitle, tree);
                navOptionsPanel.setTitleAt(navOptionsPanel.getTabCount() 1, bookmarksTitle);
            }

        else {

            //Remove borders from thumbnail and bookmark tab in windows as looks better
            tree.setBorder(null);
            thumbnails.setBorder(null);

            if (thumbnails.isShownOnscreen()) {
                final VTextIcon textIcon1 = new VTextIcon(navOptionsPanel, pageTitle);
                navOptionsPanel.addTab(null, textIcon1, thumbnails);
            }

            final VTextIcon textIcon2 = new VTextIcon(navOptionsPanel, bookmarksTitle);
            navOptionsPanel.addTab(null, textIcon2, tree);

        }

        String propValue = properties.getValue("startSideTabOpen");
        if (!propValue.isEmpty()) {
            sideTabBarOpenByDefault = "true".equalsIgnoreCase(propValue);
        }

        propValue = properties.getValue("startSelectedSideTab");
        if (!propValue.isEmpty()) {
            startSelectedTab = propValue;
        }

        if (!hasListener) {
            hasListener = true;
            navOptionsPanel.addMouseListener(new MouseAdapter() {

                @Override
                public void mouseClicked(final MouseEvent mouseEvent) {
                    handleTabbedPanes();
                }
            });
        }

    }

    /**
     * Sets up the position and visual style of the items on the bottom toolbar
     * (page navigation buttons etc).
     */
    private void setupBottomToolBarItems() {

        pageCounter2.setEditable(true);
        pageCounter2.setToolTipText(Messages.getMessage("PdfViewerTooltip.goto"));
        pageCounter2.setBorder(
                BorderFactory.createCompoundBorder(
                        BorderFactory.createEmptyBorder(1111),
                        pageCounter2.getBorder()));
        pageCounter2.setColumns(2);
        pageCounter2.setMaximumSize(pageCounter2.getPreferredSize());

        pageCounter2.addActionListener(arg0 -> {

            String value = pageCounter2.getText().trim();

            //Check if mapped and if so set the actual page number
            //This is updated later to the correct page label
            final PdfObjectReader reader = decode_pdf.getIO();
            if (reader != null) {
                final PageLabels pageLabels = reader.getPageLabels();
                if (pageLabels != null && pageLabels.containsValue(value)) {
                    final Set<Map.Entry<Integer, String>> entries = pageLabels.entrySet();
                    for (final Map.Entry<Integer, String> e : entries) {
                        if (e.getValue().equals(value)) {
                            value = e.getKey().toString();
                            break;
                        }
                    }
                }
            }
            currentCommands.executeCommand(ViewerCommands.GOTO, new Object[]{value});
        });
        pageCounter2.setHorizontalAlignment(JTextField.CENTER);
        setPageNumber();

        pageCounter3 = new JLabel(Messages.getMessage("PdfViewerOfLabel.text"' ');
        pageCounter3.setOpaque(false);

        navToolBar.setLayout(new BoxLayout(navToolBar, BoxLayout.LINE_AXIS));
        navToolBar.setFloatable(false);

        pagesToolBar.setFloatable(false);

        navButtons.setBorder(BorderFactory.createEmptyBorder());
        navButtons.setLayout(new BorderLayout());
        navButtons.setFloatable(false);
        navButtons.setPreferredSize(new Dimension(524));
    }

    /**
     * Creates a glowing border around the PDFDisplayPane.
     */
    private void setupPDFBorder() {
        //add a border
        if (decode_pdf.useNewGraphicsMode()) {
            decode_pdf.setPDFBorder(new AbstractBorder() {

                @Override
                public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
                    final Graphics2D g2 = (Graphics2Dg;

                    final int cornerDepth = (glowThickness / 21;

                    //left
                    g2.setPaint(new GradientPaint(x, 0, glowOuterColor, x + glowThickness, 0, glowInnerColor));
                    g2.fillRect(x, y + glowThickness, glowThickness, height - (glowThickness * 2));

                    //bottom left corner
                    g2.setPaint(new GradientPaint(x - cornerDepth + glowThickness, y + height + cornerDepth - glowThickness, glowOuterColor, x + glowThickness, y + height - glowThickness, glowInnerColor));
                    g2.fillRect(x, y + height - glowThickness, glowThickness, glowThickness);

                    //below
                    g2.setPaint(new GradientPaint(0, y + height, glowOuterColor, 0, y + height - glowThickness, glowInnerColor));
                    g2.fillRect(x + glowThickness, y + height - glowThickness, width - (glowThickness * 2), glowThickness);

                    //bottom right corner
                    g2.setPaint(new GradientPaint(x + width + cornerDepth - glowThickness, y + height + cornerDepth - glowThickness, glowOuterColor, x + width - glowThickness, y + height - glowThickness, glowInnerColor));
                    g2.fillRect(x + width - glowThickness, y + height - glowThickness, glowThickness, glowThickness);

                    //right
                    g2.setPaint(new GradientPaint(x + width, 0, glowOuterColor, x + width - glowThickness, 0, glowInnerColor));
                    g2.fillRect(x + width - glowThickness, y + glowThickness, glowThickness, height - (glowThickness * 2));

                    //top right corner
                    g2.setPaint(new GradientPaint(x + width - glowThickness + cornerDepth, y + glowThickness - cornerDepth, glowOuterColor, x + width - glowThickness, y + glowThickness, glowInnerColor));
                    g2.fillRect(x + width - glowThickness, y, glowThickness, glowThickness);

                    //above
                    g2.setPaint(new GradientPaint(0, y, glowOuterColor, 0, y + glowThickness, glowInnerColor));
                    g2.fillRect(x + glowThickness, y, width - (glowThickness * 2), glowThickness);

                    //top left corner
                    g2.setPaint(new GradientPaint(x - cornerDepth + glowThickness, y - cornerDepth + glowThickness, glowOuterColor, x + glowThickness, y + glowThickness, glowInnerColor));
                    g2.fillRect(x, y, glowThickness, glowThickness);

                    //draw black over top
                    g2.setPaint(Color.black);
                    g2.drawRect(x + glowThickness, y + glowThickness, width - (glowThickness * 2), height - (glowThickness * 2));

                }

                @Override
                public Insets getBorderInsets(final Component c, final Insets insets) {
                    insets.set(glowThickness, glowThickness, glowThickness, glowThickness);
                    return insets;
                }
            });

        else {
            decode_pdf.setPDFBorder(BorderFactory.createLineBorder(Color.black, 1));
        }

    }

    /**
     * Creates the top two menu bars, the file loading and viewer properties one
     * and the PDF toolbar, the one which controls printing, searching etc.
     */
    private void createTopMenuBar() {
        top.setLayout(new BorderLayout());
        if (frame instanceof JFrame) {
            ((RootPaneContainerframe).getContentPane().add(top, BorderLayout.NORTH);
        else {
            frame.add(top, BorderLayout.NORTH);
        }
    }

    /**
     * Ensure Document is redrawn when frame is resized and scaling set to
     * width, height or window.
     */
    private void redrawDocumentOnResize() {
        frame.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(final ComponentEvent e) {
                if (((Componentdecode_pdf).getParent() != null
                        && (getSelectedComboIndex(Commands.SCALING|| decode_pdf.getDisplayView() == Display.FACING)) { //always rezoom in facing mode for turnover
                    scaleAndRotate();
                }
            }
        });
    }

    /**
     * Creates the Main Display Window for all of the Swing Content.
     *
     @param width  is of type int
     @param height is of type int
     */
    private void createMainViewerWindow(final int width, final int height) {
        if (frame instanceof JFrame) {
            frame.setSize(width, height);
            ((Windowframe).setLocationRelativeTo(null)//centre on screen
            ((JFrameframe).setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
            ((Windowframe).addWindowListener(new FrameCloser(currentCommands, this, decode_pdf, thumbnails, commonValues, properties));
            frame.setVisible(true);
        }
    }

    /**
     * Returns the RecentDocuments used to hand the recent document list
     *
     @return RecentDocuments used by the user interface
     */
    public RecentDocuments getRecentDocument() {
        if (recent == null) {
            recent = new RecentDocuments(PropertiesFile.getNoRecentDocumentsToDisplay());
        }

        return recent;
    }

    /**
     * Create the RecentDocuments for the user interface
     */
    public void setRecentDocument() {
        recent = new RecentDocuments(PropertiesFile.getNoRecentDocumentsToDisplay());
    }

    /**
     * Open a file in the Viewer
     *
     @param fileToOpen String filename for the file to be opened
     */
    public void openFile(final String fileToOpen) {
        if (fileToOpen != null) {
            OpenFile.open(fileToOpen, commonValues, searchFrame, this, decode_pdf, properties, thumbnails);
        }
    }

    /**
     * Open a file in the Viewer
     *
     @param fileName String filename for the file to be opened
     */
    public void open(final String fileName) {
        OpenFile.open(fileName, commonValues, searchFrame, this, decode_pdf, properties, thumbnails);
    }

    /**
     * Enable and make visible different parts of the page counter of all of it
     *
     @param value      PageCounter value for the different page counter elements
     @param enabled    true to enable the counter section, false to disable.
     @param visibility true to make visible the counter section, false to disable.
     */
    public void enablePageCounter(final PageCounter value, final boolean enabled, final boolean visibility) {
        switch (value) {

            case PAGECOUNTER1:
                pageCounter1.setVisible(visibility);
                pageCounter1.setEnabled(enabled);
                break;

            case PAGECOUNTER2:
                pageCounter2.setVisible(visibility);
                pageCounter2.setEnabled(enabled);
                break;

            case PAGECOUNTER3:
                pageCounter3.setVisible(visibility);
                pageCounter3.setEnabled(enabled);
                break;

            case ALL:
                pageCounter1.setVisible(visibility);
                pageCounter1.setEnabled(enabled);
                pageCounter2.setVisible(visibility);
                pageCounter2.setEnabled(enabled);
                pageCounter3.setVisible(visibility);
                pageCounter3.setEnabled(enabled);
                break;

            default:
                System.out.println("No Value detected, please choose from Enum PageCounter in GUI.java");
                break;
        }
    }

    /**
     * Set the text content for a section of the page counter.
     *
     @param value PageCounter value for the different page counter elements
     @param text  String value to be place in the page counter
     */
    public void setPageCounterText(final PageCounter value, final String text) {
        switch (value) {

            case PAGECOUNTER1:
                pageCounter1.setText(text);
                break;

            case PAGECOUNTER2:
                pageCounter2.setText(text);
                break;

            case PAGECOUNTER3:
                pageCounter3.setText(text);
                break;

            default:
                System.out.println("No Value detected, please choose from Enum PageCounter in GUI.java");
                break;
        }
    }

    /**
     * Get the page counter segments
     *
     @param value PageCounter value for the different page counter elements
     @return an Object containing the section of the page counter
     */
    public Object getPageCounter(final PageCounter value) {
        switch (value) {

            case PAGECOUNTER1:
                return pageCounter1;

            case PAGECOUNTER2:
                return pageCounter2;

            case PAGECOUNTER3:
                return pageCounter3;

            default:
                System.out.println("No Value detected, please choose from Enum PageCounter in GUI.java");
                return 0;
        }
    }

    /**
     * Update the page counter input size to accommodate the contents
     */
    public void updateTextBoxSize() {
        //Set textbox size
        int col = (String.valueOf(commonValues.getPageCount())).length();

        if (decode_pdf.getIO().getPageLabels() != null) {
            final Collection<String> values = decode_pdf.getIO().getPageLabels().values();
            for (final String value : values) {
                final int length = value.length();
                if (col < length) {
                    col = length;
                }
            }
        }
        if (decode_pdf.getDisplayView() == Display.FACING || decode_pdf.getDisplayView() == Display.CONTINUOUS_FACING) {
            col *= 2;
        }
        if (col < 2) {
            col = 2;
        }

        pageCounter2.setColumns(col);
        pageCounter2.setMaximumSize(pageCounter2.getPreferredSize());

        navToolBar.invalidate();
        navToolBar.doLayout();

    }

    /**
     * Enable and or set visibility for the cursor display for multi box
     *
     @param enabled true to enable, false to disable
     @param visible true to make visible, false to make invisible
     */
    public void enableCursor(final boolean enabled, final boolean visible) {
        cursor.setEnabled(enabled);
        cursor.setVisible(visible);
    }

    /**
     * Enable and or set visibility for the memory bar display for multi box
     *
     @param enabled true to enable, false to disable
     @param visible true to make visible, false to make invisible
     */
    public void enableMemoryBar(final boolean enabled, final boolean visible) {
        multibox.setEnabled(enabled);
        multibox.setVisible(visible);
    }

    /**
     * Enable and or set visibility for the navigation bar
     *
     @param enabled true to enable, false to disable
     @param visible true to make visible, false to make invisible
     */
    public void enableNavigationBar(final boolean enabled, final boolean visible) {
        navButtons.setEnabled(enabled);
        navButtons.setVisible(visible);
    }

    /**
     * Enable and or set visibility for the download display for multi box
     *
     @param enabled true to enable, false to disable
     @param visible true to make visible, false to make invisible
     */
    public void enableDownloadBar(final boolean enabled, final boolean visible) {
        downloadBar.setEnabled(enabled);
        downloadBar.setVisible(visible);
    }

    /**
     * Get the current count of tabs on the side tab bar
     *
     @return int value representing the number of tabs currently visible
     */
    public int getSidebarTabCount() {
        return navOptionsPanel.getTabCount();
    }

    /**
     * Get the title of the tab at the given position
     *
     @param pos The position of the tab on the tabbed pane
     @return String title of the given tab or an empty String is tab not present.
     */
    public String getSidebarTabTitleAt(final int pos) {
        if (navOptionsPanel.getIconAt(pos!= null) {
            return navOptionsPanel.getIconAt(pos).toString();
        else {
            return "";
        }
    }

    /**
     * Remove the tab at the specified position from the side tab bar
     *
     @param pos Tab index to removed.
     */
    public void removeSidebarTabAt(final int pos) {
        navOptionsPanel.remove(pos);
    }

    /**
     * Get the divider location for the viewer
     *
     @return double representation of the divider location
     */
    private double getDividerLocation() {
        return displayPane.getDividerLocation();
    }

    /**
     * calculates the scalingToFit required to make the given area visible
     *
     @param left   float value representing the smallest value on the x axis
     @param right  float value representing the largest value on the x axis
     @param top    float value representing the largest value on the y axis
     @param bottom float value representing the smallest value on the y axis
     @return float value representing the scalingToFit value
     */
    public float scaleToVisible(final float left, final float right, final float top, final float bottom) {

        float width = scrollPane.getViewport().getWidth() - inset - inset;
        final float height = scrollPane.getViewport().getHeight() - inset - inset;

        if (displayPane != null) {
            width -= displayPane.getDividerSize();
        }

        final float widthScaling = (right - left/ width;
        final float heightScaling = (top - bottom/ height;

        if (widthScaling > heightScaling) {
            return decode_pdf.getDPIFactory().adjustScaling(widthScaling);
        else {
            return decode_pdf.getDPIFactory().adjustScaling(heightScaling);
        }
    }

    /**
     * Get the thickness of the glow border
     *
     @return thickness of glow as an int
     */
    public static int getDropShadowDepth() {
        return glowThickness;
    }

    /**
     * Set up the split pane divider for use
     *
     @param size       size of the divider
     @param visibility true to set left side of divider visible and enabled, false to set disabled and invisible
     */
    public void setupSplitPaneDivider(final int size, final boolean visibility) {
        displayPane.setDividerSize(size);
        displayPane.getLeftComponent().setEnabled(visibility);
        displayPane.getLeftComponent().setVisible(visibility);
    }

    /**
     * Get the start / collapsed position of the split pane divider
     *
     @return divider position as a double
     */
    private static double getStartSize() {
        return collapsedSize;
    }

    /**
     * Set the start / collapsed position of the split pane divider
     *
     @param size position as an int
     */
    public static void setStartSize(final int size) {
        collapsedSize = size;
    }

    /**
     * Get the page container for the PDF display area
     *
     @return JScrollPane containing the PdfDecoder
     */
    public JScrollPane getPageContainer() {
        return scrollPane;
    }

    /**
     * Enable and or set visibility for the status bar for multi box
     *
     @param enabled true to enable, false to disable
     @param visible true to make visible, false to make invisible
     */
    public void enableStatusBar(final boolean enabled, final boolean visible) {
        statusBar.setEnabled(enabled);
        statusBar.setVisible(visible);
    }

    /**
     * Converts mouse / components coords to pdf page coords
     *
     @param x    :: The x coordinate of the cursors location in display area coordinates
     @param y    :: The y coordinate of the cursors location in display area coordinates
     @param page :: The page we are currently on
     @return Point object of the cursor location in page coordinates
     */
    public Point convertComponentCoordsToPageCoords(final int x, final int y, final int page) {
        return SwingMouseUtils.getCoordsOnPage(x, y, page, decode_pdf, this, commonValues);
    }

    /**
     * Set the display view options to be used to display the PDF.
     *
     @param displayView int value for display view from Display
     @param orientation int value for display orientation from Display
     */
    public void setDisplayView(final int displayView, final int orientation) {

        final int lastDisplayView = decode_pdf.getDisplayView();
        if (decode_pdf.isOpen() && lastDisplayView != displayView) {
            final PdfObject collectionObj = decode_pdf.getIO().getPDFObject(PdfDictionary.Collection);
            if (displayView == Display.PORTFOLIO_DETAIL || displayView == Display.PORTFOLIO_TILE) {
                if (collectionObj == null) {
                    //Incorrect mode for file type so cancel display view change
                    showMessageDialog(Messages.getMessage("PdfPortfolio.nonPortfolioIncorrectDisplay"));
                    return;
                else {

                    switch (displayView) {
                        case Display.PORTFOLIO_DETAIL:
                            setupDetailPortfolioView();
                            break;
                        case Display.PORTFOLIO_TILE:
                            setupTilePortfolioView();
                            break;
                    }
                }
            else {
                //Only set up pdf view if we were in a portfolio mode
                if ((lastDisplayView == Display.PORTFOLIO_DETAIL || lastDisplayView == Display.PORTFOLIO_TILE)) {
                    setupPdfView();
                }
            }
        }

        //Check for null as can be called before UI is set up
        if (annotationsPanel != null && SwingAnnotationPanel.addPanel()) {
            annotationsPanel.getAnnotationListener().setFormMode(SwingAnnotationPanel.FORMMODE.CREATION);
        }

        // remove listener if setup
        if (viewListener != null) {

            ((Componentdecode_pdf).removeComponentListener(viewListener);

            viewListener.dispose();
            viewListener = null;
        }

        final boolean hasChanged = setDisplayView2(displayView, orientation);
        //move to correct page
        final int pageNumber = decode_pdf.getPageNumber();
        if (pageNumber > && hasChanged) {
            try {
                decode_pdf.setPageParameters(scaling, pageNumber, decode_pdf.getDisplayRotation());
                ((Componentdecode_pdf).invalidate();
                ((JComponentdecode_pdf).updateUI();
                decode_pdf.decodePage(pageNumber);

                //Hide annotations tab if not needed
                if (decode_pdf.getDisplayView() == Display.SINGLE_PAGE) {
                    checkTabShown(annotationTitle);
                else {
                    removeTab(annotationTitle);
                }

            catch (final Exception e) {
                LogWriter.writeLog("Exception: " + e.getMessage());
            }
        }

        //Only all search in certain modes
        enableSearchItems(displayView == Display.SINGLE_PAGE
                || displayView == Display.CONTINUOUS
                || displayView == Display.CONTINUOUS_FACING);

        // add listener if one not already there
        if (viewListener == null) {
            viewListener = new RefreshLayout(decode_pdf);
            ((Componentdecode_pdf).addComponentListener(viewListener);
        }
    }

    public boolean allowScrolling() {
        return allowScrolling;
    }

    public boolean confirmClose() {
        return confirmClose;
    }

    public void toogleAutoScrolling() {
        allowScrolling = !allowScrolling;
    }

    public int getRotation() {
        return rotation;
    }

    public float getScaling() {
        return scaling;
    }

    /**
     * Get the values used by the GUI
     *
     @return Values object to hold various details for the viewer
     */
    public Values getValues() {
        return commonValues;
    }

    private void addCombo(final String tooltip, final int ID) {

        SwingCombo combo = null;
        switch (ID) {

            case Commands.SCALING:
                combo = scalingBox;
                break;
            case Commands.ROTATION:
                combo = rotationBox;
                break;
        }

        if (combo != null) {
            combo.setID(ID);

            if (!tooltip.isEmpty()) {
                combo.setToolTipText(tooltip);

                addGUIComboBoxes(combo);

                addComboListenerAndLabel(combo);
            }
        }
    }

    /**
     * get Map containing Form Objects setup for Unique Annotations
     *
     @return Map
     */
    public Map<FormObject, String> getHotspots() {

        return Collections.unmodifiableMap(objs);
    }

    /**
     * example code which sets up an individual icon for each annotation to display - only use
     * if you require each annotation to have its own icon<p>
     * To use this you ideally need to parse the annotations first -there is a method allowing you to
     * extract just the annotations from the data.
     */
    private void createUniqueAnnotationIcons() {

        //and place to store so we can test later
        //flush list if needed
        if (objs == null) {
            objs = new HashMap<>();
        else {
            objs.clear();
        }

        //create Annots - you can replace with your own implementation using setExternalHandler()
        ((AnnotationHandlerdecode_pdf.getExternalHandler(Options.UniqueAnnotationHandler)).handleAnnotations(decode_pdf, objs, commonValues.getCurrentPage());

    }

    /**
     * Set the dpi value used to adjust scaling
     *
     @param dpi int value to represent the dpi
     */
    private void setDpi(final int dpi) {
        decode_pdf.getDPIFactory().setDpi(dpi);
    }

    /**
     * Get the thickness of the glow border
     *
     @return int value of the glow border thickness
     */
    @SuppressWarnings("MethodMayBeStatic")
    public int getGlowThickness() {
        return glowThickness;
    }

    /**
     * Get the outer colour of the glow border
     *
     @return Color used for the outer section of the glow border
     */
    public Color getGlowOuterColor() {
        return glowOuterColor;
    }

    /**
     * Get the inner colour of the glow border
     *
     @return Color used for the inner section of the glow border
     */
    public Color getGlowInnerColor() {
        return glowInnerColor;
    }

    /**
     * Set the search frame to be used as part of the user interface
     *
     @param searchFrame SwingSearchWindow object to be used as the search window
     */
    public void setSearchFrame(final SwingSearchWindow searchFrame) {
        this.searchFrame = searchFrame;
    }

    private void setRotation() {

        if (rotation > 360) {
            rotation -= 360;
        }

        if (getSelectedComboIndex(Commands.ROTATION!= (rotation / 90)) {
            setSelectedComboIndex(Commands.ROTATION, (rotation / 90));
        }
    }

    /**
     * Get selected index for the specified combo-box
     *
     @param ID int value specifying a combo-box
     @return int value of the selected index in the given combo-box or -1 if ID is not valid
     */
    public int getSelectedComboIndex(final int ID) {

        switch (ID) {
            case Commands.SCALING:
                return scalingBox.getSelectedIndex();
            case Commands.ROTATION:
                return rotationBox.getSelectedIndex();
            default:
                return -1;
        }
    }

    /**
     * Set selected index for the specified combo-box
     *
     @param ID    int value specifying a combo-box
     @param index int value of the index to select
     */
    public void setSelectedComboIndex(final int ID, final int index) {
        switch (ID) {
            case Commands.SCALING:
                scalingBox.setSelectedIndex(index);
                break;
            case Commands.ROTATION:
                rotationBox.setSelectedIndex(index);
                break;

        }

    }

    /**
     * Get selected item for the specified combo-box
     *
     @param ID int value specifying a combo-box
     @return Object representing the selected item or null if ID is not recognised
     */
    public Object getSelectedComboItem(final int ID) {

        switch (ID) {
            case Commands.SCALING:
                return scalingBox.getSelectedItem();
            case Commands.ROTATION:
                return rotationBox.getSelectedItem();
            default:
                return null;

        }
    }

    /**
     * Set selected item for the specified combo-box
     *
     @param ID    int value specifying a combo-box
     @param index String value of the item to select
     */
    public void setSelectedComboItem(final int ID, String index) {
        switch (ID) {
            case Commands.SCALING:
                //When using any of the fit scalings, adding a % will break it
                //Only add if scaling is a number
                if (StringUtils.isNumber(index)) {
                    index += '%';
                }
                scalingBox.setSelectedItem(index);
                break;
            case Commands.ROTATION:
                rotationBox.setSelectedItem(index);
                break;

        }
    }

    private void prepareForDecode() {
        //Remove Image extraction outlines when page is changed
        decode_pdf.getPages().setHighlightedImage(null);

        resetRotationBox();

        /* if running terminate first */
        if (thumbnails.isShownOnscreen()) {
            thumbnails.terminateDrawing();
        }

        if (thumbnails.isShownOnscreen()) {
            setupThumbnailPanel();

            final LinearThread linearizedBackgroundRenderer = (LinearThreaddecode_pdf.getJPedalObject(PdfDictionary.LinearizedReader);

            if (linearizedBackgroundRenderer != null && !linearizedBackgroundRenderer.isAlive()) {
                thumbnails.drawThumbnails();
            }
        }

        if (decode_pdf.getDisplayView() == Display.SINGLE_PAGE) {
            setPageCounterText(PageCounter.PAGECOUNTER2, getPageLabel(commonValues.getCurrentPage()));
            if (pageLabelDiffers(commonValues.getCurrentPage())) {
                setPageCounterText(PageCounter.PAGECOUNTER3, "(" + commonValues.getCurrentPage() ' ' + Messages.getMessage("PdfViewerOfLabel.text"' ' + commonValues.getPageCount() ')');
            else {
                setPageCounterText(PageCounter.PAGECOUNTER3, Messages.getMessage("PdfViewerOfLabel.text"' ' + commonValues.getPageCount());
            }
        }

        updateTextBoxSize();

        //allow user to now open tabs
        tabsNotInitialised = false;

        //ensure text and color extracted. If you do not need color, take out line for faster decode
        decode_pdf.setExtractionMode(PdfDecoderInt.TEXT + PdfDecoderInt.TEXTCOLOR);


        //remove any search highlight
        decode_pdf.getTextLines().clearHighlights();
    }

    @SuppressWarnings({"OverlyLongMethod""java:S138"})
    private void performDecoding() {
        Values.setProcessing(true);

        setCursor(2);

        if (LogWriter.isRunningFromIDE) {
            start = System.currentTimeMillis();
        }

        try {
            statusBar.updateStatus("Decoding Page"0);

            /*
             * decode the page
             */
            try {
                decode_pdf.decodePage(commonValues.getCurrentPage());

                final Object errorTracker = decode_pdf.getExternalHandler(Options.ErrorTracker);
                if (debugMode && errorTracker instanceof LimitDecodeTrackerNoSwing) {
                    ((LimitDecodeTrackerNoSwingerrorTracker).onPageChange();
                }

                //wait to ensure decoded
                decode_pdf.waitForDecodingToFinish();


                //value set from JVM flag org.jpedal.maxShapeCount=maxNumber
                if (decode_pdf.getPageDecodeStatus(DecodeStatus.TooManyShapes)) {

                    final String status = "Too many shapes on page";

                    showMessageDialog(status);
                }


                if (!decode_pdf.getPageDecodeStatus(DecodeStatus.ImagesProcessed)) {

                    final String status = (Messages.getMessage("PdfViewer.ImageDisplayError"+
                            Messages.getMessage("PdfViewer.ImageDisplayError1"+
                            Messages.getMessage("PdfViewer.ImageDisplayError2"+
                            Messages.getMessage("PdfViewer.ImageDisplayError3"+
                            Messages.getMessage("PdfViewer.ImageDisplayError4"+
                            Messages.getMessage("PdfViewer.ImageDisplayError5"+
                            Messages.getMessage("PdfViewer.ImageDisplayError6"+
                            Messages.getMessage("PdfViewer.ImageDisplayError7"));

                    showMessageDialog(status);
                }

                /*
                 * Tell user if hinting is probably required
                 */
                if (decode_pdf.getPageDecodeStatus(DecodeStatus.TTHintingRequired)) {

                    final String status = Messages.getMessage("PdfCustomGui.ttHintingRequired");

                    showMessageDialog(status);
                }

                if (decode_pdf.getPageDecodeStatus(DecodeStatus.NonEmbeddedCIDFonts)) {

                    final String status = ("This page contains non-embedded CID fonts \n" +
                            decode_pdf.getPageDecodeStatusReport(DecodeStatus.NonEmbeddedCIDFonts+
                            "\nwhich may need mapping to display correctly.\n" +
                            "See https://support.idrsolutions.com/jpedal/api-documents/pdf-fonts-how-they-work-and-how-they-can-be-displayed-in");

                    showMessageDialog(status);
                }

                //create custom annot icons
                if (decode_pdf.getExternalHandler(Options.UniqueAnnotationHandler!= null) {
                    /*
                     * ANNOTATIONS code to create unique icons
                     *
                     * this code allows you to create a unique set on icons for any type of annotations, with
                     * an icons for every annotation, not just types.
                     */
                    final FormFactory formfactory = decode_pdf.getFormRenderer().getFormFactory();

                    //swing needs it to be done with invokeLater
                    if (formfactory.getType() == FormFactory.SWING) {
                        final Runnable doPaintComponent2 = this::createUniqueAnnotationIcons;
                        SwingUtilities.invokeLater(doPaintComponent2);
                    else {
                        createUniqueAnnotationIcons();
                    }


                }
                statusBar.updateStatus("Displaying Page"0);

            catch (final Exception e) {
                System.err.println(Messages.getMessage("PdfViewerError.Exception"' ' + e +
                        ' ' + Messages.getMessage("PdfViewerError.DecodePage"));
                LogWriter.writeLog(e);
                Values.setProcessing(false);
            }


            //tell user if we had a memory error on decodePage
            if (DecoderOptions.showErrorMessages) {
                String status = decode_pdf.getPageDecodeReport();
                if (status.contains("java.lang.OutOfMemoryError")) {
                    status = (Messages.getMessage("PdfViewer.OutOfMemoryDisplayError"+
                            Messages.getMessage("PdfViewer.OutOfMemoryDisplayError1"+
                            Messages.getMessage("PdfViewer.OutOfMemoryDisplayError2"+
                            Messages.getMessage("PdfViewer.OutOfMemoryDisplayError3"+
                            Messages.getMessage("PdfViewer.OutOfMemoryDisplayError4"+
                            Messages.getMessage("PdfViewer.OutOfMemoryDisplayError5"));

                    showMessageDialog(status);

                }
            }

            Values.setProcessing(false);

            setViewerTitle()//restore title


            if (LogWriter.isRunningFromIDE) {
                /*
                 * show time and memory usage
                 */
                System.out
                        .println(((Runtime.getRuntime().totalMemory() - Runtime
                                .getRuntime().freeMemory()) 1_000)
                                "K");
                System.out.println((((floatMath.abs(((System
                        .currentTimeMillis() - start100))) 10)
                        "s");

            }

            if (decode_pdf.getPageCount() && thumbnails.isShownOnscreen() && decode_pdf.getDisplayView() == Display.SINGLE_PAGE) {
                thumbnails.generateOtherVisibleThumbnails(commonValues.getCurrentPage());
            }

        catch (final Exception e) {
            LogWriter.writeLog(e);
            Values.setProcessing(false)// Remove processing flag so that the viewer can be exited.
            setViewerTitle()//restore title
        }
    }

    /**
     * called by nav functions to decode next page (in GUI code as needs to
     * manipulate large part of GUI)
     */
    private void decodeGUIPage() {

        //Prepare GUI for decoding
        prepareForDecode();

        //kick-off thread to create pages
        switch (decode_pdf.getDisplayView()) {
            case Display.FACING:
                scaleAndRotate();
                scrollToPage(commonValues.getCurrentPage());

                decode_pdf.getPages().decodeOtherPages(commonValues.getCurrentPage());

                return;
            case Display.CONTINUOUS:
            case Display.CONTINUOUS_FACING:
                //resize (ensure at least certain size)
                //must be here as otherwise will not redraw if new page opened
                //in multipage mode
                scaleAndRotate();

                scrollToPage(commonValues.getCurrentPage());

                return;
            case Display.PAGEFLOW:
                return;
            default:
                break;
        }

        //stop user changing scaling while decode in progress
        resetComboBoxes(false);
        swButtons.setPageLayoutButtonsEnabled(false);

        //Decoding happens here
        performDecoding();

        //Update multibox
        statusBar.setProgress(100);

        setMultibox(new int[]{});

        //reanable user changing scaling
        resetComboBoxes(true);

        if (decode_pdf.getPageCount() 1) {
            swButtons.setPageLayoutButtonsEnabled(true);
        }

        if (frame != null) {
            reinitialiseTabs(getDividerLocation() > getStartSize());
        }

        finishedDecoding = true;

        //Ensure page is at the correct scaling and rotation for display
        scaleAndRotate();

        setCursor(1);

    }

    public void setViewerTitle() {
        String title;
        if (commonValues.getSelectedFile() == null) {
            title = (windowTitle + ' ');
        else {
            title = (windowTitle + ' ' + commonValues.getSelectedFile());
        }

        final PdfObject linearObj = (PdfObjectdecode_pdf.getJPedalObject(PdfDictionary.Linearized);
        if (linearObj != null) {
            final LinearThread linearizedBackgroundReaderer = (LinearThreaddecode_pdf.getJPedalObject(PdfDictionary.LinearizedReader);

            if (linearizedBackgroundReaderer != null && linearizedBackgroundReaderer.isAlive()) {
                title += " (still loading)";
            else {
                title += " (Linearized)";
            }
        }

        if (commonValues.isFormsChanged()) {
            title = "* " + title;
        }

        if (FileAccessHelper.bb > 0) {

            title = "(" + FileAccessHelper.bb + " days left) " + title;

        }

        setTitle(title);
    }

    private void setupSidebarTitles() {
        pageTitle = Messages.getMessage("PdfViewerJPanel.thumbnails");
        bookmarksTitle = Messages.getMessage("PdfViewerJPanel.bookmarks");
        layersTitle = Messages.getMessage("PdfViewerJPanel.layers");
        signaturesTitle = Messages.getMessage("PdfViewerJPanel.signatures");
        annotationTitle = Messages.getMessage("PdfViewerJPanel.annotations");
        attachmentsTitle = Messages.getMessage("PdfViewerJPanel.attachments");
    }

    /**
     * Get the label of the given page. PDFs can specify a label for a page
     * (such as using Roman numerals), the method will return the page label if
     * one exists, otherwise it will return the default of the page number.
     *
     @param pageNumber int value to present the page number
     @return String value representing the page label for the page.
     */
    public String getPageLabel(final int pageNumber) {
        if (commonValues.isPDF()) { //Only check labels if pdf
            final String value = decode_pdf.getIO().convertPageNumberToLabel(pageNumber);
            if (value != null) {
                return value;
            }
        }
        return String.valueOf(pageNumber);
    }

    /**
     * Get if the page label for a given page differs from the default page numbers
     *
     @param pageNumber int value representing the page number
     @return true if the page label differs, false otherwise
     */
    private boolean pageLabelDiffers(final int pageNumber) {
        final PdfObjectReader reader = decode_pdf.getIO();
        if (reader != null) {
            final String value = reader.convertPageNumberToLabel(pageNumber);
            if (value != null) {
                return !value.equals(String.valueOf(pageNumber));
            }
        }
        return false;
    }

    public void hideVersionNumber() {
        windowTitle = "JPedal PDF workflow tools";
        setViewerTitle();
    }

    public void showVersionNumber() {
        windowTitle = Messages.getMessage("PdfViewer.titlebar"' ' + version;
        setViewerTitle();
    }

    /**
     * Generic ENUMS for setting Swing values.
     */
    public enum ScrollPolicy {
        VERTICAL_AS_NEEDED, HORIZONTAL_AS_NEEDED, VERTICAL_NEVER, HORIZONTAL_NEVER
    }

    public enum PageCounter {
        PAGECOUNTER1, PAGECOUNTER2, PAGECOUNTER3, ALL
    }

    /**
     * class to repaint multiple views
     */
    private class RefreshLayout extends ComponentAdapter {

        final PdfDecoderInt decode_pdf;

        final PageMoveTracker tracker = new PageMoveTracker();

        int delay = 500;

        RefreshLayout(final PdfDecoderInt pdf) {
            decode_pdf = pdf;
            final String value = properties.getValue("pageMoveDelay");
            if (value != null && !value.isEmpty()) {
                delay = Integer.parseInt(value);
            }
        }

        @Override
        public void componentMoved(final ComponentEvent e) {
            tracker.startTimer(decode_pdf.getPages(), decode_pdf.getPageNumber()(FileAccessdecode_pdf.getExternalHandler(Options.FileAccess), delay);
        }

        @Override
        public void componentResized(final ComponentEvent e) {

            final int displayView = decode_pdf.getDisplayView();

            final PdfPageData pageData = decode_pdf.getPdfPageData();

            final int page = decode_pdf.getPageNumber();

            if (displayView != Display.SINGLE_PAGE && displayView != Display.PAGEFLOW) {
                final Rectangle r = decode_pdf.getVisibleRect();

                if (!(pageData.getScaledCropBoxWidth(page> r.width
                        || pageData.getScaledCropBoxHeight(page> r.height)) {
                    scrollToPage(page);
                }
                tracker.startTimer(decode_pdf.getPages(), decode_pdf.getPageNumber()(FileAccessdecode_pdf.getExternalHandler(Options.FileAccess), delay);
            }
        }

        //fix submitted by Niklas Matthies
        void dispose() {
            tracker.dispose();
        }
    }

    /**
     * Remove the ComponentListener from the PdfDecoder and flush cached pages
     */
    public void removePageListener() {

        // remove listener if not removed by close
        if (viewListener != null) {

            //flush any cached pages
            decode_pdf.getPages().flushPageCaches();

            ((Componentdecode_pdf).removeComponentListener(viewListener);

            viewListener.dispose();
            viewListener = null;

        }
    }

    /**
     * Trigger page turn in magazine mode. Not to be used in other display modes.
     *
     @param decode_pdf   PdfDecoder holder the PDF
     @param commonValues Values object holding PDF values
     @param updatedTotal New page total after page turn
     @param rightTurn    true if turning page right, false if turning left
     */

    public void triggerPageTurnAnimation(final PdfDecoderInt decode_pdf, final Values commonValues, final int updatedTotal, final boolean rightTurn) {
        float pageW = decode_pdf.getPdfPageData().getCropBoxWidth(1);
        float pageH = decode_pdf.getPdfPageData().getCropBoxHeight(1);
        if (decode_pdf.getPdfPageData().getRotation(1180 == 90) {
            final float temp = pageW;
            pageW = pageH;
            pageH = temp;
        }

        final Point corner = new Point();
        corner.x = (int) (decode_pdf.getVisibleRect().getWidth() 2);
        if (rightTurn) {
            corner.x -= pageW;
        else {
            corner.x += pageW;
        }
        corner.y = (int) (decode_pdf.getInsetH() + pageH);

        final Point cursor = new Point();
        cursor.x = (int) ((decode_pdf.getVisibleRect().getWidth() 2+ pageW);
        if (rightTurn) {
            cursor.x += pageW;
        else {
            cursor.x -= pageW;
        }
        cursor.y = (int) (decode_pdf.getInsetH() + pageH);

        final SwingGUI currentGUI = this;

        final Thread animation = new Thread(() -> {
            // Fall animation
            int velocity = 1;

            //ensure cursor is not outside expected range
            if (rightTurn ? cursor.x <= corner.x : cursor.x >= corner.x) {
                cursor.x = corner.x - 1;
            }

            //Calculate distance required
            final double distX = (corner.x - cursor.x);

            //Loop through animation
            while (rightTurn ? cursor.getX() >= corner.getX() : cursor.getX() <= corner.getX()) {

                //amount to move this time
                double xMove = velocity * distX * 0.001;

                //make sure always moves at least 1 pixel in each direction
                if ((rightTurn && xMove > -1)) {
                    xMove = -1;
                else {
                    if ((!rightTurn && xMove < 1)) {
                        xMove = 1;
                    }
                }

                cursor.setLocation(cursor.getX() + xMove, cursor.getY());
                if (rightTurn) {
                    decode_pdf.setUserOffsets((intcursor.getX()(intcursor.getY(), OffsetOptions.INTERNAL_DRAG_CURSOR_BOTTOM_RIGHT);
                else {
                    decode_pdf.setUserOffsets((intcursor.getX()(intcursor.getY(), OffsetOptions.INTERNAL_DRAG_CURSOR_BOTTOM_LEFT);
                }
                //Double speed til moving 32/frame
                if (velocity < 32) {
                    velocity *= 2;
                }

                //sleep til next frame
                try {
                    Thread.sleep(50);
                catch (final InterruptedException e) {
                    LogWriter.writeLog(e);
                }

            }

            //change page
            commonValues.setCurrentPage(updatedTotal);
            setPageNumber();
            decode_pdf.setPageParameters(scaling, commonValues.getCurrentPage());
            decodePage();

            //unlock corner drag
            PageNavigator.setPageTurnAnimating(false, currentGUI);

            //hide turnover
            decode_pdf.setUserOffsets(00, OffsetOptions.INTERNAL_DRAG_BLANK);

        });

        animation.setDaemon(true);
        //lock corner drag
        PageNavigator.setPageTurnAnimating(true, this);

        animation.start();

    }

    private void setCursor(final int type) {

        if (type == 1) {
            ((Componentdecode_pdf).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        else if (type == 2) {
            ((Componentdecode_pdf).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        }

    }

    /**
     * Not part of API - used internally
     * set view mode used in panel and redraw in new mode
     * SINGLE_PAGE,CONTINUOUS,FACING,CONTINUOUS_FACING delay is the time in
     * milli-seconds which scrolling can stop before background page drawing
     * starts Multipage views not in OS releases
     */
    private boolean setDisplayView2(final int displayView, final int orientation) {

        final DecoderOptions options = decode_pdf.getDecoderOptions();
        Display pages = decode_pdf.getPages();
        final FileAccess fileAccess = (FileAccessdecode_pdf.getExternalHandler(Options.FileAccess);
        final int pageNumber = decode_pdf.getPageNumber();
        final ExternalHandlers externalHandlers = decode_pdf.getExternalHandler();

        final PdfDecoder comp = (PdfDecoderdecode_pdf;
        final int lastDisplayView = decode_pdf.getDisplayView();

        options.setPageAlignment(orientation);

        if (pages != null) {
            pages.stopGeneratingPage();
        }

        final boolean hasChanged = displayView != lastDisplayView;

        options.setDisplayView(displayView);

        if (lastDisplayView != displayView && lastDisplayView == Display.PAGEFLOW && pages != null) {
            pages.dispose();
        }

        final Object customSwingHandle = externalHandlers.getExternalHandler(Options.MultiPageUpdate);

        //Set the default value, may be replaced for pageflow as not needed
        if (decode_pdf.getFormRenderer() != null) {
            decode_pdf.getFormRenderer().getCompData().setRootDisplayComponent(decode_pdf);
        }

        switch (displayView) {
            case Display.SINGLE_PAGE:
                if (pages == null || hasChanged) {
                    final DynamicVectorRenderer currentDisplay = decode_pdf.getDynamicRenderer();

                    pages = new SingleDisplay(pageNumber, currentDisplay, comp, options)//abacus-del
                }
                break;

            case Display.PAGEFLOW:


                if (pages instanceof org.jpedal.display.swing.PageFlowDisplay) { //adode-del abacus-del
                    return hasChanged; //adode-del abacus-del
                //adode-del abacus-del

                //Remove root display to prevent annotations being added behind page flow display
                //Without this pageflow display will not resize with the viewer.
                if (decode_pdf.getFormRenderer() != null) {
                    decode_pdf.getFormRenderer().getCompData().setRootDisplayComponent(null);
                }

                if (lastDisplayView != Display.SINGLE_PAGE) {
                    setDisplayView(Display.SINGLE_PAGE, 0);
                    setDisplayView(Display.PAGEFLOW, 0);
                    return hasChanged;
                }

                ((JScrollPanecomp.getParent().getParent()).setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
                ((JScrollPanecomp.getParent().getParent()).setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

                pages = new org.jpedal.display.swing.PageFlowDisplay((SwingGUIcustomSwingHandle, decode_pdf)//adode-del abacus-del

                break;

            default:

                pages = new MultiDisplay(pageNumber, null, displayView, comp, options, fileAccess)//adode-del abacus-del

                //pass in value if needed
                final RenderChangeListener customRenderChangeListener = (RenderChangeListenerexternalHandlers.getExternalHandler(Options.RenderChangeListener);
                if (customRenderChangeListener != null) {
                    pages.setObjectValue(Options.RenderChangeListener, customRenderChangeListener);
                }

                break;
        }

        //enable pageFlow mode and setup slightly different display configuration
        if (lastDisplayView == Display.PAGEFLOW) {
            comp.removeAll();

            //forms needs null layout manager
            comp.setLayout(null);

            ((JScrollPanecomp.getParent().getParent()).setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
            ((JScrollPanecomp.getParent().getParent()).setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

            final javax.swing.Timer t = new javax.swing.Timer(1_000, e -> comp.repaint());
            t.setRepeats(false);
            t.start();
        }

        //setup once per page getting all page sizes and working out settings for views
        if (fileAccess.getOffset() == null) {
            fileAccess.setOffset(new PageOffsets(decode_pdf.getPageCount(), decode_pdf.getPdfPageData()));
        }

        pages.setup(options.useHardwareAcceleration(), fileAccess.getOffset());
        final DynamicVectorRenderer currentDisplay = decode_pdf.getDynamicRenderer();
        pages.init(scaling, decode_pdf.getDisplayRotation(), decode_pdf.getPageNumber(), currentDisplay, true);

        pages.setPageRotation(decode_pdf.getDisplayRotation())//force update //adode-del

        pages.forceRedraw();

        pages.refreshDisplay();

        comp.pages = pages;

        return hasChanged;
    }

    /**
     * Get the glass panes used to allow annotations draw in the viewer for
     * adding to the PDF.
     *
     @return a JPanel to act as a glass pane.
     */
    public JPanel getGlassPane() {
        return glassPane;
    }

    private void setupTilePortfolioView() {

        if (tileView == null) {
            tileView = new SwingPortfolioTile(this);
        }
        tileView.loadCollectionValues(decode_pdf);

        if (detailView != null) {
            portfolioParent.remove(detailView);
        }

        if (tileView != null) {
            portfolioParent.remove(tileView);
        }

        portfolioParent.remove(displayPane);
        portfolioParent.add(tileView, BorderLayout.CENTER);

        menuItems.getMenuItem(Commands.PORTFOLIO_TILES).setSelected(true);
        menuItems.getMenuItem(Commands.PORTFOLIO_DETAIL).setSelected(false);

        portfolioParent.repaint();
        frame.validate();
    }

    private void setupDetailPortfolioView() {

        if (detailView == null) {
            detailView = new SwingPortfolioDetail(this);
        }

        detailView.loadCollectionValues(decode_pdf);

        if (detailView != null) {
            portfolioParent.remove(detailView);
        }

        if (tileView != null) {
            portfolioParent.remove(tileView);
        }

        portfolioParent.remove(displayPane);
        portfolioParent.add(detailView, BorderLayout.CENTER);

        menuItems.getMenuItem(Commands.PORTFOLIO_TILES).setSelected(false);
        menuItems.getMenuItem(Commands.PORTFOLIO_DETAIL).setSelected(true);

        portfolioParent.repaint();
        frame.validate();
    }

    private void setupPdfView() {

        if (detailView != null) {
            portfolioParent.remove(detailView);
        }

        if (tileView != null) {
            portfolioParent.remove(tileView);
        }

        portfolioParent.remove(displayPane);

        portfolioParent.add(displayPane, BorderLayout.CENTER);
        portfolioParent.validate();

        //Ensure display pane is displayed correctly
        displayPane.repaint();

        scaleAndRotate();

    }

    @SuppressWarnings({"OverlyLongMethod""java:S138"})
    public void setupGUIElementsForFiletype(final Values.FileType type) {

        //Set Visibility of items (also check properties)
        swButtons.getButton(Commands.ROTATELEFT).setVisible((type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("RotateLeftbutton")));
        swButtons.getButton(Commands.ROTATERIGHT).setVisible((type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("RotateRightbutton")));
        swButtons.getButton(Commands.ZOOMIN).setVisible((type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("ZoomInbutton")));
        swButtons.getButton(Commands.ZOOMOUT).setVisible((type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("ZoomOutbutton")));
        swButtons.getButton(Commands.MOUSEMODE).setVisible(type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("MouseModeButton")));
        swButtons.getButton(Commands.FIRSTPAGE).setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Firstbottom")));
        swButtons.getButton(Commands.FBACKPAGE).setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Back10bottom")));
        swButtons.getButton(Commands.BACKPAGE).setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Backbottom")));
        swButtons.getButton(Commands.FORWARDPAGE).setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Forwardbottom")));
        swButtons.getButton(Commands.FFORWARDPAGE).setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Forward10bottom")));
        swButtons.getButton(Commands.LASTPAGE).setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Lastbottom")));
        swButtons.getButton(Commands.SINGLE).setVisible(type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Singlebottom")));
        swButtons.getButton(Commands.CONTINUOUS).setVisible(type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Continuousbottom")));
        swButtons.getButton(Commands.FACING).setVisible(type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Facingbottom")));
        swButtons.getButton(Commands.CONTINUOUS_FACING).setVisible(type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Continuousfacingbottom")));
        swButtons.getButton(Commands.PAGEFLOW).setVisible(type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("PageFlowbottom")));
        swButtons.getButton(Commands.PRINT).setVisible((type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("Printbutton")));
        swButtons.getButton(Commands.SNAPSHOT).setVisible(type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Snapshotbutton")));

        multibox.setVisible(type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Memorybottom")));

        navButtons.setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("ShowNavigationbar")));

        //Handle Combo boxes
        getCombo(Commands.SCALING).setVisibility((type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("Scalingdisplay")));
        getCombo(Commands.ROTATION).setVisibility((type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("Rotationdisplay")));

        final boolean showPageCounter = "true".equalsIgnoreCase(properties.getValue("Gotobottom"));
        pageCounter1.setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff())) && showPageCounter);
        pageCounter2.setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff())) && showPageCounter);
        pageCounter3.setVisible((type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff())) && showPageCounter);

        final boolean showMenubarSearch = "2".equalsIgnoreCase(properties.getValue("searchWindowType"));
        swButtons.getButton(Commands.NEXTRESULT).setVisible(type == Values.FileType.PDF && showMenubarSearch);
        swButtons.getButton(Commands.PREVIOUSRESULT).setVisible(type == Values.FileType.PDF && showMenubarSearch);
        searchText.setVisible(type == Values.FileType.PDF);
        if (options != null) {
            options.setVisible(type == Values.FileType.PDF && showMenubarSearch);
        }

        navOptionsPanel.setVisible((type == Values.FileType.PDF)
                && "true".equalsIgnoreCase(properties.getValue("ShowSidetabbar")));

        //No property for this value as always needed
        menuItems.setMenuItemVisibility(Commands.PORTFOLIOMENU, type == Values.FileType.PORTFOLIO);

        //Set enabled for items
        menuItems.setMenuItemVisibility(Commands.SAVE, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Save")));
        menuItems.setMenuItemVisibility(Commands.SAVEFORM, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Resaveasforms")));
        menuItems.setMenuItemVisibility(Commands.DOCINFO, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Documentproperties")));
        menuItems.setMenuItemVisibility(Commands.FIND, (type == Values.FileType.PDF)
                && "true".equalsIgnoreCase(properties.getValue("Find")));
        menuItems.setMenuItemVisibility(Commands.PRINT, (type == Values.FileType.PDF)
                && "true".equalsIgnoreCase(properties.getValue("Print")));
        menuItems.setMenuItemVisibility(Commands.SEPARATECOVER, (type == Values.FileType.PDF)
                && "true".equalsIgnoreCase(properties.getValue("separateCover")));
        menuItems.setMenuItemVisibility(Commands.PANMODE, (type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("panMode")));
        menuItems.setMenuItemVisibility(Commands.TEXTSELECT, (type == Values.FileType.PDF)
                && "true".equalsIgnoreCase(properties.getValue("textSelect")));
        menuItems.setMenuItemVisibility(Commands.FULLSCREEN, (type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("Fullscreen")));
        menuItems.setMenuItemVisibility(Commands.FIRSTPAGE, (type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Firstpage")));
        menuItems.setMenuItemVisibility(Commands.BACKPAGE, (type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Backpage")));
        menuItems.setMenuItemVisibility(Commands.GOTO, (type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Goto")));
        menuItems.setMenuItemVisibility(Commands.FORWARDPAGE, (type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Forwardpage")));
        menuItems.setMenuItemVisibility(Commands.LASTPAGE, (type == Values.FileType.PDF || (type == Values.FileType.IMAGES && commonValues.isMultiTiff()))
                && "true".equalsIgnoreCase(properties.getValue("Lastpage")));
        menuItems.setMenuItemVisibility(Commands.EDITMENU, (type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("EditMenu")));
        menuItems.setMenuItemVisibility(Commands.PAGELAYOUTMENU, (type == Values.FileType.PDF || type == Values.FileType.IMAGES)
                && "true".equalsIgnoreCase(properties.getValue("PagelayoutMenu")));
        menuItems.setMenuItemVisibility(Commands.COPY, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Copy")));
        menuItems.setMenuItemVisibility(Commands.SELECTALL, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Selectall")));
        menuItems.setMenuItemVisibility(Commands.DESELECTALL, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Deselectall")));
        menuItems.setMenuItemVisibility(Commands.IMAGES, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Images")));
        menuItems.setMenuItemVisibility(Commands.TEXT, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Text")));
        menuItems.setMenuItemVisibility(Commands.CONTENTMENU, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("ContentMenu")));
        menuItems.setMenuItemVisibility(Commands.BITMAP, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("Bitmap")));
        menuItems.setMenuItemVisibility(Commands.EXPORTMENU, type == Values.FileType.PDF
                && "true".equalsIgnoreCase(properties.getValue("ExportMenu")));
        menuItems.setMenuItemVisibility(Commands.XREF, type == Values.FileType.PDF
                && debugMode);
        menuItems.setMenuItemVisibility(Commands.OBJECTS, type == Values.FileType.PDF
                && debugMode);

        menuItems.setMenuItemVisibility(Commands.PORTFOLIOMENU, type == Values.FileType.PORTFOLIO);

        if (type == Values.FileType.IMAGES) {
            currentCommands.executeCommand(ViewerCommands.PANMODE, null);
        else {
            currentCommands.executeCommand(ViewerCommands.TEXTSELECT, null);
        }

        //Check for double separaters and remove as needed
        swButtons.checkButtonSeparators();
        menuItems.checkMenuItemSeparators();

        frame.revalidate();
    }

    public void setDebugMode(final boolean enableDebugMode) {
        debugMode = enableDebugMode;
        menuItems.setDebugMode(enableDebugMode);

        imageNameCollector = new ImageNameCollector() {
            final HashMap<String, String> names = new HashMap<>();
            @Override
            public void put(final String imageName, final String cacheName) {
                names.put(decode_pdf.getPageNumber() "-" + imageName, cacheName);
            }

            @Override
            public String getCacheName(final String imageName) {
                return names.get(decode_pdf.getPageNumber() "-" + imageName);
            }

            @Override
            public String getCacheNameFromIDToken(final int tokenNumber) {
                final String prefix = decode_pdf.getPageNumber() "-";
                final String suffix = "-IN-" + tokenNumber;
                for (final String name : names.keySet()) {
                    if (name.startsWith(prefix&& name.endsWith(suffix)) {
                        return names.get(name);
                    }
                }
                return null;
            }

            @Override
            public boolean cacheAllToDisk() {
                return true;
            }
        };

        decode_pdf.setImageNameCollector(imageNameCollector);
    }

}