From a554c5aedba20b1db102fc1e28fe1c9e614983a7 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Sun, 20 Mar 2022 01:52:12 +0530 Subject: [PATCH 01/18] Change in progress to make GUI more user-friendly. * To change note association, just double click on the highlight and a new dialog box will come up. This is used to either delete/create/change note association. * List cell renderer for page numbers and highlight lists. Presents a view with icons to make the GUI look friendly. * 'Note association/Disassociation' dialog. (Not complete). * image files moved to new 'res' folder. --- .../poc/HighlightNotePairListRenderer.java | 47 ++ src/coderarjob/kpdfsync/poc/MainFrame.form | 172 ++------ src/coderarjob/kpdfsync/poc/MainFrame.java | 413 +++++++----------- .../kpdfsync/poc/NoteAssociationDialog.form | 190 ++++++++ .../kpdfsync/poc/NoteAssociationDialog.java | 191 ++++++++ .../kpdfsync/poc/PageNumberListRenderer.java | 43 ++ .../kpdfsync/poc/{ => res}/Logo.png | Bin src/coderarjob/kpdfsync/poc/res/attention.png | Bin 0 -> 551 bytes .../kpdfsync/poc/res/check-mark.png | Bin 0 -> 631 bytes .../kpdfsync/poc/res/highlighter.png | Bin 0 -> 678 bytes src/coderarjob/kpdfsync/poc/res/problems.png | Bin 0 -> 749 bytes .../kpdfsync/poc/res/sticky-note.png | Bin 0 -> 403 bytes 12 files changed, 675 insertions(+), 381 deletions(-) create mode 100644 src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java create mode 100644 src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form create mode 100644 src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java create mode 100644 src/coderarjob/kpdfsync/poc/PageNumberListRenderer.java rename src/coderarjob/kpdfsync/poc/{ => res}/Logo.png (100%) create mode 100644 src/coderarjob/kpdfsync/poc/res/attention.png create mode 100644 src/coderarjob/kpdfsync/poc/res/check-mark.png create mode 100644 src/coderarjob/kpdfsync/poc/res/highlighter.png create mode 100644 src/coderarjob/kpdfsync/poc/res/problems.png create mode 100644 src/coderarjob/kpdfsync/poc/res/sticky-note.png diff --git a/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java b/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java new file mode 100644 index 0000000..fc4aba9 --- /dev/null +++ b/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java @@ -0,0 +1,47 @@ +package coderarjob.kpdfsync.poc; + +import javax.swing.*; +import java.awt.Component; +import java.awt.BorderLayout; +import java.awt.Color; + +public class HighlightNotePairListRenderer extends JPanel implements ListCellRenderer +{ + + private JLabel highlightLabel; + private JLabel pairedNoteLabel; + private ImageIcon highlightIcon; + + public HighlightNotePairListRenderer() + { + this.setLayout (new BorderLayout()); + + highlightIcon = new ImageIcon ("src/coderarjob/kpdfsync/poc/res/highlighter.png"); + + highlightLabel = new JLabel(); + highlightLabel.setIcon (highlightIcon); + highlightLabel.setForeground (Color.BLUE); + highlightLabel.setOpaque (false); + this.add (highlightLabel, BorderLayout.PAGE_START); + + pairedNoteLabel = new JLabel(); + pairedNoteLabel.setBackground (Color.DARK_GRAY); + pairedNoteLabel.setOpaque (false); + this.add (pairedNoteLabel, BorderLayout.PAGE_END); + } + + public Component getListCellRendererComponent(JList list, + HighlightNotePair value, int index, + boolean isSelected, boolean cellHasFocus) + { + highlightLabel.setText (value.getHighlightText()); + pairedNoteLabel.setText (value.getNoteText()); + + if (isSelected) + this.setBackground (list.getSelectionBackground()); + else + this.setBackground (list.getBackground()); + + return this; + } +} diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.form b/src/coderarjob/kpdfsync/poc/MainFrame.form index 25835e1..ee9af71 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.form +++ b/src/coderarjob/kpdfsync/poc/MainFrame.form @@ -35,64 +35,51 @@ - - - - - - - - - - - - - - - + + + + + + + + - + - - - - - + + + + + + - + + + - - - + + + + + + + - - - - - - - - - - + - - - + - - + - - + - + @@ -132,23 +119,9 @@ - + - - - - - - - - - - - - - - @@ -296,12 +269,16 @@ + + + + - + @@ -313,7 +290,7 @@ - + @@ -328,39 +305,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + + - + @@ -431,28 +386,6 @@ - - - - - - - - - - - - - - - - - - - - - - @@ -468,22 +401,6 @@ - - - - - - - - - - - - - - - - @@ -497,6 +414,9 @@ + + + diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.java b/src/coderarjob/kpdfsync/poc/MainFrame.java index 67e201a..490ecb1 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.java +++ b/src/coderarjob/kpdfsync/poc/MainFrame.java @@ -43,11 +43,9 @@ private enum StatusTypes private Thread highlightThread, parseClippingsThread; // Other Swing variable declarations - private DefaultListModel statusListModel, - pageNumbersListModel, - highlightsListModel, - notesListModel, - highlightNotesMapListModel; + private DefaultListModel statusListModel; + private DefaultListModel highlightsListModel; + private DefaultListModel pageNumbersListModel; private HighlightNotePairManager mPairManager; @@ -57,8 +55,6 @@ public MainFrame() statusListModel = new DefaultListModel<> (); pageNumbersListModel = new DefaultListModel<> (); highlightsListModel = new DefaultListModel<> (); - notesListModel = new DefaultListModel<> (); - highlightNotesMapListModel = new DefaultListModel<> (); initComponents(); setStatus (ApplicationStatus.NOT_STARTED); @@ -137,7 +133,7 @@ private void browsePdfFileButtonActionPerformed(ActionEvent evt) private void updateMapButtonActionPerformed(ActionEvent evt) { - if (pageNumbersList.getSelectedValue() == null || + /*if (pageNumbersList.getSelectedValue() == null || highlightsList.getSelectedValue() == null || notesList.getSelectedValue() == null) return; @@ -158,12 +154,12 @@ private void updateMapButtonActionPerformed(ActionEvent evt) } catch (Exception ex) { ex.printStackTrace(); } - + */ } private void cancelMapButtonActionPerformed(ActionEvent evt) { - if (highlightNotesMapList.getSelectedValue() == null || + /*if (highlightNotesMapList.getSelectedValue() == null || highlightsList.getSelectedValue() == null) return; @@ -179,7 +175,7 @@ private void cancelMapButtonActionPerformed(ActionEvent evt) updateHighlightNotesPairsList (); } catch (Exception ex) { ex.printStackTrace(); - } + }*/ } private void proceedButtonActionPerformed(ActionEvent evt) @@ -262,27 +258,19 @@ private void pageNumbersListValueChanged(javax.swing.event.ListSelectionEvent ev if (pageNumbersList.getSelectedValue() == null) return; - int pageNumber = Integer.valueOf (pageNumbersList.getSelectedValue()); + PageResource res = (PageResource)pageNumbersList.getSelectedValue(); + int pageNumber = res.getPageNumber(); System.out.println ("Page number selected is " + pageNumber); // Clear highlights and notes highlightsListModel.clear(); - notesListModel.clear(); // Add highlights and notes - PageResource res = mPairManager.getPageResourceByPageNumber (pageNumber); - if (res == null) - { - System.out.println ("Not found"); - return; - } - try { - for (HighlightNotePair pair : res.getPairs()) - highlightsListModel.addElement (pair.getHighlightText()); - - for (String unpairedNote : res.getNoteList()) - notesListModel.addElement (unpairedNote); + for (HighlightNotePair pair : res.getPairs()) { + System.out.println ("Here"); + highlightsListModel.addElement (pair); + } } catch (Exception ex) { ex.printStackTrace(); } @@ -292,44 +280,6 @@ private void highlightsListValueChanged(javax.swing.event.ListSelectionEvent evt { } - private void notesListValueChanged(javax.swing.event.ListSelectionEvent evt) - { - } - - private void highlightNotesMapListValueChanged(javax.swing.event.ListSelectionEvent evt) - { - if (evt.getValueIsAdjusting() == true) - return; - - if (highlightNotesMapList.getSelectedValue() == null) - return; - - int pageNumber = Integer.valueOf (highlightNotesMapList.getSelectedValue()); - System.out.println ("Page number selected is " + pageNumber); - - // Clear highlights and notes - highlightsListModel.clear(); - notesListModel.clear(); - - // Add highlights and notes - PageResource res = mPairManager.getPageResourceByPageNumber (pageNumber); - if (res == null) - { - System.out.println ("Not found"); - return; - } - - try { - for (HighlightNotePair pair : res.getPairs()) - { - highlightsListModel.addElement (pair.getHighlightText()); - notesListModel.addElement (pair.getNoteText()); - } - } catch (Exception ex) { - ex.printStackTrace(); - } - } - private void optionsButtonActionPerformed(ActionEvent evt) { } @@ -356,6 +306,19 @@ private void exitButtonActionPerformed(ActionEvent evt) } } + private void highlightsListMouseClicked(java.awt.event.MouseEvent evt) + { + JList list = (JList)evt.getSource(); + HighlightNotePair pair = (HighlightNotePair) list.getSelectedValue(); + + if (pair == null) return; /* Nothing is selected. */ + if (evt.getClickCount() != 2) return; /* Not double click. */ + + NoteAssociationDialog form = new NoteAssociationDialog(this, true); + form.setTitle ("mk-float-Associate/Disassociate note"); + form.setVisible(true); + } + /* Other private class methods*/ private void populateHighlightNotesListBoxes (String bookTitle) { @@ -392,18 +355,17 @@ private void populateHighlightNotesListBoxes (String bookTitle) private void updateHighlightNotesPairsList () { pageNumbersListModel.clear(); - highlightNotesMapListModel.clear(); highlightsListModel.clear(); - notesListModel.clear(); try { for (int pageNumber : mPairManager.getPageNumbers()) { PageResource r = mPairManager.getPageResourceByPageNumber (pageNumber); - if (r.isPairsComplete()) + pageNumbersListModel.addElement (r); + /*if (r.isPairsComplete()) highlightNotesMapListModel.addElement (String.valueOf (pageNumber)); else - pageNumbersListModel.addElement (String.valueOf (pageNumber)); + pageNumbersListModel.addElement (String.valueOf (pageNumber));*/ } } catch (Exception ex) { addStatusLine (StatusTypes.ERROR, "", ex.getMessage ()); @@ -535,8 +497,6 @@ public void run() { browseClippingsFileButton.setEnabled (false); selectBookNameComboBox.setEnabled (false); browsePdfFileButton.setEnabled (false); - updateMapButton.setEnabled (false); - cancelMapButton.setEnabled (false); proceedButton.setEnabled (false); pdfSkipPagesSpinner.setEnabled (false); matchThressholdSpinner.setEnabled (false); @@ -552,8 +512,6 @@ public void run() { case HIGHLIGHT_COMPLETED: // intentional falling case HIGHLIGHT_FAILED: // intentional falling case PDF_SELECTED: - updateMapButton.setEnabled (true); - cancelMapButton.setEnabled (true); proceedButton.setEnabled (true); pdfSkipPagesSpinner.setEnabled (true); matchThressholdSpinner.setEnabled (true); @@ -601,21 +559,14 @@ private void initComponents() { selectHighlightLabel = new javax.swing.JLabel(); highlightsScrollPane = new javax.swing.JScrollPane(); highlightsList = new javax.swing.JList<>(); - selectNoteLabel = new javax.swing.JLabel(); - notesScrollPane = new javax.swing.JScrollPane(); - notesList = new javax.swing.JList<>(); proceedPanel = new javax.swing.JPanel(); proceedButton = new javax.swing.JButton(); jProgressBar1 = new javax.swing.JProgressBar(); statusScrollPane = new javax.swing.JScrollPane(); statusList = new javax.swing.JList<>(); statusLabel = new javax.swing.JLabel(); - pageNumbersScrollPane1 = new javax.swing.JScrollPane(); - highlightNotesMapList = new javax.swing.JList<>(); pdfSkipPagesLabel = new javax.swing.JLabel(); pdfSkipPagesSpinner = new javax.swing.JSpinner(); - updateMapButton = new javax.swing.JButton(); - cancelMapButton = new javax.swing.JButton(); matchThressholdLabel = new javax.swing.JLabel(); matchThressholdSpinner = new javax.swing.JSpinner(); percentLabel = new javax.swing.JLabel(); @@ -625,7 +576,7 @@ private void initComponents() { headerPanel.setBackground(new java.awt.Color(25, 66, 97)); - logoLabel.setIcon(new javax.swing.ImageIcon("src/coderarjob/kpdfsync/poc/Logo.png")); + logoLabel.setIcon(new javax.swing.ImageIcon("/home/coder/NetBeansProjects/GuiHelloWorld/Resources/LogoHori.png")); // NOI18N logoLabel.setToolTipText(""); exitButton.setText("Exit"); @@ -645,24 +596,24 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { javax.swing.GroupLayout headerPanelLayout = new javax.swing.GroupLayout(headerPanel); headerPanel.setLayout(headerPanelLayout); headerPanelLayout.setHorizontalGroup( - headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(headerPanelLayout.createSequentialGroup() - .addComponent(logoLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(optionsButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(exitButton) - .addContainerGap()) - ); + headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(headerPanelLayout.createSequentialGroup() + .addComponent(logoLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(optionsButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(exitButton) + .addContainerGap()) + ); headerPanelLayout.setVerticalGroup( - headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(logoLabel, javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, headerPanelLayout.createSequentialGroup() - .addGroup(headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(exitButton) - .addComponent(optionsButton)) - .addGap(24, 24, 24)) - ); + headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(logoLabel, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, headerPanelLayout.createSequentialGroup() + .addGroup(headerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(exitButton) + .addComponent(optionsButton)) + .addGap(24, 24, 24)) + ); clippingsFileLabel.setForeground(new java.awt.Color(0, 0, 0)); clippingsFileLabel.setLabelFor(clippingsFileTextBox); @@ -691,6 +642,8 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { }); pageNumbersList.setModel(pageNumbersListModel); + pageNumbersList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + pageNumbersList.setCellRenderer(new PageNumberListRenderer()); pageNumbersList.addListSelectionListener(new javax.swing.event.ListSelectionListener() { public void valueChanged(javax.swing.event.ListSelectionEvent evt) { pageNumbersListValueChanged(evt); @@ -700,9 +653,16 @@ public void valueChanged(javax.swing.event.ListSelectionEvent evt) { pageNumbersLabel.setText("Page numbers:"); - selectHighlightLabel.setText("Select a highlight from the selected page:"); + selectHighlightLabel.setText("Highlight as associated notes. (Double-click to change)"); highlightsList.setModel(highlightsListModel); + highlightsList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + highlightsList.setCellRenderer(new HighlightNotePairListRenderer()); + highlightsList.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + highlightsListMouseClicked(evt); + } + }); highlightsList.addListSelectionListener(new javax.swing.event.ListSelectionListener() { public void valueChanged(javax.swing.event.ListSelectionEvent evt) { highlightsListValueChanged(evt); @@ -710,16 +670,6 @@ public void valueChanged(javax.swing.event.ListSelectionEvent evt) { }); highlightsScrollPane.setViewportView(highlightsList); - selectNoteLabel.setText("Select the note from the selected page, that corresponds with the above highlight:"); - - notesList.setModel(notesListModel); - notesList.addListSelectionListener(new javax.swing.event.ListSelectionListener() { - public void valueChanged(javax.swing.event.ListSelectionEvent evt) { - notesListValueChanged(evt); - } - }); - notesScrollPane.setViewportView(notesList); - proceedButton.setText("Proceed"); proceedButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -732,59 +682,38 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { javax.swing.GroupLayout proceedPanelLayout = new javax.swing.GroupLayout(proceedPanel); proceedPanel.setLayout(proceedPanelLayout); proceedPanelLayout.setHorizontalGroup( - proceedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, proceedPanelLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jProgressBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(proceedButton) - .addContainerGap()) - ); + proceedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, proceedPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jProgressBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(proceedButton) + .addContainerGap()) + ); proceedPanelLayout.setVerticalGroup( - proceedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(proceedPanelLayout.createSequentialGroup() - .addGap(0, 12, Short.MAX_VALUE) - .addGroup(proceedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(proceedButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jProgressBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - ); + proceedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(proceedPanelLayout.createSequentialGroup() + .addGap(0, 12, Short.MAX_VALUE) + .addGroup(proceedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(proceedButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jProgressBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + ); statusList.setModel(this.statusListModel); statusScrollPane.setViewportView(statusList); statusLabel.setText("Status:"); - highlightNotesMapList.setModel(highlightNotesMapListModel); - highlightNotesMapList.addListSelectionListener(new javax.swing.event.ListSelectionListener() { - public void valueChanged(javax.swing.event.ListSelectionEvent evt) { - highlightNotesMapListValueChanged(evt); - } - }); - pageNumbersScrollPane1.setViewportView(highlightNotesMapList); - pdfSkipPagesLabel.setLabelFor(pdfSkipPagesSpinner); pdfSkipPagesLabel.setText("Number of pages to skip:"); pdfSkipPagesSpinner.setModel(new javax.swing.SpinnerNumberModel()); - updateMapButton.setText("Update map"); - updateMapButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - updateMapButtonActionPerformed(evt); - } - }); - - cancelMapButton.setText("Cancel map"); - cancelMapButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - cancelMapButtonActionPerformed(evt); - } - }); - matchThressholdLabel.setLabelFor(matchThressholdSpinner); matchThressholdLabel.setText("Match acceptance thresshold:"); matchThressholdSpinner.setModel(new javax.swing.SpinnerNumberModel()); + matchThressholdSpinner.setValue(90); percentLabel.setLabelFor(pdfSkipPagesSpinner); percentLabel.setText("%"); @@ -792,140 +721,116 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(headerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(proceedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(statusScrollPane) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(selectBookNameLabel) - .addComponent(clippingsFileLabel) - .addComponent(pageNumbersLabel) - .addComponent(pageNumbersScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 152, Short.MAX_VALUE) - .addComponent(statusLabel) - .addComponent(pdfSkipPagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(pageNumbersScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addComponent(selectPdfFileLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(cancelMapButton, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(updateMapButton, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(clippingsFileTextBox) - .addComponent(selectBookNameComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browseClippingsFileButton)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(selectHighlightLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 239, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(selectNoteLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 518, Short.MAX_VALUE)) - .addGap(190, 190, 190)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(selectPdfFileTextBox) - .addGroup(layout.createSequentialGroup() - .addComponent(pdfSkipPagesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(matchThressholdLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 183, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(percentLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browsePdfFileButton)) - .addComponent(highlightsScrollPane) - .addComponent(notesScrollPane)))) - .addContainerGap()) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(headerPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(clippingsFileLabel) - .addComponent(clippingsFileTextBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(browseClippingsFileButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(selectBookNameLabel) - .addComponent(selectBookNameComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(selectPdfFileLabel) - .addComponent(selectPdfFileTextBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(browsePdfFileButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(pdfSkipPagesLabel) - .addComponent(pdfSkipPagesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(matchThressholdLabel) - .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(percentLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(headerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(proceedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(statusScrollPane) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(selectBookNameLabel) + .addComponent(clippingsFileLabel) + .addComponent(statusLabel) + .addComponent(pdfSkipPagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 152, Short.MAX_VALUE) + .addComponent(selectPdfFileLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(pageNumbersLabel) - .addComponent(selectHighlightLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(highlightsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 237, Short.MAX_VALUE) - .addComponent(pageNumbersScrollPane)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(updateMapButton) - .addComponent(selectNoteLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(pageNumbersScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(cancelMapButton) + .addComponent(pdfSkipPagesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(matchThressholdLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 183, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(pageNumbersScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 178, Short.MAX_VALUE)) - .addComponent(notesScrollPane)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(proceedPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(statusLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(statusScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 108, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap()) - ); + .addComponent(percentLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(highlightsScrollPane) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(selectBookNameComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(clippingsFileTextBox, javax.swing.GroupLayout.PREFERRED_SIZE, 665, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseClippingsFileButton)) + .addGroup(layout.createSequentialGroup() + .addComponent(selectPdfFileTextBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browsePdfFileButton))) + .addGap(6, 6, 6)) + .addComponent(selectHighlightLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addGap(5, 5, 5)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(headerPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(clippingsFileLabel) + .addComponent(clippingsFileTextBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseClippingsFileButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(selectBookNameLabel) + .addComponent(selectBookNameComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(selectPdfFileLabel) + .addComponent(selectPdfFileTextBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browsePdfFileButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(pdfSkipPagesLabel) + .addComponent(pdfSkipPagesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(matchThressholdLabel) + .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(percentLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(pageNumbersLabel) + .addComponent(selectHighlightLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(highlightsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 481, Short.MAX_VALUE) + .addComponent(pageNumbersScrollPane)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(proceedPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(statusLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(statusScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 108, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); pack(); }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton browseClippingsFileButton; private javax.swing.JButton browsePdfFileButton; - private javax.swing.JButton cancelMapButton; private javax.swing.JLabel clippingsFileLabel; private javax.swing.JTextField clippingsFileTextBox; private javax.swing.JButton exitButton; private javax.swing.JFileChooser fileChooser; private javax.swing.JPanel headerPanel; - private javax.swing.JList highlightNotesMapList; - private javax.swing.JList highlightsList; + private javax.swing.JList highlightsList; private javax.swing.JScrollPane highlightsScrollPane; private javax.swing.JProgressBar jProgressBar1; private javax.swing.JLabel logoLabel; private javax.swing.JLabel matchThressholdLabel; private javax.swing.JSpinner matchThressholdSpinner; - private javax.swing.JList notesList; - private javax.swing.JScrollPane notesScrollPane; private javax.swing.JButton optionsButton; private javax.swing.JLabel pageNumbersLabel; - private javax.swing.JList pageNumbersList; + private javax.swing.JList pageNumbersList; private javax.swing.JScrollPane pageNumbersScrollPane; - private javax.swing.JScrollPane pageNumbersScrollPane1; private javax.swing.JLabel pdfSkipPagesLabel; private javax.swing.JSpinner pdfSkipPagesSpinner; private javax.swing.JLabel percentLabel; @@ -934,13 +839,11 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { private javax.swing.JComboBox selectBookNameComboBox; private javax.swing.JLabel selectBookNameLabel; private javax.swing.JLabel selectHighlightLabel; - private javax.swing.JLabel selectNoteLabel; private javax.swing.JLabel selectPdfFileLabel; private javax.swing.JTextField selectPdfFileTextBox; private javax.swing.JLabel statusLabel; private javax.swing.JList statusList; private javax.swing.JScrollPane statusScrollPane; - private javax.swing.JButton updateMapButton; // End of variables declaration//GEN-END:variables } diff --git a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form new file mode 100644 index 0000000..dcdc08b --- /dev/null +++ b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form @@ -0,0 +1,190 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java new file mode 100644 index 0000000..2cbad5e --- /dev/null +++ b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java @@ -0,0 +1,191 @@ +package coderarjob.kpdfsync.poc; + +/** + * + * @author coder + */ +public class NoteAssociationDialog extends javax.swing.JDialog { + + /** + * Creates new form NoteAssociationDialog + */ + public NoteAssociationDialog(java.awt.Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + } + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jLabel3 = new javax.swing.JLabel(); + jButton1 = new javax.swing.JButton(); + jScrollPane1 = new javax.swing.JScrollPane(); + jList1 = new javax.swing.JList<>(); + jPanel1 = new javax.swing.JPanel(); + jButton2 = new javax.swing.JButton(); + jButton3 = new javax.swing.JButton(); + jLabel4 = new javax.swing.JLabel(); + jLabel5 = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + jLabel1.setFont(new java.awt.Font("Dialog", 1, 11)); // NOI18N + jLabel1.setIcon(new javax.swing.ImageIcon("/home/coder/Work/Java/Projects/kpdfsync/src/coderarjob/kpdfsync/poc/res/sticky-note.png")); // NOI18N + jLabel1.setText("Select the note associated with the highlight"); + + jLabel2.setText("Open the book to the page, and check which note goes with the highlight."); + + jLabel3.setBackground(new java.awt.Color(255, 255, 255)); + jLabel3.setText(""); + jLabel3.setVerticalAlignment(javax.swing.SwingConstants.TOP); + jLabel3.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + jLabel3.setOpaque(true); + + jButton1.setText("Delete mapping"); + jButton1.setToolTipText(""); + + jList1.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane1.setViewportView(jList1); + + jButton2.setText("Create mapping"); + jButton2.setToolTipText(""); + + jButton3.setText("Close"); + jButton3.setToolTipText(""); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jButton2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jButton3) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jButton2) + .addComponent(jButton3)) + .addContainerGap()) + ); + + jLabel4.setText("Page number:"); + + jLabel5.setFont(new java.awt.Font("Dialog", 1, 11)); // NOI18N + jLabel5.setText(""); + + 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) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel1) + .addComponent(jLabel2)) + .addGap(0, 81, Short.MAX_VALUE)) + .addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(jLabel4) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel5) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jButton1)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING)) + .addContainerGap()) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jButton1) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel4) + .addComponent(jLabel5))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 172, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + + pack(); + }// //GEN-END:initComponents + + /** + * @param args the command line arguments + */ + public static void main(String args[]) { + /* Set the Nimbus look and feel */ + // + /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. + * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html + */ + try { + for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + javax.swing.UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (ClassNotFoundException ex) { + java.util.logging.Logger.getLogger(NoteAssociationDialog.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } catch (InstantiationException ex) { + java.util.logging.Logger.getLogger(NoteAssociationDialog.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } catch (IllegalAccessException ex) { + java.util.logging.Logger.getLogger(NoteAssociationDialog.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } catch (javax.swing.UnsupportedLookAndFeelException ex) { + java.util.logging.Logger.getLogger(NoteAssociationDialog.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); + } + // + + /* Create and display the dialog */ + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + NoteAssociationDialog dialog = new NoteAssociationDialog(new javax.swing.JFrame(), true); + dialog.addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(java.awt.event.WindowEvent e) { + System.exit(0); + } + }); + dialog.setVisible(true); + } + }); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButton1; + private javax.swing.JButton jButton2; + private javax.swing.JButton jButton3; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JList jList1; + private javax.swing.JPanel jPanel1; + private javax.swing.JScrollPane jScrollPane1; + // End of variables declaration//GEN-END:variables +} diff --git a/src/coderarjob/kpdfsync/poc/PageNumberListRenderer.java b/src/coderarjob/kpdfsync/poc/PageNumberListRenderer.java new file mode 100644 index 0000000..5edf216 --- /dev/null +++ b/src/coderarjob/kpdfsync/poc/PageNumberListRenderer.java @@ -0,0 +1,43 @@ +package coderarjob.kpdfsync.poc; + +import javax.swing.*; +import java.awt.Component; +import java.awt.FlowLayout; + +public class PageNumberListRenderer extends JLabel implements ListCellRenderer +{ + + private ImageIcon okayIcon; + private ImageIcon warningIcon; + private ImageIcon errorIcon; + + public PageNumberListRenderer() + { + okayIcon = new ImageIcon ("src/coderarjob/kpdfsync/poc/res/check-mark.png"); + warningIcon = new ImageIcon ("src/coderarjob/kpdfsync/poc/res/attention.png"); + errorIcon = new ImageIcon ("src/coderarjob/kpdfsync/poc/res/problems.png"); + + this.setOpaque (true); + } + + public Component getListCellRendererComponent(JList list, + PageResource value, int index, boolean isSelected, + boolean cellHasFocus) + { + this.setText (String.valueOf (value.getPageNumber())); + String status; + try { + this.setIcon (value.isPairsComplete() ? okayIcon : warningIcon); + } catch (Exception ex) { + this.setIcon (errorIcon); + } + + if (isSelected) + this.setBackground (list.getSelectionBackground()); + else + this.setBackground (list.getBackground()); + + return this; + } + +} diff --git a/src/coderarjob/kpdfsync/poc/Logo.png b/src/coderarjob/kpdfsync/poc/res/Logo.png similarity index 100% rename from src/coderarjob/kpdfsync/poc/Logo.png rename to src/coderarjob/kpdfsync/poc/res/Logo.png diff --git a/src/coderarjob/kpdfsync/poc/res/attention.png b/src/coderarjob/kpdfsync/poc/res/attention.png new file mode 100644 index 0000000000000000000000000000000000000000..53450291b0655414d00fa08e72d8452382b9781d GIT binary patch literal 551 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2?- zG7x6;t5)LxG9*h}BT9nv(@M${i&7cN%ggmL^RkPR6AM!H@{7`Ezq65IU|?M0>Eak- z(VLvGKy|~$je=W%_{j1$*6A&cjg1XEuT6i<0|fE=BqCyV6g*_kWLshAx5D$sf^$cH z%zyYe{2-^eo`Fh2@bMPk3wJMu+`jl@`D%vq@oaYQ>Up+R+26e@d-h9bS>vWV_fH1q z#V_fLd6WPG9cBNu3Lj~?+cPsa3pQ-&JFnmY9~X505#Av865h&$Cf( zzVG3`Jjx%W5^{3RtkU|t{*v2&cKKaS-gD>M7(J@j-nw<`%>t9$d^5MJu;urHS%fuUoJYuZ>Z`^f&cSlYC?O%6% n%~XSUe*FCWJT~acV-|*Mk`;G6W?P>JMk<4+tDnm{r-UW|9xU*} literal 0 HcmV?d00001 diff --git a/src/coderarjob/kpdfsync/poc/res/check-mark.png b/src/coderarjob/kpdfsync/poc/res/check-mark.png new file mode 100644 index 0000000000000000000000000000000000000000..4557f0ca72fe2e7b455d3da25bdd25f851d772ae GIT binary patch literal 631 zcmV--0*L*IP)IA{xdLgurT+}2y{@%G}rYO7WjXHq1#Q2fq{WR=)=z?A-c*YrZS>z6OUec@%G2h zXmkfMF{}<{`u+Fs|Gy0X7>s2_S*l!2G(-hBPVmm~o@u8j;pwF=&$Z*$!*B1ueV@ya zyX!u>0n7}mul;9oQ+asn?FVODB}pzJP7X$MS#d#eUM}q*T_v86-+wVoJaX~jyZ_I_ z7`8nBhiL!<1H-TFx37P9lo$N|=Z~(ggfIsWJ1c{-Fh2_;10zHK;frtYzxy2ZFZ(ct z?I^MEK4VMOuG^1~op}7>Crs?h%eQ~;z4>_XPu|YMxFQJ=jf@Qc-~VR}nSK1){YM|Z zF#P!QmtpF$%P(Gk{gsMKGejFYnB*xV#>u*wiII{2^RFLiU$b`Z!Dj%)000;9x*P)l R`yl`T002ovPDHLkV1g|>BDw$o literal 0 HcmV?d00001 diff --git a/src/coderarjob/kpdfsync/poc/res/highlighter.png b/src/coderarjob/kpdfsync/poc/res/highlighter.png new file mode 100644 index 0000000000000000000000000000000000000000..adcf60eb667876d88120f672534699e18e1b9c46 GIT binary patch literal 678 zcmV;X0$KfuP)zSBWnJI!tA)|Sd#^B~buDoet&RwxBQ z#u6g1f_^Bnn<6L@yewv2bQ$zRb=5^V47wdf{&XQ(u!Q~CSmvg-vmN)x&bF_sjkPV$ z2k*`I{Cw~8@DWNWyx)0 z+_|t503aqLEU%>m*$6j=+8ROW-Bu$<)`T>gbejPHk6!i@#zcrQ8aG;eoBn zcslF2a*AgfHFfCVH2{F*f4^HXwvBl(7A|8l&#?y>ClrM`t3dLK=dY!RPtasbwMcv)q7`|3tM~J#DdAx&a`Y z&1N(4@RF9JTl|68?%zTxAh!WwS+=2=k7e0SiU!>mcjS3BaYYA8WsQ~6JVz-7Aq2r- z&?}1KM@f>#s)+#rl+xn#cN;#u8}AFXY}0sED%GMwp*T>ASDRvlBm0cKwk|{O$wSTh zzD~8)WMU5{lgX+7zFdf1DAC-+&GvNYbYencwdz{OvkC8GIWYkI1-zT=zkhY!kpKVy M07*qoM6N<$f_RTIwg3PC literal 0 HcmV?d00001 diff --git a/src/coderarjob/kpdfsync/poc/res/problems.png b/src/coderarjob/kpdfsync/poc/res/problems.png new file mode 100644 index 0000000000000000000000000000000000000000..904b86588b17619c590841cdede89f936fdad740 GIT binary patch literal 749 zcmVy{b>Z+|COsBO>5yB)4 zBnWyc3CxF{_L2n=Wx}ABUIiiYAqaYr3JOYDf@F()F`O*_-ldjH@9yflyL;zu54xQB z?>wFFobUV2IlmvUdr*~C?oefwI|P8*jilM7&X&1Oe4*_2;(UrODRmwm1#omvgC>^j zt7A;Q4Oqxl9CMgStKMp}<=P79VgN+|P{){jO)S}$)@jqps=T()Tw`gMG^wRYKDS}< zdwA`)5Y-R>u^N|BdZW=>L912G)t2_R0iidoBQn=H>CE$rk{W9$G2d#hp;=<1tF8f0 zha=qkd7;Z-BFv-H@y9zF05Esd(t(~!hsprp-cS?(&{a`8T$Dk#*(6jD89UOX z4i0_x@kW>VK`EJiHvChW)e@UKsOo}cS28ahHNasSwY#Ci@I3(hgQxNsy7$)t+T{_x!355ecO=hQCn%BO7xb&Sc^ z;wHT_ONqd%ul!B_R4i+R6IaIO#6-_<FQx;X~hzbq)To00000NkvXXu0mjfpm$t{ literal 0 HcmV?d00001 diff --git a/src/coderarjob/kpdfsync/poc/res/sticky-note.png b/src/coderarjob/kpdfsync/poc/res/sticky-note.png new file mode 100644 index 0000000000000000000000000000000000000000..1b1a88f999a10ae02155177793ee500ca54b8e70 GIT binary patch literal 403 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2?- zG7x6;t5)LxG9*h}BT9nv(@M${i&7cN%ggmL^RkPR6AM!H@{7`Ezq65IU|0U2=a{C;j;KRJd8~z6j53eVOz>8`rzepWfOC1kKOi z*X!N0m*}#av;X{u&(HbU^tgR2%luUIYyKZO({Z+5tgU+^=VAsmHa504dHWe0T1T=Q z7TkFMUqsS@Q^J&C7GDSJ+skVt=NWPu{Yg(qc(D8Pw`y_ctMQX!K1}ZKeYmtkfcg3C z_xk(iKMGAqNciJFiCf~IvB8P>`d_ck=cR2u$x}HW#E p#Z&h8lOONz4Kp^WY3yWVSaB@!mehtL>wy8t;OXk;vd$@?2>=@Cqs#yR literal 0 HcmV?d00001 From fd5f0651d40d427167467db6e7c08f31346bcae0 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Sun, 20 Mar 2022 19:23:17 +0530 Subject: [PATCH 02/18] Removed unwanted testing code. * Sets default font for all UI elements by setting font for every FontUIResource properties. Default font is now set to "Dialog Plain, size 11". * Removed all the test codes that was there. * Sets decoration to true, for JFrame and JDialog. --- src/coderarjob/kpdfsync/poc/Main.java | 109 ++++---------------------- 1 file changed, 15 insertions(+), 94 deletions(-) diff --git a/src/coderarjob/kpdfsync/poc/Main.java b/src/coderarjob/kpdfsync/poc/Main.java index 436d127..0f1441c 100644 --- a/src/coderarjob/kpdfsync/poc/Main.java +++ b/src/coderarjob/kpdfsync/poc/Main.java @@ -1,109 +1,30 @@ package coderarjob.kpdfsync.poc; -import java.util.ArrayList; -import java.util.Collections; +import javax.swing.*; +import javax.swing.plaf.FontUIResource; -import coderarjob.kpdfsync.lib.*; -import coderarjob.kpdfsync.lib.clipparser.ParserResult; -import coderarjob.kpdfsync.lib.clipparser.ParserEvents; -import coderarjob.kpdfsync.lib.clipparser.ParserResult.AnnotationType; -import coderarjob.kpdfsync.lib.clipparser.ParserResult.PageNumberType; -import coderarjob.kpdfsync.lib.clipparser.AbstractParser; -import coderarjob.kpdfsync.lib.clipparser.KindleParserV1; - -import coderarjob.kpdfsync.lib.annotator.PdfAnnotatorV1; -import coderarjob.kpdfsync.lib.annotator.AbstractAnnotator; - -import coderarjob.kpdfsync.lib.pm.AbstractMatcher; -import coderarjob.kpdfsync.lib.pm.Match; -import coderarjob.kpdfsync.lib.pm.BasicMatcher; -import coderarjob.kpdfsync.lib.pm.PatternMatcherEvents; - -public class Main implements ParserEvents, PatternMatcherEvents +public class Main { - /* ParserEvents Methods */ - public void onError (String fileName, long offset, String error, ParserResult result) - { - System.out.println (error); - } - public void onSuccess (String fileName, long offset, ParserResult result) { } - - /* PatternMatcherEvents Methods */ - public void onMatchStart (String text, String pattern) - { } - - public void onMatchEnd (Match result) - { - if (result.matchPercent() > 80.0) - System.out.println (String.format("Match %f", result.matchPercent())); + public static void setUIFont (FontUIResource f){ + java.util.Enumeration keys = UIManager.getDefaults().keys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + Object value = UIManager.get (key); + if (value instanceof FontUIResource) + UIManager.put (key, f); + } } /* Class methods */ public static void main(String[] args) throws Exception { + JFrame.setDefaultLookAndFeelDecorated (true); + JDialog.setDefaultLookAndFeelDecorated (true); + setUIFont (new FontUIResource ("Dialog", FontUIResource.PLAIN, 11)); + MainFrame mainFrame = new MainFrame(); mainFrame.setVisible (true); - /*AbstractParser parser = new KindleParserV1 ("test-files/My Clippings.txt"); - parser.setParserEvents (new Main()); - - KindleClippingsFile file = new KindleClippingsFile(parser); - ArrayList titles = Collections.list(file.getBookTitles()); - - System.out.println ("Book titles"); - for (String title : titles) - System.out.println ("\t" + title); - - System.out.println ("---------------------------------"); - - AbstractMatcher matcher = new BasicMatcher(); - matcher.setPatternMatcherEventsHandler(new Main()); - - AbstractAnnotator ann = new PdfAnnotatorV1 (matcher, "test-files/progit.pdf", 6); - ann.open (); - - String bookTitle = titles.get(3); - System.out.println ("Book: " + bookTitle); - - ArrayList entries = Collections.list (file.getBookAnnotations (bookTitle)); - for (ParserResult entry : entries) - { - if (entry.annotationType() != AnnotationType.HIGHLIGHT) - continue; - - if (entry.pageNumberType() != PageNumberType.PAGE_NUMBER) - continue; - - //displayParserResult (entry); - System.out.print ("Highlighting page: " + entry.pageOrLocationNumber() + " "); - ann.highlight (entry.pageOrLocationNumber(), entry.text(), "Test highlight"); - } - - ann.save ("output.pdf");*/ - - } - - private static void displayClippingsForFile (KindleClippingsFile file, String bookTitle) - throws Exception - { - ArrayList entries = Collections.list (file.getBookAnnotations (bookTitle)); - for (ParserResult entry : entries) - displayParserResult (entry); - } - - private static void displayParserResult (ParserResult entry) throws Exception - { - System.out.println ("\t" + entry.title()); - System.out.println ("\t" + entry.annotationType()); - - int pageOrLocationNumber = entry.pageOrLocationNumber(); - if (entry.pageNumberType() == PageNumberType.PAGE_NUMBER) - System.out.println ("\tPage: " + String.valueOf(pageOrLocationNumber)); - else - System.out.println ("\tLocation: " + String.valueOf(pageOrLocationNumber)); - if (entry.annotationType() != AnnotationType.BOOKMARK) - System.out.println ("\t" + entry.text()); - System.out.println ("---------------------------------"); } } From 49b8d038288395cae5d366d1b588c48e21d37b5f Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Sun, 20 Mar 2022 19:30:11 +0530 Subject: [PATCH 03/18] Modification of Exception description. --- src/coderarjob/kpdfsync/poc/PageResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coderarjob/kpdfsync/poc/PageResource.java b/src/coderarjob/kpdfsync/poc/PageResource.java index e19374c..b29f1e1 100644 --- a/src/coderarjob/kpdfsync/poc/PageResource.java +++ b/src/coderarjob/kpdfsync/poc/PageResource.java @@ -114,7 +114,7 @@ public void unpair (String highlight) throws Exception } if (pairsListIndex < 0) - throw new Exception ("Unpair failed. Highlight is not found."); + throw new Exception ("Unpair failed. Highlight text is not found or already unpaired."); // Remove the note from the pairs list and add it to the unpaired notes list. String note = mPairs.get (pairsListIndex).getNoteText(); From 245322851a3d3df91ae426bae14274fe0d96b1c9 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Sun, 20 Mar 2022 19:34:18 +0530 Subject: [PATCH 04/18] Multiple changes in MainFrame GUI. * showDialog method in NoteAssociationDialog. Create, Delete operations in GUI. Almost all the features for this dialog is complete. * Page Numbers list is now horizontal instead of vertical. This creates more space for the highlight note pair list. * The highlight list rows, are alternatively colored. * On error, Exception description is displayed on JOptionsPane message dialog. * Options button is disabled until the time, I do something with it. * Color of the Progressbar changed from the default color. --- .../poc/HighlightNotePairListRenderer.java | 17 +- src/coderarjob/kpdfsync/poc/MainFrame.form | 143 ++++++---- src/coderarjob/kpdfsync/poc/MainFrame.java | 238 ++++++++--------- .../kpdfsync/poc/NoteAssociationDialog.form | 100 ++++--- .../kpdfsync/poc/NoteAssociationDialog.java | 246 +++++++++++------- 5 files changed, 403 insertions(+), 341 deletions(-) diff --git a/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java b/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java index fc4aba9..990106c 100644 --- a/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java +++ b/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java @@ -10,24 +10,25 @@ public class HighlightNotePairListRenderer extends JPanel implements ListCellRen private JLabel highlightLabel; private JLabel pairedNoteLabel; - private ImageIcon highlightIcon; + private Color alternateColor; public HighlightNotePairListRenderer() { this.setLayout (new BorderLayout()); - highlightIcon = new ImageIcon ("src/coderarjob/kpdfsync/poc/res/highlighter.png"); - highlightLabel = new JLabel(); - highlightLabel.setIcon (highlightIcon); - highlightLabel.setForeground (Color.BLUE); + highlightLabel.setIcon (new ImageIcon ("src/coderarjob/kpdfsync/poc/res/highlighter.png")); + highlightLabel.setForeground (Color.BLACK); highlightLabel.setOpaque (false); this.add (highlightLabel, BorderLayout.PAGE_START); pairedNoteLabel = new JLabel(); - pairedNoteLabel.setBackground (Color.DARK_GRAY); + pairedNoteLabel.setForeground (Color.DARK_GRAY); pairedNoteLabel.setOpaque (false); this.add (pairedNoteLabel, BorderLayout.PAGE_END); + + alternateColor = new Color (237, 244, 249); + this.setBorder (BorderFactory.createEmptyBorder (5, 2, 5, 0)); } public Component getListCellRendererComponent(JList list, @@ -37,10 +38,12 @@ public Component getListCellRendererComponent(JList highlightLabel.setText (value.getHighlightText()); pairedNoteLabel.setText (value.getNoteText()); + Color normalBackgroundColor = (index % 2 == 0) ? alternateColor : list.getBackground(); + if (isSelected) this.setBackground (list.getSelectionBackground()); else - this.setBackground (list.getBackground()); + this.setBackground (normalBackgroundColor); return this; } diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.form b/src/coderarjob/kpdfsync/poc/MainFrame.form index ee9af71..6c417bc 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.form +++ b/src/coderarjob/kpdfsync/poc/MainFrame.form @@ -29,57 +29,70 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + - + + + + - - - + + + + + + + + + + + + + + + + - - + + + - @@ -112,22 +125,20 @@ - - - - - - - - - - + + + + + + + + - + - + @@ -187,6 +198,7 @@ + @@ -266,6 +278,11 @@ + + + + + @@ -273,6 +290,8 @@ + + @@ -302,6 +321,11 @@ + + + + + @@ -336,12 +360,9 @@ - - - - - - + + + @@ -357,6 +378,14 @@ + + + + + + + + diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.java b/src/coderarjob/kpdfsync/poc/MainFrame.java index 490ecb1..e9be93e 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.java +++ b/src/coderarjob/kpdfsync/poc/MainFrame.java @@ -17,8 +17,6 @@ import coderarjob.kpdfsync.lib.pm.*; import coderarjob.kpdfsync.lib.annotator.*; -import javax.swing.JOptionPane; - public class MainFrame extends javax.swing.JFrame { private enum ApplicationStatus @@ -48,6 +46,7 @@ private enum StatusTypes private DefaultListModel pageNumbersListModel; private HighlightNotePairManager mPairManager; + private NoteAssociationDialog mNoteForm; /* Constructor and other methods*/ public MainFrame() @@ -56,8 +55,13 @@ public MainFrame() pageNumbersListModel = new DefaultListModel<> (); highlightsListModel = new DefaultListModel<> (); + mNoteForm = new NoteAssociationDialog (this); + initComponents(); setStatus (ApplicationStatus.NOT_STARTED); + + System.out.println (selectBookNameLabel.getFont().getFontName()); + System.out.println (selectBookNameLabel.getFont().isBold()); } /* Kindle Parser event handler*/ @@ -110,7 +114,7 @@ public void run () { { addStatusLine (StatusTypes.OTHER, "Parsing failed: " + ex.getMessage()); setStatus (ApplicationStatus.CLIPPINGS_FILE_PARSE_FAILED); - printExceptionStackTrace (ex); + reportException (ex); } } }); @@ -125,59 +129,13 @@ private void browsePdfFileButtonActionPerformed(ActionEvent evt) selectPdfFileTextBox.setText (file.getAbsolutePath()); String bookTitle = (String)selectBookNameComboBox.getSelectedItem(); - populateHighlightNotesListBoxes (bookTitle); + createPageResourceObjects (bookTitle); + populatePageNumbersList(); setStatus (ApplicationStatus.PDF_SELECTED); } } - private void updateMapButtonActionPerformed(ActionEvent evt) - { - /*if (pageNumbersList.getSelectedValue() == null || - highlightsList.getSelectedValue() == null || - notesList.getSelectedValue() == null) - return; - - int pageNumber = Integer.valueOf (pageNumbersList.getSelectedValue()); - System.out.println ("Page number selected is " + pageNumber); - - String highlightText = highlightsList.getSelectedValue(); - String noteText = notesList.getSelectedValue(); - - System.out.println ("Highlight: " + highlightText); - System.out.println ("Note: " + noteText); - - PageResource r = mPairManager.getPageResourceByPageNumber (pageNumber); - try{ - r.pair (highlightText, noteText); - updateHighlightNotesPairsList (); - } catch (Exception ex) { - ex.printStackTrace(); - } - */ - } - - private void cancelMapButtonActionPerformed(ActionEvent evt) - { - /*if (highlightNotesMapList.getSelectedValue() == null || - highlightsList.getSelectedValue() == null) - return; - - int pageNumber = Integer.valueOf (highlightNotesMapList.getSelectedValue()); - System.out.println ("Page number selected is " + pageNumber); - - String highlightText = highlightsList.getSelectedValue(); - System.out.println ("Highlight: " + highlightText); - - PageResource r = mPairManager.getPageResourceByPageNumber (pageNumber); - try{ - r.unpair (highlightText); - updateHighlightNotesPairsList (); - } catch (Exception ex) { - ex.printStackTrace(); - }*/ - } - private void proceedButtonActionPerformed(ActionEvent evt) { String bookTitle = (String)selectBookNameComboBox.getSelectedItem(); @@ -238,12 +196,12 @@ public void run() { } catch (InterruptedException ex) { addStatusLine (StatusTypes.OTHER, "Highlight canceled."); setStatus (ApplicationStatus.HIGHLIGHT_FAILED); - printExceptionStackTrace (ex); + reportException (ex); } catch (Exception ex) { addStatusLine (StatusTypes.OTHER, "Highlighting failed."); setStatus (ApplicationStatus.HIGHLIGHT_FAILED); - printExceptionStackTrace (ex); + reportException (ex); } } }); @@ -268,11 +226,11 @@ private void pageNumbersListValueChanged(javax.swing.event.ListSelectionEvent ev // Add highlights and notes try { for (HighlightNotePair pair : res.getPairs()) { - System.out.println ("Here"); highlightsListModel.addElement (pair); } } catch (Exception ex) { - ex.printStackTrace(); + addStatusLine (StatusTypes.ERROR, "", ex.getMessage ()); + reportException (ex); } } @@ -308,23 +266,45 @@ private void exitButtonActionPerformed(ActionEvent evt) private void highlightsListMouseClicked(java.awt.event.MouseEvent evt) { - JList list = (JList)evt.getSource(); + JList list = (JList)evt.getSource(); HighlightNotePair pair = (HighlightNotePair) list.getSelectedValue(); - if (pair == null) return; /* Nothing is selected. */ - if (evt.getClickCount() != 2) return; /* Not double click. */ + if (pair == null) return; // Nothing is selected. + if (evt.getClickCount() != 2) return; // Not double click. + + PageResource res = mPairManager.getPageResourceByPageNumber (pair.getPageNumber()); + + try { + switch (mNoteForm.showDialog (this, res)) + { + case CANCEL: + // Nothing to be done. Just break. + break; + case CREATE: + String noteText = mNoteForm.getSelectedNoteText(); + System.out.println ("Selected: " + noteText); + if (noteText != null) + res.pair (pair.getHighlightText(), noteText); + + break; + case DELETE: + res.unpair (pair.getHighlightText()); + break; + } - NoteAssociationDialog form = new NoteAssociationDialog(this, true); - form.setTitle ("mk-float-Associate/Disassociate note"); - form.setVisible(true); + populatePageNumbersList(); + } catch (Exception ex) { + addStatusLine (StatusTypes.ERROR, "", ex.getMessage ()); + reportException (ex); + } } /* Other private class methods*/ - private void populateHighlightNotesListBoxes (String bookTitle) + private void createPageResourceObjects (String bookTitle) { try { ArrayList entries = Collections.list (mClippingsFile.getBookAnnotations (bookTitle)); - mPairManager = new HighlightNotePairManager (); + mPairManager = new HighlightNotePairManager(); for (ParserResult entry : entries) { if (entry.annotationType() != AnnotationType.HIGHLIGHT && @@ -337,23 +317,19 @@ private void populateHighlightNotesListBoxes (String bookTitle) if (entry.annotationType () == AnnotationType.NOTE) mPairManager.addNote (entry.pageOrLocationNumber(), entry.text()); } - } catch (Exception ex) { - addStatusLine (StatusTypes.ERROR, "", ex.getMessage ()); - printExceptionStackTrace (ex); - } - // Automatic pair for pages with only one highlight and note - try { + // Automatic pair for pages with only one highlight and note mPairManager.pairAutomatic(); - updateHighlightNotesPairsList(); } catch (Exception ex) { addStatusLine (StatusTypes.ERROR, "", ex.getMessage ()); - printExceptionStackTrace (ex); + reportException (ex); } } - private void updateHighlightNotesPairsList () + private void populatePageNumbersList () { + int selectedIndex = pageNumbersList.getSelectedIndex(); + pageNumbersListModel.clear(); highlightsListModel.clear(); @@ -362,14 +338,14 @@ private void updateHighlightNotesPairsList () { PageResource r = mPairManager.getPageResourceByPageNumber (pageNumber); pageNumbersListModel.addElement (r); - /*if (r.isPairsComplete()) - highlightNotesMapListModel.addElement (String.valueOf (pageNumber)); - else - pageNumbersListModel.addElement (String.valueOf (pageNumber));*/ } + + if (selectedIndex >= 0) + pageNumbersList.setSelectedIndex (selectedIndex); + } catch (Exception ex) { addStatusLine (StatusTypes.ERROR, "", ex.getMessage ()); - printExceptionStackTrace (ex); + reportException (ex); } } @@ -398,8 +374,9 @@ public void onMatchEnd (Match result) }); } - private void printExceptionStackTrace (Exception ex) + private void reportException (Exception ex) { + JOptionPane.showMessageDialog (this, ex.getMessage(), "Error!", JOptionPane.ERROR_MESSAGE); System.err.println (ex.getMessage()); ex.printStackTrace(); } @@ -587,6 +564,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { }); optionsButton.setText("Options"); + optionsButton.setEnabled(false); optionsButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { optionsButtonActionPerformed(evt); @@ -641,9 +619,12 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { } }); + pageNumbersList.setBorder(javax.swing.BorderFactory.createEtchedBorder()); pageNumbersList.setModel(pageNumbersListModel); pageNumbersList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); pageNumbersList.setCellRenderer(new PageNumberListRenderer()); + pageNumbersList.setLayoutOrientation(javax.swing.JList.VERTICAL_WRAP); + pageNumbersList.setVisibleRowCount(3); pageNumbersList.addListSelectionListener(new javax.swing.event.ListSelectionListener() { public void valueChanged(javax.swing.event.ListSelectionEvent evt) { pageNumbersListValueChanged(evt); @@ -655,6 +636,7 @@ public void valueChanged(javax.swing.event.ListSelectionEvent evt) { selectHighlightLabel.setText("Highlight as associated notes. (Double-click to change)"); + highlightsList.setBorder(javax.swing.BorderFactory.createEtchedBorder()); highlightsList.setModel(highlightsListModel); highlightsList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); highlightsList.setCellRenderer(new HighlightNotePairListRenderer()); @@ -677,6 +659,8 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { } }); + jProgressBar1.setForeground(new java.awt.Color(0, 204, 204)); + jProgressBar1.setBorder(javax.swing.BorderFactory.createEtchedBorder()); jProgressBar1.setStringPainted(true); javax.swing.GroupLayout proceedPanelLayout = new javax.swing.GroupLayout(proceedPanel); @@ -692,11 +676,9 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { ); proceedPanelLayout.setVerticalGroup( proceedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(proceedPanelLayout.createSequentialGroup() - .addGap(0, 12, Short.MAX_VALUE) - .addGroup(proceedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(proceedButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jProgressBar1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addGroup(proceedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jProgressBar1, javax.swing.GroupLayout.PREFERRED_SIZE, 24, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(proceedButton)) ); statusList.setModel(this.statusListModel); @@ -723,47 +705,55 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(headerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(proceedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(statusScrollPane) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(selectBookNameLabel) - .addComponent(clippingsFileLabel) - .addComponent(statusLabel) - .addComponent(pdfSkipPagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 152, Short.MAX_VALUE) - .addComponent(selectPdfFileLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(pageNumbersLabel) - .addComponent(pageNumbersScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(pdfSkipPagesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(matchThressholdLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 183, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(statusScrollPane) + .addContainerGap()) + .addGroup(layout.createSequentialGroup() + .addComponent(highlightsScrollPane) + .addContainerGap()) + .addGroup(layout.createSequentialGroup() + .addComponent(pageNumbersScrollPane) + .addContainerGap()) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(selectBookNameLabel) + .addComponent(clippingsFileLabel) + .addComponent(statusLabel) + .addComponent(pdfSkipPagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 152, Short.MAX_VALUE) + .addComponent(selectPdfFileLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pageNumbersLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(percentLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) - .addComponent(highlightsScrollPane) - .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(selectBookNameComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(clippingsFileTextBox, javax.swing.GroupLayout.PREFERRED_SIZE, 665, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browseClippingsFileButton)) .addGroup(layout.createSequentialGroup() - .addComponent(selectPdfFileTextBox) + .addComponent(pdfSkipPagesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browsePdfFileButton))) - .addGap(6, 6, 6)) - .addComponent(selectHighlightLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) - .addGap(5, 5, 5)) + .addComponent(matchThressholdLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 183, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(percentLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 378, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(selectBookNameComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(clippingsFileTextBox)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseClippingsFileButton)) + .addGroup(layout.createSequentialGroup() + .addComponent(selectPdfFileTextBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browsePdfFileButton))) + .addGap(6, 6, 6)))) + .addComponent(proceedPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(5, 5, 5)) + .addComponent(selectHighlightLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -790,20 +780,20 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(matchThressholdLabel) .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(percentLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(pageNumbersLabel) - .addComponent(selectHighlightLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(highlightsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 481, Short.MAX_VALUE) - .addComponent(pageNumbersScrollPane)) + .addComponent(pageNumbersLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pageNumbersScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 81, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(selectHighlightLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(highlightsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 431, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(proceedPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(statusLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(statusScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 108, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(statusScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 81, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); diff --git a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form index dcdc08b..17e3366 100644 --- a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form +++ b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form @@ -3,6 +3,13 @@
+ + + + + + + @@ -23,29 +30,26 @@ - + + + + + + + + - - - - - - - - - - @@ -56,17 +60,12 @@ - - - - - - - - + + + - + @@ -77,7 +76,7 @@ - + @@ -90,27 +89,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -118,10 +96,10 @@ - + - - + + @@ -137,10 +115,12 @@ + + - + - + @@ -150,8 +130,9 @@ - - + + + @@ -159,17 +140,32 @@ - + + + + - + + + + + + + + + + + + + @@ -178,7 +174,7 @@ - + diff --git a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java index 2cbad5e..76726eb 100644 --- a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java +++ b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java @@ -1,17 +1,93 @@ package coderarjob.kpdfsync.poc; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +import java.util.List; + /** * * @author coder */ -public class NoteAssociationDialog extends javax.swing.JDialog { +public class NoteAssociationDialog extends javax.swing.JDialog +{ + public enum NoteAssociationDialogOptions + { + DELETE, CREATE, CANCEL + } + + private NoteAssociationDialogOptions mSelectedDialogOption; + private String mSelectedNoteText; + private DefaultListModel mNotesListModel; /** * Creates new form NoteAssociationDialog */ - public NoteAssociationDialog(java.awt.Frame parent, boolean modal) { - super(parent, modal); - initComponents(); + public NoteAssociationDialog(Frame parent) + { + super(parent, true); + mNotesListModel = new DefaultListModel<>(); + initComponents(); + } + + private void reset() + { + mSelectedDialogOption = NoteAssociationDialogOptions.CANCEL; + mSelectedNoteText = null; + mNotesListModel.clear(); + notesList.setEnabled (true); + createMappingButton.setEnabled (true); + } + + private void populateUI (PageResource res) + { + pageNumberLabel.setText (String.valueOf(res.getPageNumber())); + List notesTextList = res.getNoteList(); + + if (notesTextList.size() == 0) { + // There are no notes in this page. Show a pop-up and close. + mNotesListModel.addElement ("No unpaired note on this page."); + notesList.setEnabled (false); + createMappingButton.setEnabled (false); + return; + } + + // Display notes on the list. + for (String note : notesTextList) + mNotesListModel.addElement (note); + } + + public String getSelectedNoteText() + { + return mSelectedNoteText; + } + + public NoteAssociationDialogOptions showDialog(Frame parent, PageResource pageResource) + { + this.reset(); + this.populateUI (pageResource); + this.setVisible(true); + + return this.mSelectedDialogOption; + } + + private void closeButtonActionPerformed(ActionEvent evt) + { + this.setVisible (false); + } + + private void createMappingButtonActionPerformed(ActionEvent evt) + { + mSelectedNoteText = notesList.getSelectedValue(); + mSelectedDialogOption = NoteAssociationDialogOptions.CREATE; + this.setVisible (false); + } + + private void deleteMappingButtonActionPerformed(ActionEvent evt) + { + mSelectedDialogOption = NoteAssociationDialogOptions.DELETE; + this.setVisible (false); } /** @@ -24,51 +100,67 @@ private void initComponents() { jLabel1 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); - jLabel3 = new javax.swing.JLabel(); - jButton1 = new javax.swing.JButton(); jScrollPane1 = new javax.swing.JScrollPane(); - jList1 = new javax.swing.JList<>(); + notesList = new javax.swing.JList<>(); jPanel1 = new javax.swing.JPanel(); - jButton2 = new javax.swing.JButton(); - jButton3 = new javax.swing.JButton(); + createMappingButton = new javax.swing.JButton(); + closeButton = new javax.swing.JButton(); + deleteMappingButton = new javax.swing.JButton(); jLabel4 = new javax.swing.JLabel(); - jLabel5 = new javax.swing.JLabel(); + pageNumberLabel = new javax.swing.JLabel(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Note association dialog"); + setAlwaysOnTop(true); + setName("noteAssociationDialog"); // NOI18N + setResizable(false); + setType(java.awt.Window.Type.POPUP); - jLabel1.setFont(new java.awt.Font("Dialog", 1, 11)); // NOI18N + jLabel1.setFont(new java.awt.Font("Dialog", 1, 12)); // NOI18N jLabel1.setIcon(new javax.swing.ImageIcon("/home/coder/Work/Java/Projects/kpdfsync/src/coderarjob/kpdfsync/poc/res/sticky-note.png")); // NOI18N jLabel1.setText("Select the note associated with the highlight"); jLabel2.setText("Open the book to the page, and check which note goes with the highlight."); - jLabel3.setBackground(new java.awt.Color(255, 255, 255)); - jLabel3.setText(""); - jLabel3.setVerticalAlignment(javax.swing.SwingConstants.TOP); - jLabel3.setBorder(javax.swing.BorderFactory.createEtchedBorder()); - jLabel3.setOpaque(true); - - jButton1.setText("Delete mapping"); - jButton1.setToolTipText(""); - - jList1.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - jScrollPane1.setViewportView(jList1); - - jButton2.setText("Create mapping"); - jButton2.setToolTipText(""); - - jButton3.setText("Close"); - jButton3.setToolTipText(""); + notesList.setModel(mNotesListModel); + notesList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + jScrollPane1.setViewportView(notesList); + + createMappingButton.setText("Create mapping"); + createMappingButton.setToolTipText(""); + createMappingButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + createMappingButtonActionPerformed(evt); + } + }); + + closeButton.setText("Close"); + closeButton.setToolTipText(""); + closeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + closeButtonActionPerformed(evt); + } + }); + + deleteMappingButton.setText("Delete mapping"); + deleteMappingButton.setToolTipText(""); + deleteMappingButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteMappingButtonActionPerformed(evt); + } + }); javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jButton2) + .addContainerGap() + .addComponent(deleteMappingButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(createMappingButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jButton3) + .addComponent(closeButton) .addContainerGap()) ); jPanel1Layout.setVerticalGroup( @@ -76,38 +168,36 @@ private void initComponents() { .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jButton2) - .addComponent(jButton3)) + .addComponent(createMappingButton) + .addComponent(closeButton) + .addComponent(deleteMappingButton)) .addContainerGap()) ); jLabel4.setText("Page number:"); - jLabel5.setFont(new java.awt.Font("Dialog", 1, 11)); // NOI18N - jLabel5.setText(""); + pageNumberLabel.setFont(new java.awt.Font("Dialog", 1, 11)); // NOI18N + pageNumberLabel.setText(""); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel1) - .addComponent(jLabel2)) - .addGap(0, 81, Short.MAX_VALUE)) - .addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(jLabel4) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel5) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jButton1)) - .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING)) + .addComponent(jLabel2) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel4) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pageNumberLabel))) + .addGap(0, 81, Short.MAX_VALUE))) .addContainerGap()) - .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -117,15 +207,11 @@ private void initComponents() { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jButton1) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel4) - .addComponent(jLabel5))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel4) + .addComponent(pageNumberLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 172, Short.MAX_VALUE) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 243, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) ); @@ -133,59 +219,17 @@ private void initComponents() { pack(); }// //GEN-END:initComponents - /** - * @param args the command line arguments - */ - public static void main(String args[]) { - /* Set the Nimbus look and feel */ - // - /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. - * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html - */ - try { - for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { - if ("Nimbus".equals(info.getName())) { - javax.swing.UIManager.setLookAndFeel(info.getClassName()); - break; - } - } - } catch (ClassNotFoundException ex) { - java.util.logging.Logger.getLogger(NoteAssociationDialog.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); - } catch (InstantiationException ex) { - java.util.logging.Logger.getLogger(NoteAssociationDialog.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); - } catch (IllegalAccessException ex) { - java.util.logging.Logger.getLogger(NoteAssociationDialog.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); - } catch (javax.swing.UnsupportedLookAndFeelException ex) { - java.util.logging.Logger.getLogger(NoteAssociationDialog.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); - } - // - - /* Create and display the dialog */ - java.awt.EventQueue.invokeLater(new Runnable() { - public void run() { - NoteAssociationDialog dialog = new NoteAssociationDialog(new javax.swing.JFrame(), true); - dialog.addWindowListener(new java.awt.event.WindowAdapter() { - @Override - public void windowClosing(java.awt.event.WindowEvent e) { - System.exit(0); - } - }); - dialog.setVisible(true); - } - }); - } // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton jButton1; - private javax.swing.JButton jButton2; - private javax.swing.JButton jButton3; + private javax.swing.JButton closeButton; + private javax.swing.JButton createMappingButton; + private javax.swing.JButton deleteMappingButton; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; - private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; - private javax.swing.JLabel jLabel5; - private javax.swing.JList jList1; private javax.swing.JPanel jPanel1; private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JList notesList; + private javax.swing.JLabel pageNumberLabel; // End of variables declaration//GEN-END:variables } From 4d00821e2cb7bb663ccc03a2e61ec34a363536b1 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Mon, 21 Mar 2022 13:12:34 +0530 Subject: [PATCH 05/18] JDK target and source version added. * Additionally, the build script now copies the 'poc/res' folder to 'build/classes/.../poc'. These files are taken as Resources and are accessed as such from the source files. --- build.sh | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/build.sh b/build.sh index 0c4d5cd..e3e6ecf 100755 --- a/build.sh +++ b/build.sh @@ -10,23 +10,34 @@ find src -name "*.java" -exec sed -i s/\ \*$//g {} \; || exit export CLASSPATH="lib/pdfclown.jar:$BIN_DIR" +JDK_VER_TARGET=8 + # Build AJL -javac -Xlint -d "$BIN_DIR/" src/coderarjob/ajl/file/*.java || exit +javac --release $JDK_VER_TARGET -Xlint -d "$BIN_DIR/" \ + src/coderarjob/ajl/file/*.java || exit # Build Pattern Matcher -javac -Xlint -d "$BIN_DIR/" src/coderarjob/kpdfsync/lib/pm/*.java || exit +javac --release $JDK_VER_TARGET -Xlint -d "$BIN_DIR/" \ + src/coderarjob/kpdfsync/lib/pm/*.java || exit # Build Annotator -javac -Xlint -d "$BIN_DIR/" src/coderarjob/kpdfsync/lib/annotator/*.java || exit +javac --release $JDK_VER_TARGET -Xlint -d "$BIN_DIR/" \ + src/coderarjob/kpdfsync/lib/annotator/*.java || exit # Build Kindle Clippings File Parser -javac -Xlint -d "$BIN_DIR/" src/coderarjob/kpdfsync/lib/clipparser/*.java || exit +javac --release $JDK_VER_TARGET -Xlint -d "$BIN_DIR/" \ + src/coderarjob/kpdfsync/lib/clipparser/*.java || exit # Build kpdfsync library -javac -Xlint -d "$BIN_DIR/" src/coderarjob/kpdfsync/lib/*.java || exit +javac --release $JDK_VER_TARGET -Xlint -d "$BIN_DIR/" \ + src/coderarjob/kpdfsync/lib/*.java || exit # Build POC -javac -Xlint -d "$BIN_DIR/" src/coderarjob/kpdfsync/poc/*.java || exit +javac --release $JDK_VER_TARGET -Xlint -d "$BIN_DIR/" \ + src/coderarjob/kpdfsync/poc/*.java || exit + +# Copy resources +cp -r src/coderarjob/kpdfsync/poc/res $BIN_DIR/coderarjob/kpdfsync/poc || exit # Generate tags file ctags --recurse ./src || exit From 4e253997fb622c75a503835bd7fd1250a3422787 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Mon, 21 Mar 2022 13:18:20 +0530 Subject: [PATCH 06/18] Script to create .jar packages. --- Manifest.txt | 3 +++ pack.sh | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 Manifest.txt create mode 100755 pack.sh diff --git a/Manifest.txt b/Manifest.txt new file mode 100644 index 0000000..8ee500c --- /dev/null +++ b/Manifest.txt @@ -0,0 +1,3 @@ +Main-Class: coderarjob.kpdfsync.poc.Main +Class-Path: pdfclown.jar + diff --git a/pack.sh b/pack.sh new file mode 100755 index 0000000..e028541 --- /dev/null +++ b/pack.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +echo :: Building packages + +rm -rf dist +mkdir dist + +pushd build/classes + +# Create testplib.jar +# BasicParser is what that can change. So it is packaged separately. +jar cfm kpdfsync.jar ../../Manifest.txt \ + coderarjob +popd + +mv build/classes/kpdfsync.jar ./dist/ +cp lib/pdfclown.jar ./dist/ + + +echo :: Building packages completed + From 9e38fab64c100f8f96ef5912952f39f5b20ee070 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Mon, 21 Mar 2022 13:22:05 +0530 Subject: [PATCH 07/18] Image files are accessed as resourced. --- .../poc/HighlightNotePairListRenderer.java | 3 ++- src/coderarjob/kpdfsync/poc/MainFrame.form | 4 ++-- src/coderarjob/kpdfsync/poc/MainFrame.java | 18 +++++------------- .../kpdfsync/poc/NoteAssociationDialog.form | 2 +- .../kpdfsync/poc/NoteAssociationDialog.java | 2 +- .../kpdfsync/poc/PageNumberListRenderer.java | 11 ++++++++--- 6 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java b/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java index 990106c..3049437 100644 --- a/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java +++ b/src/coderarjob/kpdfsync/poc/HighlightNotePairListRenderer.java @@ -17,7 +17,8 @@ public HighlightNotePairListRenderer() this.setLayout (new BorderLayout()); highlightLabel = new JLabel(); - highlightLabel.setIcon (new ImageIcon ("src/coderarjob/kpdfsync/poc/res/highlighter.png")); + String iconResourceName = "/coderarjob/kpdfsync/poc/res/highlighter.png"; + highlightLabel.setIcon (new ImageIcon (getClass().getResource (iconResourceName))); highlightLabel.setForeground (Color.BLACK); highlightLabel.setOpaque (false); this.add (highlightLabel, BorderLayout.PAGE_START); diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.form b/src/coderarjob/kpdfsync/poc/MainFrame.form index 6c417bc..1a0853f 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.form +++ b/src/coderarjob/kpdfsync/poc/MainFrame.form @@ -132,7 +132,7 @@ - + @@ -182,7 +182,7 @@ - + diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.java b/src/coderarjob/kpdfsync/poc/MainFrame.java index e9be93e..a92f8b5 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.java +++ b/src/coderarjob/kpdfsync/poc/MainFrame.java @@ -29,7 +29,7 @@ private enum ApplicationStatus private enum StatusTypes { - PARSER_ERROR, INPUT_ERROR, MATCH_FOUND, MATCH_NOT_FOUND, OTHER, ERROR, INFORMATION + PARSER_ERROR, MATCH_FOUND, MATCH_NOT_FOUND, OTHER, ERROR, INFORMATION } /* Private fields */ @@ -59,9 +59,6 @@ public MainFrame() initComponents(); setStatus (ApplicationStatus.NOT_STARTED); - - System.out.println (selectBookNameLabel.getFont().getFontName()); - System.out.println (selectBookNameLabel.getFont().isBold()); } /* Kindle Parser event handler*/ @@ -216,7 +213,7 @@ private void pageNumbersListValueChanged(javax.swing.event.ListSelectionEvent ev if (pageNumbersList.getSelectedValue() == null) return; - PageResource res = (PageResource)pageNumbersList.getSelectedValue(); + PageResource res = pageNumbersList.getSelectedValue(); int pageNumber = res.getPageNumber(); System.out.println ("Page number selected is " + pageNumber); @@ -267,7 +264,7 @@ private void exitButtonActionPerformed(ActionEvent evt) private void highlightsListMouseClicked(java.awt.event.MouseEvent evt) { JList list = (JList)evt.getSource(); - HighlightNotePair pair = (HighlightNotePair) list.getSelectedValue(); + HighlightNotePair pair = list.getSelectedValue(); if (pair == null) return; // Nothing is selected. if (evt.getClickCount() != 2) return; // Not double click. @@ -428,11 +425,6 @@ private void addStatusLine (final StatusTypes type, final Object... values) String pattern = (String)values[0]; newEntryString = String.format (fmt, pattern); break; - case INPUT_ERROR: - fmt = (String)values[0]; - args = Arrays.copyOfRange (values, 1, values.length); - newEntryString = String.format ("Input Error : " + fmt, args); - break; case PARSER_ERROR: fmt = (String)values[0]; args = Arrays.copyOfRange (values, 1, values.length); @@ -553,7 +545,7 @@ private void initComponents() { headerPanel.setBackground(new java.awt.Color(25, 66, 97)); - logoLabel.setIcon(new javax.swing.ImageIcon("/home/coder/NetBeansProjects/GuiHelloWorld/Resources/LogoHori.png")); // NOI18N + logoLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/coderarjob/kpdfsync/poc/res/Logo.png"))); // NOI18N logoLabel.setToolTipText(""); exitButton.setText("Exit"); @@ -787,7 +779,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(selectHighlightLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(highlightsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 431, Short.MAX_VALUE) + .addComponent(highlightsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 458, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(proceedPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) diff --git a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form index 17e3366..74ef693 100644 --- a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form +++ b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.form @@ -79,7 +79,7 @@ - + diff --git a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java index 76726eb..cd34413 100644 --- a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java +++ b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java @@ -117,7 +117,7 @@ private void initComponents() { setType(java.awt.Window.Type.POPUP); jLabel1.setFont(new java.awt.Font("Dialog", 1, 12)); // NOI18N - jLabel1.setIcon(new javax.swing.ImageIcon("/home/coder/Work/Java/Projects/kpdfsync/src/coderarjob/kpdfsync/poc/res/sticky-note.png")); // NOI18N + jLabel1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/coderarjob/kpdfsync/poc/res/sticky-note.png"))); // NOI18N jLabel1.setText("Select the note associated with the highlight"); jLabel2.setText("Open the book to the page, and check which note goes with the highlight."); diff --git a/src/coderarjob/kpdfsync/poc/PageNumberListRenderer.java b/src/coderarjob/kpdfsync/poc/PageNumberListRenderer.java index 5edf216..d2be0ab 100644 --- a/src/coderarjob/kpdfsync/poc/PageNumberListRenderer.java +++ b/src/coderarjob/kpdfsync/poc/PageNumberListRenderer.java @@ -13,13 +13,18 @@ public class PageNumberListRenderer extends JLabel implements ListCellRenderer

list, PageResource value, int index, boolean isSelected, boolean cellHasFocus) From ba876b9145f0315b5e8c40868bbdd2a55d967136 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Mon, 21 Mar 2022 13:22:38 +0530 Subject: [PATCH 08/18] Replaced the old placeholder logo image file. --- src/coderarjob/kpdfsync/poc/res/Logo.png | Bin 6100 -> 7523 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/coderarjob/kpdfsync/poc/res/Logo.png b/src/coderarjob/kpdfsync/poc/res/Logo.png index 9bfba9d756c8403650759547683b5f354148a946..0ed4f6c4e79e28ba226fc78b35c10f5df7e74523 100644 GIT binary patch delta 7436 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 Date: Mon, 21 Mar 2022 13:23:59 +0530 Subject: [PATCH 09/18] Replacement for List.copyOf (unavailable in JDK 8) --- src/coderarjob/kpdfsync/poc/PageResource.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/coderarjob/kpdfsync/poc/PageResource.java b/src/coderarjob/kpdfsync/poc/PageResource.java index b29f1e1..a316e5c 100644 --- a/src/coderarjob/kpdfsync/poc/PageResource.java +++ b/src/coderarjob/kpdfsync/poc/PageResource.java @@ -32,7 +32,8 @@ public int getPageNumber () public List getNoteList () { // That way any changes do not effect object here. - return List.copyOf(this.mNoteList); + // List.copyOf added in JDK 10, thus cannot be used as we are targetting JDK 8. + return copyOf(this.mNoteList); } /*** Returns an unmodifiable list of Highlights with already associated notes. @@ -45,7 +46,13 @@ public List getPairs () throws Exception throw new Exception ("Invalid. There are more notes than highlights"); // That way any changes do not effect object here. - return List.copyOf(this.mPairs); + // List.copyOf added in JDK 10, thus cannot be used as we are targetting JDK 8. + return copyOf(this.mPairs); + } + + private List copyOf (List list) + { + return new ArrayList(list); } /*** Adds a new note to unpaired notes list. From ccd0d98227ced800828bdea9d5981347f4c89dd1 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Mon, 21 Mar 2022 21:32:06 +0530 Subject: [PATCH 10/18] GUI:PDF file is selected after highlight mappings. * This is done, because this makes more sense with the usual work flow. Select clippings file -> select book title -> map highlight with notes -> select source pdf file -> set threshold and number of pages to skip -> enter destination pdf file -> highlight. Previous flow was: select clippings file -> select book title -> select source pdf file -> set threshold and number of pages to skip -> map highlight with notes -> enter destination pdf file -> highlight. This means, selecting another PDF file will reset the highlight-note pairs. This is wrong, as the source PDF has nothing to do with highlight-note pairs. --- src/coderarjob/kpdfsync/poc/MainFrame.form | 92 +++++++------ src/coderarjob/kpdfsync/poc/MainFrame.java | 146 ++++++++++++--------- 2 files changed, 135 insertions(+), 103 deletions(-) diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.form b/src/coderarjob/kpdfsync/poc/MainFrame.form index 1a0853f..cdb03c5 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.form +++ b/src/coderarjob/kpdfsync/poc/MainFrame.form @@ -32,12 +32,37 @@ + + + + - + + + + + + + + + + + + + + + + + + + + + + @@ -47,51 +72,29 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + @@ -112,6 +115,14 @@ + + + + + + + + @@ -119,21 +130,13 @@ - + - - - - - - - - - + @@ -233,6 +236,9 @@ + + + diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.java b/src/coderarjob/kpdfsync/poc/MainFrame.java index a92f8b5..af0736a 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.java +++ b/src/coderarjob/kpdfsync/poc/MainFrame.java @@ -23,6 +23,7 @@ private enum ApplicationStatus { NOT_STARTED, CLIPPINGS_FILE_PARSE_STARTED, CLIPPINGS_FILE_PARSE_COMPLETED, CLIPPINGS_FILE_PARSE_FAILED, + BOOK_TITLE_SELECTED, PDF_SELECTED, HIGHLIGHT_STARTED, HIGHLIGHT_COMPLETED, HIGHLIGHT_FAILED } @@ -124,18 +125,12 @@ private void browsePdfFileButtonActionPerformed(ActionEvent evt) { File file= fileChooser.getSelectedFile (); selectPdfFileTextBox.setText (file.getAbsolutePath()); - - String bookTitle = (String)selectBookNameComboBox.getSelectedItem(); - createPageResourceObjects (bookTitle); - populatePageNumbersList(); - setStatus (ApplicationStatus.PDF_SELECTED); } } private void proceedButtonActionPerformed(ActionEvent evt) { - String bookTitle = (String)selectBookNameComboBox.getSelectedItem(); String pdfFileName = selectPdfFileTextBox.getText(); int numberOfPagesToSkip = (Integer)pdfSkipPagesSpinner.getValue(); int matchThreshold = (Integer)matchThressholdSpinner.getValue(); @@ -193,7 +188,6 @@ public void run() { } catch (InterruptedException ex) { addStatusLine (StatusTypes.OTHER, "Highlight canceled."); setStatus (ApplicationStatus.HIGHLIGHT_FAILED); - reportException (ex); } catch (Exception ex) { addStatusLine (StatusTypes.OTHER, "Highlighting failed."); @@ -296,6 +290,17 @@ private void highlightsListMouseClicked(java.awt.event.MouseEvent evt) } } + private void selectBookNameComboBoxActionPerformed(java.awt.event.ActionEvent evt) + { + String bookTitle = (String)selectBookNameComboBox.getSelectedItem(); + if (bookTitle != null) + { + createPageResourceObjects (bookTitle); + populatePageNumbersList(); + setStatus (ApplicationStatus.BOOK_TITLE_SELECTED); + } + } + /* Other private class methods*/ private void createPageResourceObjects (String bookTitle) { @@ -376,6 +381,14 @@ private void reportException (Exception ex) JOptionPane.showMessageDialog (this, ex.getMessage(), "Error!", JOptionPane.ERROR_MESSAGE); System.err.println (ex.getMessage()); ex.printStackTrace(); + + Throwable cause = ex.getCause(); + for (int i = 1; cause != null; i++) + { + System.err.println (":: Cause #" + i); + cause.printStackTrace(); + cause = cause.getCause(); + } } private void updateUIBooksComboBox (final String item) @@ -478,21 +491,28 @@ public void run() { selectBookNameComboBox.removeAllItems (); browseClippingsFileButton.setEnabled (true); break; - case HIGHLIGHT_COMPLETED: // intentional falling - case HIGHLIGHT_FAILED: // intentional falling - case PDF_SELECTED: - proceedButton.setEnabled (true); - pdfSkipPagesSpinner.setEnabled (true); - matchThressholdSpinner.setEnabled (true); - // intentional falling case CLIPPINGS_FILE_PARSE_COMPLETED: selectBookNameComboBox.setEnabled (true); - selectPdfFileTextBox.setEnabled (true); - browsePdfFileButton.setEnabled (true); - // intentional falling + browseClippingsFileButton.setEnabled (true); + break; case CLIPPINGS_FILE_PARSE_FAILED: browseClippingsFileButton.setEnabled (true); break; + case BOOK_TITLE_SELECTED: + selectBookNameComboBox.setEnabled (true); + browseClippingsFileButton.setEnabled (true); + browsePdfFileButton.setEnabled (true); + break; + case HIGHLIGHT_COMPLETED: // intentional falling: + case HIGHLIGHT_FAILED: // intentional falling: + case PDF_SELECTED: + selectBookNameComboBox.setEnabled (true); + browseClippingsFileButton.setEnabled (true); + browsePdfFileButton.setEnabled (true); + proceedButton.setEnabled (true); + pdfSkipPagesSpinner.setEnabled (true); + matchThressholdSpinner.setEnabled (true); + break; case CLIPPINGS_FILE_PARSE_STARTED: // intentional falling case HIGHLIGHT_STARTED: jProgressBar1.setValue (0); @@ -596,6 +616,12 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { } }); + selectBookNameComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + selectBookNameComboBoxActionPerformed(evt); + } + }); + selectBookNameLabel.setForeground(new java.awt.Color(0, 0, 0)); selectBookNameLabel.setLabelFor(selectBookNameComboBox); selectBookNameLabel.setText("Select book name :"); @@ -700,11 +726,31 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(highlightsScrollPane) + .addContainerGap()) .addGroup(layout.createSequentialGroup() .addComponent(statusScrollPane) .addContainerGap()) .addGroup(layout.createSequentialGroup() - .addComponent(highlightsScrollPane) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(selectPdfFileLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 152, Short.MAX_VALUE) + .addComponent(pdfSkipPagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(pdfSkipPagesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(matchThressholdLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 183, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(percentLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(372, 372, 372)) + .addGroup(layout.createSequentialGroup() + .addComponent(selectPdfFileTextBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browsePdfFileButton))) .addContainerGap()) .addGroup(layout.createSequentialGroup() .addComponent(pageNumbersScrollPane) @@ -712,40 +758,23 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(selectBookNameLabel) .addComponent(clippingsFileLabel) - .addComponent(statusLabel) - .addComponent(pdfSkipPagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 152, Short.MAX_VALUE) - .addComponent(selectPdfFileLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(pageNumbersLabel)) + .addComponent(statusLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(pdfSkipPagesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(matchThressholdLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 183, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 63, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(percentLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 378, Short.MAX_VALUE)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(selectBookNameComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(clippingsFileTextBox)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browseClippingsFileButton)) - .addGroup(layout.createSequentialGroup() - .addComponent(selectPdfFileTextBox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browsePdfFileButton))) - .addGap(6, 6, 6)))) + .addComponent(selectBookNameComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(clippingsFileTextBox)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseClippingsFileButton) + .addGap(6, 6, 6)) .addComponent(proceedPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addGap(5, 5, 5)) - .addComponent(selectHighlightLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addComponent(selectHighlightLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(pageNumbersLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 110, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)))) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -761,26 +790,26 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(selectBookNameLabel) .addComponent(selectBookNameComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pageNumbersLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pageNumbersScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 81, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(selectHighlightLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(highlightsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 459, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(selectPdfFileLabel) .addComponent(selectPdfFileTextBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(browsePdfFileButton)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(pdfSkipPagesLabel) .addComponent(pdfSkipPagesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(matchThressholdLabel) .addComponent(matchThressholdSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(percentLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(pageNumbersLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(pageNumbersScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 81, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(selectHighlightLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(highlightsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 458, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(percentLabel) + .addComponent(pdfSkipPagesLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(proceedPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(statusLabel) @@ -792,9 +821,6 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { pack(); }// //GEN-END:initComponents - - - // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton browseClippingsFileButton; private javax.swing.JButton browsePdfFileButton; From c49457f8a3d4f0d41e9d10f7d5ccf9efce11a56d Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Thu, 24 Mar 2022 13:51:53 +0530 Subject: [PATCH 11/18] Dialog now opens centered to the parent. --- src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java index cd34413..fb2a3c2 100644 --- a/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java +++ b/src/coderarjob/kpdfsync/poc/NoteAssociationDialog.java @@ -6,10 +6,6 @@ import java.util.List; -/** - * - * @author coder - */ public class NoteAssociationDialog extends javax.swing.JDialog { public enum NoteAssociationDialogOptions @@ -29,6 +25,7 @@ public NoteAssociationDialog(Frame parent) super(parent, true); mNotesListModel = new DefaultListModel<>(); initComponents(); + this.setLocationRelativeTo(parent); // Center of the parent. } private void reset() From 84c81cbe84f6d5df967367bac44ee166d0bda9b5 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Thu, 24 Mar 2022 13:53:13 +0530 Subject: [PATCH 12/18] Logging implemented in Main Frame. * Almost all the UI updates and status updates are logged to a text file. --- src/coderarjob/kpdfsync/poc/Log.java | 101 +++++++++++++ src/coderarjob/kpdfsync/poc/MainFrame.java | 156 +++++++++++++++------ 2 files changed, 211 insertions(+), 46 deletions(-) create mode 100644 src/coderarjob/kpdfsync/poc/Log.java diff --git a/src/coderarjob/kpdfsync/poc/Log.java b/src/coderarjob/kpdfsync/poc/Log.java new file mode 100644 index 0000000..bbb3c01 --- /dev/null +++ b/src/coderarjob/kpdfsync/poc/Log.java @@ -0,0 +1,101 @@ +package coderarjob.kpdfsync.poc; + +import java.io.*; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.time.LocalTime; +import java.time.LocalDate; + +public class Log +{ + public enum LogType + { + INFORMATION, WARNING, ERROR; + } + + private static Log mInstance; + public static final String LOG_FILE; + + private BufferedWriter mWriter; + private DateTimeFormatter mLogDateTimeFormatter; + private LocalTime mPrev; /* Used to determine passage of time from last log. */ + + static + { + DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern ("Hms"); + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern ("YMd"); + LOG_FILE = String.format ("log_%s_%s.txt", LocalDate.now().format (dateFormatter) + , LocalTime.now().format (timeFormatter)); + } + + private Log (String fileName) throws IOException + { + mWriter = new BufferedWriter (new FileWriter (fileName, false)); + mLogDateTimeFormatter = DateTimeFormatter.ofPattern ("HH:mm:ss"); + mPrev = LocalTime.now(); + } + + public static Log getInstance() + { + if (mInstance == null) + { + try { + mInstance = new Log(Log.LOG_FILE); + } catch (IOException ex) { + new RuntimeException (ex); + } + } + + return mInstance; + } + + public void log (LogType type, String fmt, Object... values) + { + String logText = ""; + + String datetimeText = LocalTime.now().format (mLogDateTimeFormatter); + boolean continueWithPrevious = ChronoUnit.SECONDS.between (mPrev, LocalTime.now()) < 1; + mPrev = LocalTime.now(); + + switch (type) + { + case INFORMATION: + if (!continueWithPrevious) + logText = "%n[" + datetimeText + "]%n"; + + logText += "\t" + fmt + "%n"; + logText = String.format (logText, values); // call to format is required to parse the %n. + + break; + case ERROR: + if (!continueWithPrevious) + logText = "%n[ERROR] [" + datetimeText + "]%n"; + + logText += "\t" + fmt + "%n"; + logText = String.format (logText, values); + break; + case WARNING: + if (!continueWithPrevious) + logText = "%n[WARN] [" + datetimeText + "]%n"; + + logText += "\t" + fmt + "%n"; + logText = String.format (logText, values); + break; + } + + try { + mWriter.write (logText); + } catch (IOException ex) { + new RuntimeException (ex); + } + } + + public void flush() + { + try { + mWriter.flush(); + } catch (IOException ex) { + new RuntimeException (ex); + } + } +} diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.java b/src/coderarjob/kpdfsync/poc/MainFrame.java index af0736a..cb308a2 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.java +++ b/src/coderarjob/kpdfsync/poc/MainFrame.java @@ -8,13 +8,13 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import coderarjob.kpdfsync.lib.clipparser.*; import coderarjob.kpdfsync.lib.clipparser.ParserResult.AnnotationType; import coderarjob.kpdfsync.lib.*; import coderarjob.kpdfsync.lib.pm.*; +import coderarjob.kpdfsync.poc.Log.LogType; import coderarjob.kpdfsync.lib.annotator.*; public class MainFrame extends javax.swing.JFrame @@ -30,7 +30,7 @@ private enum ApplicationStatus private enum StatusTypes { - PARSER_ERROR, MATCH_FOUND, MATCH_NOT_FOUND, OTHER, ERROR, INFORMATION + ERROR, INFORMATION, WARNING } /* Private fields */ @@ -65,14 +65,21 @@ public MainFrame() /* Kindle Parser event handler*/ private void parserErrorHander (String fileName, long offset, String error, ParserResult result) { - addStatusLine (StatusTypes.PARSER_ERROR, "'%s' at %d", error, offset); + addStatusLine (StatusTypes.WARNING, "'%s' at %d", error, offset); } /* Matcher event handler */ private void matchCompletedEventHandler (Match match) { if (match.matchPercent() > mAnnotator.getMatchThreshold()) - addStatusLine (StatusTypes.MATCH_FOUND, match); + { + String fmt = "%.30s... %.0f%% matched on line %d."; + StatusTypes type = (match.matchPercent() == 100) ? StatusTypes.INFORMATION + : StatusTypes.WARNING; + + addStatusLine (type, fmt, match.pattern(), match.matchPercent() + , match.beginFrom().lineNumber()); + } } /* Button and other UI event handlers*/ @@ -89,7 +96,7 @@ private void browseClippingsFileButtonActionPerformed(ActionEvent evt) parseClippingsThread = new Thread (new Runnable () { public void run () { try { - addStatusLine (StatusTypes.OTHER, "Parsing %s ...", file.getName()); + addStatusLine (StatusTypes.INFORMATION, "Parsing %s ...", file.getName()); createNewParser (file.getAbsolutePath()); mClippingsFile = new KindleClippingsFile(mParser); @@ -101,16 +108,16 @@ public void run () { if (statusList.getModel().getSize() == 0) throw new Exception ("Empty or invalid clippings file."); - addStatusLine (StatusTypes.OTHER, "Parsing complete"); + addStatusLine (StatusTypes.INFORMATION, "Parsing complete"); setStatus (ApplicationStatus.CLIPPINGS_FILE_PARSE_COMPLETED); } catch (InterruptedException ex) { - addStatusLine (StatusTypes.OTHER, "Parsing canceled"); + addStatusLine (StatusTypes.WARNING, "Parsing canceled"); setStatus (ApplicationStatus.CLIPPINGS_FILE_PARSE_FAILED); } catch (Exception ex) { - addStatusLine (StatusTypes.OTHER, "Parsing failed: " + ex.getMessage()); + addStatusLine (StatusTypes.ERROR, "Parsing failed: " + ex.getMessage()); setStatus (ApplicationStatus.CLIPPINGS_FILE_PARSE_FAILED); reportException (ex); } @@ -169,9 +176,17 @@ public void run() { for (HighlightNotePair pair : res.getPairs()) { boolean okay; + + Log.getInstance().log (LogType.INFORMATION + , "%n\t[Highlighting] '%s' with note '%s' @ page %d" + , pair.getHighlightText() + , pair.getNoteText() + , pageNum); + okay = mAnnotator.highlight (pageNum, pair.getHighlightText(), pair.getNoteText()); if (!okay) - addStatusLine (StatusTypes.MATCH_NOT_FOUND, pair.getHighlightText()); + addStatusLine (StatusTypes.ERROR, "%.30s... not found." + , pair.getHighlightText()); float progress = (float) highlightIndex/(totalHighlightCount - 1) * 100; updateUIProgressBar ((int)progress); @@ -180,17 +195,17 @@ public void run() { } // Save pdf to a new file. - addStatusLine (StatusTypes.OTHER, "Saving to file " + destinationPdfFileName); + addStatusLine (StatusTypes.INFORMATION, "Saving to file " + destinationPdfFileName); mAnnotator.save (destinationPdfFileName); - addStatusLine (StatusTypes.OTHER, "Highlighting complete."); + addStatusLine (StatusTypes.INFORMATION, "Highlighting complete."); setStatus (ApplicationStatus.HIGHLIGHT_COMPLETED); } catch (InterruptedException ex) { - addStatusLine (StatusTypes.OTHER, "Highlight canceled."); + addStatusLine (StatusTypes.WARNING, "Highlight canceled."); setStatus (ApplicationStatus.HIGHLIGHT_FAILED); } catch (Exception ex) { - addStatusLine (StatusTypes.OTHER, "Highlighting failed."); + addStatusLine (StatusTypes.ERROR, "Highlighting failed."); setStatus (ApplicationStatus.HIGHLIGHT_FAILED); reportException (ex); } @@ -209,6 +224,8 @@ private void pageNumbersListValueChanged(javax.swing.event.ListSelectionEvent ev PageResource res = pageNumbersList.getSelectedValue(); int pageNumber = res.getPageNumber(); + + Log.getInstance().log (LogType.INFORMATION, "[PageNumberList] selected page: " + pageNumber); System.out.println ("Page number selected is " + pageNumber); // Clear highlights and notes @@ -236,22 +253,24 @@ private void optionsButtonActionPerformed(ActionEvent evt) private void exitButtonActionPerformed(ActionEvent evt) { String buttonText = exitButton.getText().toUpperCase (); - if (buttonText.equals ("EXIT")) + boolean isExit = buttonText.equals ("EXIT"); + + // Flush log when Exit/Cancel button is pressed. + Log.getInstance().log (LogType.INFORMATION, (isExit) ? "Exiting" : "Cancelling Operation"); + Log.getInstance().flush(); + + if (isExit) { // TODO: Possibly add confirmation before closing. System.exit (0); } else { - if (highlightThread != null && highlightThread.isAlive ()) { - System.out.println ("highlight thread interrupted"); + if (highlightThread != null && highlightThread.isAlive ()) highlightThread.interrupt(); - } - if (parseClippingsThread != null && parseClippingsThread.isAlive ()) { - System.out.println ("parser thread interrupted"); + if (parseClippingsThread != null && parseClippingsThread.isAlive ()) parseClippingsThread.interrupt(); - } } } @@ -274,11 +293,26 @@ private void highlightsListMouseClicked(java.awt.event.MouseEvent evt) case CREATE: String noteText = mNoteForm.getSelectedNoteText(); System.out.println ("Selected: " + noteText); - if (noteText != null) + + Log.getInstance().log (LogType.INFORMATION + , "[pair create] Highlight: '%s'%n\tNote: '%s'" + , pair.getHighlightText() + , noteText); + + if (noteText != null) { + Log.getInstance().log (LogType.INFORMATION, "[pair create] Created."); res.pair (pair.getHighlightText(), noteText); + } else { + Log.getInstance().log (LogType.WARNING, "[pair create] Not created."); + } break; case DELETE: + Log.getInstance().log (LogType.INFORMATION + , "[pair delete] Highlight: '%s'%n\tNote: '%s'" + , pair.getHighlightText() + , pair.getNoteText()); + res.unpair (pair.getHighlightText()); break; } @@ -378,13 +412,19 @@ public void onMatchEnd (Match result) private void reportException (Exception ex) { - JOptionPane.showMessageDialog (this, ex.getMessage(), "Error!", JOptionPane.ERROR_MESSAGE); - System.err.println (ex.getMessage()); + String exceptionMessage = ex.getMessage() == null ? ex.toString() : ex.getMessage(); + + JOptionPane.showMessageDialog (this, exceptionMessage, "Error!", JOptionPane.ERROR_MESSAGE); + System.err.println (exceptionMessage); ex.printStackTrace(); + Log.getInstance().log (LogType.ERROR, "Exception: %s", exceptionMessage); + Throwable cause = ex.getCause(); for (int i = 1; cause != null; i++) { + Log.getInstance().log (LogType.ERROR, "\tCause %d: %s", i, cause.getMessage()); + System.err.println (":: Cause #" + i); cause.printStackTrace(); cause = cause.getCause(); @@ -417,47 +457,65 @@ public void run () { SwingUtilities.invokeLater (r); } - private void addStatusLine (final StatusTypes type, final Object... values) + private void addStatusLine (final StatusTypes type, final String fmt, final Object... values) { - String newEntryString; - String fmt; - Object[] args; + String statusText; switch (type) { - case MATCH_FOUND: - fmt = "%.30s... %.0f%% matched on line %d."; - Match match = (Match)values[0]; - newEntryString = String.format (fmt - , match.pattern() - , match.matchPercent() - , match.beginFrom().lineNumber()); + case WARNING: + statusText = String.format ("[Warning] " + fmt, values); + Log.getInstance().log (LogType.WARNING, fmt, values); break; - case MATCH_NOT_FOUND: - fmt = "%.30s... not found."; - String pattern = (String)values[0]; - newEntryString = String.format (fmt, pattern); + case ERROR: + statusText = String.format ("[Error] " + fmt, values); + Log.getInstance().log (LogType.ERROR, fmt, values); break; - case PARSER_ERROR: - fmt = (String)values[0]; - args = Arrays.copyOfRange (values, 1, values.length); - newEntryString = String.format ("Parser Error : " + fmt, args); + case INFORMATION: + statusText = String.format (fmt, values); + Log.getInstance().log (LogType.INFORMATION, fmt, values); break; default: - fmt = (String)values[0]; - args = Arrays.copyOfRange (values, 1, values.length); - newEntryString = String.format (fmt, args); + statusText = ""; + break; } Runnable r = new Runnable () { public void run () { - statusListModel.addElement (newEntryString); + statusListModel.addElement (statusText); //statusList.ensureIndexIsVisible (statusListModel.getSize() - 1); // Slows down UI } }; SwingUtilities.invokeLater (r); } + private void logApplicationStatus (final ApplicationStatus status) + { + String fmt = "At %s%n"; + fmt += "\tClip file: %s%n"; + fmt += "\tBook: %s%n"; + fmt += "\tSkip pages: %d%n"; + fmt += "\tThreshold :%d%n"; + fmt += "\tPDF souce :%s"; + + + String clipFileName = clippingsFileTextBox.getText(); + String bookTitle = (selectBookNameComboBox.getSelectedIndex() >= 0) + ? (String)selectBookNameComboBox.getSelectedItem() + : ""; + Integer skipPage = (Integer)pdfSkipPagesSpinner.getValue(); + Integer threshold = (Integer)matchThressholdSpinner.getValue(); + String sourcePDF = selectPdfFileTextBox.getText(); + + Log.getInstance().log (LogType.INFORMATION, fmt + , status.toString() + , clipFileName + , bookTitle + , skipPage + , threshold + , sourcePDF); + } + private void setStatus (final ApplicationStatus status) { if (!SwingUtilities.isEventDispatchThread()) @@ -471,6 +529,9 @@ public void run() { return; } + // Log Application Status + logApplicationStatus (status); + // Always keep the text boxes disabled. selectPdfFileTextBox.setEnabled (false); clippingsFileTextBox.setEnabled (false); @@ -514,6 +575,9 @@ public void run() { matchThressholdSpinner.setEnabled (true); break; case CLIPPINGS_FILE_PARSE_STARTED: // intentional falling + statusListModel.clear (); + selectBookNameComboBox.removeAllItems (); + browseClippingsFileButton.setEnabled (true); case HIGHLIGHT_STARTED: jProgressBar1.setValue (0); exitButton.setText ("Cancel"); From eb08b8cf501d85f96fbd022d34c1199ab6a67bd4 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Thu, 24 Mar 2022 14:20:40 +0530 Subject: [PATCH 13/18] Exceptions are logged with stack trace. --- src/coderarjob/kpdfsync/poc/Log.java | 17 +++++++++++++++++ src/coderarjob/kpdfsync/poc/MainFrame.java | 4 +--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/coderarjob/kpdfsync/poc/Log.java b/src/coderarjob/kpdfsync/poc/Log.java index bbb3c01..fa7f524 100644 --- a/src/coderarjob/kpdfsync/poc/Log.java +++ b/src/coderarjob/kpdfsync/poc/Log.java @@ -17,6 +17,7 @@ public enum LogType public static final String LOG_FILE; private BufferedWriter mWriter; + private PrintWriter mPrinter; private DateTimeFormatter mLogDateTimeFormatter; private LocalTime mPrev; /* Used to determine passage of time from last log. */ @@ -31,6 +32,7 @@ public enum LogType private Log (String fileName) throws IOException { mWriter = new BufferedWriter (new FileWriter (fileName, false)); + mPrinter = new PrintWriter (mWriter); mLogDateTimeFormatter = DateTimeFormatter.ofPattern ("HH:mm:ss"); mPrev = LocalTime.now(); } @@ -49,6 +51,21 @@ public static Log getInstance() return mInstance; } + public void logException (Exception ex) + { + String exceptionMessage = ex.getMessage() == null ? ex.toString() : ex.getMessage(); + + log (LogType.ERROR, "Exception :" + exceptionMessage); + ex.printStackTrace (mPrinter); + + Throwable cause = ex.getCause(); + while (cause != null) + { + cause.printStackTrace(mPrinter); + cause = cause.getCause(); + } + } + public void log (LogType type, String fmt, Object... values) { String logText = ""; diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.java b/src/coderarjob/kpdfsync/poc/MainFrame.java index cb308a2..b0c4d6b 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.java +++ b/src/coderarjob/kpdfsync/poc/MainFrame.java @@ -418,13 +418,11 @@ private void reportException (Exception ex) System.err.println (exceptionMessage); ex.printStackTrace(); - Log.getInstance().log (LogType.ERROR, "Exception: %s", exceptionMessage); + Log.getInstance().logException (ex); Throwable cause = ex.getCause(); for (int i = 1; cause != null; i++) { - Log.getInstance().log (LogType.ERROR, "\tCause %d: %s", i, cause.getMessage()); - System.err.println (":: Cause #" + i); cause.printStackTrace(); cause = cause.getCause(); From 0bd21e528d201798c64b4b6efb5acee7b94d3a4f Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Thu, 24 Mar 2022 14:50:54 +0530 Subject: [PATCH 14/18] Newine is inserted if old and new type is not same * Previously this was done only if subsequent calls to log method differ by 1 sec. But this can cause quick succession of different log types be grouped together. So now it also checks previous log types. If it differs from the current log type, it is teated as a new log entry. --- src/coderarjob/kpdfsync/poc/Log.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/coderarjob/kpdfsync/poc/Log.java b/src/coderarjob/kpdfsync/poc/Log.java index fa7f524..499e2bb 100644 --- a/src/coderarjob/kpdfsync/poc/Log.java +++ b/src/coderarjob/kpdfsync/poc/Log.java @@ -10,7 +10,7 @@ public class Log { public enum LogType { - INFORMATION, WARNING, ERROR; + UNSET, INFORMATION, WARNING, ERROR; } private static Log mInstance; @@ -19,7 +19,8 @@ public enum LogType private BufferedWriter mWriter; private PrintWriter mPrinter; private DateTimeFormatter mLogDateTimeFormatter; - private LocalTime mPrev; /* Used to determine passage of time from last log. */ + private LocalTime mPrev; // Used to determine passage of time from last log. + private LogType mPrevLogType; // Used with mPrev to determine if discontinuation is needed. static { @@ -35,6 +36,7 @@ private Log (String fileName) throws IOException mPrinter = new PrintWriter (mWriter); mLogDateTimeFormatter = DateTimeFormatter.ofPattern ("HH:mm:ss"); mPrev = LocalTime.now(); + mPrevLogType = LogType.UNSET; } public static Log getInstance() @@ -72,7 +74,10 @@ public void log (LogType type, String fmt, Object... values) String datetimeText = LocalTime.now().format (mLogDateTimeFormatter); boolean continueWithPrevious = ChronoUnit.SECONDS.between (mPrev, LocalTime.now()) < 1; + continueWithPrevious = continueWithPrevious & type == mPrevLogType; + mPrev = LocalTime.now(); + mPrevLogType = type; switch (type) { From d523eb7c124f4791dfa1e6d09a0986976f2a1558 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Thu, 24 Mar 2022 14:56:01 +0530 Subject: [PATCH 15/18] File type filer added to open and save dialogs. --- src/coderarjob/kpdfsync/poc/MainFrame.form | 6 ++++++ src/coderarjob/kpdfsync/poc/MainFrame.java | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.form b/src/coderarjob/kpdfsync/poc/MainFrame.form index cdb03c5..d519adc 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.form +++ b/src/coderarjob/kpdfsync/poc/MainFrame.form @@ -3,6 +3,12 @@ + + + + + + diff --git a/src/coderarjob/kpdfsync/poc/MainFrame.java b/src/coderarjob/kpdfsync/poc/MainFrame.java index b0c4d6b..6485653 100644 --- a/src/coderarjob/kpdfsync/poc/MainFrame.java +++ b/src/coderarjob/kpdfsync/poc/MainFrame.java @@ -17,6 +17,9 @@ import coderarjob.kpdfsync.poc.Log.LogType; import coderarjob.kpdfsync.lib.annotator.*; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; + public class MainFrame extends javax.swing.JFrame { private enum ApplicationStatus @@ -49,6 +52,9 @@ private enum StatusTypes private HighlightNotePairManager mPairManager; private NoteAssociationDialog mNoteForm; + private FileFilter clippingsFileFilter; + private FileFilter pdfFileFilter; + /* Constructor and other methods*/ public MainFrame() { @@ -58,6 +64,9 @@ public MainFrame() mNoteForm = new NoteAssociationDialog (this); + clippingsFileFilter = new FileNameExtensionFilter ("Kindle Clippings file", "txt"); + pdfFileFilter = new FileNameExtensionFilter ("PDF file", "pdf"); + initComponents(); setStatus (ApplicationStatus.NOT_STARTED); } @@ -85,6 +94,8 @@ private void matchCompletedEventHandler (Match match) /* Button and other UI event handlers*/ private void browseClippingsFileButtonActionPerformed(ActionEvent evt) { + fileChooser.removeChoosableFileFilter (pdfFileFilter); + fileChooser.addChoosableFileFilter (clippingsFileFilter); if (fileChooser.showOpenDialog (this) != JFileChooser.APPROVE_OPTION) return; @@ -128,6 +139,8 @@ public void run () { private void browsePdfFileButtonActionPerformed(ActionEvent evt) { + fileChooser.removeChoosableFileFilter (clippingsFileFilter); + fileChooser.addChoosableFileFilter (pdfFileFilter); if (fileChooser.showOpenDialog (this) == JFileChooser.APPROVE_OPTION) { File file= fileChooser.getSelectedFile (); @@ -142,6 +155,8 @@ private void proceedButtonActionPerformed(ActionEvent evt) int numberOfPagesToSkip = (Integer)pdfSkipPagesSpinner.getValue(); int matchThreshold = (Integer)matchThressholdSpinner.getValue(); + fileChooser.removeChoosableFileFilter (clippingsFileFilter); + fileChooser.addChoosableFileFilter (pdfFileFilter); if (fileChooser.showSaveDialog (this) != JFileChooser.APPROVE_OPTION) return; @@ -622,6 +637,9 @@ private void initComponents() { matchThressholdSpinner = new javax.swing.JSpinner(); percentLabel = new javax.swing.JLabel(); + fileChooser.setAcceptAllFileFilterUsed(false); + fileChooser.setSelectedFile(new java.io.File("/home/coder/ ")); + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setTitle("mk-float-kpdfsync-gui"); From e88f414b3b1ea5978b153bf727d3abee9ea0a059 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Thu, 24 Mar 2022 15:00:01 +0530 Subject: [PATCH 16/18] TOOD updated. Readme updated for Alpha release. --- README.md | 19 +++-- TODO | 141 +++++++++++++++++++++++++++---- docs/images/screenshot_alpha.png | Bin 0 -> 68328 bytes 3 files changed, 139 insertions(+), 21 deletions(-) create mode 100644 docs/images/screenshot_alpha.png diff --git a/README.md b/README.md index 7a546b9..54d6f47 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## About kpdfsync -![Screenshot](/docs/images/screenshot.png) +![Screenshot](/docs/images/screenshot_alpha.png) If you use Kindle to read PDF books or documents, you might have seen that the highlights or notes made on the Kindle are not saved on the PDF file itself. This means, if you take the file and @@ -15,16 +15,23 @@ Kindle device. Currently it is in development, so not all the features work or even present. Here is the rough roadmap. +## Requirements +- JRE 1.8 or higher +- Linux, Mac, Windows + ## Roadmap - [X] Parsing the Clippings.txt file - [X] Search for the highlighted text in a page of the PDF file. - [X] Annotate highlight and notes on the PDF file. -- [X] Graphical User Interface testing. -- [X] Comments to notes mapping. This is required, because the clippings text file does not provide - information which can used to determine which comments are related to which note on a single - page. -- [ ] Debug loggings +- [X] Graphical User Interface (GUI) testing. +- [X] Highlights to notes mapping. This is required, because the clippings text file does not + provide information which can used to determine which notes are related to which highlight on a + single page. Some cases where a page contains a single note and highlight, automatic pairs are + created, however in cases where there are more than one note, these associations can be created + manually by the user. +- [X] GUI finalizing for the Alpha release. +- [X] Debug loggings - [ ] **Alpha Release** ---- diff --git a/TODO b/TODO index aae06aa..ff17f64 100644 --- a/TODO +++ b/TODO @@ -1,36 +1,46 @@ Kpdfsync THINGS-TO-DO --------------------------------------------------------------------------------------------------- +# Alpha Release # TASKS Estimated Actual -[X] String comparison algorithm, that can analyze the degree of match. +[X] String comparison algorithm, that can analyze the degree of match. So that minor differences between the pattern and the read text from pdf files are handled. - [X] Use PDFClown library to highlight the text which matches the most with the highlighted text from My Clippings file. - [X] Parse the 'My Clippings.txt' file. - [-] Gui POC +[X] Manual and Automatic creation of association between highlights. + and notes. +[ ] Use grid layout for displaying and creating page mappings. + (Not done, in favor of below) +[X] Use custom renderer in list box to show highlight nore mappings. +[X] A separate dialog window for selection of notes for a highlight. +[X] Loging -[ ] Finalize GUI - +# Beta Release +# TASKS Estimated Actual [ ] Optimization and cleanup objects. - +[ ] Lib - Use Iterator instead of Enumeration. (Not sure) +[ ] GUI - Status bar showing last error or success message. +[ ] Lib - parseLine function can be protected. It is public now. +[ ] Lib - matching Bom bytes can be put inside a method in the + ByteOrderMarkTypes enum. It is now separe in + ByteOrderMark file. # BUGS: -[ ] The string matching algo is too simple, and gives wrong match - percentage, if the strings being compared differ in the number - of non-whitespace characters. The two indexes get out of sync - at the first mismatch and never recover. +[ ] The string matching algo is too simple, and gives wrong match + percentage, if the strings being compared differ in the number + of non-whitespace characters. The two indexes get out of sync + at the first mismatch and never recover. Example: PDF text = 123 56 789 Clipping text = 123 456 789 % match = 3/8 (Wrong) % match = 7/8 (What is expected) -[ ] Related to the above bug, we are highlighting more characters - - by that many characters as the diffence in the number of - characters, between the text read from the PDF and the pattern +[ ] Related to the above bug, we are highlighting more characters - + by that many characters as the diffence in the number of + characters, between the text read from the PDF and the pattern read from the clippings file. The algorithm matches character by character, the pattern and the text from the pdf. The matching and thus the highlighting is as @@ -46,7 +56,37 @@ Kpdfsync highlighting) [ ] For some PDF files, org.pdfclown.tools.TextExtractor.extract() is returning null. - This is seen with the Concrete Mathematics original PDF file. + This is seen with the Concrete Mathematics original PDF file. May be a TrueType font issue. + Here is the stack trace: + java.lang.NullPointerException + at java.base/java.util.Hashtable.put(Hashtable.java:476) + at org.pdfclown.documents.contents.fonts.PfbParser.parse(PfbParser.java:99) + at org.pdfclown.documents.contents.fonts.Type1Font.getNativeEncoding(Type1Font.java:96) + at org.pdfclown.documents.contents.fonts.Type1Font.loadEncoding(Type1Font.java:141) + at org.pdfclown.documents.contents.fonts.SimpleFont.onLoad(SimpleFont.java:118) + at org.pdfclown.documents.contents.fonts.Font.load(Font.java:738) + at org.pdfclown.documents.contents.fonts.Font.(Font.java:351) + at org.pdfclown.documents.contents.fonts.SimpleFont.(SimpleFont.java:62) + at org.pdfclown.documents.contents.fonts.Type1Font.(Type1Font.java:75) + at org.pdfclown.documents.contents.fonts.Font.wrap(Font.java:249) + at org.pdfclown.documents.contents.FontResources.wrap(FontResources.java:72) + at org.pdfclown.documents.contents.FontResources.wrap(FontResources.java:1) + at org.pdfclown.documents.contents.ResourceItems.get(ResourceItems.java:119) + at org.pdfclown.documents.contents.objects.SetFont.getResource(SetFont.java:119) + at org.pdfclown.documents.contents.objects.SetFont.getFont(SetFont.java:83) + at org.pdfclown.documents.contents.objects.SetFont.scan(SetFont.java:97) + at org.pdfclown.documents.contents.ContentScanner.moveNext(ContentScanner.java:1330) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.extract(ContentScanner.java:811) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.extract(ContentScanner.java:817) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.(ContentScanner.java:777) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.(ContentScanner.java:770) + at org.pdfclown.documents.contents.ContentScanner$GraphicsObjectWrapper.get(ContentScanner.java:690) + at org.pdfclown.documents.contents.ContentScanner$GraphicsObjectWrapper.access$0(ContentScanner.java:682) + at org.pdfclown.documents.contents.ContentScanner.getCurrentWrapper(ContentScanner.java:1154) + at org.pdfclown.tools.TextExtractor.extract(TextExtractor.java:633) + at org.pdfclown.tools.TextExtractor.extract(TextExtractor.java:296) + at coderarjob.kpdfsync.lib.annotator.PdfAnnotatorV1.highlight(PdfAnnotatorV1.java:62) + at coderarjob.kpdfsync.poc.MainFrame$2.run(MainFrame.java:172) [ ] Highlight is not visible on the output PDF file. This was seen on the Concrete Mathematics cropped PDF file. @@ -62,3 +102,74 @@ Kpdfsync 5. Begin highlighting. The times, this exception occures, it occures around the 73% mark. + +[ ] EOFException at org.pdfclown.tools.TextExtractor.extract() method. This is seen on + 'the_evolution_of_operating_system_cropped.pdf' file. Could also be a font issue. + Here is the stack trace +java.lang.RuntimeException: java.io.EOFException + at org.pdfclown.documents.contents.fonts.CffParser.load(CffParser.java:703) + at org.pdfclown.documents.contents.fonts.CffParser.(CffParser.java:640) + at org.pdfclown.documents.contents.fonts.Type1Font.getNativeEncoding(Type1Font.java:104) + at org.pdfclown.documents.contents.fonts.Type1Font.loadEncoding(Type1Font.java:151) + at org.pdfclown.documents.contents.fonts.SimpleFont.onLoad(SimpleFont.java:118) + at org.pdfclown.documents.contents.fonts.Font.load(Font.java:738) + at org.pdfclown.documents.contents.fonts.Font.(Font.java:351) + at org.pdfclown.documents.contents.fonts.SimpleFont.(SimpleFont.java:62) + at org.pdfclown.documents.contents.fonts.Type1Font.(Type1Font.java:75) + at org.pdfclown.documents.contents.fonts.Font.wrap(Font.java:249) + at org.pdfclown.documents.contents.FontResources.wrap(FontResources.java:72) + at org.pdfclown.documents.contents.FontResources.wrap(FontResources.java:1) + at org.pdfclown.documents.contents.ResourceItems.get(ResourceItems.java:119) + at org.pdfclown.documents.contents.objects.SetFont.getResource(SetFont.java:119) + at org.pdfclown.documents.contents.objects.SetFont.getFont(SetFont.java:83) + at org.pdfclown.documents.contents.objects.SetFont.scan(SetFont.java:97) + at org.pdfclown.documents.contents.ContentScanner.moveNext(ContentScanner.java:1330) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.extract(ContentScanner.java:811) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.(ContentScanner.java:777) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.(ContentScanner.java:770) + at org.pdfclown.documents.contents.ContentScanner$GraphicsObjectWrapper.get(ContentScanner.java:690) + at org.pdfclown.documents.contents.ContentScanner$GraphicsObjectWrapper.access$0(ContentScanner.java:682) + at org.pdfclown.documents.contents.ContentScanner.getCurrentWrapper(ContentScanner.java:1154) + at org.pdfclown.tools.TextExtractor.extract(TextExtractor.java:633) + at org.pdfclown.tools.TextExtractor.extract(TextExtractor.java:296) + at coderarjob.kpdfsync.lib.annotator.PdfAnnotatorV1.highlight(PdfAnnotatorV1.java:62) + at coderarjob.kpdfsync.poc.MainFrame$2.run(MainFrame.java:172) + at java.base/java.lang.Thread.run(Thread.java:833) +Caused by: java.io.EOFException + at org.pdfclown.bytes.Buffer.readUnsignedShort(Buffer.java:511) + at org.pdfclown.documents.contents.fonts.CffParser$Index.parse(CffParser.java:306) + at org.pdfclown.documents.contents.fonts.CffParser$Index.parse(CffParser.java:324) + at org.pdfclown.documents.contents.fonts.CffParser.load(CffParser.java:669) + ... 27 more +:: Cause #1 +java.io.EOFException + at org.pdfclown.bytes.Buffer.readUnsignedShort(Buffer.java:511) + at org.pdfclown.documents.contents.fonts.CffParser$Index.parse(CffParser.java:306) + at org.pdfclown.documents.contents.fonts.CffParser$Index.parse(CffParser.java:324) + at org.pdfclown.documents.contents.fonts.CffParser.load(CffParser.java:669) + at org.pdfclown.documents.contents.fonts.CffParser.(CffParser.java:640) + at org.pdfclown.documents.contents.fonts.Type1Font.getNativeEncoding(Type1Font.java:104) + at org.pdfclown.documents.contents.fonts.Type1Font.loadEncoding(Type1Font.java:151) + at org.pdfclown.documents.contents.fonts.SimpleFont.onLoad(SimpleFont.java:118) + at org.pdfclown.documents.contents.fonts.Font.load(Font.java:738) + at org.pdfclown.documents.contents.fonts.Font.(Font.java:351) + at org.pdfclown.documents.contents.fonts.SimpleFont.(SimpleFont.java:62) + at org.pdfclown.documents.contents.fonts.Type1Font.(Type1Font.java:75) + at org.pdfclown.documents.contents.fonts.Font.wrap(Font.java:249) + at org.pdfclown.documents.contents.FontResources.wrap(FontResources.java:72) + at org.pdfclown.documents.contents.FontResources.wrap(FontResources.java:1) + at org.pdfclown.documents.contents.ResourceItems.get(ResourceItems.java:119) + at org.pdfclown.documents.contents.objects.SetFont.getResource(SetFont.java:119) + at org.pdfclown.documents.contents.objects.SetFont.getFont(SetFont.java:83) + at org.pdfclown.documents.contents.objects.SetFont.scan(SetFont.java:97) + at org.pdfclown.documents.contents.ContentScanner.moveNext(ContentScanner.java:1330) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.extract(ContentScanner.java:811) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.(ContentScanner.java:777) + at org.pdfclown.documents.contents.ContentScanner$TextWrapper.(ContentScanner.java:770) + at org.pdfclown.documents.contents.ContentScanner$GraphicsObjectWrapper.get(ContentScanner.java:690) + at org.pdfclown.documents.contents.ContentScanner$GraphicsObjectWrapper.access$0(ContentScanner.java:682) + at org.pdfclown.documents.contents.ContentScanner.getCurrentWrapper(ContentScanner.java:1154) + at org.pdfclown.tools.TextExtractor.extract(TextExtractor.java:633) + at org.pdfclown.tools.TextExtractor.extract(TextExtractor.java:296) + at coderarjob.kpdfsync.lib.annotator.PdfAnnotatorV1.highlight(PdfAnnotatorV1.java:62) + at coderarjob.kpdfsync.poc.MainFrame$2.run(MainFrame.java:172) diff --git a/docs/images/screenshot_alpha.png b/docs/images/screenshot_alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..b81276c53d6d5a96e1b470b0d05409f83b8a32ed GIT binary patch literal 68328 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 Date: Thu, 24 Mar 2022 16:35:39 +0530 Subject: [PATCH 17/18] Updated the Readme text. --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 54d6f47..dab62d3 100644 --- a/README.md +++ b/README.md @@ -2,23 +2,18 @@ ![Screenshot](/docs/images/screenshot_alpha.png) -If you use Kindle to read PDF books or documents, you might have seen that the highlights or notes -made on the Kindle are not saved on the PDF file itself. This means, if you take the file and -read on another device, you will not see those highlights and notes make on the Kindle. +If you use Kindle to read PDF books or documents, you might have seen that the highlights and notes +made on the Kindle are not saved on the PDF file itself. This means, that if you take the PDF file +from your Kindle and read on another device, you will not see those highlights and notes there. -This software tries to provide a solution. The basis on which this solution stands is the -Clippings.txt file on your Kindle. - -This is the file, where Kindle saves the page numbers of all the highlights and notes done on the -Kindle device. +This software tries to provide a solution. The basis is the Clippings.txt file on your Kindle. +Kindle saves the page numbers and content of the highlights and notes in the text file. So in +theory, one can read the Clippings file and reapply the highlights and notes on the PDF separetely. +This software automates the process. Currently it is in development, so not all the features work or even present. Here is the rough roadmap. -## Requirements -- JRE 1.8 or higher -- Linux, Mac, Windows - ## Roadmap - [X] Parsing the Clippings.txt file @@ -41,6 +36,10 @@ roadmap. - [ ] Finalizing and optimizing the Graphical User Interface. - [ ] **Beta Release** +## Requirements +- JRE 1.8 or higher +- Linux, Mac, Windows + ## 3rd-party License * PDF Clown library is used to read and highlight on PDF files. PDF Clown library is covered under From 1265df3cd98b39a406e45de6b40f927fe6780a28 Mon Sep 17 00:00:00 2001 From: Arjob Mukherjee Date: Thu, 24 Mar 2022 16:36:28 +0530 Subject: [PATCH 18/18] Fix spelling mistake --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dab62d3..cec2ed3 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ from your Kindle and read on another device, you will not see those highlights a This software tries to provide a solution. The basis is the Clippings.txt file on your Kindle. Kindle saves the page numbers and content of the highlights and notes in the text file. So in -theory, one can read the Clippings file and reapply the highlights and notes on the PDF separetely. +theory, one can read the Clippings file and reapply the highlights and notes on the PDF separately. This software automates the process. Currently it is in development, so not all the features work or even present. Here is the rough