pl.umk.mat.faramir.beamer.MainFrame.java Source code

Java tutorial

Introduction

Here is the source code for pl.umk.mat.faramir.beamer.MainFrame.java

Source

/*
 * Copyright (c) 2015, Marek Nowicki
 * All rights reserved.
 *
 * This file is distributable under the Simplified BSD license. See the terms
 * of the Simplified BSD license in the documentation provided with this file.
 */
package pl.umk.mat.faramir.beamer;

import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.rendering.ImageType;

/**
 *
 * @author faramir
 */
public class MainFrame extends javax.swing.JFrame implements KeyEventDispatcher {

    private final Map<Integer, BufferedImage> presentationMap = new ConcurrentHashMap<>();
    private final Map<Integer, SlideNote> notesMap = new LinkedHashMap<>();
    private final long doubleClickTime;
    private final String endSlideString = "--- end slide ---";
    private final Pattern pattern = Pattern
            .compile(endSlideString + "(?:[ ]*(?<sign>\\+?)(?<time>(?:[0-9]+:)?(?:[0-9]+)))?");
    private NotesFrame notesFrame;
    private FullscreenWindow fullscreenWindow;
    private Thread loadPdfThread;
    private File presentationFile;
    private File notesFile;
    private int currentPage;
    private int pagesCount;
    private LocalDateTime startTime;
    private long lastBlankClicked = System.nanoTime();

