/*
* Zettelkasten - nach Luhmann
* Copyright (C) 2001-2015 by Daniel Lüdecke (http://www.danielluedecke.de)
*
* Homepage: http://zettelkasten.danielluedecke.de
*
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, see <http://www.gnu.org/licenses/>.
*
*
* Dieses Programm ist freie Software. Sie können es unter den Bedingungen der GNU
* General Public License, wie von der Free Software Foundation veröffentlicht, weitergeben
* und/oder modifizieren, entweder gemäß Version 3 der Lizenz oder (wenn Sie möchten)
* jeder späteren Version.
*
* Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, daß es Ihnen von Nutzen sein
* wird, aber OHNE IRGENDEINE GARANTIE, sogar ohne die implizite Garantie der MARKTREIFE oder
* der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Details finden Sie in der
* GNU General Public License.
*
* Sie sollten ein Exemplar der GNU General Public License zusammen mit diesem Programm
* erhalten haben. Falls nicht, siehe <http://www.gnu.org/licenses/>.
*/
package de.danielluedecke.zettelkasten.tasks;
import com.explodingpixels.macwidgets.MacWidgetFactory;
import com.explodingpixels.widgets.TableUtils;
import de.danielluedecke.zettelkasten.ZettelkastenView;
import de.danielluedecke.zettelkasten.database.Daten;
import de.danielluedecke.zettelkasten.database.Settings;
import de.danielluedecke.zettelkasten.util.Constants;
import de.danielluedecke.zettelkasten.util.classes.InitStatusbarForTasks;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.table.DefaultTableModel;
import org.jdesktop.application.Action;
import org.jdesktop.application.Application;
import org.jdesktop.application.ApplicationContext;
import org.jdesktop.application.Task;
import org.jdesktop.application.TaskMonitor;
import org.jdesktop.application.TaskService;
/**
*
* @author danielludecke
*/
public class FindDoubleEntriesTask extends javax.swing.JDialog {
/**
*
*/
private FindDoubleEntryTask doubleEntryTask = null;
/**
* Reference to the main frame.
*/
private ZettelkastenView mainframe;
/**
*
*/
private final Daten dataObj;
private final Settings settingsObj;
/**
* get the strings for file descriptions from the resource map
*/
private final org.jdesktop.application.ResourceMap resourceMap =
org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class).
getContext().getResourceMap(FindDoubleEntriesTask.class);
/**
*
* @param parent
* @param zkn
* @param data
* @param set
*/
public FindDoubleEntriesTask(java.awt.Frame parent, ZettelkastenView zkn, Daten data, Settings set) {
super(parent);
dataObj = data;
mainframe = zkn;
settingsObj = set;
initComponents();
// set application icon
setIconImage(Constants.zknicon.getImage());
// init the progress bar and status icon for
// the swingworker background thread
// creates a new class object. This variable is not used, it just associates task monitors to
// the background tasks. furthermore, by doing this, this class object also animates the
// busy icon and the progress bar of this frame.
InitStatusbarForTasks isb = new InitStatusbarForTasks(statusAnimationLabel, progressBar, null);
// these codelines add an escape-listener to the dialog. so, when the user
// presses the escape-key, the same action is performed as if the user
// presses the cancel button...
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
ActionListener cancelAction = new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
cancel();
}
};
getRootPane().registerKeyboardAction(cancelAction, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
// get the default fontsize for tables and lists
int defaultsize = settingsObj.getTableFontSize();
// only set new fonts, when fontsize differs from the initial value
if (defaultsize>0) {
// get current font
Font f = jTable1.getFont();
// create new font, add fontsize-value
f = new Font(f.getName(), f.getStyle(), f.getSize()+defaultsize);
// set new font
jTable1.setFont(f);
}
// create auto-sorter for tabel
jTable1.setAutoCreateRowSorter(false);
jTable1.setGridColor(settingsObj.getTableGridColor());
// make extra table-sorter for itunes-tables
if (settingsObj.isMacAqua()) {
TableUtils.SortDelegate sortDelegate = new TableUtils.SortDelegate() {
@Override
public void sort(int columnModelIndex, TableUtils.SortDirection sortDirection) {
}
};
TableUtils.makeSortable(jTable1, sortDelegate);
}
// disable table while thread is running
jTable1.setEnabled(false);
// add mouse-listener, so a doubleclick displays the entry
jTable1.addMouseListener(new java.awt.event.MouseAdapter() {
@Override public void mouseClicked(java.awt.event.MouseEvent evt) {
if (2==evt.getClickCount()) {
Object val = jTable1.getValueAt(jTable1.getSelectedRow(), jTable1.getSelectedColumn());
if (val!=null) {
mainframe.showEntry(Integer.parseInt(val.toString()));
}
}
}
});
// create action for delete-key. by doing this instead of using a key-event, hitting
// the enter-key does not select a new cell.
AbstractAction a_enter = new AbstractAction(){
@Override public void actionPerformed(ActionEvent e) {
Object val = jTable1.getValueAt(jTable1.getSelectedRow(), jTable1.getSelectedColumn());
if (val!=null) {
mainframe.showEntry(Integer.parseInt(val.toString()));
}
}
};
jTable1.getActionMap().put("EnterKeyPressed",a_enter);
// associate enter-keystroke with that action
KeyStroke ks = KeyStroke.getKeyStroke("ENTER");
jTable1.getInputMap().put(ks, "EnterKeyPressed");
// create action which should be executed when the user presses
// the delete/backspace-key
AbstractAction a_delete = new AbstractAction(){
@Override public void actionPerformed(ActionEvent e) {
// retrieve selected row and column
int row = jTable1.getSelectedRow();
int col = jTable1.getSelectedColumn();
// check for valid values
if (row!=-1 && col!=-1) {
// retrieve selected value
Object val = jTable1.getValueAt(row, col);
// check for valid content
if (val!=null) {
try {
// convert string to integer value
int selectedEntry = Integer.parseInt(val.toString());
// show entry that should be deleted, so the user knows which entry
// is to be deleted
mainframe.showEntry(selectedEntry);
// and delete entry
mainframe.deleteEntries(new int[] {selectedEntry});
}
catch (NumberFormatException ex) {
}
}
}
}
};
jTable1.getActionMap().put("DeleteKeyPressed",a_delete);
// check for os, and use appropriate controlKey
ks = KeyStroke.getKeyStroke((System.getProperty("os.name").toLowerCase().startsWith("mac os"))?"BACK_SPACE":"DELETE");
jTable1.getInputMap().put(ks, "DeleteKeyPressed");
}
public void startTask() {
// start the background task manually
Task fdeT = findDoubleEntries();
// get the application's context...
ApplicationContext appC = Application.getInstance().getContext();
// ...to get the TaskMonitor and TaskService
TaskMonitor tM = appC.getTaskMonitor();
TaskService tS = appC.getTaskService();
// with these we can execute the task and bring it to the foreground
// i.e. making the animated progressbar and busy icon visible
tS.execute(fdeT);
tM.setForegroundTask(fdeT);
}
@Action
public Task findDoubleEntries() {
// initiate the "statusbar" (the loading splash screen), giving visiual
// feedback during open and save operations
return new FindDoubleEntryTask(org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class));
}
@SuppressWarnings("LeakingThisInConstructor")
private class FindDoubleEntryTask extends org.jdesktop.application.Task<Object, Void> {
// create list that will contain all double entries
private final ArrayList<Integer[]> doubleentries = new ArrayList<>();
FindDoubleEntryTask(org.jdesktop.application.Application app) {
// Runs on the EDT. Copy GUI state that
// doInBackground() depends on from parameters
// to ImportFileTask fields, here.
super(app);
doubleEntryTask = this;
}
@Override
protected Object doInBackground() {
// Your Task's code here. This method runs
// on a background thread, so don't reference
// the Swing GUI from here.
// create array with all available entries.
int[] entries = new int[dataObj.getCount(Daten.ZKNCOUNT)];
// copy all entry-numbers to that array. empty entries will have the index "-1"
for (int cnt=1; cnt<=dataObj.getCount(Daten.ZKNCOUNT); cnt++) {
// if we have an entry, add that entry-numbers to the array. else, in
// case the entry was deleted, add -1 as number.
entries[cnt-1] = (dataObj.isEmpty(cnt))?-1:cnt;
}
// create array list that will contain possible multiple occurences of
// the entry with the index-number "entrynr"
ArrayList<Integer> founddoubles = new ArrayList<>();
// go through all entries. We have the entry-numbers saves in an array. each
// previously found double entry's index-number will be replaced with a "-1",
// so already found double entries will not appear multiple times in the final list
for (int cnt=0; cnt<entries.length; cnt++) {
// get current entry to check
int entrynr = entries[cnt];
// check whether entry-index-number is a valid entry-number
if (entrynr!=-1) {
// clear list
founddoubles.clear();
// retrieve content of the entry "entrynr" so we can compare that content
// will all other entries
String zettelContent = dataObj.getZettelContent(entrynr);
String cleanZettelContent = dataObj.getCleanZettelContent(entrynr);
// go through all entries, starting with the next entry after "entrynr". all
// previous entries have already been check for multiple occurences in a former
// iteration
for (int counter=entrynr+1;counter<=dataObj.getCount(Daten.ZKNCOUNT);counter++) {
// compare zettelcontent with the following entries.
if (zettelContent.equalsIgnoreCase(dataObj.getZettelContent(counter)) ||
cleanZettelContent.equalsIgnoreCase(dataObj.getCleanZettelContent(counter))) {
// if we found a multiple occurence, set that entry-index-number to "-1", so
// this entry will not be checked again.
entries[counter-1] = -1;
// if "entrynr", which is a double entry, is not already in our list,
// add it now
if (!founddoubles.contains(entrynr)) {
founddoubles.add(entrynr);
}
// add index-number of found double entries
founddoubles.add(counter);
}
}
// if we found any double entries, add those to our final list
if (!founddoubles.isEmpty()) {
doubleentries.add(founddoubles.toArray(new Integer[founddoubles.size()]));
}
}
// update progressbar
setProgress(cnt,0,entries.length);
}
return null; // return your result
}
@Override
protected void succeeded(Object result) {
// Runs on the EDT. Update the GUI based on
// the result computed by doInBackground().
}
@Override
protected void finished()
{
super.finished();
// when the task is finished, clear it
doubleEntryTask = null;
// hide task-progress bar
jPanel1.setVisible(false);
// create tablemodel for the table data, which is not editable
DefaultTableModel tm = new DefaultTableModel(new Object[] {}, 0) {
@Override public boolean isCellEditable(int row, int column) { return false; }
};
// apply model to table
jTable1.setModel(tm);
// and delete all rows and columns
tm.setRowCount(0);
tm.setColumnCount(0);
// now fill the table
// check whether we have any multiple entries at all.
if (!doubleentries.isEmpty()) {
// create iterator for the list
Iterator<Integer[]> it = doubleentries.iterator();
// iterate all multiple entries
while (it.hasNext()) {
// retrieve the row data
Integer[] rowdata = it.next();
// if the rowdate has more columns than the table model, adjust
// column count
while (tm.getColumnCount()<rowdata.length) {
tm.addColumn(String.valueOf(tm.getColumnCount()+1));
}
// add it to the table model
tm.addRow((Object[])rowdata);
}
// finally, enable table
jTable1.setEnabled(true);
}
else {
JOptionPane.showMessageDialog(null,resourceMap.getString("noMultipleEntriesFoundMsg"),resourceMap.getString("noMultipleEntriesFoundTitle"),JOptionPane.PLAIN_MESSAGE);
setVisible(false);
dispose();
}
}
}
@Action
public void cancel() {
if (doubleEntryTask!=null && !doubleEntryTask.isDone()) {
// cancel task
doubleEntryTask.cancel(true);
}
dispose();
setVisible(false);
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jScrollPane1 = new javax.swing.JScrollPane();
jTable1 = (settingsObj.isMacStyle()) ? MacWidgetFactory.createITunesTable(null) : new javax.swing.JTable();
jPanel1 = new javax.swing.JPanel();
progressBar = new javax.swing.JProgressBar();
statusAnimationLabel = new javax.swing.JLabel();
msgLabel = new javax.swing.JLabel();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class).getContext().getResourceMap(FindDoubleEntriesTask.class);
setTitle(resourceMap.getString("FormFindDoubleEntries.title")); // NOI18N
setName("FormFindDoubleEntries"); // NOI18N
jScrollPane1.setBorder(null);
jScrollPane1.setName("jScrollPane1"); // NOI18N
jTable1.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF);
jTable1.setCellSelectionEnabled(true);
jTable1.setName("jTable1"); // NOI18N
jTable1.getTableHeader().setReorderingAllowed(false);
jScrollPane1.setViewportView(jTable1);
jPanel1.setName("jPanel1"); // NOI18N
progressBar.setName("progressBar"); // NOI18N
statusAnimationLabel.setIcon(resourceMap.getIcon("statusAnimationLabel.icon")); // NOI18N
statusAnimationLabel.setName("statusAnimationLabel"); // NOI18N
msgLabel.setText(resourceMap.getString("msgLabel.text")); // NOI18N
msgLabel.setName("msgLabel"); // NOI18N
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addContainerGap()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(msgLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 388, Short.MAX_VALUE)
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(statusAnimationLabel)))
.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)
.addComponent(msgLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(statusAnimationLabel))
.addContainerGap())
);
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.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 238, 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();
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JPanel jPanel1;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTable jTable1;
private javax.swing.JLabel msgLabel;
private javax.swing.JProgressBar progressBar;
private javax.swing.JLabel statusAnimationLabel;
// End of variables declaration//GEN-END:variables
}