/*
* 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;
import de.danielluedecke.zettelkasten.database.Settings;
import de.danielluedecke.zettelkasten.mac.MacSourceList;
import de.danielluedecke.zettelkasten.util.ColorUtil;
import de.danielluedecke.zettelkasten.util.Tools;
import de.danielluedecke.zettelkasten.util.Constants;
import de.danielluedecke.zettelkasten.util.ListUtil;
import de.danielluedecke.zettelkasten.util.classes.Comparer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.jdesktop.application.Action;
/**
*
* @author danielludecke
*/
public class CFilterSearch extends javax.swing.JDialog {
/**
* create a variable for a list model. this list model is used for
* the JList-component which displays the search terms which should be filtered
*/
private final DefaultListModel filterListModel = new DefaultListModel();
/**
* The list of available keywords or authors from the current search results that
* should be filtered
*/
private LinkedList<String> terms = new LinkedList<>();
/**
* This variable stores the data of the terms-list when this list is filtered.
* All changes to a fitered table-list are also applied to this linked list. When
* the list is being refreshed, we don't need to run the time-consuming task; instead
* we simply iterate this list and set the values to the table
*/
private LinkedList<String> linkedtermslist = null;
/**
* The selected entries which are used for filtering
*/
private String [] filterterms = null;
public String[] getFilterTerms() {
return filterterms;
}
/**
* The logical-combination of the filter-request
*/
private int logical = -1;
public int getLogical() {
return logical;
}
/**
*
* @param parent the parent window
* @param se
* @param t a linked list containing the items that should be display in the jList
* @param title the dialog's title. use {@code null} for standard-title
* @param showoptions if true, the search-options for logical-filtering will be shown, if false, this
* panel will be hidden. */
public CFilterSearch(java.awt.Frame parent, Settings se, LinkedList<String> t, String title, boolean showoptions) {
super(parent);
initComponents();
// set application icon
setIconImage(Constants.zknicon.getImage());
// if we have a title, set it
if (title!=null) setTitle(title);
// show or hide options-panel
jPanel6.setVisible(showoptions);
// sort linked list
terms = t;
Collections.sort(terms, new Comparer());
// when we have aqua-style, change scrollbars
if (se.isMacAqua() || se.isSeaGlass()) {
jTextFieldFilter.putClientProperty("JTextField.variant", "search");
if (se.isSeaGlass()) {
jButtonOk.putClientProperty("JComponent.sizeVariant", "small");
jButtonCancel.putClientProperty("JComponent.sizeVariant", "small");
}
}
initBorders(se);
initList();
initListeners();
}
private void initBorders(Settings settingsObj) {
/*
* Constructor for Matte Border
* public MatteBorder(int top, int left, int bottom, int right, Color matteColor)
*/
jScrollPane1.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, ColorUtil.getBorderGray(settingsObj)));
}
private void initListeners() {
// 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);
jButtonOk.addActionListener(new java.awt.event.ActionListener() {
@Override public void actionPerformed(java.awt.event.ActionEvent evt) {
applyFilter();
}
});
jButtonCancel.addActionListener(new java.awt.event.ActionListener() {
@Override public void actionPerformed(java.awt.event.ActionEvent evt) {
cancel();
}
});
jTextFieldFilter.addKeyListener(new java.awt.event.KeyAdapter() {
@Override public void keyReleased(java.awt.event.KeyEvent evt) {
if (Tools.isNavigationKey(evt.getKeyCode())) {
// if user pressed navigation key, select next table entry
ListUtil.navigateThroughList(jList1, evt.getKeyCode());
}
else {
// select table-entry live, while the user is typing...
ListUtil.selectByTyping(jList1,jTextFieldFilter);
}
}
});
// create action which should be executed when the user presses
// the enter-key
AbstractAction a_enter = new AbstractAction(){
@Override public void actionPerformed(ActionEvent e) {
filterList();
}
};
jTextFieldFilter.getActionMap().put("EnterKeyPressed",a_enter);
// associate enter-keystroke with that action
KeyStroke ks = KeyStroke.getKeyStroke("ENTER");
jTextFieldFilter.getInputMap().put(ks, "EnterKeyPressed");
}
/**
* This method initializes the filter list. When a search request should be filtered,
* all available items (keywords or authors) from the current search result are
* passed to this dialog in the parameter {@link #terms terms}.<br><br>
* This method now lists all these terms in a jList, so the user can select
* terms for filtering his search.
*/
private void initList() {
// clear jList
filterListModel.clear();
// create iterator to iterate all terms of the current search
Iterator<String> i = terms.iterator();
// and add all terms to the jList
while (i.hasNext()) filterListModel.addElement(i.next());
jButtonRefresh.setEnabled(false);
// enable textfield only if we have more than 1 element in the jtable
jTextFieldFilter.setEnabled(filterListModel.size()>0);
}
private void cancel() {
filterterms = null;
dispose();
setVisible(false);
}
/**
* This method retrieves all selected values from the jList. The current
* search results will now be filtered according to this selection, i.e. only
* those search entries remain, that match this search/filter request.
*/
private void applyFilter() {
// check which radio button is selected, for the logical-combination of the filter-request
if (jRadioButtonLogAnd.isSelected()) logical = Constants.LOG_AND;
else if (jRadioButtonLogOr.isSelected()) logical = Constants.LOG_OR;
if (jRadioButtonLogNot.isSelected()) logical = Constants.LOG_NOT;
// retrieve selected values
List<String> sel = jList1.getSelectedValuesList();
// create return value
filterterms = sel.toArray(new String[sel.size()]);
// close window
dispose();
setVisible(false);
}
/**
* This method filters the list of filter-itemts, according to the text-input
* from the textfield. I.e. you can reduce larger filter-lists. In case text
* is written into the textfield, only those items remain in the filterlist (jList)
* that match the text in the textfield.
*/
@Action
public void filterList() {
// when we filter the table and want to restore it, we don't need to run the
// time-consuming task that creates the author-list and related author-frequencies.
// instead, we simply copy the values from the linkedlist to the table-model, which is
// much faster. but therefore we have to apply all changes to the filtered-table
// (like adding/changing values in a filtered list) to the linked list as well.
// get text from the textfield containing the filter string
// convert to lowercase, we don't want case-sensitive search
String text = jTextFieldFilter.getText().toLowerCase();
// when we have no text, do nothing
if (text.isEmpty()) return;
// if we haven't already stored the current complete table data, do this now
if (null==linkedtermslist) {
// create new instance of list
linkedtermslist = new LinkedList<>();
// go through all table-data
for (int cnt=0; cnt<filterListModel.getSize(); cnt++) linkedtermslist.add(filterListModel.get(cnt).toString());
}
// go through table and delete all rows that don't contain the filter text
for (int cnt=(filterListModel.size()-1); cnt>=0; cnt--) {
// get the string (author) value from the table
// convert to lowercase, we don't want case-sensitive search
String value = filterListModel.get(cnt).toString().toLowerCase();
// if the text is *not* part of the author, delete that row
if (!value.contains(text)) filterListModel.remove(cnt);
}
// reset textfield
jTextFieldFilter.setText("");
jTextFieldFilter.requestFocusInWindow();
// enable textfield only if we have more than 1 element in the jtable
jTextFieldFilter.setEnabled(filterListModel.size()>0);
// enable refresh button
jButtonRefresh.setEnabled(true);
}
@Action
public void refreshList() {
initList();
}
/** 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() {
buttonGroup1 = new javax.swing.ButtonGroup();
jScrollPane1 = new javax.swing.JScrollPane();
jList1 = MacSourceList.createMacSourceList();
jButtonOk = new javax.swing.JButton();
jButtonCancel = new javax.swing.JButton();
jPanel6 = new javax.swing.JPanel();
jRadioButtonLogAnd = new javax.swing.JRadioButton();
jRadioButtonLogOr = new javax.swing.JRadioButton();
jRadioButtonLogNot = new javax.swing.JRadioButton();
jTextFieldFilter = new javax.swing.JTextField();
jButtonRefresh = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class).getContext().getResourceMap(CFilterSearch.class);
setTitle(resourceMap.getString("FormFilterSearch.title")); // NOI18N
setModal(true);
setName("FormFilterSearch"); // NOI18N
jScrollPane1.setBorder(null);
jScrollPane1.setName("jScrollPane1"); // NOI18N
jList1.setModel(filterListModel);
jList1.setName("jList1"); // NOI18N
jScrollPane1.setViewportView(jList1);
jButtonOk.setText(resourceMap.getString("jButtonOk.text")); // NOI18N
jButtonOk.setName("jButtonOk"); // NOI18N
jButtonCancel.setText(resourceMap.getString("jButtonCancel.text")); // NOI18N
jButtonCancel.setName("jButtonCancel"); // NOI18N
jPanel6.setBorder(javax.swing.BorderFactory.createTitledBorder(resourceMap.getString("jPanel6.border.title"))); // NOI18N
jPanel6.setName("jPanel6"); // NOI18N
buttonGroup1.add(jRadioButtonLogAnd);
jRadioButtonLogAnd.setSelected(true);
jRadioButtonLogAnd.setText(resourceMap.getString("jRadioButtonLogAnd.text")); // NOI18N
jRadioButtonLogAnd.setName("jRadioButtonLogAnd"); // NOI18N
buttonGroup1.add(jRadioButtonLogOr);
jRadioButtonLogOr.setText(resourceMap.getString("jRadioButtonLogOr.text")); // NOI18N
jRadioButtonLogOr.setName("jRadioButtonLogOr"); // NOI18N
buttonGroup1.add(jRadioButtonLogNot);
jRadioButtonLogNot.setText(resourceMap.getString("jRadioButtonLogNot.text")); // NOI18N
jRadioButtonLogNot.setName("jRadioButtonLogNot"); // NOI18N
javax.swing.GroupLayout jPanel6Layout = new javax.swing.GroupLayout(jPanel6);
jPanel6.setLayout(jPanel6Layout);
jPanel6Layout.setHorizontalGroup(
jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel6Layout.createSequentialGroup()
.addContainerGap()
.addGroup(jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jRadioButtonLogAnd)
.addComponent(jRadioButtonLogOr)
.addComponent(jRadioButtonLogNot))
.addContainerGap(169, Short.MAX_VALUE))
);
jPanel6Layout.setVerticalGroup(
jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel6Layout.createSequentialGroup()
.addComponent(jRadioButtonLogAnd)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jRadioButtonLogOr)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jRadioButtonLogNot)
.addContainerGap(10, Short.MAX_VALUE))
);
jTextFieldFilter.setToolTipText(resourceMap.getString("jTextFieldFilter.toolTipText")); // NOI18N
jTextFieldFilter.setName("jTextFieldFilter"); // NOI18N
javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class).getContext().getActionMap(CFilterSearch.class, this);
jButtonRefresh.setAction(actionMap.get("refreshList")); // NOI18N
jButtonRefresh.setIcon(resourceMap.getIcon("jButtonRefresh.icon")); // NOI18N
jButtonRefresh.setBorderPainted(false);
jButtonRefresh.setContentAreaFilled(false);
jButtonRefresh.setFocusPainted(false);
jButtonRefresh.setName("jButtonRefresh"); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 434, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap(188, Short.MAX_VALUE)
.addComponent(jButtonCancel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jButtonOk)
.addContainerGap())
.addGroup(layout.createSequentialGroup()
.addGap(6, 6, 6)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jPanel6, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addContainerGap())
.addGroup(layout.createSequentialGroup()
.addComponent(jTextFieldFilter, javax.swing.GroupLayout.DEFAULT_SIZE, 396, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jButtonRefresh, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(6, 6, 6))))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 287, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(jTextFieldFilter)
.addComponent(jButtonRefresh, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jPanel6, 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(jButtonCancel)
.addComponent(jButtonOk))
.addGap(3, 3, 3))
);
pack();
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.ButtonGroup buttonGroup1;
private javax.swing.JButton jButtonCancel;
private javax.swing.JButton jButtonOk;
private javax.swing.JButton jButtonRefresh;
private javax.swing.JList jList1;
private javax.swing.JPanel jPanel6;
private javax.swing.JRadioButton jRadioButtonLogAnd;
private javax.swing.JRadioButton jRadioButtonLogNot;
private javax.swing.JRadioButton jRadioButtonLogOr;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextField jTextFieldFilter;
// End of variables declaration//GEN-END:variables
}