    /**
     * Creates new form MainFrame
     */
    public MainFrame() {
        initComponents();

        Object doubleClick = Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval");
        if (doubleClick == null || !(doubleClick instanceof Integer)) {
            doubleClickTime = 330_000_000;
        } else {
            doubleClickTime = ((Integer) doubleClick).longValue() * 1_000_000L;
        }

        presentationScreenRefreshButtonActionPerformed(null);
        notesScreenRefreshButtonActionPerformed(null);
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        openFileChooser = new JFileChooser() {
            @Override
            public void approveSelection() {
                if (getSelectedFile().exists()) {
                    super.approveSelection();
                } else {
                    JOptionPane.showMessageDialog(this,
                            String.format("Podany plik nie istnieje:\n%s", this.getSelectedFile()),
                            "Plik nie istnieje", JOptionPane.ERROR_MESSAGE);
                }
            }
        };
        presentationPanel = new javax.swing.JPanel();
        presentationFileLabel = new javax.swing.JLabel();
        presentationFileTextField = new javax.swing.JTextField();
        presentationScreenLabel = new javax.swing.JLabel();
        presentationScreenComboBox = new javax.swing.JComboBox<ScreenDevice>();
        presentationLoadingProgressBar = new javax.swing.JProgressBar();
        presentationScreenRefreshButton = new javax.swing.JButton();
        notesPanel = new javax.swing.JPanel();
        notesFileLabel = new javax.swing.JLabel();
        notesFileTextField = new javax.swing.JTextField();
        notesScreenLabel = new javax.swing.JLabel();
        notesScreenComboBox = new javax.swing.JComboBox<ScreenDevice>();
        notesScreenRefreshButton = new javax.swing.JButton();
        startPresentationButton = new javax.swing.JButton();

        FormListener formListener = new FormListener();

        openFileChooser.setCurrentDirectory(null);

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Beamer Presenter");
        setLocationByPlatform(true);
        setResizable(false);

        presentationPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Prezentacja"));

        presentationFileLabel.setText("Plik:");

        presentationFileTextField.setEditable(false);
        presentationFileTextField.addMouseListener(formListener);

        presentationScreenLabel.setText("Monitor:");

        presentationScreenComboBox.addItemListener(formListener);

        presentationLoadingProgressBar.setString("0/0");

        presentationScreenRefreshButton.setText("Odwie");
        presentationScreenRefreshButton.addActionListener(formListener);

        javax.swing.GroupLayout presentationPanelLayout = new javax.swing.GroupLayout(presentationPanel);
        presentationPanel.setLayout(presentationPanelLayout);
        presentationPanelLayout.setHorizontalGroup(presentationPanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(presentationPanelLayout.createSequentialGroup().addContainerGap()
                        .addGroup(presentationPanelLayout
                                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addComponent(presentationLoadingProgressBar, javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addGroup(presentationPanelLayout.createSequentialGroup()
                                        .addGroup(presentationPanelLayout
                                                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                                .addComponent(presentationScreenLabel)
                                                .addComponent(presentationFileLabel))
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                        .addGroup(presentationPanelLayout
                                                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                                .addComponent(presentationFileTextField)
                                                .addGroup(javax.swing.GroupLayout.Alignment.TRAILING,
                                                        presentationPanelLayout.createSequentialGroup()
                                                                .addComponent(presentationScreenComboBox, 0,
                                                                        javax.swing.GroupLayout.DEFAULT_SIZE,
                                                                        Short.MAX_VALUE)
                                                                .addPreferredGap(
                                                                        javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                                .addComponent(presentationScreenRefreshButton,
                                                                        javax.swing.GroupLayout.PREFERRED_SIZE, 73,
                                                                        javax.swing.GroupLayout.PREFERRED_SIZE)))))
                        .addContainerGap()));
        presentationPanelLayout.setVerticalGroup(presentationPanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(presentationPanelLayout.createSequentialGroup().addGroup(presentationPanelLayout
                        .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                        .addComponent(presentationFileLabel).addComponent(presentationFileTextField,
                                javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE,
                                javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(presentationPanelLayout
                                .createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                .addComponent(presentationScreenLabel)
                                .addComponent(presentationScreenComboBox, javax.swing.GroupLayout.PREFERRED_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addComponent(presentationScreenRefreshButton))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(presentationLoadingProgressBar, javax.swing.GroupLayout.PREFERRED_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addContainerGap()));

        notesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Notatki"));

        notesFileLabel.setText("Plik:");

        notesFileTextField.setEditable(false);
        notesFileTextField.addMouseListener(formListener);

        notesScreenLabel.setText("Monitor:");

        notesScreenRefreshButton.setText("Odwie");
        notesScreenRefreshButton.setName(""); // NOI18N
        notesScreenRefreshButton.addActionListener(formListener);

        javax.swing.GroupLayout notesPanelLayout = new javax.swing.GroupLayout(notesPanel);
        notesPanel.setLayout(notesPanelLayout);
        notesPanelLayout.setHorizontalGroup(notesPanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(notesPanelLayout.createSequentialGroup().addContainerGap()
                        .addGroup(notesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addComponent(notesScreenLabel).addComponent(notesFileLabel))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addGroup(notesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addComponent(notesFileTextField)
                                .addGroup(notesPanelLayout.createSequentialGroup()
                                        .addComponent(notesScreenComboBox, 0, 334, Short.MAX_VALUE)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(notesScreenRefreshButton,
                                                javax.swing.GroupLayout.PREFERRED_SIZE, 73,
                                                javax.swing.GroupLayout.PREFERRED_SIZE)))
                        .addContainerGap()));
        notesPanelLayout.setVerticalGroup(notesPanelLayout
                .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(notesPanelLayout.createSequentialGroup()
                        .addGroup(notesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                .addComponent(notesFileLabel).addComponent(notesFileTextField,
                                        javax.swing.GroupLayout.PREFERRED_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(notesPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                .addComponent(notesScreenLabel)
                                .addComponent(notesScreenComboBox, javax.swing.GroupLayout.PREFERRED_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addComponent(notesScreenRefreshButton))
                        .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)));

        startPresentationButton.setText("Rozpocznij prezentacj");
        startPresentationButton.setEnabled(false);
        startPresentationButton.addActionListener(formListener);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup().addContainerGap()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addComponent(presentationPanel, javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(notesPanel, javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(startPresentationButton, javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                        .addContainerGap()));
        layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup().addContainerGap()
                        .addComponent(presentationPanel, javax.swing.GroupLayout.PREFERRED_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(notesPanel, javax.swing.GroupLayout.DEFAULT_SIZE,
                                javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(startPresentationButton).addContainerGap()));

        pack();
    }

    // Code for dispatching events from components to event handlers.

    private class FormListener
            implements java.awt.event.ActionListener, java.awt.event.ItemListener, java.awt.event.MouseListener {
        FormListener() {
        }

        public void actionPerformed(java.awt.event.ActionEvent evt) {
            if (evt.getSource() == presentationScreenRefreshButton) {
                MainFrame.this.presentationScreenRefreshButtonActionPerformed(evt);
            } else if (evt.getSource() == notesScreenRefreshButton) {
                MainFrame.this.notesScreenRefreshButtonActionPerformed(evt);
            } else if (evt.getSource() == startPresentationButton) {
                MainFrame.this.startPresentationButtonActionPerformed(evt);
            }
        }

        public void itemStateChanged(java.awt.event.ItemEvent evt) {
            if (evt.getSource() == presentationScreenComboBox) {
                MainFrame.this.presentationScreenComboBoxItemStateChanged(evt);
            }
        }

        public void mouseClicked(java.awt.event.MouseEvent evt) {
            if (evt.getSource() == presentationFileTextField) {
                MainFrame.this.presentationFileMouseClicked(evt);
            } else if (evt.getSource() == notesFileTextField) {
                MainFrame.this.notesFileTextFieldMouseClicked(evt);
            }
        }

        public void mouseEntered(java.awt.event.MouseEvent evt) {
        }

        public void mouseExited(java.awt.event.MouseEvent evt) {
        }

        public void mousePressed(java.awt.event.MouseEvent evt) {
        }

        public void mouseReleased(java.awt.event.MouseEvent evt) {
        }
    }// </editor-fold>//GEN-END:initComponents

    @Override
    public boolean dispatchKeyEvent(KeyEvent e) {
        if (openFileChooser.isShowing()) {
            return false;
        }

        if (e.getID() == KeyEvent.KEY_PRESSED) {
            if (e.getKeyCode() == KeyEvent.VK_F5) {
                startPresentationButtonActionPerformed(null);
            }
            /**
             * Prezenter: PG UP, PG DOWN, PERIOD ('.'), (F5, Escape)
             */
            switch (e.getKeyCode()) {
            case KeyEvent.VK_PERIOD:
                if (notesFrame != null) {
                    if (System.nanoTime() - lastBlankClicked < doubleClickTime) {
                        notesFrame.clearTime();
                    }
                }
                lastBlankClicked = System.nanoTime();
                return true;
            case KeyEvent.VK_ESCAPE:
                if (fullscreenWindow != null) {
                    fullscreenWindow.dispose();
                    fullscreenWindow = null;
                }
                if (notesFrame != null) {
                    if (notesFrame.isUndecorated()) {
                        notesFrame.dispose();
                        notesFrame = null;
                    }
                }
                return true;
            case KeyEvent.VK_ENTER:
            case KeyEvent.VK_PAGE_DOWN:
            case KeyEvent.VK_DOWN:
            case KeyEvent.VK_RIGHT:
            case KeyEvent.VK_SPACE:
                if (currentPage + 1 < pagesCount) {
                    ++currentPage;
                }
                refreshFullscreenPage();
                showTiming();
                return true;
            case KeyEvent.VK_PAGE_UP:
            case KeyEvent.VK_UP:
            case KeyEvent.VK_LEFT:
                if (currentPage - 1 >= 0) {
                    --currentPage;
                }
                refreshFullscreenPage();
                showTiming();
                return true;
            case KeyEvent.VK_HOME:
                currentPage = 0;
                refreshFullscreenPage();
                showTiming();
                return true;
            case KeyEvent.VK_END:
                currentPage = pagesCount - 1;
                refreshFullscreenPage();
                showTiming();
                return true;
            case KeyEvent.VK_R:
                refreshFullscreenPage();
                return true;
            }
        }

        return false;
    }

    private void presentationFileMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_presentationFileMouseClicked
        if (evt.getClickCount() >= 2 && evt.getButton() == MouseEvent.BUTTON1) {
            openFileChooser.setFileFilter(new FileNameExtensionFilter("PDF files", "pdf"));
            openFileChooser.setSelectedFile(presentationFile);
            if (presentationFile == null && notesFile != null) {
                openFileChooser.setCurrentDirectory(notesFile.getParentFile());
            }
            if (openFileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
                presentationFile = openFileChooser.getSelectedFile();
                openPresentationFile();
            }
        }
    }//GEN-LAST:event_presentationFileMouseClicked

    private void openPresentationFile() throws HeadlessException {
        presentationFileTextField.setText(presentationFile.getAbsolutePath());

        loadPdfFile();
        startTime = null;

        String name = presentationFile.getName().replaceAll("\\.[^.]*$", ".txt");
        File guessNotesFile = Paths.get(presentationFile.getParent(), name).toFile();
        if (guessNotesFile.canRead()) {
            notesFile = guessNotesFile;
            openNotesFile();
        }
    }

    private void notesFileTextFieldMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_notesFileTextFieldMouseClicked
        if (evt.getClickCount() >= 2) {
            if (evt.getButton() != MouseEvent.BUTTON1) {
                notesFileTextField.setText("");
                notesFile = null;
                notesMap.clear();
            } else {
                openFileChooser.setFileFilter(new FileNameExtensionFilter("TXT files", "txt"));
                openFileChooser.setSelectedFile(notesFile);
                if (openFileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
                    notesFile = openFileChooser.getSelectedFile();
                    openNotesFile();
                }
            }
            checkStartPresentationButtonEnabled();
        }
    }//GEN-LAST:event_notesFileTextFieldMouseClicked

    private void openNotesFile() throws HeadlessException {
        notesFileTextField.setText(notesFile.getAbsolutePath());
        loadTxtFile();
    }

    private void presentationScreenRefreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_presentationScreenRefreshButtonActionPerformed
        presentationScreenComboBox.removeAllItems();
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] devices = env.getScreenDevices();
        for (GraphicsDevice device : devices) {
            if (device.isFullScreenSupported()) {
                presentationScreenComboBox.addItem(new ScreenDevice(device));
            }
        }
    }//GEN-LAST:event_presentationScreenRefreshButtonActionPerformed

    private void notesScreenRefreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_notesScreenRefreshButtonActionPerformed
        notesScreenComboBox.removeAllItems();
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] devices = env.getScreenDevices();
        notesScreenComboBox.addItem(new ScreenDevice(null));
        for (GraphicsDevice device : devices) {
            if (device.isFullScreenSupported()) {
                notesScreenComboBox.addItem(new ScreenDevice(device));
            }
        }
        loadTxtFile();
    }//GEN-LAST:event_notesScreenRefreshButtonActionPerformed

    private void presentationScreenComboBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_presentationScreenComboBoxItemStateChanged
        startPresentationButton.setEnabled(false);
        loadPdfFile();
    }//GEN-LAST:event_presentationScreenComboBoxItemStateChanged

    private void startPresentationButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startPresentationButtonActionPerformed
        if (startPresentationButton.isEnabled() == false) {
            return;
        }

        if (startTime == null) {
            startTime = LocalDateTime.now();
        }

        GraphicsDevice slidesDevice = ((ScreenDevice) presentationScreenComboBox.getSelectedItem()).getDevice();
        GraphicsDevice notesDevice = ((ScreenDevice) notesScreenComboBox.getSelectedItem()).getDevice();

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

        if (notesDevice != null) {
            boolean fullscreen = notesDevice.getIDstring().equals(slidesDevice.getIDstring()) == false;

            notesFrame = new NotesFrame(fullscreen, this);
            if (fullscreen) {
                notesFrame.setLocation(notesDevice.getDefaultConfiguration().getBounds().x, notesFrame.getY());
            } else {
                notesDevice.setFullScreenWindow(notesFrame);
            }
            notesFrame.setVisible(true);
        }

        if (fullscreenWindow != null) {
            fullscreenWindow.dispose();
            fullscreenWindow = null;
        }
        fullscreenWindow = new FullscreenWindow(this);
        fullscreenWindow.createWindow(slidesDevice);
        if (notesFrame != null) {
            notesFrame.toFront();
        }

        refreshFullscreenPage();
        showTiming();
    }//GEN-LAST:event_startPresentationButtonActionPerformed

    private void checkStartPresentationButtonEnabled() {
        startPresentationButton.setEnabled(presentationMap.isEmpty() == false);
    }

    private void loadPdfFile() {
        if (presentationFile == null) {
            return;
        }
        if (loadPdfThread != null) {
            loadPdfThread.interrupt();
            loadPdfThread = null;
        }
        loadPdfThread = new Thread(() -> {
            try (PDDocument pdfDocument = PDDocument.load(presentationFile)) {
                PdfRenderer renderer = new PdfRenderer(pdfDocument);
                ScreenDevice screenDevice = (ScreenDevice) presentationScreenComboBox.getSelectedItem();
                GraphicsDevice selectedDevice = screenDevice.getDevice();

                pagesCount = pdfDocument.getNumberOfPages();
                presentationLoadingProgressBar.setMaximum(pagesCount);
                presentationLoadingProgressBar.setValue(0);
                //                presentationLoadingProgressBar.setStringPainted(false);
                presentationMap.clear();
                currentPage = 0;

                for (int processedPage = 0; processedPage < pagesCount; ++processedPage) {
                    presentationLoadingProgressBar.setString(String.format("%d/%d", processedPage, pagesCount));
                    PDRectangle rect = pdfDocument.getPage(processedPage).getCropBox();

                    float width = selectedDevice.getDisplayMode().getWidth();
                    float height = selectedDevice.getDisplayMode().getHeight();

                    float screenProportion = width / height;
                    float scale;
                    if (screenProportion <= rect.getWidth() / rect.getHeight()) {
                        scale = width / rect.getWidth();
                    } else {
                        scale = height / rect.getHeight();
                    }

                    BufferedImage bi = renderer.renderImage(processedPage, scale * 2.0f, ImageType.RGB);
                    if (Thread.interrupted()) {
                        return;
                    }

                    presentationMap.put(processedPage, bi);

                    presentationLoadingProgressBar.setValue(processedPage + 1);
                    checkStartPresentationButtonEnabled();
                    refreshFullscreenPage();
                }
                presentationLoadingProgressBar.setString(String.format("%d/%d", pagesCount, pagesCount));
            } catch (IOException ex) {
                JOptionPane.showMessageDialog(this,
                        String.format("Wystpi wyjtek:\n%s", ex.getLocalizedMessage()), "Bd",
                        JOptionPane.ERROR_MESSAGE);
            }
        });
        loadPdfThread.start();
    }

    private String trimToString(StringBuilder sb) {
        int begin;

        for (begin = 0; begin < sb.length(); ++begin) {
            if (!Character.isWhitespace(sb.charAt(begin))) {
                break;
            }
        }

        int end;
        for (end = sb.length() - 1; end > begin; --end) {
            if (!Character.isWhitespace(sb.charAt(end))) {
                break;
            }
        }

        return sb.substring(begin, end + 1);
    }

    private void loadTxtFile() throws HeadlessException {
        notesMap.clear();
        if (notesFile == null) {
            return;
        }
        int page = 0;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try (BufferedInputStream input = new BufferedInputStream(new FileInputStream(notesFile))) {
                byte[] buf = new byte[1024];
                int len;
                while ((len = input.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                }

            }

            byte[] bytes = baos.toByteArray();
            Charset charset = CharsetDetector.detectCharset(bytes);
            try (BufferedReader br = new BufferedReader(new StringReader(new String(bytes, charset)))) {
                String line;
                StringBuilder sb = new StringBuilder();
                long time = 0;
                do {
                    line = br.readLine();
                    if (line == null || line.startsWith(endSlideString)) {
                        if (line != null) {
                            try {
                                Matcher matcher = pattern.matcher(line);
                                if (matcher.matches() && matcher.groupCount() > 0) {
                                    String signStr = matcher.group("sign");
                                    boolean sign = (signStr != null && "+".equals(signStr));
                                    String timeStr = matcher.group("time");
                                    if (timeStr != null) {
                                        if (timeStr.contains(":")) {
                                            String[] timeSplit = timeStr.split(":");
                                            time = (sign ? time : 0L) + 60L * Integer.parseInt(timeSplit[0])
                                                    + Integer.parseInt(timeSplit[1]);
                                        } else {
                                            time = (sign ? time : 0L) + Integer.parseInt(timeStr);
                                        }
                                    }
                                }
                            } catch (Exception ex) {
                                JOptionPane.showMessageDialog(this,
                                        String.format("Wystpi wyjtek:\n%s", ex.getLocalizedMessage()),
                                        "Bd", JOptionPane.ERROR_MESSAGE);
                            }
                        }
                        SlideNote note = new SlideNote(trimToString(sb), time);
                        System.out.println("Note: " + note);
                        notesMap.put(page++, note);
                        sb.setLength(0);
                    } else {
                        sb.append(line).append(System.lineSeparator());
                    }
                } while (line != null);
            }
        } catch (IOException ex) {
            JOptionPane.showMessageDialog(this, String.format("Wystpi wyjtek:\n%s", ex.getLocalizedMessage()),
                    "Bd", JOptionPane.ERROR_MESSAGE);
        }
    }

    private void refreshFullscreenPage() {
        if (fullscreenWindow != null) {
            fullscreenWindow.drawOnCenter(presentationMap.get(currentPage));
        }
        if (notesFrame != null) {
            long previousStart = 0;
            SlideNote previousNote = notesMap.get(currentPage - 1);
            if (previousNote != null) {
                previousStart = previousNote.getSeconds();
            }
            notesFrame.changeSlideInfo(currentPage + 1, pagesCount, notesMap.get(currentPage), previousStart,
                    presentationMap.get(currentPage), presentationMap.get(currentPage + 1));
        }
    }

    private void showTiming() {
        if (fullscreenWindow != null || notesFrame != null) {
            LocalDateTime now = LocalDateTime.now();//.truncatedTo(ChronoUnit.SECONDS);
            long elapsedMillis = ChronoUnit.MILLIS.between(getStartTime(), now);
            System.out.printf("Slide %d/%d: %.1f%n", currentPage + 1, pagesCount, elapsedMillis / 1e3);
        }
    }

    public static void main(String args[]) {
        try {
            javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                | javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(MainFrame.class.getName()).log(java.util.logging.Level.SEVERE, null,
                    ex);
        }
        MainFrame mainFrame = new MainFrame();
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(mainFrame);

        if (args.length > 0) {
            try {
                mainFrame.presentationFile = Paths.get(args[0]).toFile();
                mainFrame.openPresentationFile();
                if (args.length > 1) {
                    mainFrame.notesFile = Paths.get(args[1]).toFile();
                    mainFrame.openNotesFile();
                }
            } catch (Exception ex) {
                ex.printStackTrace(System.err);
            }
        }

        java.awt.EventQueue.invokeLater(() -> mainFrame.setVisible(true));
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel notesFileLabel;
    private javax.swing.JTextField notesFileTextField;
    private javax.swing.JPanel notesPanel;
    private javax.swing.JComboBox<ScreenDevice> notesScreenComboBox;
    private javax.swing.JLabel notesScreenLabel;
    private javax.swing.JButton notesScreenRefreshButton;
    private javax.swing.JFileChooser openFileChooser;
    private javax.swing.JLabel presentationFileLabel;
    private javax.swing.JTextField presentationFileTextField;
    private javax.swing.JProgressBar presentationLoadingProgressBar;
    private javax.swing.JPanel presentationPanel;
    private javax.swing.JComboBox<ScreenDevice> presentationScreenComboBox;
    private javax.swing.JLabel presentationScreenLabel;
    private javax.swing.JButton presentationScreenRefreshButton;
    private javax.swing.JButton startPresentationButton;
    // End of variables declaration//GEN-END:variables

    public LocalDateTime getStartTime() {
        if (startTime == null) {
            return LocalDateTime.now();//.truncatedTo(ChronoUnit.SECONDS);
        } else {
            return startTime;
        }
    }

    public void setStartTime(LocalDateTime startTime) {
        if (startTime == null) {
            this.startTime = null;
        } else {
            this.startTime = startTime;//.truncatedTo(ChronoUnit.SECONDS);
        }
    }

    //    private Charset detectCharset(byte[] bytes, String[] charsets) throws IOException, CharacterCodingException {
    //        for (String charsetName : charsets) {
    //            Charset charset = Charset.forName(charsetName);
    //
    //            CharsetDecoder decoder = charset.newDecoder()
    //                    .onUnmappableCharacter(CodingErrorAction.REPORT)
    //                    .onMalformedInput(CodingErrorAction.REPORT);
    //
    //            try {
    //                decoder.decode(ByteBuffer.wrap(bytes));
    //                return charset;
    //            } catch (CharacterCodingException ex) {
    //            }
    //        }
    //        throw new CharacterCodingException();
    //    }
}