4 * License : The MIT License
5 * Copyright(c) 2008 olyutorskii
8 package jp.sourceforge.jindolf;
10 import java.awt.BorderLayout;
11 import java.awt.Component;
12 import java.awt.Container;
13 import java.awt.Frame;
14 import java.awt.GridBagConstraints;
15 import java.awt.GridBagLayout;
16 import java.awt.GridLayout;
17 import java.awt.Insets;
18 import java.awt.event.ActionEvent;
19 import java.awt.event.ActionListener;
20 import java.awt.event.ItemEvent;
21 import java.awt.event.ItemListener;
22 import java.awt.event.WindowAdapter;
23 import java.awt.event.WindowEvent;
24 import java.beans.PropertyChangeEvent;
25 import java.beans.PropertyChangeListener;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.regex.Pattern;
32 import java.util.regex.PatternSyntaxException;
33 import javax.swing.BorderFactory;
34 import javax.swing.ComboBoxEditor;
35 import javax.swing.ComboBoxModel;
36 import javax.swing.DefaultListCellRenderer;
37 import javax.swing.Icon;
38 import javax.swing.JButton;
39 import javax.swing.JCheckBox;
40 import javax.swing.JComboBox;
41 import javax.swing.JDialog;
42 import javax.swing.JLabel;
43 import javax.swing.JList;
44 import javax.swing.JOptionPane;
45 import javax.swing.JPanel;
46 import javax.swing.JSeparator;
47 import javax.swing.border.Border;
48 import javax.swing.event.ChangeEvent;
49 import javax.swing.event.ChangeListener;
50 import javax.swing.event.EventListenerList;
51 import javax.swing.event.ListDataEvent;
52 import javax.swing.event.ListDataListener;
53 import javax.swing.text.JTextComponent;
54 import jp.sourceforge.jovsonz.JsArray;
55 import jp.sourceforge.jovsonz.JsObject;
56 import jp.sourceforge.jovsonz.JsValue;
61 @SuppressWarnings("serial")
62 public class FindPanel extends JDialog
63 implements ActionListener,
66 PropertyChangeListener {
68 private static final String HIST_FILE = "searchHistory.json";
69 private static final String FRAMETITLE = "発言検索 - " + Jindolf.TITLE;
70 private static final String LABEL_REENTER = "再入力";
71 private static final String LABEL_IGNORE = "無視して検索をキャンセル";
73 private final JComboBox findBox = new JComboBox();
74 private final JButton searchButton = new JButton("検索");
75 private final JButton clearButton = new JButton("クリア");
76 private final JCheckBox capitalSwitch =
77 new JCheckBox("大文字/小文字を区別する");
78 private final JCheckBox regexSwitch =
79 new JCheckBox("正規表現");
80 private final JCheckBox dotallSwitch =
81 new JCheckBox("正規表現 \".\" を行末記号にもマッチさせる");
82 private final JCheckBox multilineSwitch =
83 new JCheckBox("正規表現 \"^\" や \"$\" を"
85 private final JCheckBox bulkSearchSwitch =
86 new JCheckBox("全日程を一括検索");
87 private final JButton closeButton = new JButton("キャンセル");
89 private final CustomModel model = new CustomModel();
91 private JsObject loadedHistory = null;
93 private boolean canceled = false;
94 private RegexPattern regexPattern = null;
100 public FindPanel(Frame owner){
101 super(owner, FRAMETITLE, true);
103 GUIUtils.modifyWindowAttributes(this, true, false, true);
105 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
106 addWindowListener(new WindowAdapter(){
108 public void windowClosing(WindowEvent event){
116 this.findBox.setModel(this.model);
117 this.findBox.setToolTipText("検索文字列を入力してください");
118 this.findBox.setEditable(true);
119 this.findBox.setRenderer(new CustomRenderer());
120 this.findBox.setMaximumRowCount(15);
122 ComboBoxEditor editor = this.findBox.getEditor();
123 modifyComboBoxEditor(editor);
124 this.findBox.addPropertyChangeListener("UI", this);
126 this.searchButton.setToolTipText("発言内容を検索する");
127 this.clearButton.setToolTipText("入力をクリアする");
129 this.findBox.addItemListener(this);
130 this.searchButton.addActionListener(this);
131 this.clearButton.addActionListener(this);
132 this.regexSwitch.addChangeListener(this);
133 this.closeButton.addActionListener(this);
135 setRegexPattern(null);
143 private void design(){
144 Container content = getContentPane();
146 GridBagLayout layout = new GridBagLayout();
147 GridBagConstraints constraints = new GridBagConstraints();
149 content.setLayout(layout);
151 constraints.insets = new Insets(2, 2, 2, 2);
153 constraints.weightx = 1.0;
154 constraints.fill = GridBagConstraints.HORIZONTAL;
155 constraints.gridwidth = 2;
158 .createTitledBorder("検索文字列を入力してください");
159 JPanel panel = new JPanel();
160 panel.setLayout(new BorderLayout());
161 panel.add(this.findBox, BorderLayout.CENTER);
162 panel.setBorder(border);
163 content.add(panel, constraints);
165 constraints.weightx = 0.0;
166 constraints.fill = GridBagConstraints.NONE;
167 constraints.gridwidth = GridBagConstraints.REMAINDER;
168 constraints.anchor = GridBagConstraints.SOUTH;
169 content.add(this.searchButton, constraints);
171 constraints.gridwidth = GridBagConstraints.REMAINDER;
172 constraints.anchor = GridBagConstraints.WEST;
173 content.add(this.clearButton, constraints);
175 constraints.gridwidth = GridBagConstraints.REMAINDER;
176 constraints.anchor = GridBagConstraints.WEST;
177 content.add(this.capitalSwitch, constraints);
179 constraints.gridwidth = GridBagConstraints.REMAINDER;
180 constraints.anchor = GridBagConstraints.WEST;
181 content.add(this.regexSwitch, constraints);
183 JPanel regexPanel = new JPanel();
184 regexPanel.setBorder(BorderFactory.createTitledBorder(""));
185 regexPanel.setLayout(new GridLayout(2, 1));
186 regexPanel.add(this.dotallSwitch);
187 regexPanel.add(this.multilineSwitch);
189 constraints.gridwidth = GridBagConstraints.REMAINDER;
190 constraints.anchor = GridBagConstraints.WEST;
191 content.add(regexPanel, constraints);
193 constraints.weightx = 1.0;
194 constraints.gridwidth = GridBagConstraints.REMAINDER;
195 constraints.fill = GridBagConstraints.HORIZONTAL;
196 content.add(new JSeparator(), constraints);
198 constraints.weightx = 0.0;
199 constraints.gridwidth = GridBagConstraints.REMAINDER;
200 constraints.anchor = GridBagConstraints.WEST;
201 constraints.fill = GridBagConstraints.NONE;
202 content.add(this.bulkSearchSwitch, constraints);
204 constraints.weightx = 1.0;
205 constraints.gridwidth = GridBagConstraints.REMAINDER;
206 constraints.fill = GridBagConstraints.HORIZONTAL;
207 content.add(new JSeparator(), constraints);
209 constraints.weightx = 1.0;
210 constraints.gridwidth = GridBagConstraints.REMAINDER;
211 constraints.anchor = GridBagConstraints.EAST;
212 constraints.fill = GridBagConstraints.NONE;
213 content.add(this.closeButton, constraints);
221 * @param show 表示フラグ。真なら表示。{@inheritDoc}
224 public void setVisible(boolean show){
225 super.setVisible(show);
226 getRootPane().setDefaultButton(this.searchButton);
227 this.findBox.requestFocusInWindow();
232 * ダイアログが閉じられた原因を判定する。
233 * @return キャンセルもしくはクローズボタンでダイアログが閉じられたらtrue
235 public boolean isCanceled(){
236 return this.canceled;
241 * @return 一括検索が指定されたらtrue
243 public boolean isBulkSearch(){
244 return this.bulkSearchSwitch.isSelected();
251 private void actionCancel(){
252 this.canceled = true;
262 private void actionSubmit(){
263 Object selected = this.findBox.getSelectedItem();
264 if(selected == null){
265 this.regexPattern = null;
268 String edit = selected.toString();
270 boolean isRegex = this.regexSwitch.isSelected();
272 int flag = 0x00000000;
273 if( ! this.capitalSwitch.isSelected() ){
274 flag |= RegexPattern.IGNORECASEFLAG;
276 if(this.dotallSwitch.isSelected()) flag |= Pattern.DOTALL;
277 if(this.multilineSwitch.isSelected()) flag |= Pattern.MULTILINE;
280 this.regexPattern = new RegexPattern(edit, isRegex, flag);
281 }catch(PatternSyntaxException e){
282 this.regexPattern = null;
283 if(showRegexError(e)){
290 this.model.addHistory(this.regexPattern);
292 this.canceled = false;
300 * 正規表現パターン異常系のダイアログ表示。
302 * @return 再入力が押されたらtrue。それ以外はfalse。
304 private boolean showRegexError(PatternSyntaxException e){
305 String pattern = e.getPattern();
307 String position = "";
308 int index = e.getIndex();
309 if(0 <= index && index <= pattern.length() - 1){
310 char errChar = pattern.charAt(index);
311 position = "エラーの発生箇所は、おおよそ"
312 + (index+1) + "文字目 [ " + errChar + " ] "
317 "入力された検索文字列 [ " + pattern + " ] は"
318 +"正しい正規表現として認識されませんでした。\n"
321 +" [ http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/"
322 +"java/util/regex/Pattern.html#sum ] "
325 +"「正規表現」のチェックボックスを外しましょう。\n"
328 Object[] buttons = new Object[2];
329 buttons[0] = LABEL_REENTER;
330 buttons[1] = LABEL_IGNORE;
333 int optionNo = JOptionPane.showOptionDialog(this,
336 JOptionPane.YES_NO_OPTION,
337 JOptionPane.ERROR_MESSAGE,
342 if(optionNo == JOptionPane.CLOSED_OPTION) return false;
343 if(buttons[optionNo].equals(LABEL_REENTER)) return true;
344 if(buttons[optionNo].equals(LABEL_IGNORE) ) return false;
353 public RegexPattern getRegexPattern(){
354 return this.regexPattern;
359 * @param pattern 検索パターン
361 public final void setRegexPattern(RegexPattern pattern){
362 if(pattern == null) this.regexPattern = CustomModel.INITITEM;
363 else this.regexPattern = pattern;
365 String edit = this.regexPattern.getEditSource();
366 this.findBox.getEditor().setItem(edit);
368 this.regexSwitch.setSelected(this.regexPattern.isRegex());
370 int initflag = this.regexPattern.getRegexFlag();
371 this.capitalSwitch.setSelected(
372 (initflag & RegexPattern.IGNORECASEFLAG) == 0);
373 this.dotallSwitch.setSelected((initflag & Pattern.DOTALL) != 0);
374 this.multilineSwitch.setSelected((initflag & Pattern.MULTILINE) != 0);
384 * @param event イベント {@inheritDoc}
387 public void actionPerformed(ActionEvent event){
388 Object source = event.getSource();
389 if(source == this.closeButton){
391 }else if(source == this.searchButton){
393 }else if(source == this.clearButton){
394 this.findBox.getEditor().setItem("");
395 this.findBox.requestFocusInWindow();
403 * @param event アイテム選択イベント {@inheritDoc}
406 public void itemStateChanged(ItemEvent event){
407 int stateChange = event.getStateChange();
408 if(stateChange != ItemEvent.SELECTED) return;
410 Object item = event.getItem();
411 if( ! (item instanceof RegexPattern) ) return;
412 RegexPattern regex = (RegexPattern) item;
414 setRegexPattern(regex);
422 * @param event チェックボックス操作イベント {@inheritDoc}
425 public void stateChanged(ChangeEvent event){
426 if(event.getSource() != this.regexSwitch) return;
432 * 正規表現でしか使わないUIのマスク処理。
434 private void maskRegexUI(){
435 boolean isRegex = this.regexSwitch.isSelected();
436 this.dotallSwitch .setEnabled(isRegex);
437 this.multilineSwitch.setEnabled(isRegex);
443 * コンボボックスのUI変更通知を受け取るリスナ。
444 * @param event UI差し替えイベント {@inheritDoc}
447 public void propertyChange(PropertyChangeEvent event){
448 if( ! event.getPropertyName().equals("UI") ) return;
449 if(event.getSource() != this.findBox) return;
451 ComboBoxEditor editor = this.findBox.getEditor();
452 modifyComboBoxEditor(editor);
462 private void modifyComboBoxEditor(ComboBoxEditor editor){
463 if(editor == null) return;
465 Component editComp = editor.getEditorComponent();
466 if(editComp == null) return;
468 if(editComp instanceof JTextComponent){
469 JTextComponent textEditor = (JTextComponent) editComp;
470 textEditor.setComponentPopupMenu(new TextPopup());
473 GUIUtils.addMargin(editComp, 1, 4, 1, 4);
481 public void loadHistory(){
482 JsValue value = ConfigFile.loadJson(new File(HIST_FILE));
483 if(value == null) return;
485 if( ! (value instanceof JsObject) ) return;
486 JsObject root = (JsObject) value;
488 value = root.getValue("history");
489 if( ! (value instanceof JsArray) ) return;
490 JsArray array = (JsArray) value;
492 for(JsValue elem : array){
493 if( ! (elem instanceof JsObject) ) continue;
494 JsObject regObj = (JsObject) elem;
495 RegexPattern regex = RegexPattern.decodeJson(regObj);
496 if(regex == null) continue;
497 this.model.addHistory(regex);
500 this.loadedHistory = root;
508 public void saveHistory(){
509 AppSetting setting = Jindolf.getAppSetting();
510 if( ! setting.useConfigPath() ) return;
511 File configPath = setting.getConfigPath();
512 if(configPath == null) return;
514 JsObject root = new JsObject();
515 JsArray array = new JsArray();
516 root.putValue("history", array);
518 List<RegexPattern> history = this.model.getOriginalHistoryList();
519 history = new ArrayList<RegexPattern>(history);
520 Collections.reverse(history);
521 for(RegexPattern regex : history){
522 JsObject obj = RegexPattern.encodeJson(regex);
526 if(this.loadedHistory != null){
527 if(this.loadedHistory.equals(root)) return;
530 ConfigFile.saveJson(new File(HIST_FILE), root);
538 private static class CustomRenderer extends DefaultListCellRenderer{
543 public CustomRenderer(){
550 * @param list {@inheritDoc}
551 * @param value {@inheritDoc}
552 * @param index {@inheritDoc}
553 * @param isSelected {@inheritDoc}
554 * @param cellHasFocus {@inheritDoc}
555 * @return {@inheritDoc}
558 public Component getListCellRendererComponent(
563 boolean cellHasFocus ){
564 if(value instanceof JSeparator){
565 return (JSeparator) value;
569 (JLabel) super.getListCellRendererComponent(list,
575 if(value instanceof RegexPattern){
576 RegexPattern regexPattern = (RegexPattern) value;
578 if(regexPattern.isRegex()){
579 text = "[R] " + regexPattern.getEditSource();
581 text = regexPattern.getEditSource();
583 text += regexPattern.getComment();
585 superLabel.setText(text);
588 GUIUtils.addMargin(superLabel, 1, 4, 1, 4);
597 private static class CustomModel implements ComboBoxModel{
599 private static final int HISTORY_MAX = 7;
600 private static final RegexPattern INITITEM =
602 "", false, RegexPattern.IGNORECASEFLAG | Pattern.DOTALL);
603 private static final List<RegexPattern> PREDEF_PATTERN_LIST =
604 new LinkedList<RegexPattern>();
607 PREDEF_PATTERN_LIST.add(
608 new RegexPattern("【[^】]*】",
612 PREDEF_PATTERN_LIST.add(
613 new RegexPattern("[■●▼★□○▽☆〇◯∇]",
617 PREDEF_PATTERN_LIST.add(
618 new RegexPattern("Jindolf",
620 RegexPattern.IGNORECASEFLAG,
624 private final List<RegexPattern> history =
625 new LinkedList<RegexPattern>();
626 private final JSeparator separator1st = new JSeparator();
627 private final JSeparator separator2nd = new JSeparator();
628 private Object selected;
629 private final EventListenerList listenerList =
630 new EventListenerList();
635 public CustomModel(){
642 * @return {@inheritDoc}
645 public Object getSelectedItem(){
646 return this.selected;
651 * @param item {@inheritDoc}
654 public void setSelectedItem(Object item){
655 if(item instanceof JSeparator) return;
656 this.selected = item;
662 * @param index {@inheritDoc}
663 * @return {@inheritDoc}
666 public Object getElementAt(int index){
667 int historySize = this.history.size();
673 return this.separator1st;
675 if(2 <= index && index <= 1 + historySize){
676 return this.history.get(index - 2);
678 if(index == historySize + 2){
679 return this.separator2nd;
681 if(historySize + 3 <= index){
682 return PREDEF_PATTERN_LIST.get(index - 1
693 * @return {@inheritDoc}
696 public int getSize(){
698 size += 1; // first separator
699 size += this.history.size();
700 size += 1; // second separator
701 size += PREDEF_PATTERN_LIST.size();
707 * @param listener {@inheritDoc}
710 public void addListDataListener(ListDataListener listener){
711 this.listenerList.add(ListDataListener.class, listener);
717 * @param listener {@inheritDoc}
720 public void removeListDataListener(ListDataListener listener){
721 this.listenerList.remove(ListDataListener.class, listener);
727 * @param regexPattern 検索履歴
729 public void addHistory(RegexPattern regexPattern){
730 if(regexPattern == null) return;
731 if(regexPattern.equals(INITITEM)) return;
732 if(PREDEF_PATTERN_LIST.contains(regexPattern)) return;
733 if(this.history.contains(regexPattern)){
734 this.history.remove(regexPattern);
737 this.history.add(0, regexPattern);
739 while(this.history.size() > HISTORY_MAX){
740 this.history.remove(HISTORY_MAX);
749 * プリセットでない検索ヒストリリストを返す。
752 public List<RegexPattern> getOriginalHistoryList(){
753 return Collections.unmodifiableList(this.history);
760 ListDataEvent event =
761 new ListDataEvent(this,
762 ListDataEvent.CONTENTS_CHANGED,
764 ListDataListener[] listeners =
765 this.listenerList.getListeners(ListDataListener.class);
766 for(ListDataListener listener : listeners){
767 listener.contentsChanged(event);