OSDN Git Service

from subversion repository
[jindolf/Jindolf.git] / src / main / java / jp / sourceforge / jindolf / FindPanel.java
1 /*\r
2  * Find panel\r
3  *\r
4  * Copyright(c) 2008 olyutorskii\r
5  * $Id: FindPanel.java 956 2009-12-13 15:14:07Z olyutorskii $\r
6  */\r
7 \r
8 package jp.sourceforge.jindolf;\r
9 \r
10 import java.awt.BorderLayout;\r
11 import java.awt.Component;\r
12 import java.awt.Container;\r
13 import java.awt.Frame;\r
14 import java.awt.GridBagConstraints;\r
15 import java.awt.GridBagLayout;\r
16 import java.awt.GridLayout;\r
17 import java.awt.Insets;\r
18 import java.awt.event.ActionEvent;\r
19 import java.awt.event.ActionListener;\r
20 import java.awt.event.ItemEvent;\r
21 import java.awt.event.ItemListener;\r
22 import java.awt.event.WindowAdapter;\r
23 import java.awt.event.WindowEvent;\r
24 import java.beans.PropertyChangeEvent;\r
25 import java.beans.PropertyChangeListener;\r
26 import java.io.File;\r
27 import java.util.ArrayList;\r
28 import java.util.Collections;\r
29 import java.util.LinkedList;\r
30 import java.util.List;\r
31 import java.util.regex.Pattern;\r
32 import java.util.regex.PatternSyntaxException;\r
33 import javax.swing.BorderFactory;\r
34 import javax.swing.ComboBoxEditor;\r
35 import javax.swing.ComboBoxModel;\r
36 import javax.swing.DefaultListCellRenderer;\r
37 import javax.swing.Icon;\r
38 import javax.swing.JButton;\r
39 import javax.swing.JCheckBox;\r
40 import javax.swing.JComboBox;\r
41 import javax.swing.JDialog;\r
42 import javax.swing.JLabel;\r
43 import javax.swing.JList;\r
44 import javax.swing.JOptionPane;\r
45 import javax.swing.JPanel;\r
46 import javax.swing.JSeparator;\r
47 import javax.swing.border.Border;\r
48 import javax.swing.event.ChangeEvent;\r
49 import javax.swing.event.ChangeListener;\r
50 import javax.swing.event.EventListenerList;\r
51 import javax.swing.event.ListDataEvent;\r
52 import javax.swing.event.ListDataListener;\r
53 import javax.swing.text.JTextComponent;\r
54 import jp.sourceforge.jindolf.json.JsArray;\r
55 import jp.sourceforge.jindolf.json.JsObject;\r
56 import jp.sourceforge.jindolf.json.JsValue;\r
57 \r
58 /**\r
59  * 検索パネルGUI。\r
60  */\r
61 @SuppressWarnings("serial")\r
62 public class FindPanel extends JDialog\r
63         implements ActionListener,\r
64                    ItemListener,\r
65                    ChangeListener,\r
66                    PropertyChangeListener {\r
67 \r
68     private static final String HIST_FILE = "searchHistory.json";\r
69     private static final String FRAMETITLE = "発言検索 - " + Jindolf.TITLE;\r
70     private static final String LABEL_REENTER = "再入力";\r
71     private static final String LABEL_IGNORE = "無視して検索をキャンセル";\r
72 \r
73     private final JComboBox findBox = new JComboBox();\r
74     private final JButton searchButton = new JButton("検索");\r
75     private final JButton clearButton = new JButton("クリア");\r
76     private final JCheckBox capitalSwitch =\r
77             new JCheckBox("大文字/小文字を区別する");\r
78     private final JCheckBox regexSwitch =\r
79             new JCheckBox("正規表現");\r
80     private final JCheckBox dotallSwitch =\r
81             new JCheckBox("正規表現 \".\" を行末記号にもマッチさせる");\r
82     private final JCheckBox multilineSwitch =\r
83             new JCheckBox("正規表現 \"^\" や \"$\" を"\r
84                          +"行末記号の前後に反応させる");\r
85     private final JCheckBox bulkSearchSwitch =\r
86             new JCheckBox("全日程を一括検索");\r
87     private final JButton closeButton = new JButton("キャンセル");\r
88 \r
89     private final CustomModel model = new CustomModel();\r
90 \r
91     private JsObject loadedHistory = null;\r
92 \r
93     private boolean canceled = false;\r
94     private RegexPattern regexPattern = null;\r
95 \r
96     /**\r
97      * 検索パネルを生成する。\r
98      * @param owner 親フレーム。\r
99      */\r
100     public FindPanel(Frame owner){\r
101         super(owner, FRAMETITLE, true);\r
102 \r
103         GUIUtils.modifyWindowAttributes(this, true, false, true);\r
104 \r
105         setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);\r
106         addWindowListener(new WindowAdapter(){\r
107             @Override\r
108             public void windowClosing(WindowEvent event){\r
109                 actionCancel();\r
110                 return;\r
111             }\r
112         });\r
113 \r
114         design();\r
115 \r
116         this.findBox.setModel(this.model);\r
117         this.findBox.setToolTipText("検索文字列を入力してください");\r
118         this.findBox.setEditable(true);\r
119         this.findBox.setRenderer(new CustomRenderer());\r
120         this.findBox.setMaximumRowCount(15);\r
121 \r
122         ComboBoxEditor editor = this.findBox.getEditor();\r
123         modifyComboBoxEditor(editor);\r
124         this.findBox.addPropertyChangeListener("UI", this);\r
125 \r
126         this.searchButton.setToolTipText("発言内容を検索する");\r
127         this.clearButton.setToolTipText("入力をクリアする");\r
128 \r
129         this.findBox.addItemListener(this);\r
130         this.searchButton.addActionListener(this);\r
131         this.clearButton.addActionListener(this);\r
132         this.regexSwitch.addChangeListener(this);\r
133         this.closeButton.addActionListener(this);\r
134 \r
135         setRegexPattern(null);\r
136 \r
137         return;\r
138     }\r
139 \r
140     /**\r
141      * デザイン・レイアウトを行う。\r
142      */\r
143     private void design(){\r
144         Container content = getContentPane();\r
145 \r
146         GridBagLayout layout = new GridBagLayout();\r
147         GridBagConstraints constraints = new GridBagConstraints();\r
148 \r
149         content.setLayout(layout);\r
150 \r
151         constraints.insets = new Insets(2, 2, 2, 2);\r
152 \r
153         constraints.weightx = 1.0;\r
154         constraints.fill = GridBagConstraints.HORIZONTAL;\r
155         constraints.gridwidth = 2;\r
156         Border border =\r
157                 BorderFactory\r
158                 .createTitledBorder("検索文字列を入力してください");\r
159         JPanel panel = new JPanel();\r
160         panel.setLayout(new BorderLayout());\r
161         panel.add(this.findBox, BorderLayout.CENTER);\r
162         panel.setBorder(border);\r
163         content.add(panel, constraints);\r
164 \r
165         constraints.weightx = 0.0;\r
166         constraints.fill = GridBagConstraints.NONE;\r
167         constraints.gridwidth = GridBagConstraints.REMAINDER;\r
168         constraints.anchor = GridBagConstraints.SOUTH;\r
169         content.add(this.searchButton, constraints);\r
170 \r
171         constraints.gridwidth = GridBagConstraints.REMAINDER;\r
172         constraints.anchor = GridBagConstraints.WEST;\r
173         content.add(this.clearButton, constraints);\r
174 \r
175         constraints.gridwidth = GridBagConstraints.REMAINDER;\r
176         constraints.anchor = GridBagConstraints.WEST;\r
177         content.add(this.capitalSwitch, constraints);\r
178 \r
179         constraints.gridwidth = GridBagConstraints.REMAINDER;\r
180         constraints.anchor = GridBagConstraints.WEST;\r
181         content.add(this.regexSwitch, constraints);\r
182 \r
183         JPanel regexPanel = new JPanel();\r
184         regexPanel.setBorder(BorderFactory.createTitledBorder(""));\r
185         regexPanel.setLayout(new GridLayout(2, 1));\r
186         regexPanel.add(this.dotallSwitch);\r
187         regexPanel.add(this.multilineSwitch);\r
188 \r
189         constraints.gridwidth = GridBagConstraints.REMAINDER;\r
190         constraints.anchor = GridBagConstraints.WEST;\r
191         content.add(regexPanel, constraints);\r
192 \r
193         constraints.weightx = 1.0;\r
194         constraints.gridwidth = GridBagConstraints.REMAINDER;\r
195         constraints.fill = GridBagConstraints.HORIZONTAL;\r
196         content.add(new JSeparator(), constraints);\r
197 \r
198         constraints.weightx = 0.0;\r
199         constraints.gridwidth = GridBagConstraints.REMAINDER;\r
200         constraints.anchor = GridBagConstraints.WEST;\r
201         constraints.fill = GridBagConstraints.NONE;\r
202         content.add(this.bulkSearchSwitch, constraints);\r
203 \r
204         constraints.weightx = 1.0;\r
205         constraints.gridwidth = GridBagConstraints.REMAINDER;\r
206         constraints.fill = GridBagConstraints.HORIZONTAL;\r
207         content.add(new JSeparator(), constraints);\r
208 \r
209         constraints.weightx = 1.0;\r
210         constraints.gridwidth = GridBagConstraints.REMAINDER;\r
211         constraints.anchor = GridBagConstraints.EAST;\r
212         constraints.fill = GridBagConstraints.NONE;\r
213         content.add(this.closeButton, constraints);\r
214 \r
215         return;\r
216     }\r
217 \r
218     /**\r
219      * {@inheritDoc}\r
220      * 検索ダイアログを表示・非表示する。\r
221      * @param show 表示フラグ。真なら表示。{@inheritDoc}\r
222      */\r
223     @Override\r
224     public void setVisible(boolean show){\r
225         super.setVisible(show);\r
226         getRootPane().setDefaultButton(this.searchButton);\r
227         this.findBox.requestFocusInWindow();\r
228         return;\r
229     }\r
230 \r
231     /**\r
232      * ダイアログが閉じられた原因を判定する。\r
233      * @return キャンセルもしくはクローズボタンでダイアログが閉じられたらtrue\r
234      */\r
235     public boolean isCanceled(){\r
236         return this.canceled;\r
237     }\r
238 \r
239     /**\r
240      * 一括検索が指定されたか否か返す。\r
241      * @return 一括検索が指定されたらtrue\r
242      */\r
243     public boolean isBulkSearch(){\r
244         return this.bulkSearchSwitch.isSelected();\r
245     }\r
246 \r
247     /**\r
248      * キャンセルボタン押下処理。\r
249      * このモーダルダイアログを閉じる。\r
250      */\r
251     private void actionCancel(){\r
252         this.canceled = true;\r
253         setVisible(false);\r
254         dispose();\r
255         return;\r
256     }\r
257 \r
258     /**\r
259      * 検索ボタン押下処理。\r
260      * このモーダルダイアログを閉じる。\r
261      */\r
262     private void actionSubmit(){\r
263         Object selected = this.findBox.getSelectedItem();\r
264         if(selected == null){\r
265             this.regexPattern = null;\r
266             return;\r
267         }\r
268         String edit = selected.toString();\r
269 \r
270         boolean isRegex = this.regexSwitch.isSelected();\r
271 \r
272         int flag = 0x00000000;\r
273         if( ! this.capitalSwitch.isSelected() ){\r
274             flag |= RegexPattern.IGNORECASEFLAG;\r
275         }\r
276         if(this.dotallSwitch.isSelected())    flag |= Pattern.DOTALL;\r
277         if(this.multilineSwitch.isSelected()) flag |= Pattern.MULTILINE;\r
278 \r
279         try{\r
280             this.regexPattern = new RegexPattern(edit, isRegex, flag);\r
281         }catch(PatternSyntaxException e){\r
282             this.regexPattern = null;\r
283             if(showRegexError(e)){\r
284                 return;\r
285             }\r
286             actionCancel();\r
287             return;\r
288         }\r
289 \r
290         this.model.addHistory(this.regexPattern);\r
291 \r
292         this.canceled = false;\r
293         setVisible(false);\r
294         dispose();\r
295 \r
296         return;\r
297     }\r
298 \r
299     /**\r
300      * 正規表現パターン異常系のダイアログ表示。\r
301      * @param e 正規表現構文エラー\r
302      * @return 再入力が押されたらtrue。それ以外はfalse。\r
303      */\r
304     private boolean showRegexError(PatternSyntaxException e){\r
305         String pattern = e.getPattern();\r
306 \r
307         String position = "";\r
308         int index = e.getIndex();\r
309         if(0 <= index && index <= pattern.length() - 1){\r
310             char errChar = pattern.charAt(index);\r
311             position = "エラーの発生箇所は、おおよそ"\r
312                       + (index+1) + "文字目 [ " + errChar + " ] "\r
313                       +"かその前後と推測されます。\n";\r
314         }\r
315 \r
316         String message =\r
317                 "入力された検索文字列 [ " + pattern + " ] は"\r
318                 +"正しい正規表現として認識されませんでした。\n"\r
319                 +position\r
320                 +"正規表現の書き方は"\r
321                 +" [ http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/"\r
322                 +"java/util/regex/Pattern.html#sum ] "\r
323                 +"を参照してください。\n"\r
324                 +"ただの文字列を検索したい場合は"\r
325                 +"「正規表現」のチェックボックスを外しましょう。\n"\r
326                 ;\r
327 \r
328         Object[] buttons = new Object[2];\r
329         buttons[0] = LABEL_REENTER;\r
330         buttons[1] = LABEL_IGNORE;\r
331         Icon icon = null;\r
332 \r
333         int optionNo = JOptionPane.showOptionDialog(this,\r
334                                                     message,\r
335                                                     "不正な正規表現",\r
336                                                     JOptionPane.YES_NO_OPTION,\r
337                                                     JOptionPane.ERROR_MESSAGE,\r
338                                                     icon,\r
339                                                     buttons,\r
340                                                     LABEL_REENTER);\r
341 \r
342         if(optionNo == JOptionPane.CLOSED_OPTION) return false;\r
343         if(buttons[optionNo].equals(LABEL_REENTER)) return true;\r
344         if(buttons[optionNo].equals(LABEL_IGNORE) ) return false;\r
345 \r
346         return true;\r
347     }\r
348 \r
349     /**\r
350      * 現時点での検索パターンを得る。\r
351      * @return 検索パターン\r
352      */\r
353     public RegexPattern getRegexPattern(){\r
354         return this.regexPattern;\r
355     }\r
356 \r
357     /**\r
358      * 検索パターンを設定する。\r
359      * @param pattern 検索パターン\r
360      */\r
361     public final void setRegexPattern(RegexPattern pattern){\r
362         if(pattern == null) this.regexPattern = CustomModel.INITITEM;\r
363         else                this.regexPattern = pattern;\r
364 \r
365         String edit = this.regexPattern.getEditSource();\r
366         this.findBox.getEditor().setItem(edit);\r
367 \r
368         this.regexSwitch.setSelected(this.regexPattern.isRegex());\r
369 \r
370         int initflag = this.regexPattern.getRegexFlag();\r
371         this.capitalSwitch.setSelected(\r
372                 (initflag & RegexPattern.IGNORECASEFLAG) == 0);\r
373         this.dotallSwitch.setSelected((initflag & Pattern.DOTALL) != 0);\r
374         this.multilineSwitch.setSelected((initflag & Pattern.MULTILINE) != 0);\r
375 \r
376         maskRegexUI();\r
377 \r
378         return;\r
379     }\r
380 \r
381     /**\r
382      * {@inheritDoc}\r
383      * ボタン操作時にリスナとして呼ばれる。\r
384      * @param event イベント {@inheritDoc}\r
385      */\r
386     public void actionPerformed(ActionEvent event){\r
387         Object source = event.getSource();\r
388         if(source == this.closeButton){\r
389             actionCancel();\r
390         }else if(source == this.searchButton){\r
391             actionSubmit();\r
392         }else if(source == this.clearButton){\r
393             this.findBox.getEditor().setItem("");\r
394             this.findBox.requestFocusInWindow();\r
395         }\r
396         return;\r
397     }\r
398 \r
399     /**\r
400      * {@inheritDoc}\r
401      * コンボボックスのアイテム選択リスナ。\r
402      * @param event アイテム選択イベント {@inheritDoc}\r
403      */\r
404     public void itemStateChanged(ItemEvent event){\r
405         int stateChange = event.getStateChange();\r
406         if(stateChange != ItemEvent.SELECTED) return;\r
407 \r
408         Object item = event.getItem();\r
409         if( ! (item instanceof RegexPattern) ) return;\r
410         RegexPattern regex = (RegexPattern) item;\r
411 \r
412         setRegexPattern(regex);\r
413 \r
414         return;\r
415     }\r
416 \r
417     /**\r
418      * {@inheritDoc}\r
419      * チェックボックス操作のリスナ。\r
420      * @param event チェックボックス操作イベント {@inheritDoc}\r
421      */\r
422     public void stateChanged(ChangeEvent event){\r
423         if(event.getSource() != this.regexSwitch) return;\r
424         maskRegexUI();\r
425         return;\r
426     }\r
427 \r
428     /**\r
429      * 正規表現でしか使わないUIのマスク処理。\r
430      */\r
431     private void maskRegexUI(){\r
432         boolean isRegex = this.regexSwitch.isSelected();\r
433         this.dotallSwitch   .setEnabled(isRegex);\r
434         this.multilineSwitch.setEnabled(isRegex);\r
435         return;\r
436     }\r
437 \r
438     /**\r
439      * {@inheritDoc}\r
440      * コンボボックスのUI変更通知を受け取るリスナ。\r
441      * @param event UI差し替えイベント {@inheritDoc}\r
442      */\r
443     public void propertyChange(PropertyChangeEvent event){\r
444         if( ! event.getPropertyName().equals("UI") ) return;\r
445         if(event.getSource() != this.findBox) return;\r
446 \r
447         ComboBoxEditor editor = this.findBox.getEditor();\r
448         modifyComboBoxEditor(editor);\r
449 \r
450         return;\r
451     }\r
452 \r
453     /**\r
454      * コンボボックスエディタを修飾する。\r
455      * マージン修飾と等幅フォントをいじる。\r
456      * @param editor エディタ\r
457      */\r
458     private void modifyComboBoxEditor(ComboBoxEditor editor){\r
459         if(editor == null) return;\r
460 \r
461         Component editComp = editor.getEditorComponent();\r
462         if(editComp == null) return;\r
463 \r
464         if(editComp instanceof JTextComponent){\r
465             JTextComponent textEditor = (JTextComponent) editComp;\r
466             textEditor.setComponentPopupMenu(new TextPopup());\r
467         }\r
468 \r
469         GUIUtils.addMargin(editComp, 1, 4, 1, 4);\r
470 \r
471         return;\r
472     }\r
473 \r
474     /**\r
475      * 検索履歴をロードする。\r
476      */\r
477     public void loadHistory(){\r
478         JsValue value = ConfigFile.loadJson(new File(HIST_FILE));\r
479         if(value == null) return;\r
480 \r
481         if( ! (value instanceof JsObject) ) return;\r
482         JsObject root = (JsObject) value;\r
483 \r
484         value = root.getValue("history");\r
485         if( ! (value instanceof JsArray) ) return;\r
486         JsArray array = (JsArray) value;\r
487 \r
488         for(JsValue elem : array){\r
489             if( ! (elem instanceof JsObject) ) continue;\r
490             JsObject regObj = (JsObject) elem;\r
491             RegexPattern regex = RegexPattern.decodeJson(regObj);\r
492             if(regex == null) continue;\r
493             this.model.addHistory(regex);\r
494         }\r
495 \r
496         this.loadedHistory = root;\r
497 \r
498         return;\r
499     }\r
500 \r
501     /**\r
502      * 検索履歴をセーブする。\r
503      */\r
504     public void saveHistory(){\r
505         AppSetting setting = Jindolf.getAppSetting();\r
506         if( ! setting.useConfigPath() ) return;\r
507         File configPath = setting.getConfigPath();\r
508         if(configPath == null) return;\r
509 \r
510         JsObject root = new JsObject();\r
511         JsArray array = new JsArray();\r
512         root.putValue("history", array);\r
513 \r
514         List<RegexPattern> history = this.model.getOriginalHistoryList();\r
515         history = new ArrayList<RegexPattern>(history);\r
516         Collections.reverse(history);\r
517         for(RegexPattern regex : history){\r
518             JsObject obj = RegexPattern.encodeJson(regex);\r
519             array.add(obj);\r
520         }\r
521 \r
522         if(this.loadedHistory != null){\r
523             if(this.loadedHistory.equals(root)) return;\r
524         }\r
525 \r
526         ConfigFile.saveJson(new File(HIST_FILE), root);\r
527 \r
528         return;\r
529     }\r
530 \r
531     /**\r
532      * コンボボックスの独自レンダラ。\r
533      */\r
534     private static class CustomRenderer extends DefaultListCellRenderer{\r
535 \r
536         /**\r
537          * コンストラクタ。\r
538          */\r
539         public CustomRenderer(){\r
540             super();\r
541             return;\r
542         }\r
543 \r
544         /**\r
545          * {@inheritDoc}\r
546          * @param list {@inheritDoc}\r
547          * @param value {@inheritDoc}\r
548          * @param index {@inheritDoc}\r
549          * @param isSelected {@inheritDoc}\r
550          * @param cellHasFocus {@inheritDoc}\r
551          * @return {@inheritDoc}\r
552          */\r
553         @Override\r
554         public Component getListCellRendererComponent(\r
555                 JList list,\r
556                 Object value,\r
557                 int index,\r
558                 boolean isSelected,\r
559                 boolean cellHasFocus ){\r
560             if(value instanceof JSeparator){\r
561                 return (JSeparator) value;\r
562             }\r
563 \r
564             JLabel superLabel =\r
565                     (JLabel) super.getListCellRendererComponent(list,\r
566                                                                 value,\r
567                                                                 index,\r
568                                                                 isSelected,\r
569                                                                 cellHasFocus);\r
570 \r
571             if(value instanceof RegexPattern){\r
572                 RegexPattern regexPattern = (RegexPattern) value;\r
573                 String text;\r
574                 if(regexPattern.isRegex()){\r
575                     text = "[R] " + regexPattern.getEditSource();\r
576                 }else{\r
577                     text = regexPattern.getEditSource();\r
578                 }\r
579                 text += regexPattern.getComment();\r
580 \r
581                 superLabel.setText(text);\r
582             }\r
583 \r
584             GUIUtils.addMargin(superLabel, 1, 4, 1, 4);\r
585 \r
586             return superLabel;\r
587         }\r
588     }\r
589 \r
590     /**\r
591      * コンボボックスの独自データモデル。\r
592      */\r
593     private static class CustomModel implements ComboBoxModel{\r
594 \r
595         private static final int HISTORY_MAX = 7;\r
596         private static final RegexPattern INITITEM =\r
597             new RegexPattern(\r
598                 "", false, RegexPattern.IGNORECASEFLAG | Pattern.DOTALL);\r
599         private static final List<RegexPattern> PREDEF_PATTERN_LIST =\r
600                 new LinkedList<RegexPattern>();\r
601 \r
602         static{\r
603             PREDEF_PATTERN_LIST.add(\r
604                     new RegexPattern("【[^】]*】",\r
605                                      true,\r
606                                      Pattern.DOTALL,\r
607                                      "     ※ 重要事項") );\r
608             PREDEF_PATTERN_LIST.add(\r
609                     new RegexPattern("[■●▼★□○▽☆〇◯∇]",\r
610                                      true,\r
611                                      Pattern.DOTALL,\r
612                                      "     ※ 議題") );\r
613             PREDEF_PATTERN_LIST.add(\r
614                     new RegexPattern("Jindolf",\r
615                                      false,\r
616                                      RegexPattern.IGNORECASEFLAG,\r
617                                      "     ※ 宣伝") );\r
618         }\r
619 \r
620         private final List<RegexPattern> history =\r
621                 new LinkedList<RegexPattern>();\r
622         private final JSeparator separator1st = new JSeparator();\r
623         private final JSeparator separator2nd = new JSeparator();\r
624         private Object selected;\r
625         private final EventListenerList listenerList =\r
626                 new EventListenerList();\r
627 \r
628         /**\r
629          * コンストラクタ。\r
630          */\r
631         public CustomModel(){\r
632             super();\r
633             return;\r
634         }\r
635 \r
636         /**\r
637          * {@inheritDoc}\r
638          * @return {@inheritDoc}\r
639          */\r
640         public Object getSelectedItem(){\r
641             return this.selected;\r
642         }\r
643 \r
644         /**\r
645          * {@inheritDoc}\r
646          * @param item {@inheritDoc}\r
647          */\r
648         public void setSelectedItem(Object item){\r
649             if(item instanceof JSeparator) return;\r
650             this.selected = item;\r
651             return;\r
652         }\r
653 \r
654         /**\r
655          * {@inheritDoc}\r
656          * @param index {@inheritDoc}\r
657          * @return {@inheritDoc}\r
658          */\r
659         public Object getElementAt(int index){\r
660             int historySize = this.history.size();\r
661 \r
662             if(index == 0){\r
663                 return INITITEM;\r
664             }\r
665             if(index == 1){\r
666                 return this.separator1st;\r
667             }\r
668             if(2 <= index && index <= 1 + historySize){\r
669                 return this.history.get(index - 2);\r
670             }\r
671             if(index == historySize + 2){\r
672                 return this.separator2nd;\r
673             }\r
674             if(historySize + 3 <= index){\r
675                 return PREDEF_PATTERN_LIST.get(index - 1\r
676                                                      - 1\r
677                                                      - historySize\r
678                                                      - 1 );\r
679             }\r
680 \r
681             return null;\r
682         }\r
683 \r
684         /**\r
685          * {@inheritDoc}\r
686          * @return {@inheritDoc}\r
687          */\r
688         public int getSize(){\r
689             int size = 1;\r
690             size += 1;         // first separator\r
691             size += this.history.size();\r
692             size += 1;         // second separator\r
693             size += PREDEF_PATTERN_LIST.size();\r
694             return size;\r
695         }\r
696 \r
697         /**\r
698          * {@inheritDoc}\r
699          * @param listener {@inheritDoc}\r
700          */\r
701         public void addListDataListener(ListDataListener listener){\r
702             this.listenerList.add(ListDataListener.class, listener);\r
703             return;\r
704         }\r
705 \r
706         /**\r
707          * {@inheritDoc}\r
708          * @param listener {@inheritDoc}\r
709          */\r
710         public void removeListDataListener(ListDataListener listener){\r
711             this.listenerList.remove(ListDataListener.class, listener);\r
712             return;\r
713         }\r
714 \r
715         /**\r
716          * 検索履歴ヒストリ追加。\r
717          * @param regexPattern 検索履歴\r
718          */\r
719         public void addHistory(RegexPattern regexPattern){\r
720             if(regexPattern == null) return;\r
721             if(regexPattern.equals(INITITEM)) return;\r
722             if(PREDEF_PATTERN_LIST.contains(regexPattern)) return;\r
723             if(this.history.contains(regexPattern)){\r
724                 this.history.remove(regexPattern);\r
725             }\r
726 \r
727             this.history.add(0, regexPattern);\r
728 \r
729             while(this.history.size() > HISTORY_MAX){\r
730                 this.history.remove(HISTORY_MAX);\r
731             }\r
732 \r
733             fire();\r
734 \r
735             return;\r
736         }\r
737 \r
738         /**\r
739          * プリセットでない検索ヒストリリストを返す。\r
740          * @return 検索ヒストリリスト\r
741          */\r
742         public List<RegexPattern> getOriginalHistoryList(){\r
743             return Collections.unmodifiableList(this.history);\r
744         }\r
745 \r
746         /**\r
747          * ヒストリ追加イベント発火。\r
748          */\r
749         private void fire(){\r
750             ListDataEvent event =\r
751                     new ListDataEvent(this,\r
752                                       ListDataEvent.CONTENTS_CHANGED,\r
753                                       0, getSize() - 1 );\r
754             ListDataListener[] listeners =\r
755                     this.listenerList.getListeners(ListDataListener.class);\r
756             for(ListDataListener listener : listeners){\r
757                 listener.contentsChanged(event);\r
758             }\r
759             return;\r
760         }\r
761     }\r
762 \r
763     // TODO ブックマーク機能との統合\r
764 }\r