/*
* 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.util.Constants;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.jdesktop.application.Action;
/**
*
* @author danielludecke
*/
public class CFindReplaceDialog extends javax.swing.JDialog {
private final javax.swing.JTextArea textarea;
private int findpos = -1;
private final LinkedList<Integer[]> findselections = new LinkedList<>();
private static final int REPLACE_TEXT = 0;
private static final int FIND_TEXT = 1;
/**
* 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(CFindReplaceDialog.class);
/**
* Creates new form CFindReplaceDialog
* @param parent
* @param ta
* @param settingsObj
*/
public CFindReplaceDialog(java.awt.Frame parent, javax.swing.JTextArea ta, Settings settingsObj) {
super(parent);
initComponents();
// set application icon
setIconImage(Constants.zknicon.getImage());
textarea = ta;
initListeners();
if (settingsObj.isSeaGlass()) {
jButtonFindNext.putClientProperty("JComponent.sizeVariant", "small");
jButtonFindPrev.putClientProperty("JComponent.sizeVariant", "small");
jButtonReplace.putClientProperty("JComponent.sizeVariant", "small");
jButtonReplaceAll.putClientProperty("JComponent.sizeVariant", "small");
}
}
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) {
dispose();
setVisible(false);
}
};
getRootPane().registerKeyboardAction(cancelAction, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
// set default button
getRootPane().setDefaultButton(jButtonFindNext);
jTextFieldFind.getDocument().addDocumentListener(new DocumentListener() {
@Override public void changedUpdate(DocumentEvent e) { initValues(); }
@Override public void insertUpdate(DocumentEvent e) { initValues(); }
@Override public void removeUpdate(DocumentEvent e) { initValues(); }
});
jCheckBoxMatchCase.addActionListener(new java.awt.event.ActionListener() {
@Override public void actionPerformed(java.awt.event.ActionEvent evt) {
resetAll();
}
});
jCheckBoxWholeWord.addActionListener(new java.awt.event.ActionListener() {
@Override public void actionPerformed(java.awt.event.ActionEvent evt) {
resetAll();
}
});
jCheckBoxRegEx.addActionListener(new java.awt.event.ActionListener() {
@Override public void actionPerformed(java.awt.event.ActionEvent evt) {
resetAll();
}
});
jTextFieldReplace.addMouseListener(new java.awt.event.MouseAdapter() {
@Override public void mousePressed(java.awt.event.MouseEvent evt) {
if (evt.isPopupTrigger() && !jPopupMenuCCP.isVisible()) {
jPopupMenuCCP.show(jTextFieldReplace, evt.getPoint().x, evt.getPoint().y);
}
}
@Override public void mouseReleased(java.awt.event.MouseEvent evt) {
if (evt.isPopupTrigger() && !jPopupMenuCCP.isVisible()) {
jPopupMenuCCP.show(jTextFieldReplace, evt.getPoint().x, evt.getPoint().y);
}
}
});
jTextFieldFind.addMouseListener(new java.awt.event.MouseAdapter() {
@Override public void mousePressed(java.awt.event.MouseEvent evt) {
if (evt.isPopupTrigger() && !jPopupMenuCCP.isVisible()) {
jPopupMenuCCP.show(jTextFieldFind, evt.getPoint().x, evt.getPoint().y);
}
}
@Override public void mouseReleased(java.awt.event.MouseEvent evt) {
if (evt.isPopupTrigger() && !jPopupMenuCCP.isVisible()) {
jPopupMenuCCP.show(jTextFieldFind, evt.getPoint().x, evt.getPoint().y);
}
}
});
// finally, we have to manuall init the actions for the popup-menu, since the gui-builder always
// puts the menu-items before the line where the action-map is initialised. we cannot change
// this because it is in the protected area, and when changing it from outside, it will
// always be re-arranged by the gui-designer
// get the action map
javax.swing.ActionMap actionMap = org.jdesktop.application.Application.
getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class).getContext().
getActionMap(CFindReplaceDialog.class, this);
popupCCPcut.setAction(actionMap.get("cut"));
popupCCPcopy.setAction(actionMap.get("copy"));
popupCCPpaste.setAction(actionMap.get("paste"));
}
private void resetAll() {
initValues();
if (initmatcher()) findNext();
}
private void initValues() {
// init list where we store the start/end-positions of the found terms
findselections.clear();
// disable buttons
jButtonReplace.setEnabled(false);
jButtonFindPrev.setEnabled(false);
findpos = -1;
}
private boolean initmatcher() {
return initmatcher(true);
}
private boolean initmatcher(boolean resetFindPos) {
Matcher findmatcher;
// retrieve findtext
String text = jTextFieldFind.getText();
// if we have no findtext, reset buttons
if (text.isEmpty()) {
initValues();
return false;
}
// check whether the user wants to find a regular expression or not
// if not, prepare findterm and surround it with the regular expressions
// for whole word and matchcase.
if (!jCheckBoxRegEx.isSelected()) {
// if the findterm contains meta-chars of a regular expression, although no regular
// expression search is requested, escape all these meta-chars...
text = Pattern.quote(text);
// when we have a whole-word-find&replace, surround findterm with
// the regular expression that indicates word beginning and ending (i.e. whole word)
if (jCheckBoxWholeWord.isSelected()) text = "\\b"+text+"\\b";
// when the find & replace is *not* case-sensitive, set regular expression
// to ignore the case...
if (!jCheckBoxMatchCase.isSelected()) text = "(?i)"+text;
// the final findterm now might look like this:
// "(?i)\\b<findterm>\\b", in case we ignore case and have whole word search
}
try {
// create a pattern from the first search term. if it fails, go on
// to the catch-block, else contiue here.
Pattern p = Pattern.compile(text);
// now we know we have a valid regular expression. we now want to
// retrieve all matching groups
findmatcher = p.matcher(textarea.getText());
// init findpos...
if (resetFindPos) findpos = 0;
// init list where we store the start/end-positions of the found terms
findselections.clear();
// set textcolor of textfield to black to indicate the regular expression
// is a valid term
jTextFieldFind.setForeground(Color.BLACK);
// find all matches and copy the start/end-positions to our arraylist
// we now can easily retrieve the found terms and their positions via this
// array, thus navigation with find-next and find-prev-buttons is simple
while (findmatcher.find()) findselections.add(new Integer[] {findmatcher.start(),findmatcher.end()});
// update match-label
updateMatchLabel(FIND_TEXT);
}
catch (PatternSyntaxException e) {
// set textcolor of textfield to red to indicate the regular expression
// is an invalid syntax
jTextFieldFind.setForeground(Color.RED);
// init list where we store the start/end-positions of the found terms
findselections.clear();
// disable buttons
jButtonReplace.setEnabled(false);
jButtonFindPrev.setEnabled(false);
// and leave method
return false;
}
return (findselections.size()>0);
}
private void updateMatchLabel(int val) {
switch (val) {
case REPLACE_TEXT:
// set status label to tell the user about the amounf of found matches
jLabelMatches.setText(resourceMap.getString("replacedTermsText",String.valueOf(findselections.size())));
jLabelMatches.setForeground(resourceMap.getColor("jLabelMatches.foreground"));
break;
default:
// set status label to tell the user about the amounf of found matches
jLabelMatches.setText(findselections.size()>0 ?
resourceMap.getString("matchesText",String.valueOf(findselections.size())) :
resourceMap.getString("noMatchText"));
// switch color according to match count
jLabelMatches.setForeground(findselections.size()>0 ? resourceMap.getColor("jLabelMatches.foreground") : new Color(160,51,51));
break;
}
}
@Action
public void findNext() {
// when we have no founds, init matcher
if (findselections.isEmpty()) {
initmatcher();
}
// check whether we have any found at all
if (findselections.size()>0) {
// as long as we haven't reached the last match...
if (findpos<findselections.size()) {
// when we have a negative index (might be possible, when
// using the "findPrev"-method and the first match was found.
// in this case, findpos was zero and by "findpos--" it was decreased to -1
if (findpos<0) findpos=0;
// select next occurence of find term
textarea.setSelectionStart(findselections.get(findpos)[0]);
textarea.moveCaretPosition(findselections.get(findpos)[1]);
// increase our find-counter
findpos++;
// enable buttons
jButtonReplace.setEnabled(true);
jButtonFindPrev.setEnabled(true);
}
else {
findpos=0;
// select next occurence of find term
textarea.setSelectionStart(findselections.get(findpos)[0]);
textarea.moveCaretPosition(findselections.get(findpos)[1]);
// increase our find-counter
findpos++;
}
}
else {
initValues();
}
}
@Action
public void findPrev() {
// check whether we have any found at all
if (findselections.size()>0) {
// as long as we havem't reached the last match...
if (findpos>=0) {
// when we have a larger index that array-size (might be possible, when
// using the "findNext"-method and the last match was found.
// in this case, findpos is equal to the array-size and by "findpos++"
// it was increased to a larger index than array size
if (findpos>=findselections.size()) findpos=findselections.size()-1;
// select next occurence of find term
textarea.setSelectionStart(findselections.get(findpos)[0]);
textarea.moveCaretPosition(findselections.get(findpos)[1]);
// decrease our find-counter
findpos--;
// enable buttons
jButtonReplace.setEnabled(true);
jButtonFindPrev.setEnabled(true);
}
// when we reached the first match, start from end again
else {
findpos = findselections.size()-1;
// select next occurence of find term
textarea.setSelectionStart(findselections.get(findpos)[0]);
textarea.moveCaretPosition(findselections.get(findpos)[1]);
// decrease our find-counter
findpos--;
}
}
else {
initValues();
}
}
@Action
public void replace() {
if (textarea.getSelectedText()!=null) {
textarea.replaceSelection(jTextFieldReplace.getText());
}
if (initmatcher(false)) {
findNext();
}
else {
initValues();
}
}
@Action
public void replaceAll() {
if (initmatcher()) {
for (int cnt=findselections.size()-1;cnt>=0; cnt--) {
textarea.setSelectionStart(findselections.get(cnt)[0]);
textarea.moveCaretPosition(findselections.get(cnt)[1]);
if (textarea.getSelectedText()!=null) textarea.replaceSelection(jTextFieldReplace.getText());
}
}
if (findselections.size()>0) {
textarea.setCaretPosition(findselections.get(findselections.size()-1)[1]);
// update match-label
updateMatchLabel(REPLACE_TEXT);
// reset values
initValues();
}
}
/** 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() {
jPopupMenuCCP = new javax.swing.JPopupMenu();
popupCCPcut = new javax.swing.JMenuItem();
popupCCPcopy = new javax.swing.JMenuItem();
popupCCPpaste = new javax.swing.JMenuItem();
jPanel1 = new javax.swing.JPanel();
jLabel1 = new javax.swing.JLabel();
jTextFieldFind = new javax.swing.JTextField();
jLabel2 = new javax.swing.JLabel();
jTextFieldReplace = new javax.swing.JTextField();
jCheckBoxMatchCase = new javax.swing.JCheckBox();
jCheckBoxWholeWord = new javax.swing.JCheckBox();
jButtonFindNext = new javax.swing.JButton();
jButtonFindPrev = new javax.swing.JButton();
jButtonReplace = new javax.swing.JButton();
jButtonReplaceAll = new javax.swing.JButton();
jCheckBoxRegEx = new javax.swing.JCheckBox();
jLabelMatches = new javax.swing.JLabel();
jPopupMenuCCP.setName("jPopupMenuCCP"); // NOI18N
popupCCPcut.setName("popupCCPcut"); // NOI18N
jPopupMenuCCP.add(popupCCPcut);
popupCCPcopy.setName("popupCCPcopy"); // NOI18N
jPopupMenuCCP.add(popupCCPcopy);
popupCCPpaste.setName("popupCCPpaste"); // NOI18N
jPopupMenuCCP.add(popupCCPpaste);
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class).getContext().getResourceMap(CFindReplaceDialog.class);
setTitle(resourceMap.getString("CFindReplaceDialog.title")); // NOI18N
setAlwaysOnTop(true);
setName("CFindReplaceDialog"); // NOI18N
setResizable(false);
jPanel1.setName("jPanel1"); // NOI18N
jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
jLabel1.setName("jLabel1"); // NOI18N
jTextFieldFind.setName("jTextFieldFind"); // NOI18N
jLabel2.setText(resourceMap.getString("jLabel2.text")); // NOI18N
jLabel2.setName("jLabel2"); // NOI18N
jTextFieldReplace.setName("jTextFieldReplace"); // NOI18N
jCheckBoxMatchCase.setText(resourceMap.getString("jCheckBoxMatchCase.text")); // NOI18N
jCheckBoxMatchCase.setName("jCheckBoxMatchCase"); // NOI18N
jCheckBoxWholeWord.setText(resourceMap.getString("jCheckBoxWholeWord.text")); // NOI18N
jCheckBoxWholeWord.setName("jCheckBoxWholeWord"); // NOI18N
javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class).getContext().getActionMap(CFindReplaceDialog.class, this);
jButtonFindNext.setAction(actionMap.get("findNext")); // NOI18N
jButtonFindNext.setName("jButtonFindNext"); // NOI18N
jButtonFindPrev.setAction(actionMap.get("findPrev")); // NOI18N
jButtonFindPrev.setName("jButtonFindPrev"); // NOI18N
jButtonReplace.setAction(actionMap.get("replace")); // NOI18N
jButtonReplace.setName("jButtonReplace"); // NOI18N
jButtonReplaceAll.setAction(actionMap.get("replaceAll")); // NOI18N
jButtonReplaceAll.setName("jButtonReplaceAll"); // NOI18N
jCheckBoxRegEx.setText(resourceMap.getString("jCheckBoxRegEx.text")); // NOI18N
jCheckBoxRegEx.setName("jCheckBoxRegEx"); // NOI18N
jLabelMatches.setForeground(resourceMap.getColor("jLabelMatches.foreground")); // NOI18N
jLabelMatches.setText(resourceMap.getString("jLabelMatches.text")); // NOI18N
jLabelMatches.setName("jLabelMatches"); // NOI18N
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()
.addComponent(jButtonReplaceAll)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jButtonReplace)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jButtonFindPrev)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jButtonFindNext))
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(jLabel1)
.addComponent(jLabel2))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(jLabelMatches)
.addContainerGap())
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(jCheckBoxRegEx)
.addContainerGap())
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(jCheckBoxMatchCase)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jCheckBoxWholeWord)
.addContainerGap())
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jTextFieldFind, javax.swing.GroupLayout.DEFAULT_SIZE, 389, Short.MAX_VALUE)
.addComponent(jTextFieldReplace, javax.swing.GroupLayout.DEFAULT_SIZE, 389, Short.MAX_VALUE))))))
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jTextFieldFind, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(jLabel1))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel2)
.addComponent(jTextFieldReplace, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jCheckBoxMatchCase)
.addComponent(jCheckBoxWholeWord))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jCheckBoxRegEx)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jLabelMatches)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jButtonFindNext)
.addComponent(jButtonFindPrev)
.addComponent(jButtonReplace)
.addComponent(jButtonReplaceAll)))
);
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()
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addContainerGap())
);
pack();
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton jButtonFindNext;
private javax.swing.JButton jButtonFindPrev;
private javax.swing.JButton jButtonReplace;
private javax.swing.JButton jButtonReplaceAll;
private javax.swing.JCheckBox jCheckBoxMatchCase;
private javax.swing.JCheckBox jCheckBoxRegEx;
private javax.swing.JCheckBox jCheckBoxWholeWord;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabelMatches;
private javax.swing.JPanel jPanel1;
private javax.swing.JPopupMenu jPopupMenuCCP;
private javax.swing.JTextField jTextFieldFind;
private javax.swing.JTextField jTextFieldReplace;
private javax.swing.JMenuItem popupCCPcopy;
private javax.swing.JMenuItem popupCCPcut;
private javax.swing.JMenuItem popupCCPpaste;
// End of variables declaration//GEN-END:variables
}