• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Frequently used words (click to add to your profile)

javaandroidc++linuxc#objective-c誰得cocoaqtpythonrubywindowsphpgameguibathyscaphec翻訳omegat計画中(planning stage)frameworktwitterdombtronvb.nettestarduinodirectxpreviewerゲームエンジン

タイニー番組ナビゲータ本体


Commit MetaInfo

Revisão0448adb98e7524fa891f71c252f9b8f2a82122a8 (tree)
Hora2021-02-05 18:12:39
AutorMasahiko Kimura <mkimura@u01....>
CommiterMasahiko Kimura

Mensagem de Log

Ver.1.12.19 (2021/2/5)
1.[検索設定画面]放送日の指定をカレンダーで行えるようにする
2.[検索設定画面]チャンネル、ジャンル、サブジャンル、番組長、フラグを指定できるようにする
3.[検索設定画面]検索条件に名前を付けられるようにする
4.[検索設定画面][ツールバー]検索履歴のプルダウンで検索条件や検索回数、直近検索日時をツールチップ表示する
5.[リスト形式]キーワード検索ごとにリストのソート列やソート方向を指定できるようにする
6.[全体]内閣府の「国民の祝日」CSVを取得して祝日を赤色で識別できるようにする(デフォルトONで設定でOFF可能)

Mudança Sumário

Diff

--- a/TinyBannavi/src/tainavi/AbsKeywordDialog.java
+++ b/TinyBannavi/src/tainavi/AbsKeywordDialog.java
@@ -13,11 +13,9 @@ import java.util.regex.Matcher;
1313 import java.util.regex.Pattern;
1414 import java.util.regex.PatternSyntaxException;
1515
16-import javax.swing.AbstractAction;
1716 import javax.swing.DefaultComboBoxModel;
1817 import javax.swing.JButton;
1918 import javax.swing.JComboBox;
20-import javax.swing.JDialog;
2119 import javax.swing.JLabel;
2220 import javax.swing.JOptionPane;
2321 import javax.swing.JPanel;
@@ -46,7 +44,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
4644 /*******************************************************************************
4745 * 抽象メソッド
4846 ******************************************************************************/
49-
47+
5048 abstract void preview(SearchKey search);
5149
5250 /*******************************************************************************
@@ -54,46 +52,46 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
5452 ******************************************************************************/
5553
5654 // レイアウト関連
57-
55+
5856 private static final int PARTS_HEIGHT = 30;
5957 private static final int SEP_WIDTH = 10;
6058 private static final int SEP_HEIGHT = 10;
6159 private static final int SEP_HEIGHT_NARROW = 3;
6260
63- private static final int LABEL_WIDTH_S = 50;
64- private static final int LABEL_WIDTH_L = 75;
65-
61+ private static final int LABEL_WIDTH_S = 60;
62+ private static final int LABEL_WIDTH_L = 120;
63+
6664 private static final int TEXT_WIDTH = 300;
6765
6866 private static final int BUTTON_WIDTH = 75;
6967
7068 private static final int TARGET_WIDTH = 175;
7169 private static final int REGEX_WIDTH = 300;
72-
70+
7371 private static final int CHECKBOX_WIDTH = 200;
7472 private static final int CHECKLABEL_WIDTH = CHECKBOX_WIDTH-25;
75-
73+
7674 private static final int CONDITION_WIDTH = 220;
77-
75+
7876 private static final int SELECTION_WIDTH = 150;
7977
8078 private static final int TABLE_WIDTH = REGEX_WIDTH+TARGET_WIDTH*2+SEP_WIDTH*2; // スクロールバー分
8179 private static final int TABLEPANE_WIDTH = TABLE_WIDTH+25; // スクロールバー分
8280 private static final int TABLE_HEIGHT = 160;
83-
81+
8482 private static final int PANEL_WIDTH = TABLEPANE_WIDTH+BUTTON_WIDTH+SEP_WIDTH*3;
8583
8684 // カラム関連
87-
85+
8886 private static enum KDColumn {
8987 TARGET ("", TARGET_WIDTH+SEP_WIDTH/2),
9088 REGEX ("", REGEX_WIDTH+SEP_WIDTH),
9189 CONTAIN ("", TARGET_WIDTH+SEP_WIDTH/2)
9290 ;
93-
91+
9492 private String name;
9593 private int iniWidth;
96-
94+
9795 private KDColumn(String name, int iniWidth) {
9896 this.name = name;
9997 this.iniWidth = iniWidth;
@@ -106,18 +104,18 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
106104 public int getIniWidth() {
107105 return iniWidth;
108106 }
109-
107+
110108 public int getColumn() {
111109 return ordinal();
112110 }
113111 }
114-
112+
115113 /*******************************************************************************
116114 * 部品
117115 ******************************************************************************/
118-
116+
119117 // コンポーネント
120-
118+
121119 private JPanel jPanel = null;
122120 private JLabel jLabel_label = null;
123121 private JTextField jTextField_label = null;
@@ -143,22 +141,27 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
143141 private JButton jButton_up = null;
144142 private JButton jButton_down = null;
145143
144+ private JLabel jLabel_sortBy = null;
145+ private JComboBox<String> jComboBox_sortBy = null;
146+ private JLabel jLabel_sortDir = null;
147+ private JComboBox<String> jComboBox_sortDir = null;
148+
146149 // コンポーネント以外
147-
150+
148151 private String windowTitle = "";
149152 private SearchProgram xKeys = null;
150153 private SearchGroupList xGroups = null;
151154 private SearchKey xKey = null;
152-
155+
153156 private boolean reg = false;
154-
157+
155158 private int selectedRow = -1;
156-
159+
157160 private ArrayList<TargetId> target_items = new ArrayList<TargetId>();
158- private ArrayList<String> contain_items = new ArrayList<String>();
159- private ArrayList<String> condition_items = new ArrayList<String>();
161+ private ArrayList<String> contain_items = new ArrayList<String>();
162+ private ArrayList<String> condition_items = new ArrayList<String>();
160163 private ArrayList<String> infection_items = new ArrayList<String>();
161- private ArrayList<String> okiniiri_items = new ArrayList<String>();
164+ private ArrayList<String> okiniiri_items = new ArrayList<String>();
162165 public void clean_target_items() { target_items.clear(); }
163166 public void add_target_item(TargetId ti) { target_items.add(ti); }
164167 public void clean_contain_items() { contain_items.clear(); }
@@ -169,26 +172,26 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
169172 public void add_infection_item(String s) { infection_items.add(s); }
170173 public void clean_okiniiri_items() { okiniiri_items.clear(); }
171174 public void add_okiniiri_item(String s) { okiniiri_items.add(s); }
172-
175+
173176 // 分類的にここでないような
174-
177+
175178 public String getNewLabel() { return jTextField_label.getText(); }
176179 public String getNewGroup() { return (String)jComboBox_group.getSelectedItem(); }
177180
178181 /*******************************************************************************
179182 * コンストラクタ
180183 ******************************************************************************/
181-
184+
182185 public AbsKeywordDialog() {
183-
186+
184187 super();
185188
186189 //
187190 reg = false;
188-
191+
189192 //
190193 setItems();
191-
194+
192195 //
193196 this.setModal(true);
194197 this.setContentPane(getJPanel());
@@ -204,28 +207,28 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
204207 //
205208 this.setTitle(windowTitle);
206209 }
207-
208-
210+
211+
209212 /*******************************************************************************
210213 * アクション
211214 ******************************************************************************/
212-
215+
213216 // 公開メソッド
214-
217+
215218 /**
216219 * 検索条件が登録が実行されたかな?
217220 */
218- public boolean isRegistered() { return reg; }
219-
221+ public boolean isRegistered() { return reg; }
222+
220223 /***************************************
221224 * 新規オープン2種+1種
222225 **************************************/
223-
226+
224227 // キーワード検索管理としてのオープン(右クリックメニューから新規)
225228 public void open(SearchProgram sKeys, SearchGroupList gList, ProgDetailList tvd) {
226-
229+
227230 SearchKey sK = new SearchKey();
228-
231+
229232 // タイトル
230233 {
231234 sK.setCondition("0");
@@ -233,7 +236,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
233236 sK.alContain.add("0");
234237 sK.alKeyword.add(tvd.title);
235238 }
236-
239+
237240 // 放送局
238241 {
239242 sK.setCondition("0");
@@ -241,20 +244,20 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
241244 sK.alContain.add("0");
242245 sK.alKeyword.add(tvd.center);
243246 }
244-
247+
245248 sK.setCaseSensitive(false);
246249 sK.setShowInStandby(true);
247-
250+
248251 open(tvd.title,sKeys,gList,sK);
249252 }
250-
253+
251254 // キーワード検索管理としてのオープン(ツールバーから新規)
252255 public void open(String label, SearchProgram sKeys, SearchGroupList gList, SearchKey sK) {
253-
256+
254257 xKeys = sKeys;
255258 xGroups = gList;
256259 xKey = null;
257-
260+
258261 // テーブルの作り直し
259262 jComboBox_target.setSelectedIndex(0);
260263 jComboBox_contain.setSelectedIndex(0);
@@ -262,21 +265,28 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
262265 for (int i=jTable_keywords.getRowCount()-1; i>=0; i--) {
263266 jTable_keywords.getRowItemList().remove(i);
264267 }
265- //
266- for (int i=0; i<sK.alTarget.size(); i++) {
268+
269+ String [] ts = sK.getTarget().split("\t");
270+ String [] rs = sK.getKeyword().split("\t");
271+ String [] cs = sK.getContain().split("\t");
272+
273+ for (int n=0; n<ts.length; n++) {
267274 KDItem data = new KDItem();
268- data.target = sK.alTarget.get(i);
269- data.regex = sK.alKeyword.get(i);
270- data.contain = contain_items.get(Integer.valueOf(sK.alContain.get(i)));
275+ data.target = TargetId.getTargetId(ts[n]);
276+ data.regex = rs[n];
277+ if (data.target == TargetId.LENGTH)
278+ data.regex += "分以上である";
279+ data.contain = contain_items.get(Integer.valueOf(cs[n]));
271280 data.fireChanged();
281+
272282 jTable_keywords.getRowItemList().add(data);
273283 }
274284 jTable_keywords.fireChanged();
275-
285+
276286 // ラベル
277287 jTextField_label.setText(label);
278288 jTextField_label.setCaretPosition(0);
279-
289+
280290 // グループリスト
281291 if ( xGroups != null && xGroups.size() != 0 ) {
282292 jComboBox_group.setEnabled(true);
@@ -291,11 +301,14 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
291301 jCheckBox_caseSensitive.setSelected(sK.getCaseSensitive());
292302 jCheckBox_showInStandby.setSelected(sK.getShowInStandby());
293303 jCheckBox_showInStandby.setEnabled(true);
294-
304+
305+ // ソート順
306+ setSortByAndDir(sK.getSortBy());
307+
295308 //
296309 jButton_label.setText("登録");
297310 }
298-
311+
299312 // 延長警告管理としてのオープン(新規)
300313 public void open(String title, String center, boolean isInfection, SearchProgram sKeys) {
301314 //
@@ -310,7 +323,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
310323 jTextField_label.setText("○"+title);
311324 }
312325 jTextField_label.setCaretPosition(0);
313-
326+
314327 jComboBox_regex.addItem("");
315328 jComboBox_target.setSelectedIndex(0);
316329 jComboBox_contain.setSelectedIndex(0);
@@ -325,7 +338,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
325338 d1.contain = (String) jComboBox_contain.getItemAt(0);
326339 d1.fireChanged();
327340 jTable_keywords.getRowItemList().add(d1);
328-
341+
329342 KDItem d2 = new KDItem();
330343 d2.target = TargetId.CHANNEL;
331344 d2.regex = center;
@@ -338,29 +351,35 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
338351 jComboBox_infection.setSelectedItem(infection_items.get(1));
339352 }
340353 jTable_keywords.getRowItemList().add(d2);
341-
354+
342355 jTable_keywords.fireChanged();
343-
356+
344357 // オプション
345358 jComboBox_okiniiri.setEnabled(false);
346359 jCheckBox_caseSensitive.setSelected(false);
347360 jCheckBox_showInStandby.setSelected(false);
348361 jCheckBox_showInStandby.setEnabled(false);
349-
362+
363+ // ソート順
364+ jLabel_sortBy.setVisible(false);
365+ jComboBox_sortBy.setVisible(false);
366+ jLabel_sortDir.setVisible(false);
367+ jComboBox_sortDir.setVisible(false);
368+
350369 //
351370 jButton_label.setText("登録");
352371 }
353-
372+
354373 /***************************************
355374 * 新規オープン1種+1種+α
356375 **************************************/
357-
376+
358377 // キーワード検索管理としてのオープン(更新)
359378 public void reopen(String label, SearchProgram sKeys) {
360-
379+
361380 xKeys = sKeys;
362381 xGroups = null;
363-
382+
364383 for (SearchKey k : xKeys.getSearchKeys()) {
365384 if (k.getLabel().equals(label)) {
366385 // 操作対象をみつけた
@@ -369,13 +388,13 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
369388 }
370389 }
371390 }
372-
391+
373392 // 延長警告管理としてのオープン(更新)
374393 public void reopen(String label, ExtProgram sKeys) {
375-
394+
376395 xKeys = sKeys;
377396 xGroups = null;
378-
397+
379398 for (SearchKey k : sKeys.getSearchKeys()) {
380399 if (k.getLabel().equals(label)) {
381400 // 操作対象をみつけた
@@ -388,11 +407,11 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
388407 }
389408 }
390409 }
391-
410+
392411 private void _reopen(SearchProgram sKeys, SearchKey sKey) {
393-
412+
394413 xKey = sKey;
395-
414+
396415 jComboBox_regex.addItem("");
397416 jComboBox_target.setSelectedIndex(0);
398417 jComboBox_contain.setSelectedIndex(0);
@@ -400,9 +419,9 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
400419 for (int i=jTable_keywords.getRowCount()-1; i>=0; i--) {
401420 jTable_keywords.getRowItemList().remove(i);
402421 }
403-
422+
404423 jComboBox_condition.setSelectedIndex(Integer.valueOf(xKey.getCondition()));
405-
424+
406425 Matcher ma = Pattern.compile("(.*?)\t").matcher(xKey.getTarget());
407426 Matcher mb = Pattern.compile("(.*?)\t").matcher(xKey.getKeyword());
408427 Matcher mc = Pattern.compile("(.*?)\t").matcher(xKey.getContain());
@@ -416,9 +435,9 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
416435 data.fireChanged();
417436 jTable_keywords.getRowItemList().add(data);
418437 }
419-
438+
420439 jComboBox_okiniiri.setSelectedItem(xKey.getOkiniiri());
421-
440+
422441 jCheckBox_caseSensitive.setSelected(xKey.getCaseSensitive());
423442 if ( sKeys instanceof ExtProgram ) {
424443 jComboBox_okiniiri.setEnabled(false);
@@ -430,33 +449,36 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
430449 jCheckBox_showInStandby.setSelected(xKey.getShowInStandby());
431450 jCheckBox_showInStandby.setEnabled(true);
432451 }
433-
452+
434453 jTextField_label.setText(xKey.getLabel());
435454 jTextField_label.setCaretPosition(0);
436455
456+ // ソート順を解析する
457+ setSortByAndDir(xKey.getSortBy());
458+
437459 //
438460 jTable_keywords.fireChanged();
439-
461+
440462 //
441463 jButton_label.setText("更新");
442464 }
443-
465+
444466 /**
445467 * コンパイル部分を分離してみました
446468 */
447469 protected SearchKey skCompile() {
448-
470+
449471 SearchKey sk = new SearchKey();
450-
472+
451473 String tStr = "";
452474 String rStr = "";
453475 String cStr = "";
454476 for (int row=0; row<jTable_keywords.getRowCount(); row++) {
455-
477+
456478 KDItem c = jTable_keywords.getRowItemList().get(row);
457479 TargetId ti = c.target;
458480 tStr += ti.getId()+"\t";
459-
481+
460482 if ( ti.getUseRegexpr() ) {
461483 try {
462484 Pattern.compile(TraceProgram.replacePop(c.regex));
@@ -467,7 +489,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
467489 }
468490 }
469491 rStr += c.regex+"\t";
470-
492+
471493 for (int i=0; i<contain_items.size(); i++) {
472494 if (contain_items.get(i).equals(c.contain)) {
473495 cStr += i+"\t";
@@ -478,7 +500,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
478500 sk.setTarget(tStr); // compile後は順番が変わるので残すことにする
479501 sk.setKeyword(rStr); // 同上
480502 sk.setContain(cStr); // 同上
481-
503+
482504 for (int i=0; i<condition_items.size(); i++) {
483505 if (condition_items.get(i).equals((String) jComboBox_condition.getSelectedItem())) {
484506 sk.setCondition(String.valueOf(i));
@@ -491,14 +513,17 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
491513 break;
492514 }
493515 }
494-
516+
495517 sk.setLabel(jTextField_label.getText());
496518 sk.setOkiniiri((String) jComboBox_okiniiri.getSelectedItem());
497519 sk.setCaseSensitive(jCheckBox_caseSensitive.isSelected());
498520 sk.setShowInStandby(jCheckBox_showInStandby.isSelected());
499521
522+ // ソート順
523+ sk.setSortBy(getSortByAndDir());
524+
500525 new SearchProgram().compile(sk);
501-
526+
502527 return sk;
503528 }
504529
@@ -509,11 +534,11 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
509534 private void showExprFmtWarn(String expr) {
510535 JOptionPane.showMessageDialog(this, "正規表現の文法に則っていません: "+expr, "エラー", JOptionPane.ERROR_MESSAGE);
511536 }
512-
537+
513538 /*******************************************************************************
514539 * リスナー
515540 ******************************************************************************/
516-
541+
517542 /**
518543 * 条件を保存する
519544 */
@@ -526,12 +551,12 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
526551 }
527552 }
528553 };
529-
554+
530555 private boolean addToSearchKeyList() {
531556 if (jTextField_label.getText().equals("") || jTable_keywords.getRowCount()<=0) {
532557 return false;
533558 }
534-
559+
535560 // 重複登録を許さない
536561 for ( SearchKey k : xKeys.getSearchKeys() ) {
537562 if ( k != xKey && k.getLabel().equals(getNewLabel()) ) {
@@ -539,12 +564,12 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
539564 return false;
540565 }
541566 }
542-
567+
543568 SearchKey sk = skCompile();
544569 if (sk == null) {
545570 return false;
546571 }
547-
572+
548573 // 検索キーワードを保存する
549574 if ( xKey == null ) {
550575 xKeys.add(sk);
@@ -553,17 +578,17 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
553578 xKeys.replace(xKey, sk);
554579 }
555580 reg = xKeys.save();
556-
581+
557582 // グループに登録するかも
558583 String grpName = (String)jComboBox_group.getSelectedItem();
559584 if ( grpName != null && grpName.length() > 0 ) {
560585 xGroups.add(grpName,sk.getLabel());
561586 xGroups.save();
562587 }
563-
588+
564589 return reg;
565590 }
566-
591+
567592 /**
568593 * キャンセルしたい
569594 */
@@ -573,25 +598,25 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
573598 doCancel();
574599 }
575600 };
576-
601+
577602 @Override
578603 protected void doCancel() {
579604 dispose();
580605 }
581-
606+
582607 /**
583608 * プレビューしたい
584609 */
585610 private final ActionListener al_preview = new ActionListener() {
586611 @Override
587612 public void actionPerformed(ActionEvent e) {
588- SearchKey sk = skCompile();
613+ SearchKey sk = skCompile();
589614 if (jTable_keywords.getRowCount() > 0 && sk != null) {
590615 preview(sk);
591616 }
592617 }
593618 };
594-
619+
595620 /**
596621 * 条件選択コンボボックスが選択された
597622 */
@@ -604,7 +629,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
604629 }
605630 //
606631 TargetId target = (TargetId) jComboBox_target.getSelectedItem();
607-
632+
608633 jComboBox_regex.setEditable(target.getUseRegexpr());
609634 jComboBox_regex.setEnabled(target.getUseKeyword());
610635 jComboBox_regex.removeAllItems();
@@ -642,8 +667,8 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
642667 }
643668 }
644669 };
645-
646-
670+
671+
647672 /**
648673 * 条件を追加する
649674 */
@@ -652,12 +677,12 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
652677 public void actionPerformed(ActionEvent e) {
653678 TargetId ti = (TargetId) jComboBox_target.getSelectedItem();
654679 String re = (String) jComboBox_regex.getSelectedItem();
655-
680+
656681 if ( re == null || ti.getUseKeyword() && re.length() == 0 ) {
657682 // キーワードが必要なのに入力されていなければNG
658683 return;
659684 }
660-
685+
661686 KDItem data = new KDItem();
662687 data.target = ti;
663688 data.regex = re;
@@ -670,19 +695,19 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
670695 jTable_keywords.getRowItemList().add(selectedRow+1, data);
671696 }
672697 jTable_keywords.fireChanged();
673-
698+
674699 if (jComboBox_regex.getItemCount() <= 1) {
675700 jComboBox_regex.removeAllItems();
676701 jComboBox_regex.addItem("");
677702 }
678-
703+
679704 selectedRow = -1;
680705 jButton_replace.setEnabled(false);
681706 jButton_remove.setEnabled(false);
682707 }
683708 };
684-
685-
709+
710+
686711 /**
687712 * 条件を置換する
688713 */
@@ -692,34 +717,34 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
692717 if ( selectedRow == -1 ) {
693718 return;
694719 }
695-
720+
696721 TargetId ti = (TargetId) jComboBox_target.getSelectedItem();
697722 String re = (String) jComboBox_regex.getSelectedItem();
698-
723+
699724 if ( ti.getUseKeyword() && re.length() == 0 ) {
700725 // キーワードが必要なのに入力されていなければNG
701726 return;
702727 }
703-
728+
704729 KDItem data = jTable_keywords.getRowItemList().get(selectedRow);
705730 data.target = ti;
706731 data.regex = re;
707732 data.contain = (String) jComboBox_contain.getSelectedItem();
708733 data.fireChanged();
709734 jTable_keywords.fireChanged();
710-
735+
711736 if (jComboBox_regex.getItemCount() <= 1) {
712737 jComboBox_regex.removeAllItems();
713738 jComboBox_regex.addItem("");
714739 }
715-
740+
716741 selectedRow = -1;
717742 jButton_replace.setEnabled(false);
718743 jButton_remove.setEnabled(false);
719744 }
720745 };
721-
722-
746+
747+
723748 /**
724749 * 条件を削除する(コンボボックスに戻す)
725750 */
@@ -729,18 +754,18 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
729754 if ( selectedRow == -1 ) {
730755 return;
731756 }
732-
757+
733758 jTable_keywords.getRowItemList().remove(selectedRow);
734759 jTable_keywords.fireChanged();
735-
760+
736761 selectedRow = -1;
737762 jButton_replace.setEnabled(false);
738763 jButton_remove.setEnabled(false);
739764 }
740765 };
741-
766+
742767 /**
743- *
768+ *
744769 */
745770 private final ActionListener al_upTarget = new ActionListener() {
746771 @Override
@@ -753,9 +778,9 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
753778 }
754779 }
755780 };
756-
781+
757782 /**
758- *
783+ *
759784 */
760785 private final ActionListener al_downTarget = new ActionListener() {
761786 @Override
@@ -779,15 +804,15 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
779804 if (row == -1) {
780805 return;
781806 }
782-
807+
783808 KDItem c = jTable_keywords.getRowItemList().get(row);
784-
809+
785810 jComboBox_target.setSelectedItem(c.target);
786-
811+
787812 if ( ! c.target.getUseRegexpr() && c.target.getUseKeyword() ) {
788813 jComboBox_regex.setSelectedItem(null);
789814 jComboBox_regex.setSelectedItem(c.regex);
790-
815+
791816 // 旧版互換
792817 String s = (String) jComboBox_regex.getSelectedItem();
793818 if ( c.target == TargetId.SUBGENRE ) {
@@ -808,59 +833,59 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
808833 jComboBox_regex.addItem(c.regex);
809834 }
810835 }
811-
836+
812837 jComboBox_contain.setSelectedItem(c.contain);
813-
838+
814839 selectedRow = row;
815840 jButton_replace.setEnabled(true);
816841 jButton_remove.setEnabled(true);
817842 }
818843 };
819-
820-
844+
845+
821846 /*******************************************************************************
822847 * コンポーネント
823848 ******************************************************************************/
824-
849+
825850 private JPanel getJPanel() {
826851 if (jPanel == null) {
827852 jPanel = new JPanel();
828853
829854 jPanel.setLayout(new SpringLayout());
830-
855+
831856 int y = SEP_HEIGHT;
832857 int x = SEP_WIDTH;
833-
858+
834859 x = SEP_WIDTH;
835860 CommonSwingUtils.putComponentOn(jPanel, getJLabel_label("設定名"), LABEL_WIDTH_S, PARTS_HEIGHT, x, y);
836861 CommonSwingUtils.putComponentOn(jPanel, getJTextField_label(), TEXT_WIDTH, PARTS_HEIGHT, x+=LABEL_WIDTH_S, y);
837-
862+
838863 int xz = PANEL_WIDTH-(BUTTON_WIDTH+SEP_WIDTH);
839864 CommonSwingUtils.putComponentOn(jPanel, getJButton_label("登録"), BUTTON_WIDTH, PARTS_HEIGHT, xz, y);
840865 CommonSwingUtils.putComponentOn(jPanel, getJButton_cancel("キャンセル"), BUTTON_WIDTH, PARTS_HEIGHT, xz, y+(SEP_HEIGHT_NARROW+PARTS_HEIGHT));
841866 CommonSwingUtils.putComponentOn(jPanel, getJButton_preview("プレビュー"), BUTTON_WIDTH, PARTS_HEIGHT, xz, y+(SEP_HEIGHT_NARROW+PARTS_HEIGHT)*2);
842-
867+
843868 y += PARTS_HEIGHT+SEP_HEIGHT;
844869 x = SEP_WIDTH;
845870 CommonSwingUtils.putComponentOn(jPanel, getJComboBox_target(), TARGET_WIDTH, PARTS_HEIGHT, x, y);
846871 CommonSwingUtils.putComponentOn(jPanel, getJTextField_regex(), REGEX_WIDTH, PARTS_HEIGHT, x+=TARGET_WIDTH+SEP_WIDTH, y);
847872 CommonSwingUtils.putComponentOn(jPanel, getJComboBox_contain(), TARGET_WIDTH, PARTS_HEIGHT, x+=REGEX_WIDTH+SEP_WIDTH, y);
848-
873+
849874 y += PARTS_HEIGHT+SEP_HEIGHT_NARROW;
850875 CommonSwingUtils.putComponentOn(jPanel, getJCheckBox_caseSensitive("文字列比較は完全一致で",CHECKLABEL_WIDTH,false), CHECKBOX_WIDTH, PARTS_HEIGHT, x, y);
851-
876+
852877 x = SEP_HEIGHT+TABLE_WIDTH/2;
853878 CommonSwingUtils.putComponentOn(jPanel, getJButton_add("↓追加"), BUTTON_WIDTH, PARTS_HEIGHT, x-(BUTTON_WIDTH+SEP_WIDTH*2) , y);
854879 CommonSwingUtils.putComponentOn(jPanel, getJButton_replace("↓置換"), BUTTON_WIDTH, PARTS_HEIGHT, x+(SEP_WIDTH*2), y);
855-
856-
880+
881+
857882 y += PARTS_HEIGHT+SEP_HEIGHT_NARROW;
858883 CommonSwingUtils.putComponentOn(jPanel, getJComboBox_condition(), CONDITION_WIDTH, PARTS_HEIGHT, SEP_WIDTH, y);
859884 CommonSwingUtils.putComponentOn(jPanel, getJComboBox_infection(), CONDITION_WIDTH, PARTS_HEIGHT, TABLE_WIDTH-CONDITION_WIDTH+SEP_WIDTH, y);
860-
885+
861886 y += PARTS_HEIGHT+SEP_HEIGHT_NARROW;
862887 CommonSwingUtils.putComponentOn(jPanel, getJScrollPane_keywords(), TABLEPANE_WIDTH, TABLE_HEIGHT, SEP_WIDTH, y);
863-
888+
864889 int yz = y + (TABLE_HEIGHT/2-(PARTS_HEIGHT+SEP_HEIGHT/2));
865890 CommonSwingUtils.putComponentOn(jPanel, getJButton_up("↑"), BUTTON_WIDTH, PARTS_HEIGHT, xz, yz);
866891 CommonSwingUtils.putComponentOn(jPanel, getJButton_down("↓"), BUTTON_WIDTH, PARTS_HEIGHT, xz, yz+=SEP_HEIGHT+PARTS_HEIGHT);
@@ -868,20 +893,27 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
868893
869894 y += TABLE_HEIGHT+SEP_HEIGHT;
870895 x = SEP_WIDTH;
871- CommonSwingUtils.putComponentOn(jPanel, getJLabel_okiniiri("お気に入り度"), LABEL_WIDTH_L, PARTS_HEIGHT, x, y);
896+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_sortBy("リストのソート列"), LABEL_WIDTH_L, PARTS_HEIGHT, x, y);
897+ CommonSwingUtils.putComponentOn(jPanel, getJComboBox_sortBy(), SELECTION_WIDTH, PARTS_HEIGHT, x+=LABEL_WIDTH_L+SEP_WIDTH, y);
898+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_okiniiri("お気に入り度"), LABEL_WIDTH_L, PARTS_HEIGHT, x+=SELECTION_WIDTH+SEP_WIDTH, y);
872899 CommonSwingUtils.putComponentOn(jPanel, getJComboBox_okiniiri(), SELECTION_WIDTH, PARTS_HEIGHT, x+=LABEL_WIDTH_L+SEP_WIDTH, y);
900+
901+ y += PARTS_HEIGHT+SEP_HEIGHT;
902+ x = SEP_WIDTH;
903+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_sortDir("同ソート方向"), LABEL_WIDTH_L, PARTS_HEIGHT, x, y);
904+ CommonSwingUtils.putComponentOn(jPanel, getJComboBox_sortDir(), SELECTION_WIDTH, PARTS_HEIGHT, x+=LABEL_WIDTH_L+SEP_WIDTH, y);
873905 CommonSwingUtils.putComponentOn(jPanel, getJLabel_group("グループ"), LABEL_WIDTH_L, PARTS_HEIGHT, x+=SELECTION_WIDTH+SEP_WIDTH, y);
874906 CommonSwingUtils.putComponentOn(jPanel, getJComboBox_group(), SELECTION_WIDTH, PARTS_HEIGHT, x+=LABEL_WIDTH_L+SEP_WIDTH, y);
875-
907+
876908 CommonSwingUtils.putComponentOn(jPanel, getJCheckBox_showInStandby("予約待機に表示する",CHECKLABEL_WIDTH,true), CHECKBOX_WIDTH, PARTS_HEIGHT, PANEL_WIDTH-CHECKBOX_WIDTH-SEP_WIDTH, y);
877-
878- y += PARTS_HEIGHT+SEP_HEIGHT*2;
909+
910+ y += PARTS_HEIGHT+SEP_HEIGHT;
879911
880912 jPanel.setPreferredSize(new Dimension(PANEL_WIDTH, y));
881913 }
882914 return jPanel;
883915 }
884-
916+
885917 //
886918 private JLabel getJLabel_label(String s) {
887919 if (jLabel_label == null) {
@@ -889,7 +921,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
889921 }
890922 return(jLabel_label);
891923 }
892-
924+
893925 //
894926 private JTextField getJTextField_label() {
895927 if (jTextField_label == null) {
@@ -897,57 +929,57 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
897929 }
898930 return(jTextField_label);
899931 }
900-
932+
901933 //
902934 private JButton getJButton_label(String s) {
903935 if (jButton_label == null) {
904936 jButton_label = new JButton();
905937 jButton_label.setText(s);
906-
938+
907939 jButton_label.addActionListener(al_save);
908940 }
909941 return(jButton_label);
910942 }
911-
943+
912944 //
913945 private JButton getJButton_cancel(String s) {
914946 if (jButton_cancel == null) {
915947 jButton_cancel = new JButton();
916948 jButton_cancel.setText(s);
917-
949+
918950 jButton_cancel.addActionListener(al_cancel);
919951 }
920952 return jButton_cancel;
921953 }
922-
954+
923955 //
924956 private JButton getJButton_preview(String s) {
925957 if (jButton_preview == null) {
926958 jButton_preview = new JButton();
927959 jButton_preview.setText(s);
928-
960+
929961 jButton_preview.addActionListener(al_preview);
930962 }
931963 return jButton_preview;
932964 }
933-
934-
965+
966+
935967 //
936968 private JComboBox getJComboBox_target() {
937969 if (jComboBox_target == null) {
938970 jComboBox_target = new JComboBox();
939-
971+
940972 DefaultComboBoxModel aModel = new DefaultComboBoxModel();
941973 jComboBox_target.setModel(aModel);
942974 for (TargetId k : target_items) {
943975 aModel.addElement(k);
944976 }
945-
977+
946978 jComboBox_target.addItemListener(il_targetChanged);
947979 }
948980 return(jComboBox_target);
949981 }
950-
982+
951983 //
952984 private JComboBox getJTextField_regex() {
953985 if (jComboBox_regex == null) {
@@ -956,12 +988,12 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
956988 }
957989 return(jComboBox_regex);
958990 }
959-
991+
960992 //
961993 private JComboBox getJComboBox_contain() {
962994 if (jComboBox_contain == null) {
963995 jComboBox_contain = new JComboBox();
964-
996+
965997 DefaultComboBoxModel aModel = new DefaultComboBoxModel();
966998 jComboBox_contain.setModel(aModel);
967999 for (String k : contain_items) {
@@ -970,12 +1002,12 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
9701002 }
9711003 return(jComboBox_contain);
9721004 }
973-
1005+
9741006 //
9751007 private JComboBox getJComboBox_condition() {
9761008 if (jComboBox_condition == null) {
9771009 jComboBox_condition = new JComboBox();
978-
1010+
9791011 DefaultComboBoxModel aModel = new DefaultComboBoxModel();
9801012 jComboBox_condition.setModel(aModel);
9811013 for (String k : condition_items) {
@@ -984,12 +1016,12 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
9841016 }
9851017 return(jComboBox_condition);
9861018 }
987-
1019+
9881020 //
9891021 private JComboBox getJComboBox_infection() {
9901022 if (jComboBox_infection == null) {
9911023 jComboBox_infection = new JComboBox();
992-
1024+
9931025 if (infection_items.size() > 0) {
9941026 DefaultComboBoxModel aModel = new DefaultComboBoxModel();
9951027 jComboBox_infection.setModel(aModel);
@@ -1003,7 +1035,57 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
10031035 }
10041036 return(jComboBox_infection);
10051037 }
1006-
1038+
1039+ /*
1040+ * 「リストのソート列」ラベル
1041+ */
1042+ private JLabel getJLabel_sortBy(String s) {
1043+ if (jLabel_sortBy == null) {
1044+ jLabel_sortBy = new JLabel(s, SwingConstants.RIGHT);
1045+ }
1046+ return(jLabel_sortBy);
1047+ }
1048+
1049+ /*
1050+ * 「ソート列」コンボボックス
1051+ */
1052+ private JComboBox<String> getJComboBox_sortBy() {
1053+ if (jComboBox_sortBy == null) {
1054+ jComboBox_sortBy = new JComboBox<String>();
1055+
1056+ jComboBox_sortBy.addItem(" ");
1057+ for (AbsListedView.ListedColumn col : AbsListedView.ListedColumn.values()){
1058+ jComboBox_sortBy.addItem(col.getName());
1059+ }
1060+ }
1061+ return(jComboBox_sortBy);
1062+ }
1063+
1064+ /*
1065+ * 「同ソート方向」ラベル
1066+ *
1067+ */
1068+ private JLabel getJLabel_sortDir(String s) {
1069+ if (jLabel_sortDir == null) {
1070+ jLabel_sortDir = new JLabel(s, SwingConstants.RIGHT);
1071+ }
1072+ return(jLabel_sortDir);
1073+ }
1074+
1075+ /*
1076+ * 「ソート方向」コンボボックス
1077+ */
1078+ private JComboBox<String> getJComboBox_sortDir() {
1079+ if (jComboBox_sortDir == null) {
1080+ jComboBox_sortDir = new JComboBox<String>();
1081+
1082+ jComboBox_sortDir.addItem(" ");
1083+ jComboBox_sortDir.addItem("昇順");
1084+ jComboBox_sortDir.addItem("降順");
1085+ }
1086+ return(jComboBox_sortDir);
1087+ }
1088+
10071089 //
10081090 private JLabel getJLabel_okiniiri(String s) {
10091091 if (jLabel_okiniiri == null) {
@@ -1014,7 +1096,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
10141096 private JComboBox getJComboBox_okiniiri() {
10151097 if (jComboBox_okiniiri == null) {
10161098 jComboBox_okiniiri = new JComboBox();
1017-
1099+
10181100 DefaultComboBoxModel aModel = new DefaultComboBoxModel();
10191101 jComboBox_okiniiri.setModel(aModel);
10201102 for (String k : okiniiri_items) {
@@ -1024,7 +1106,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
10241106 return(jComboBox_okiniiri);
10251107 }
10261108
1027-
1109+
10281110 //
10291111 private JLabel getJLabel_group(String s) {
10301112 if (jLabel_group == null) {
@@ -1035,12 +1117,12 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
10351117 private JComboBox getJComboBox_group() {
10361118 if (jComboBox_group == null) {
10371119 jComboBox_group = new JComboBox();
1038-
1120+
10391121 jComboBox_group.setEnabled(false);
10401122 }
10411123 return(jComboBox_group);
10421124 }
1043-
1125+
10441126 //
10451127 private JCheckBoxPanel getJCheckBox_caseSensitive(String s,int labelWidth, boolean b) {
10461128 if (jCheckBox_caseSensitive == null) {
@@ -1049,7 +1131,7 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
10491131 }
10501132 return(jCheckBox_caseSensitive);
10511133 }
1052-
1134+
10531135 //
10541136 private JCheckBoxPanel getJCheckBox_showInStandby(String s,int labelWidth, boolean b) {
10551137 if (jCheckBox_showInStandby == null) {
@@ -1058,19 +1140,19 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
10581140 }
10591141 return(jCheckBox_showInStandby);
10601142 }
1061-
1143+
10621144 // 条件追加のボタン
10631145 private JButton getJButton_add(String s) {
10641146 if (jButton_add == null) {
10651147 jButton_add = new JButton();
10661148 jButton_add.setText(s);
10671149 jButton_add.setEnabled(true);
1068-
1150+
10691151 jButton_add.addActionListener(al_addTarget);
10701152 }
10711153 return(jButton_add);
10721154 }
1073-
1155+
10741156 // 条件置換のボタン
10751157 private JButton getJButton_replace(String s) {
10761158 if (jButton_replace == null) {
@@ -1078,12 +1160,12 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
10781160 jButton_replace.setText(s);
10791161 jButton_replace.setEnabled(false);
10801162 jButton_replace.setForeground(Color.BLUE);
1081-
1163+
10821164 jButton_replace.addActionListener(al_replaceTarget);
10831165 }
10841166 return(jButton_replace);
10851167 }
1086-
1168+
10871169 // 条件削除のボタン
10881170 private JButton getJButton_remove(String s) {
10891171 if (jButton_remove == null) {
@@ -1091,32 +1173,32 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
10911173 jButton_remove.setText(s);
10921174 jButton_remove.setEnabled(false);
10931175 jButton_remove.setForeground(Color.RED);
1094-
1176+
10951177 jButton_remove.addActionListener(al_removeTarget);
10961178 }
10971179 return(jButton_remove);
10981180 }
1099-
1181+
11001182 private JButton getJButton_up(String s) {
11011183 if (jButton_up == null) {
11021184 jButton_up = new JButton();
11031185 jButton_up.setText(s);
1104-
1186+
11051187 jButton_up.addActionListener(al_upTarget);
11061188 }
11071189 return(jButton_up);
11081190 }
1109-
1191+
11101192 private JButton getJButton_down(String s) {
11111193 if (jButton_down == null) {
11121194 jButton_down = new JButton();
11131195 jButton_down.setText(s);
1114-
1196+
11151197 jButton_down.addActionListener(al_downTarget);
11161198 }
11171199 return(jButton_down);
11181200 }
1119-
1201+
11201202 //
11211203 private JScrollPane getJScrollPane_keywords() {
11221204 if (jScrollPane_keywords == null) {
@@ -1125,13 +1207,13 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
11251207 }
11261208 return jScrollPane_keywords;
11271209 }
1128-
1210+
11291211 //
11301212 private KDTable getJTable_keywords() {
11311213 if (jTable_keywords == null) {
1132-
1214+
11331215 jTable_keywords = new KDTable(false, new RowItemList<KDItem>());
1134-
1216+
11351217 // テーブルの基本的な設定
11361218 ArrayList<String> cola = new ArrayList<String>();
11371219 for ( KDColumn rc : KDColumn.values() ) {
@@ -1140,11 +1222,11 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
11401222 }
11411223 }
11421224 jTable_keywords.setModel(new DefaultTableModel(cola.toArray(new String[0]), 0));
1143-
1225+
11441226 jTable_keywords.setAutoResizeMode(JNETable.AUTO_RESIZE_OFF);
11451227 jTable_keywords.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
11461228 jTable_keywords.getTableHeader().setReorderingAllowed(false);
1147-
1229+
11481230 // 各カラムの幅
11491231 DefaultTableColumnModel columnModel = (DefaultTableColumnModel)jTable_keywords.getColumnModel();
11501232 //TableColumn column = null;
@@ -1160,16 +1242,16 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
11601242 }
11611243 return(jTable_keywords);
11621244 }
1163-
1245+
11641246 /*******************************************************************************
11651247 * 独自コンポーネント
11661248 ******************************************************************************/
1167-
1249+
11681250 private class KDItem extends RowItem implements Cloneable {
11691251 TargetId target;
11701252 String regex;
11711253 String contain;
1172-
1254+
11731255 @Override
11741256 protected void myrefresh(RowItem o) {
11751257 KDItem c = (KDItem) o;
@@ -1177,36 +1259,36 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
11771259 c.addData(regex);
11781260 c.addData(contain);
11791261 }
1180-
1262+
11811263 @Override
11821264 public KDItem clone() {
11831265 return (KDItem) super.clone();
11841266 }
11851267 }
1186-
1268+
11871269 private class KDTable extends JNETable {
11881270
11891271 private static final long serialVersionUID = 1L;
1190-
1272+
11911273 RowItemList<KDItem> rowdata = null;
1192-
1274+
11931275 public KDTable(boolean b, RowItemList<KDItem> rowdata) {
11941276 super(b);
11951277 this.rowdata = rowdata;
11961278 }
1197-
1279+
11981280 public RowItemList<KDItem> getRowItemList() { return rowdata; }
1199-
1281+
12001282 public void fireChanged() { ((DefaultTableModel) this.getModel()).fireTableDataChanged(); }
1201-
1283+
12021284 @Override
12031285 public int getRowCount() { return rowdata.size(); }
1204-
1286+
12051287 @Override
12061288 public Object getValueAt(int row, int column) {
12071289 return rowdata.get(row).get(column);
12081290 }
1209-
1291+
12101292 @Override
12111293 public void setValueAt(Object aValue, int row, int column) {
12121294 KDItem c = rowdata.get(row);
@@ -1221,16 +1303,16 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
12211303 }
12221304 c.fireChanged();
12231305 }
1224-
1306+
12251307 }
12261308
12271309 /*******************************************************************************
12281310 * 延長警告管理では以下をオーバーライドしてください
12291311 ******************************************************************************/
1230-
1312+
12311313 protected void setItems() {
12321314 setWindowTitle("キーワード検索の設定");
1233-
1315+
12341316 clean_target_items();
12351317 add_target_item(TargetId.TITLEANDDETAIL);
12361318 add_target_item(TargetId.TITLE);
@@ -1257,11 +1339,11 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
12571339 clean_contain_items();
12581340 add_contain_item("を含む番組");
12591341 add_contain_item("を含む番組を除く");
1260-
1342+
12611343 clean_condition_items();
12621344 add_condition_item("次のすべての条件に一致");
12631345 add_condition_item("次のいずれかの条件に一致");
1264-
1346+
12651347 clean_infection_items();
12661348 //add_infection_item();
12671349
@@ -1270,4 +1352,49 @@ abstract class AbsKeywordDialog extends JEscCancelDialog {
12701352 add_okiniiri_item(okini);
12711353 }
12721354 }
1355+
1356+ /*
1357+ * ソート列とソート順を更新する
1358+ *
1359+ * @param sortBy ソート列:ソート順の文字列
1360+ */
1361+ void setSortByAndDir(String sortBy){
1362+ jComboBox_sortBy.setSelectedIndex(0);
1363+ jComboBox_sortDir.setSelectedIndex(0);
1364+
1365+ if (sortBy == null)
1366+ return;
1367+
1368+ Matcher ma = Pattern.compile("^(.*):(.*)$").matcher(sortBy);
1369+ if (ma.find()){
1370+ AbsListedView.ListedColumn col = AbsListedView.ListedColumn.getColumnFromName(ma.group(1));
1371+ if (col != null)
1372+ jComboBox_sortBy.setSelectedItem(col.getName());
1373+
1374+ try{
1375+ jComboBox_sortDir.setSelectedIndex(Integer.parseInt(ma.group(2)));
1376+ }
1377+ catch(NumberFormatException e){
1378+ e.printStackTrace();
1379+ }
1380+ }
1381+ }
1382+
1383+ /*
1384+ * ソート列とソート順を取得する
1385+ *
1386+ * @return ソート列:ソート順の文字列
1387+ */
1388+ String getSortByAndDir(){
1389+ String sortBy = "";
1390+ int no = jComboBox_sortBy.getSelectedIndex();
1391+ AbsListedView.ListedColumn [] cols = AbsListedView.ListedColumn.values();
1392+
1393+ if (no > 0 && no <= cols.length)
1394+ sortBy = AbsListedView.ListedColumn.values()[no-1].name();
1395+
1396+ String sortDir = String.valueOf(jComboBox_sortDir.getSelectedIndex());
1397+
1398+ return sortBy + ":" + sortDir;
1399+ }
12731400 }
--- a/TinyBannavi/src/tainavi/AbsListedView.java
+++ b/TinyBannavi/src/tainavi/AbsListedView.java
@@ -26,6 +26,7 @@ import java.util.Calendar;
2626 import java.util.Comparator;
2727 import java.util.GregorianCalendar;
2828 import java.util.HashMap;
29+import java.util.List;
2930 import java.util.Map.Entry;
3031 import java.util.regex.Matcher;
3132 import java.util.regex.Pattern;
@@ -41,6 +42,8 @@ import javax.swing.JScrollPane;
4142 import javax.swing.JSplitPane;
4243 import javax.swing.JTable;
4344 import javax.swing.JTree;
45+import javax.swing.RowSorter.SortKey;
46+import javax.swing.SortOrder;
4447 import javax.swing.SwingConstants;
4548 import javax.swing.SwingUtilities;
4649 import javax.swing.event.ListSelectionEvent;
@@ -302,6 +305,22 @@ public abstract class AbsListedView extends JPanel implements TickTimerListener
302305 public boolean equals(String s) {
303306 return name.equals(s);
304307 }
308+
309+ /*
310+ * 名称から列情報を取得する
311+ *
312+ * @param s 名称
313+ * @return 列情報
314+ */
315+ static ListedColumn getColumnFromName(String s){
316+ for (ListedColumn col : ListedColumn.values()){
317+ if (col.toString().equals(s))
318+ return col;
319+
320+ }
321+
322+ return null;
323+ }
305324 };
306325
307326 /**
@@ -1176,6 +1195,53 @@ public abstract class AbsListedView extends JPanel implements TickTimerListener
11761195
11771196 // 時間重複マーク
11781197 setOverlapMark();
1198+
1199+ // ソート順を設定する
1200+ if (sKey != null && sKey.getSortBy() != null){
1201+ setSortBy(sKey.getSortBy());
1202+ }
1203+
1204+ }
1205+
1206+ /*
1207+ * ソート順を設定する
1208+ *
1209+ * @param sortBy ソート列:ソート方向の形式のソート情報
1210+ */
1211+ private void setSortBy(String sortBy){
1212+ if (sortBy == null)
1213+ return;
1214+
1215+ // ソート列、ソート方向を取得する
1216+ Matcher ma = Pattern.compile("^(.*):(.*)$").matcher(sortBy);
1217+ if (!ma.find())
1218+ return;
1219+
1220+ // ソート列の列情報を取得する
1221+ ListedColumn col = ListedColumn.getColumnFromName(ma.group(1));
1222+
1223+ // ソート方向を取得する
1224+ int dir = 0;
1225+ try{
1226+ dir = Integer.parseInt(ma.group(2));
1227+ }
1228+ catch(NumberFormatException e){
1229+ e.printStackTrace();
1230+ }
1231+
1232+ if (col == null || dir == 0)
1233+ return;
1234+
1235+ // ソート列のインデックスを取得する
1236+ int idx = lvitems.getIndexById(col.getColumn()+1);
1237+ if (idx == -1)
1238+ return;
1239+
1240+ // ソート情報をセットする
1241+ List <SortKey> list = new ArrayList<SortKey>();
1242+ list.add(new SortKey(idx, dir == 2 ? SortOrder.DESCENDING : SortOrder.ASCENDING));
1243+
1244+ jTable_listed.getRowSorter().setSortKeys(list);
11791245 }
11801246
11811247 private void addPickedPrograms(String curDateTime) {
--- a/TinyBannavi/src/tainavi/AbsPaperView.java
+++ b/TinyBannavi/src/tainavi/AbsPaperView.java
@@ -1789,7 +1789,10 @@ public abstract class AbsPaperView extends JPanel implements TickTimerListener,H
17891789 b1.setValue(date);
17901790 b1.setText(date.substring(5));
17911791 b1.setOpaque(true);
1792- if ( c.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY ) {
1792+ if ( HolidayInfo.IsHoliday(pcl.Date)){
1793+ b1.setBackground(new Color(255,90,90));
1794+ }
1795+ else if ( c.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY ) {
17931796 b1.setBackground(new Color(90,90,255));
17941797 }
17951798 else if ( c.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY ) {
@@ -1857,7 +1860,10 @@ public abstract class AbsPaperView extends JPanel implements TickTimerListener,H
18571860 b1.setValue(date);
18581861 b1.setText(date.substring(5));
18591862 b1.setOpaque(true);
1860- if ( c.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY ) {
1863+ if ( HolidayInfo.IsHoliday(pcl.Date)){
1864+ b1.setBackground(new Color(255,90,90));
1865+ }
1866+ else if ( c.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY ) {
18611867 b1.setBackground(new Color(90,90,255));
18621868 }
18631869 else if ( c.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY ) {
@@ -3270,6 +3276,8 @@ public abstract class AbsPaperView extends JPanel implements TickTimerListener,H
32703276
32713277 if (!env.getWeekEndColoring() || label == null)
32723278 ;
3279+ else if (HolidayInfo.IsHoliday(label))
3280+ return tnode_selected ? Color.CYAN : Color.RED;
32733281 else if (label.endsWith("(土)"))
32743282 return tnode_selected ? Color.YELLOW : Color.BLUE;
32753283 else if (label.endsWith("(日)"))
--- a/TinyBannavi/src/tainavi/AbsReserveListView.java
+++ b/TinyBannavi/src/tainavi/AbsReserveListView.java
@@ -713,11 +713,21 @@ public abstract class AbsReserveListView extends JPanel implements TickTimerList
713713 for ( ReservedItem a : rowData ) {
714714
715715 ProgDetailList tvd = new ProgDetailList();
716+
717+ // タイトルを整形しなおす
716718 tvd.title = a.title;
717719 tvd.titlePop = TraceProgram.replacePop(tvd.title);
718720
719- // タイトルを整形しなおす
720- boolean isFind = SearchProgram.isMatchKeyword(keyword, "", tvd);
721+ // 放送局
722+ tvd.center = a.chname != null ? a.chname : "";
723+
724+ // 番組長
725+ try{
726+ tvd.length = Integer.parseInt(a.length);
727+ }
728+ catch(Exception e){}
729+
730+ boolean isFind = SearchProgram.isMatchKeyword(keyword, tvd.center, tvd);
721731
722732 if ( isFind ) {
723733 rowViewTemp.add(a);
--- a/TinyBannavi/src/tainavi/AbsSettingView.java
+++ b/TinyBannavi/src/tainavi/AbsSettingView.java
@@ -348,6 +348,8 @@ public abstract class AbsSettingView extends JScrollPane {
348348
349349 // その他
350350 private JComboBoxPanel jCBX_updateMethod = null;
351+ private JCheckBoxPanel jCBP_useHolidayCSV = null;
352+ private JSliderPanel jSP_holidayCSVFetchInterval = null;
351353 private JCheckBoxPanel jCBP_disableBeep = null;
352354 private JCheckBoxPanel jCBP_showSysTray = null;
353355 private JCheckBoxPanel jCBP_hideToTray = null;
@@ -1211,6 +1213,23 @@ public abstract class AbsSettingView extends JScrollPane {
12111213 CommonSwingUtils.putComponentOn(jPanel_setting, new JLabel("<<<その他>>>"), LABEL_WIDTH, PARTS_HEIGHT, SEP_WIDTH, y);
12121214 y+=(PARTS_HEIGHT+SEP_HEIGHT);
12131215
1216+ CommonSwingUtils.putComponentOn(jPanel_setting, jCBP_useHolidayCSV = new JCheckBoxPanel("内閣府の祝日CSVを使用する(要再起動)",LABEL_WIDTH), PARTS_WIDTH, PARTS_HEIGHT, SEP_WIDTH, y);
1217+ jCBP_useHolidayCSV.setSelected(env.getUseHolidayCSV());
1218+ jCBP_useHolidayCSV.addItemListener(new ItemListener() {
1219+ @Override
1220+ public void itemStateChanged(ItemEvent e) {
1221+ jSP_holidayCSVFetchInterval.setEnabled(jCBP_useHolidayCSV.isSelected());
1222+ }
1223+ });
1224+ // RELOADリスナー不要
1225+ y+=(PARTS_HEIGHT+SEP_HEIGHT);
1226+
1227+ CommonSwingUtils.putComponentOn(jPanel_setting, jSP_holidayCSVFetchInterval = new JSliderPanel("┗ 取得間隔(日)",LABEL_WIDTH,0,360,200), PARTS_WIDTH, PARTS_HEIGHT, SEP_WIDTH, y);
1228+ jSP_holidayCSVFetchInterval.setValue(env.getHolidayFetchInterval());
1229+ jSP_holidayCSVFetchInterval.setEnabled(jCBP_useHolidayCSV.isSelected());
1230+ // RELOADリスナー不要
1231+ y+=(PARTS_HEIGHT+SEP_HEIGHT);
1232+
12141233 CommonSwingUtils.putComponentOn(jPanel_setting, jCBX_updateMethod = new JComboBoxPanel("起動時にアップデートを確認する",LABEL_WIDTH,250,true), PARTS_WIDTH, PARTS_HEIGHT, SEP_WIDTH, y);
12151234 for ( UpdateOn u : UpdateOn.values() ) {
12161235 jCBX_updateMethod.addItem(u);
@@ -1574,6 +1593,8 @@ public abstract class AbsSettingView extends JScrollPane {
15741593 env.setShowTitleDetail(jCBP_showTitleDetail.isSelected());
15751594
15761595 // その他の設定
1596+ env.setUseHolidayCSV(jCBP_useHolidayCSV.isSelected());
1597+ env.setHolidayFetchInterval(jSP_holidayCSVFetchInterval.getValue());
15771598 env.setUpdateMethod((UpdateOn) jCBX_updateMethod.getSelectedItem());
15781599 env.setDisableBeep(jCBP_disableBeep.isSelected());
15791600 env.setShowSysTray(jCBP_showSysTray.isSelected());
--- a/TinyBannavi/src/tainavi/AbsTitleListView.java
+++ b/TinyBannavi/src/tainavi/AbsTitleListView.java
@@ -923,20 +923,51 @@ public abstract class AbsTitleListView extends JPanel {
923923 /**
924924 * 絞り込み検索の本体(現在リストアップされているものから絞り込みを行う)(親から呼ばれるよ!)
925925 */
926- public void redrawListByKeywordFilter(SearchKey keyword, String target) {
926+ public void redrawListByKeywordFilter(SearchKey keyword, String target, String range) {
927927
928928 rowView.clear();
929929
930930 // 情報を一行ずつチェックする
931931 if ( keyword != null ) {
932+ String from = null;
933+ String to = null;
934+
935+ if (range != null && !range.isEmpty()){
936+ // <from>-<to> を分解して、放送日の下限、上限を取得する
937+ Matcher ma = Pattern.compile("^(.*)-(.*)$").matcher(range);
938+ if (ma.find()){
939+ from = ma.group(1);
940+ to = ma.group(2);
941+ }
942+ }
943+
932944 for ( TitleItem a : rowData ) {
945+ // 放送日下限が指定されている場合
946+ if (from != null && !from.isEmpty()){
947+ if (a.start == null || a.start.length() < 10 || a.start.substring(0, 10).compareTo(from) < 0)
948+ continue;
949+ }
950+ // 放送日上限が指定されている場合
951+ if (to != null && !to.isEmpty()){
952+ if (a.start == null || a.start.length() < 10 || a.start.substring(0, 10).compareTo(to) > 0)
953+ continue;
954+ }
933955
934956 ProgDetailList tvd = new ProgDetailList();
957+
958+ // タイトルを整形しなおす
935959 tvd.title = a.title;
936960 tvd.titlePop = TraceProgram.replacePop(tvd.title);
961+ // ジャンル
962+ if (a.genre != null && a.genre.length() > 2)
963+ tvd.genre = ProgGenre.get(a.genre.substring(2));
964+ // 番組長
965+ try{
966+ tvd.length = Integer.parseInt(a.length);
967+ }
968+ catch(Exception e){ }
937969
938- // タイトルを整形しなおす
939- boolean isFind = SearchProgram.isMatchKeyword(keyword, "", tvd);
970+ boolean isFind = SearchProgram.isMatchKeyword(keyword, a.chname, tvd);
940971
941972 if ( isFind ) {
942973 rowView.add(a);
--- a/TinyBannavi/src/tainavi/AbsToolBar.java
+++ b/TinyBannavi/src/tainavi/AbsToolBar.java
@@ -20,7 +20,6 @@ import java.util.Calendar;
2020 import java.util.GregorianCalendar;
2121 import java.util.regex.Matcher;
2222 import java.util.regex.Pattern;
23-import java.util.regex.PatternSyntaxException;
2423
2524 import javax.swing.AbstractAction;
2625 import javax.swing.ActionMap;
@@ -39,7 +38,6 @@ import javax.swing.JToolBar;
3938 import javax.swing.KeyStroke;
4039
4140 import tainavi.HDDRecorder.RecType;
42-import tainavi.SearchKey.TargetId;
4341 import tainavi.TVProgramIterator.IterationType;
4442 import tainavi.VWMainWindow.MWinTab;
4543 import tainavi.VWUpdate.UpdateResult;
@@ -224,7 +222,7 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
224222
225223 // ツールバーのコンポーネント
226224
227- private JComboBoxWithPopup jComboBox_keyword = null;
225+ protected JSearchWordComboBox jComboBox_keyword = null;
228226 protected JTextField jTextField_keyword = null;
229227 private JButton jButton_search = null;
230228 private JButton jButton_searchmenu = null;
@@ -506,12 +504,34 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
506504 /**
507505 * キーワード検索ボックスからの検索の実行
508506 */
509- protected void toolbarSearch(String keywordArg) {
507+ protected void toolbarSearch() {
508+ int no = jComboBox_keyword.getSelectedIndex();
509+ String keywordStr = jTextField_keyword.getText().trim();
510+ String from = null;
511+ String to = null;
512+ SearchKey search = null;
513+
514+ // 検索履歴を再度実行する場合
515+ if (no > 0){
516+ SearchWordItem swi = (SearchWordItem)swlist.getWordList().get(no-1).clone();
517+ if (keywordStr.equals(swi.getLabel())){
518+ keywordStr = swi.getKeyword();
519+ search = swi.getSearchKey();
520+ from = swi.getFrom();
521+ to = swi.getTo();
522+
523+ if (search != null)
524+ new SearchProgram().compile(search);
525+
526+ keywordSearch(swi.getLabel(), keywordStr, search, keywordStr, from, to, false);
527+ return;
528+ }
529+ }
530+
510531 // 入力形式による分岐
511532 boolean doFilter = false;
512533 String sStr = null;
513534 String kStr = null;
514- String keywordStr = keywordArg != null ? keywordArg : jTextField_keyword.getText().trim();
515535
516536 // 過去ログ閲覧
517537 if (keywordStr.matches("^\\d\\d\\d\\d/\\d\\d/\\d\\d$")) {
@@ -521,12 +541,6 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
521541 return;
522542 }
523543
524- if (swlist.add(keywordStr)){
525- swlist.save();
526- updateKeywordComboBox();
527- jTextField_keyword.setText(keywordStr);
528- }
529-
530544 if ( keywordStr.matches(("^@d(?:rop)?$"))) {
531545 doKeywordSerach(null,null,null,true);
532546 return;
@@ -534,6 +548,7 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
534548
535549 Matcher ma = Pattern.compile("^(@(.+?)[  ]+)").matcher(keywordStr);
536550 if ( ma.find() ) {
551+ // オプション指定
537552 if ( ma.group(2).matches("^f(ilter)?$")) {
538553 // 絞込検索
539554 kStr = keywordStr;
@@ -545,44 +560,36 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
545560 ma = Pattern.compile("^(\\d\\d\\d\\d/)?(\\d\\d/\\d\\d)([  ]+((\\d\\d\\d\\d/)?\\d\\d/\\d\\d))?[  ]+").matcher(keywordStr);
546561 if (ma.find()) {
547562 // 過去ログ検索(範囲指定あり)
548- String sD;
549563 if (ma.group(1) == null || ma.group(1).length() == 0) {
550564 GregorianCalendar c = CommonUtils.getCalendar(0);
551- sD = String.format("%04d/%s", c.get(Calendar.YEAR), ma.group(2));
565+ from = String.format("%04d/%s", c.get(Calendar.YEAR), ma.group(2));
552566 }
553567 else {
554- sD = ma.group(1)+ma.group(2);
568+ from = ma.group(1)+ma.group(2);
555569 }
556570 String cD = CommonUtils.getDate529(0,false);
557571 String pD = CommonUtils.getDate529(-86400,false);
558- //String sD = ma.group(2);
559- String eD;
572+
560573 if (ma.group(4) == null) {
561- eD = pD;
574+ to = pD;
562575 }
563576 else {
564577 if (ma.group(5) == null) {
565578 GregorianCalendar c = CommonUtils.getCalendar(0);
566- eD = String.format("%04d/%s", c.get(Calendar.YEAR), ma.group(4));
579+ to = String.format("%04d/%s", c.get(Calendar.YEAR), ma.group(4));
567580 }
568581 else {
569- eD = ma.group(4);
582+ to = ma.group(4);
570583 }
571584 }
572- if (sD.compareTo(eD) > 0) {
585+ if (from.compareTo(to) > 0) {
573586 // 開始日と終了日が逆転していたら入れ替える
574- String tD = sD;
575- sD = eD;
576- eD = tD;
577- }
578- if (eD.compareTo(cD) >= 0) {
579- MWin.appendError(ERRID+"[過去ログ検索] 終了日付には前日以前の日付("+pD+")を指定してください");
580- ringBeep();
581- return;
582- }
583- else {
584- sStr = String.format("%s-%s", sD, eD);
587+ String tD = from;
588+ from = to;
589+ to = tD;
585590 }
591+
592+ sStr = String.format("%s-%s", from, to);
586593 kStr = ma.replaceFirst("");
587594 }
588595 else {
@@ -590,6 +597,7 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
590597 kStr = keywordStr.trim();
591598 }
592599 }
600+
593601 if ( kStr == null || kStr.matches("^[  ]*$") ) {
594602 // 検索キーワードがない
595603 doKeywordSerach(null,null,null,false);
@@ -597,77 +605,148 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
597605 }
598606
599607 // 検索キーワードの解析
600- SearchKey search = decSearchKeyText(kStr);
608+ if (search == null)
609+ search = decSearchKeyText(kStr);
601610 if ( search == null ) {
602611 return;
603612 }
604613
605- // 検索実行
614+ // 検索履歴に追加する
615+ if (swlist.add(keywordStr, kStr, search, from, to)){
616+ swlist.save();
617+ updateKeywordComboBox();
618+ selectKeywordComboBox(kStr, search, from, to);
619+ }
620+
621+ // 検索を実行する
606622 if (search.alTarget.size() > 0) {
607- doKeywordSerach(search,kStr,sStr,doFilter);
623+ doKeywordSerach(search, kStr, sStr, doFilter);
608624 }
609625 }
610626
611627 /**
612- * キーワード検索ボックスに入力された字句の解析
613- * @param kStr キーワード検索ボックスに入力された字句
614- * @return
628+ * キーワード検索指定画面からの検索の実行
629+ *
630+ * @param label 検索履歴の名称
631+ * @param keywordStr 検索キーワードの名称
632+ * @param search 検索キー
633+ * @param kStr 検索キーワード
634+ * @param from 日付の下限
635+ * @param to 日付の上限
636+ * @param doFileter 絞り込みかどうか
615637 */
616- private SearchKey decSearchKeyText(String kStr) {
638+ protected void keywordSearch(String label, String keywordStr, SearchKey search, String kStr, String from, String to, boolean doFilter) {
617639
618- // オプション
619- TargetId tId = TargetId.TITLEANDDETAIL;
620- while ( true ) {
621- Matcher ma = Pattern.compile("^#(.+?)\\s+").matcher(kStr);
622- if ( ! ma.find() ) {
623- break;
640+ // 過去ログ閲覧
641+ if (keywordStr.matches("^\\d\\d\\d\\d/\\d\\d/\\d\\d$")) {
642+ if ( ! jumpToPassed(keywordStr)) {
643+ JOptionPane.showConfirmDialog(null, keywordStr+"はみつからなかったでゲソ!", "警告", JOptionPane.CLOSED_OPTION);
624644 }
645+ return;
646+ }
625647
626- if ( ma.group(1).matches("^t(itle)?$") ) {
627- tId = TargetId.TITLE;
628- }
629- else if ( ma.group(1).matches("^d(etail)?$") ) {
630- tId = TargetId.DETAIL;
631- }
632- kStr = ma.replaceFirst("");
648+ if ( keywordStr.matches(("^@d(?:rop)?$"))) {
649+ doKeywordSerach(null,null,null,true);
650+ return;
633651 }
634652
635- SearchKey search = new SearchKey();
653+ // 検索履歴に追加する
654+ if (swlist.add(label, kStr, search, from, to)){
655+ swlist.save();
656+ updateKeywordComboBox();
657+ selectKeywordComboBox(kStr, search, from, to);
658+ }
636659
637- search.setLabel(kStr);
660+ // 検索キーワードを解析する
661+ if (search == null)
662+ search = decSearchKeyText(kStr);
663+ if ( search == null ) {
664+ return;
665+ }
638666
639- // 完全一致
640- if ( kStr.matches("^\".+\"$") ) {
641- search.setCondition("0");
642- search.alTarget.add(tId);
643- search.alContain.add("0");
644- search.alKeyword_plane.add(kStr.substring(1,kStr.length()-1));
645- search.alKeyword.add(kStr.substring(1,kStr.length()-1));
646- search.setCaseSensitive(true);
647- return search;
667+ // 日付指定を文字列に変換する
668+ String range = null;
669+ if (from != null && !from.isEmpty() && to != null && !to.isEmpty()){
670+ if (from.compareTo(to) > 0){
671+ range = to + "-" + from;
672+ }
673+ else
674+ range = from + "-" + to;
648675 }
676+ else if (from != null && !from.isEmpty())
677+ range = from;
678+ else if (to != null && !to.isEmpty())
679+ range = to;
649680
650- // 正規表現
651- {
652- search.setCondition("0");
653- for (String s : kStr.split("[  ]")) {
654- if ( ! s.equals("")) {
655- search.alTarget.add(tId);
656- search.alContain.add("0");
657- try {
658- search.alKeyword_regex.add(Pattern.compile("("+TraceProgram.replacePop(s)+")"));
659- search.alKeyword.add(s);
660- }
661- catch (PatternSyntaxException e) {
662- MWin.appendError(ERRID+"正規表現の文法に則っていません: "+s);
663- ringBeep();
664- return null;
665- }
666- }
681+ // 検索を実行する
682+ doKeywordSerach(search, kStr, range, doFilter);
683+ }
684+
685+ /*
686+ * 検索キーワードボックスから検索履歴を選択する
687+ *
688+ * @param s 検索キーワード
689+ * @param k 検索キー
690+ * @param f 日付の下限
691+ * @param t 日付の上限
692+ */
693+ protected void selectKeywordComboBox(String s, SearchKey k, String f, String t){
694+ for (int n=1; n<jComboBox_keyword.getItemCount(); n++){
695+ SearchWordItem swi = swlist.getWordList().get(n-1);
696+ if (swi.equals(s, k, f, t)){
697+ jComboBox_keyword.setSelectedIndex(n);
698+ break;
699+ }
700+ }
701+ }
702+
703+ /**
704+ * キーワード検索ボックスに入力された字句の解析
705+ * @param kStr キーワード検索ボックスに入力された字句
706+ * @return
707+ */
708+ private SearchKey decSearchKeyText(String kStr) {
709+ SearchKey sk = new SearchKey();
710+
711+ String tStr = "";
712+ String rStr = "";
713+ String cStr = "";
714+
715+ // オプションあり
716+ Matcher mc = Pattern.compile("^(#title|#detail)?[  ]+(.*)$").matcher(kStr);
717+ if (mc.find()){
718+ String keyword = mc.group(2);
719+ if (mc.group(1).equals("#title")){
720+ tStr += "1\t";
721+ rStr += keyword + "\t";
722+ cStr += "0\t";
723+ }
724+ else{
725+ tStr += "2\t";
726+ rStr += keyword + "\t";
727+ cStr += "0\t";
667728 }
668- search.setCaseSensitive(false);
669- return search;
670729 }
730+ else{
731+ tStr += "0\t";
732+ rStr += kStr + "\t";
733+ cStr += "0\t";
734+ }
735+
736+ sk.setLabel(kStr);
737+ sk.setTarget(tStr); // compile後は順番が変わるので残すことにする
738+ sk.setKeyword(rStr); // 同上
739+ sk.setContain(cStr); // 同上
740+
741+ sk.setCondition("0");
742+ sk.setInfection("0");
743+ sk.setOkiniiri("0");
744+ sk.setCaseSensitive(false);
745+ sk.setShowInStandby(false);
746+
747+ new SearchProgram().compile(sk);
748+
749+ return sk;
671750 }
672751
673752 public void setFocusInSearchBox() {
@@ -1279,7 +1358,7 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
12791358 if (num >= MAX_SEARCH_WORDS)
12801359 break;
12811360
1282- jComboBox_keyword.addItem(item.getKeyword());
1361+ jComboBox_keyword.addItem(item.getLabel());
12831362 num++;
12841363 }
12851364 }
@@ -1324,7 +1403,7 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
13241403 private final ActionListener al_keywordEntered = new ActionListener() {
13251404 @Override
13261405 public void actionPerformed(ActionEvent e) {
1327- toolbarSearch(null);
1406+ toolbarSearch();
13281407 }
13291408 };
13301409
@@ -1344,11 +1423,30 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
13441423 // 「キーワード検索の設定」ウィンドウを開く
13451424 String kStr = jTextField_keyword.getText().trim();
13461425
1347- SearchKey search = decSearchKeyText(kStr);
1348- if ( search == null ) {
1349- return;
1426+ // 履歴から検索キーを取得する
1427+ int no = jComboBox_keyword.getSelectedIndex();
1428+ SearchKey search = null;
1429+ if (no > 0){
1430+ SearchWordItem swi = swlist.getWordList().get(no-1);
1431+
1432+ // 検索キーワードが変更されていたら履歴は無視する
1433+ if (kStr.equals(swi.getLabel())){
1434+ kStr = swi.getKeyword();
1435+ search = swi.getSearchKey();
1436+ if (search != null)
1437+ new SearchProgram().compile(search);
1438+ }
1439+ else
1440+ no = 0;
13501441 }
13511442
1443+ // なければ検索キーワードを解析する
1444+ if (no == 0 || search == null)
1445+ search = decSearchKeyText(kStr);
1446+
1447+ if ( search == null )
1448+ return;
1449+
13521450 jTextField_keyword.setText("");
13531451
13541452 addKeywordSearch(search);
@@ -1666,11 +1764,9 @@ public abstract class AbsToolBar extends JToolBar implements HDDRecorderSelectab
16661764 // キーワード検索ボックス
16671765 private JComboBoxWithPopup getJComboBox_keyword() {
16681766 if (jComboBox_keyword == null){
1669- jComboBox_keyword = new JComboBoxWithPopup();
1767+ jComboBox_keyword = new JSearchWordComboBox(swlist);
16701768 jComboBox_keyword.setEditable(true);
16711769
1672- jComboBox_keyword.setMaximumRowCount(32);
1673-
16741770 Dimension d = jComboBox_keyword.getPreferredSize();
16751771 d.width = bounds.getSearchBoxAreaWidth();
16761772
--- /dev/null
+++ b/TinyBannavi/src/tainavi/CalendarListener.java
@@ -0,0 +1,20 @@
1+package tainavi;
2+
3+/*
4+ * カレンダーのイベントを処理するリスナー
5+ */
6+public interface CalendarListener {
7+
8+ /**
9+ * 日付が選択された
10+ *
11+ * @param cal カレンダー部品
12+ * @param date 選択された日(YYYY/MM/DD)
13+ */
14+ public void notifyDateChange(JCalendar cal, String date);
15+
16+ /*
17+ * 祝日かどうかを取得する
18+ */
19+ public boolean isHoliday(int year, int month, int day);
20+}
\ No newline at end of file
--- a/TinyBannavi/src/tainavi/Env.java
+++ b/TinyBannavi/src/tainavi/Env.java
@@ -983,6 +983,21 @@ public class Env {
983983 public void setDebug(boolean b) { debug = b; }
984984 private boolean debug = false;
985985
986+ // 内閣府の「国民の祝日」CSVを使用する
987+ public boolean getUseHolidayCSV(){ return useHolidayCSV; }
988+ public void setUseHolidayCSV(boolean b){ useHolidayCSV = b; }
989+ private boolean useHolidayCSV = true;
990+
991+ // 内閣府の「国民の祝日」CSVのURL
992+ public String getHolidayFetchURL(){ return holidayFetchURL; }
993+ public void setHolidayFetchURL(String s){ holidayFetchURL = s; }
994+ private String holidayFetchURL = "https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv";
995+
996+ // 内閣府の「国民の祝日」CSVの取得間隔(単位:日数)
997+ public int getHolidayFetchInterval(){ return holidayFetchInterval; }
998+ public void setHolidayFetchInterval(int n){ holidayFetchInterval = n; }
999+ private int holidayFetchInterval = 90;
1000+
9861001 /*******************************************************************************
9871002 * 作ったけど使ってないもの
9881003 ******************************************************************************/
--- /dev/null
+++ b/TinyBannavi/src/tainavi/HolidayInfo.java
@@ -0,0 +1,313 @@
1+package tainavi;
2+
3+import java.io.BufferedReader;
4+import java.io.BufferedWriter;
5+import java.io.File;
6+import java.io.FileInputStream;
7+import java.io.FileWriter;
8+import java.io.IOException;
9+import java.io.InputStreamReader;
10+import java.net.HttpURLConnection;
11+import java.net.MalformedURLException;
12+import java.net.URL;
13+import java.nio.file.Files;
14+import java.nio.file.attribute.BasicFileAttributes;
15+import java.nio.file.attribute.FileTime;
16+import java.time.Instant;
17+import java.util.HashMap;
18+import java.util.regex.Matcher;
19+import java.util.regex.Pattern;
20+
21+/*
22+ * 内閣府の「国民の祝日」CSVを取得して祝日情報を管理するクラス
23+ */
24+public class HolidayInfo{
25+ /*******************************************************************************
26+ * 定数
27+ ******************************************************************************/
28+ private final static String FETCH_URI = "https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv";
29+ private final static String FILENAME = "env"+File.separator+"holidays.csv";
30+ private final static String encoding = "MS932";
31+
32+ /*******************************************************************************
33+ * 静的メンバー
34+ ******************************************************************************/
35+ private static HashMap<String, HolidayInfo> holidays = null;
36+
37+ /*******************************************************************************
38+ * 部品以外のインスタンスメンバー
39+ ******************************************************************************/
40+ private int year;
41+ private int month;
42+ private int day;
43+ private String holidayName;
44+
45+ /*******************************************************************************
46+ * コンストラクタ
47+ ******************************************************************************/
48+ public HolidayInfo(){
49+ year = 0;
50+ month = 0;
51+ day = 0;
52+ holidayName = null;
53+ }
54+
55+ /*******************************************************************************
56+ * 公開メソッド
57+ ******************************************************************************/
58+ // 祝日の西暦年を返す
59+ public int getYear(){
60+ return year;
61+ }
62+
63+ // 祝日の月を返す
64+ public int getMonth(){
65+ return month;
66+ }
67+
68+ // 祝日の日を返す
69+ public int getDay(){
70+ return day;
71+ }
72+
73+ // 祝日の名称を返す
74+ public String getHolidayName(){
75+ return holidayName;
76+ }
77+
78+ /*******************************************************************************
79+ * 静的公開メソッド
80+ ******************************************************************************/
81+ /*
82+ * CSVファイルを読み込む
83+ */
84+ public static boolean Load(String uri, int interval){
85+ if (IsCSVFileLifetimeExpired(interval)){
86+ FetchCSVFile(uri);
87+ }
88+
89+ return LoadFromCSVFile(FILENAME);
90+ }
91+
92+ /*
93+ * CSVファイルを更新する
94+ */
95+ public static boolean Refresh(String uri, int interval){
96+ if (!IsCSVFileLifetimeExpired(interval))
97+ return true;
98+
99+ FetchCSVFile(uri);
100+
101+ return LoadFromCSVFile(FILENAME);
102+ }
103+
104+ /*
105+ * CSVファイルを内閣府のサーバーからダウンロードする
106+ */
107+ public static boolean FetchCSVFile(String uri){
108+ if (uri == null || uri.isEmpty())
109+ uri = FETCH_URI;
110+
111+ BufferedWriter bw = null;
112+ BufferedReader br = null;
113+ try {
114+ // 内閣府のホームページに接続する
115+ URL url = new URL(uri);
116+ HttpURLConnection ucon = (HttpURLConnection)url.openConnection();
117+ ucon.setConnectTimeout(5000);
118+ ucon.setReadTimeout(5000);
119+ ucon.setRequestMethod("GET");
120+ ucon.connect();
121+
122+ // レスポンスを一時ファイルに書き出す
123+ String tmpfile = FILENAME + ".tmp";
124+ bw = new BufferedWriter(new FileWriter(tmpfile));
125+ br = new BufferedReader(new InputStreamReader(ucon.getInputStream(), encoding));
126+
127+ String str;
128+ while((str = br.readLine()) != null){
129+ bw.write(str);
130+ bw.write("\n");
131+ }
132+
133+ br.close();
134+ br = null;
135+ bw.close();
136+ bw = null;
137+
138+ // CSVファイルにリネームする
139+ File ofile = new File(FILENAME);
140+ if ( ofile.exists() && ! ofile.delete() ) {
141+ System.err.println("[HolidayInfo]CSVファイルを削除できない:" + ofile.getAbsolutePath());
142+ }
143+
144+ File nfile = new File(tmpfile);
145+ if ( ! nfile.renameTo(ofile) ) {
146+ System.err.println("[HolidayInfo]CSVファイルをリネームできない:" +
147+ nfile.getAbsolutePath() + "=>" + ofile.getAbsolutePath());
148+ return false;
149+ }
150+
151+ return true;
152+ } catch (MalformedURLException e) {
153+ e.printStackTrace();
154+ } catch (IOException e) {
155+ e.printStackTrace();
156+ }
157+ finally{
158+ if (br != null) try { br.close(); } catch (Exception e) {}
159+ if (bw != null) try { bw.close(); } catch (Exception e) {}
160+ }
161+
162+ return false;
163+ }
164+
165+ /*
166+ * CSVファイルを読み込む
167+ */
168+ public static boolean LoadFromCSVFile(String path){
169+ if (path == null)
170+ return false;
171+
172+ HashMap<String, HolidayInfo> map = new HashMap<String, HolidayInfo>();
173+ BufferedReader br = null;
174+
175+ // CSVファイルが存在するかチェックする
176+ File file = new File(path);
177+ try {
178+ if (!file.exists()){
179+ System.err.println("[HolidayInfo]ファイルがない: " + file.getAbsolutePath());
180+ return false;
181+ }
182+
183+ br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
184+
185+ // 1行ずつ読み込む
186+ String str;
187+ while ((str = br.readLine()) != null) {
188+ // CSVの形式は YYYY/MM/DD,名称となっている
189+ Matcher ma = Pattern.compile("^(\\d{4})/(\\d{1,2})/(\\d{1,2}),(.*)$").matcher(str);
190+ if (!ma.find())
191+ continue;
192+
193+ // 祝日情報を生成する
194+ HolidayInfo hi = new HolidayInfo();
195+ hi.year = Integer.parseInt(ma.group(1));
196+ hi.month = Integer.parseInt(ma.group(2));
197+ hi.day = Integer.parseInt(ma.group(3));
198+ hi.holidayName = ma.group(4);
199+
200+ // キーに紐づけてハッシュマップに追加する
201+ String s = FormatKey( hi.year, hi.month, hi.day);
202+ map.put(s, hi);
203+ }
204+ }
205+ catch (Exception e) {
206+ e.printStackTrace();
207+ System.err.println("[HolidayInfo]ファイルの読み込みに失敗: "+file.getAbsolutePath());
208+ return false;
209+ }
210+ finally {
211+ if (br != null) try { br.close(); } catch (Exception e) {}
212+ }
213+
214+ holidays = map;
215+ System.out.println("[HolidayInfo]祝日数: "+map.size());
216+
217+ return true;
218+ }
219+
220+ /*
221+ * 指定した日が祝日かどうかを返す
222+ */
223+ public static boolean IsHoliday(int year, int month, int day){
224+ return IsHoliday(FormatKey(year, month, day));
225+ }
226+
227+ /*
228+ * 指定した日が祝日の場合、その名前を返す
229+ */
230+ public static String GetHolidayName(int year, int month, int day){
231+ return GetHolidayName(FormatKey(year, month, day));
232+ }
233+
234+ /*
235+ * 指定した日の祝日情報を返す
236+ */
237+ public static HolidayInfo GetHolidayInfo(int year, int month, int day){
238+ return GetHolidayInfo(FormatKey(year, month, day));
239+ }
240+
241+ /*
242+ * YYYY/MM/DD形式で指定した日が祝日かどうかを返す
243+ */
244+ public static boolean IsHoliday(String date){
245+ HolidayInfo hi = GetHolidayInfo(date);
246+
247+ return (hi != null);
248+ }
249+
250+ /*
251+ * YYYY/MM/DD形式で指定した日が祝日の場合、その名前を返す
252+ */
253+ public static String GetHolidayName(String date){
254+ HolidayInfo hi = GetHolidayInfo(date);
255+
256+ return (hi != null) ? hi.holidayName : null;
257+ }
258+
259+ /*
260+ * YYYY/MM/DD形式で指定した日の祝日情報を返す
261+ */
262+ public static HolidayInfo GetHolidayInfo(String date){
263+ if (date == null || holidays == null)
264+ return null;
265+
266+ if (date.length() > 10)
267+ date = date.substring(0, 10);
268+
269+ return holidays.get(date);
270+ }
271+
272+ /*******************************************************************************
273+ * 静的内部関数
274+ ******************************************************************************/
275+ /*
276+ * ハッシュマップのキーを生成する
277+ */
278+ private static String FormatKey(int year, int month, int day){
279+ return String.format("%04d/%02d/%02d", year, month, day);
280+ }
281+
282+ /*
283+ * CSVファイルの寿命が尽きているかどうかを返す
284+ */
285+ private static boolean IsCSVFileLifetimeExpired(int interval){
286+ // インターバルが0以下の場合は寿命はない
287+ if (interval <= 0)
288+ return false;
289+
290+ // CSVファイルが存在しない場合は尽きているとみなす
291+ File file = new File(FILENAME);
292+ if (!file.exists())
293+ return true;
294+
295+ try {
296+ BasicFileAttributes attrs = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
297+ FileTime time = attrs.creationTime();
298+ Instant now = Instant.now();
299+
300+ // ファイル生成後の経過ミリ秒数を計算する
301+ long elapsed = now.toEpochMilli() - time.toMillis();
302+ System.out.println("[HolidayInfo]経過時間(日):" + (elapsed/1000/24/3600));
303+
304+ // インターバルを超えていない場合は尽きていない
305+ if ( elapsed/1000 < interval*24*3600)
306+ return false;
307+ } catch (IOException e) {
308+ }
309+
310+ return true;
311+ }
312+
313+}
\ No newline at end of file
--- /dev/null
+++ b/TinyBannavi/src/tainavi/JCalendar.java
@@ -0,0 +1,821 @@
1+package tainavi;
2+
3+import java.awt.Color;
4+import java.awt.Dimension;
5+import java.awt.FlowLayout;
6+import java.awt.Font;
7+import java.awt.event.ActionEvent;
8+import java.awt.event.ActionListener;
9+import java.awt.event.MouseAdapter;
10+import java.awt.event.MouseEvent;
11+import java.util.Calendar;
12+import java.util.GregorianCalendar;
13+import java.util.regex.Matcher;
14+import java.util.regex.Pattern;
15+
16+import javax.swing.BoxLayout;
17+import javax.swing.JButton;
18+import javax.swing.JComponent;
19+import javax.swing.JLabel;
20+import javax.swing.JMenuItem;
21+import javax.swing.JPanel;
22+import javax.swing.JPopupMenu;
23+import javax.swing.JSpinner;
24+import javax.swing.JTextField;
25+import javax.swing.SpinnerNumberModel;
26+import javax.swing.SwingConstants;
27+import javax.swing.border.Border;
28+import javax.swing.border.LineBorder;
29+import javax.swing.event.ChangeEvent;
30+import javax.swing.event.ChangeListener;
31+
32+/*
33+ * カレンダー部品
34+ */
35+public class JCalendar extends JPanel {
36+
37+ /*******************************************************************************
38+ * 定数
39+ ******************************************************************************/
40+ private static final int DAY_FONT_SIZE = 14;
41+ private static final int DAY_WIDTH = 33;
42+ private static final int DAY_HEIGHT = 25;
43+
44+ private static final int YEAR_SPINNER_WIDTH = 85;
45+ private static final int MONTH_SPINNER_WIDTH = 70;
46+
47+ private static final int YM_FONT_SIZE = 10;
48+ private static final int YM_BUTTON_WIDTH = DAY_WIDTH;
49+ private static final int YM_LABEL_WIDTH = DAY_WIDTH*3;
50+ private static final int YM_HEIGHT = DAY_HEIGHT;
51+
52+ private static final int COLNUM = 7;
53+ private static final int ROWNUM = 6;
54+
55+ private static final String[] WEEK_NAMES =
56+ new String[] {"日", "月", "火", "水", "木", "金", "土"};
57+
58+ private static final Border BORDER_NORMAL = null;
59+ private static final Border BORDER_SELECTED = new LineBorder(Color.BLACK, 2);
60+
61+ enum Style{
62+ Spinner,
63+ Button,
64+ };
65+
66+ /*******************************************************************************
67+ * 部品
68+ ******************************************************************************/
69+ private JSpinner jSpinnerYear = null;
70+ private JSpinner jSpinnerMonth = null;
71+
72+ private JButton jButtonYearMinus = null;
73+ private JButton jButtonMonthMinus = null;
74+ private JLabel jLabelYearMonth = null;
75+ private JButton jButtonMonthPlus = null;
76+ private JButton jButtonYearPlus = null;
77+
78+ private JLabel[] jLabelWeeks = new JLabel[COLNUM];
79+ private JButton[][] jButtonDays = new JButton[ROWNUM][COLNUM];
80+ private JButton jButtonThisMonth = null;
81+
82+ private Font fontNormal = null;
83+ private Font fontToday = null;
84+ private Font fontWeek = null;
85+ private Font fontYearMonth = null;
86+
87+ private CalendarListener listener = null;
88+
89+ private GregorianCalendar calSelDate = null;
90+ private GregorianCalendar calMinDate = null;
91+ private GregorianCalendar calMaxDate = null;
92+
93+ private Style style = Style.Button;
94+ private boolean popupMenuEnabled = true;
95+
96+ /*******************************************************************************
97+ * コンストラクタ
98+ ******************************************************************************/
99+ public JCalendar() {
100+ this(Style.Button, true);
101+ }
102+
103+ public JCalendar(Style s, boolean b){
104+ style = s;
105+ popupMenuEnabled = b;
106+
107+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
108+
109+ createFont();
110+
111+ add(createYearMonthPanel());
112+ add(createDayPanel());
113+
114+ setSelectedDate((GregorianCalendar)null);
115+ }
116+
117+ /*******************************************************************************
118+ * static 公開メソッド
119+ ******************************************************************************/
120+ /*******************************************************************************
121+ * 公開メソッド
122+ ******************************************************************************/
123+ /*
124+ * リスナーを登録する
125+ */
126+ public void setListener(CalendarListener l){
127+ listener = l;
128+ }
129+
130+ /*
131+ * 選択日をYYYY/MM/DD形式で指定する
132+ */
133+ public void setSelectedDate(String date){
134+ setSelectedDate(getCalendar(date));
135+ }
136+
137+ /*
138+ * 選択日をGregorianCalendarクラスで指定する
139+ */
140+ public void setSelectedDate(GregorianCalendar cal){
141+ calSelDate = cal;
142+
143+ if (cal == null)
144+ cal = new GregorianCalendar();
145+
146+ setYearMonth(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1);
147+
148+ updateDayButtons();
149+ }
150+
151+ /*
152+ * 有効な最小日をYYYY/MM/DD形式で指定する
153+ */
154+ public void setMinDate(String date){
155+ setMinDate(getCalendar(date));
156+ }
157+
158+ /*
159+ * 有効な最小日をGregorianCalendarクラスで指定する
160+ */
161+ public void setMinDate(GregorianCalendar cal){
162+ calMinDate = cal;
163+ }
164+
165+ /*
166+ * 有効な最大日をYYYY/MM/DD形式で指定する
167+ */
168+ public void setMaxDate(String date){
169+ setMaxDate(getCalendar(date));
170+ }
171+
172+ /*
173+ * 有効な最大日をGregorianCalendarクラスで指定する
174+ */
175+ public void setMaxDate(GregorianCalendar cal){
176+ calMaxDate = cal;
177+ }
178+
179+ /*
180+ * キーボードフォーカスを取得する
181+ *
182+ * (非 Javadoc)
183+ * @see javax.swing.JComponent#requestFocus()
184+ */
185+ @Override
186+ public void requestFocus(){
187+ if (style == Style.Spinner){
188+ JTextField tm = ((JSpinner.DefaultEditor)jSpinnerMonth.getEditor()).getTextField();
189+ tm.requestFocus();
190+ }
191+ }
192+
193+ /*******************************************************************************
194+ * コンポーネント
195+ ******************************************************************************/
196+ /*
197+ * フォントを生成する
198+ */
199+ private void createFont(){
200+ JButton b = new JButton();
201+
202+ Font font = b.getFont();
203+ fontNormal = font.deriveFont(font.getStyle() & ~Font.BOLD, DAY_FONT_SIZE);
204+ fontToday = font.deriveFont(font.getStyle() | Font.BOLD, DAY_FONT_SIZE);
205+ fontWeek = font.deriveFont(font.getStyle() | Font.BOLD, DAY_FONT_SIZE);
206+ fontYearMonth = font.deriveFont(font.getStyle() | Font.BOLD, YM_FONT_SIZE);
207+ }
208+
209+ /*
210+ * 年月選択用パネル
211+ */
212+ private JPanel createYearMonthPanel(){
213+ JPanel panel = new JPanel();
214+
215+ if (style == Style.Spinner){
216+ FlowLayout layout = new FlowLayout();
217+ layout.setHgap(10);
218+ layout.setVgap(0);
219+ panel.setLayout(layout);
220+
221+ jSpinnerYear = createYearSpinner();
222+ jSpinnerMonth = createMonthSpinner();
223+
224+ panel.add(jSpinnerYear);
225+ panel.add(jSpinnerMonth);
226+ }
227+ else{
228+ FlowLayout layout = new FlowLayout();
229+ layout.setHgap(0);
230+ layout.setVgap(0);
231+ panel.setLayout(layout);
232+
233+ jButtonYearMinus = createYMButton("<<", "前年へ移動", ma_yearMinus);
234+ jButtonMonthMinus = createYMButton("<", "前月へ移動", ma_monthMinus);
235+ jLabelYearMonth = createYMLabel();
236+ jButtonMonthPlus = createYMButton(">", "翌月へ移動", ma_monthPlus);
237+ jButtonYearPlus = createYMButton(">>", "翌年へ移動", ma_yearPlus);
238+
239+ panel.add(jButtonYearMinus);
240+ panel.add(jButtonMonthMinus);
241+ panel.add(jLabelYearMonth);
242+ panel.add(jButtonMonthPlus);
243+ panel.add(jButtonYearPlus);
244+ }
245+
246+ return panel;
247+ }
248+
249+ /*
250+ * 年選択用スピナーを作成する
251+ */
252+ private JSpinner createYearSpinner(){
253+ JSpinner spinner = new JSpinner();
254+ spinner.setModel(new SpinnerNumberModel(2021, 2000, 2999, 1 ));
255+ spinner.setEditor(new JSpinner.NumberEditor(spinner, "#年"));
256+ spinner.addChangeListener(cl_yearChanged);
257+
258+ Dimension dy = spinner.getPreferredSize();
259+ dy.width = YEAR_SPINNER_WIDTH;
260+ spinner.setPreferredSize(dy);
261+
262+ return spinner;
263+ }
264+
265+ /*
266+ * 月選択用スピナーを作成する
267+ */
268+ private JSpinner createMonthSpinner() {
269+ JSpinner spinner = new JSpinner();
270+ spinner.setModel(new SpinnerNumberModel(1, 0, 13, 1 ));
271+ spinner.setEditor(new JSpinner.NumberEditor(spinner, "#月"));
272+ spinner.addChangeListener(cl_monthChanged);
273+
274+ Dimension dm = spinner.getPreferredSize();
275+ dm.width = MONTH_SPINNER_WIDTH;
276+ spinner.setPreferredSize(dm);
277+
278+ return spinner;
279+ }
280+
281+ /*
282+ * 年月用ボタンを作成する
283+ */
284+ private JButton createYMButton(String text, String tooltip, MouseAdapter ma){
285+ JButton button = new JButton();
286+
287+ button.setText(text);
288+ button.setPreferredSize(new Dimension(YM_BUTTON_WIDTH, YM_HEIGHT));
289+ button.setFont(fontYearMonth);
290+ button.setToolTipText(tooltip + (popupMenuEnabled ? "(右クリックでメニュー)" : ""));
291+
292+ button.setBorderPainted(false);
293+ button.setFocusPainted(false);
294+ button.setContentAreaFilled(false);
295+
296+ button.addMouseListener(ma_flatButton);
297+ button.addMouseListener(ma);
298+
299+ return button;
300+ }
301+
302+ /*
303+ * 年月用ラベルを作成する
304+ */
305+ private JLabel createYMLabel(){
306+ JLabel label = new JLabel();
307+ label.setBackground(Color.LIGHT_GRAY);
308+ label.setPreferredSize(new Dimension(YM_LABEL_WIDTH, YM_HEIGHT));
309+ label.setFont(fontNormal);
310+ label.setHorizontalAlignment(JLabel.CENTER);
311+ label.setToolTipText(popupMenuEnabled ? "(右クリックでメニュー)" : "");
312+ label.addMouseListener(ma_ymLabel);
313+
314+ return label;
315+ }
316+
317+ /*
318+ * カレンダーパネル
319+ */
320+ private JPanel createDayPanel(){
321+ JPanel panel = new JPanel();
322+
323+ panel.setLayout(null);
324+
325+ for( int c=0; c<COLNUM; c++) {
326+ JLabel label = createWeekLabel(c);
327+ label.setBounds(DAY_WIDTH*c, 0, DAY_WIDTH, DAY_HEIGHT);
328+ panel.add(label);
329+ jLabelWeeks[c] = label;
330+ }
331+
332+ JCalendar cal = this;
333+
334+ ActionListener al_daySelected = new ActionListener(){
335+ @Override
336+ public void actionPerformed(ActionEvent e) {
337+ JButton button = (JButton) e.getSource();
338+
339+ String date = String.format("%04d/%02d/%02d",
340+ getSelectedYear(),
341+ getSelectedMonth(),
342+ Integer.valueOf(button.getText()));
343+
344+ if (listener != null)
345+ listener.notifyDateChange(cal, date);
346+ }
347+ };
348+
349+ for( int r=0; r<ROWNUM; r++) {
350+ for( int c=0; c<COLNUM; c++) {
351+ JButton button = createDayButton(c, al_daySelected);
352+ button.setBounds(DAY_WIDTH*c, DAY_HEIGHT*(r+1), DAY_WIDTH, DAY_HEIGHT);
353+ panel.add(button);
354+ jButtonDays[r][c] = button;
355+ }
356+ }
357+
358+ JButton button = createDayButton(1, al_thisMonthButton);
359+ button.setFont(fontNormal);
360+ button.setText("今月へ");
361+ button.setBounds(DAY_WIDTH*5, DAY_HEIGHT*ROWNUM, DAY_WIDTH*2, DAY_HEIGHT);
362+ panel.add(button);
363+ jButtonThisMonth = button;
364+
365+ panel.setPreferredSize(new Dimension(DAY_WIDTH*COLNUM, DAY_HEIGHT*(ROWNUM+1)));
366+
367+ return panel;
368+ }
369+
370+ /*
371+ * 曜日用ラベルを作成する
372+ */
373+ private JLabel createWeekLabel(int c){
374+ JLabel label = new JLabel(WEEK_NAMES[c]);
375+ label.setHorizontalAlignment(SwingConstants.CENTER);
376+ label.setVerticalAlignment(SwingConstants.CENTER);
377+ label.setPreferredSize(new Dimension(DAY_WIDTH, DAY_HEIGHT));
378+ label.setOpaque(true);
379+ label.setBorder(null);
380+ label.setForeground(c == 0 ? Color.RED : c == 6 ? Color.BLUE : Color.BLACK);
381+ label.setFont(fontWeek);
382+
383+ return label;
384+ }
385+
386+ /*
387+ * 日付用ボタンを作成する
388+ */
389+ private JButton createDayButton(int c, ActionListener al){
390+ JButton button = new JButton();
391+ button.setHorizontalAlignment(SwingConstants.CENTER);
392+ button.setVerticalAlignment(SwingConstants.CENTER);
393+ button.setOpaque(true);
394+ button.setPreferredSize(new Dimension(DAY_WIDTH, DAY_HEIGHT));
395+ button.setForeground(c == 0 ? Color.RED : c == 6 ? Color.BLUE : Color.BLACK);
396+ button.setBorderPainted(false);
397+ button.setFocusPainted(false);
398+ button.setContentAreaFilled(false);
399+
400+ button.addMouseListener(ma_flatButton);
401+ button.addActionListener(al);
402+
403+ return button;
404+ }
405+
406+ /*******************************************************************************
407+ * リスナー
408+ ******************************************************************************/
409+ /*
410+ * 年の選択変更
411+ */
412+ private ChangeListener cl_yearChanged = new ChangeListener(){
413+ public void stateChanged(ChangeEvent e) {
414+ updateDayButtons();
415+ }
416+ };
417+
418+ /*
419+ * 月の選択変更
420+ */
421+ private ChangeListener cl_monthChanged = new ChangeListener(){
422+ public void stateChanged(ChangeEvent e) {
423+ int month = getSelectedMonth();
424+ if (month >= 13){
425+ jSpinnerYear.setValue((int)jSpinnerYear.getValue()+1);
426+ jSpinnerMonth.setValue(1);
427+ }
428+ else if (month <= 0){
429+ jSpinnerYear.setValue((int)jSpinnerYear.getValue()-1);
430+ jSpinnerMonth.setValue(12);
431+ }
432+
433+ updateDayButtons();
434+ }
435+ };
436+
437+ /*
438+ * 前年へ
439+ */
440+ private MouseAdapter ma_yearMinus = new MouseAdapter(){
441+ @Override
442+ public void mouseClicked(MouseEvent e) {
443+ setYearMonth(getSelectedYear()-1, getSelectedMonth());
444+ updateDayButtons();
445+ }
446+ };
447+
448+ /*
449+ * 前月へ
450+ */
451+ private MouseAdapter ma_monthMinus = new MouseAdapter(){
452+ @Override
453+ public void mouseClicked(MouseEvent e) {
454+ int year = getSelectedYear();
455+ int month = getSelectedMonth()-1;
456+ if (month <= 0){
457+ year --;
458+ month = 12;
459+ }
460+
461+ setYearMonth(year, month);
462+ updateDayButtons();
463+ }
464+ };
465+
466+ /*
467+ * 翌月へ
468+ */
469+ private MouseAdapter ma_monthPlus = new MouseAdapter(){
470+ @Override
471+ public void mouseClicked(MouseEvent e) {
472+ int year = getSelectedYear();
473+ int month = getSelectedMonth()+1;
474+ if (month >= 13){
475+ year ++;
476+ month = 1;
477+ }
478+
479+ setYearMonth(year, month);
480+ updateDayButtons();
481+ }
482+ };
483+
484+ /*
485+ * 翌年へ
486+ */
487+ private MouseAdapter ma_yearPlus = new MouseAdapter(){
488+ @Override
489+ public void mouseClicked(MouseEvent e) {
490+ setYearMonth(getSelectedYear()+1, getSelectedMonth());
491+ updateDayButtons();
492+ }
493+ };
494+
495+ /*
496+ * 年月ラベルのマウスリスナー
497+ */
498+ MouseAdapter ma_ymLabel = new MouseAdapter(){
499+ @Override
500+ public void mousePressed(MouseEvent e) {
501+ if (e.getButton() == MouseEvent.BUTTON3){
502+ JLabel label = (JLabel)e.getSource();
503+ Dimension sz = label.getSize();
504+
505+ if (e.getX() < sz.width/2){
506+ showYearMenu(label, e.getX(), e.getY());
507+ }
508+ else{
509+ showMonthMenu(label, e.getX(), e.getY());
510+ }
511+ }
512+ }
513+ };
514+
515+ /*
516+ * フラットボタンのマウスリスナー
517+ */
518+ MouseAdapter ma_flatButton = new MouseAdapter(){
519+ @Override
520+ public void mousePressed(MouseEvent e) {
521+ JButton button = (JButton) e.getSource();
522+ if (e.getButton() == MouseEvent.BUTTON3){
523+ if (button == jButtonYearMinus || button == jButtonYearPlus)
524+ showYearMenu(button, e.getX(), e.getY());
525+ else if (button == jButtonMonthMinus || button == jButtonMonthPlus)
526+ showMonthMenu(button, e.getX(), e.getY());
527+ }
528+ }
529+
530+ @Override
531+ public void mouseEntered(MouseEvent e) {
532+ JButton button = (JButton) e.getSource();
533+ if (button.isEnabled())
534+ button.setContentAreaFilled(true);
535+ }
536+
537+ @Override
538+ public void mouseExited(MouseEvent e) {
539+ JButton button = (JButton) e.getSource();
540+ if (button.isEnabled())
541+ button.setContentAreaFilled(false);
542+ }
543+ };
544+
545+ /*
546+ * 今月へ
547+ */
548+ private ActionListener al_thisMonthButton = new ActionListener(){
549+ @Override
550+ public void actionPerformed(ActionEvent e) {
551+ GregorianCalendar cal = new GregorianCalendar();
552+
553+ setYearMonth(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH)+1);
554+ updateDayButtons();
555+ }
556+ };
557+
558+ /*******************************************************************************
559+ * 内部関数
560+ ******************************************************************************/
561+ /*
562+ * カレンダーを更新する
563+ */
564+ private void updateDayButtons(){
565+ int year = getSelectedYear();
566+ int month = getSelectedMonth();
567+
568+ GregorianCalendar cal = new GregorianCalendar();
569+ boolean isThisMonth = cal.get(Calendar.YEAR) == year && cal.get(Calendar.MONTH) == month-1;
570+
571+ cal.set(Calendar.YEAR, year);
572+ cal.set(Calendar.MONTH, month-1);
573+ cal.set(Calendar.DATE, 1);
574+
575+ int dow0 = cal.get(Calendar.DAY_OF_WEEK);
576+
577+ clearDayButtons();
578+
579+ int dayNum = cal.getActualMaximum(Calendar.DATE);
580+ for( int day=1; day<=dayNum; day++ ) {
581+ int no = day + dow0 - 2;
582+ JButton button = jButtonDays[no/COLNUM][no%COLNUM];
583+
584+ button.setText(Integer.toString(day));
585+ }
586+
587+ for( int r=0; r<ROWNUM; r++) {
588+ for( int c=0; c<COLNUM; c++) {
589+ JButton button = jButtonDays[r][c];
590+
591+ int day = r*COLNUM + c + 2 - dow0;
592+ boolean valid = isValidDate(year, month, day);
593+ boolean selected = isSelectedDate(year, month, day);
594+ boolean today = isTodayDate(year, month, day);
595+ boolean holiday = isHolidayDate(year, month, day);
596+
597+ button.setFont(today ? fontToday : fontNormal);
598+ button.setBorder(selected ? BORDER_SELECTED : BORDER_NORMAL);
599+ button.setVisible( button.getText().length() != 0 );
600+ button.setEnabled(valid);
601+ button.setBorderPainted(selected);
602+ button.setContentAreaFilled(!valid);
603+ button.setForeground((c == 0 || holiday) ? Color.RED : c == 6 ? Color.BLUE : Color.BLACK);
604+ }
605+ }
606+
607+ jButtonThisMonth.setVisible(!isThisMonth);
608+ }
609+
610+ /*
611+ * 日付ボタンのテキストをクリアする
612+ */
613+ private void clearDayButtons(){
614+ for( int r=0; r<ROWNUM; r++) {
615+ for( int c=0; c<COLNUM; c++) {
616+ JButton button = jButtonDays[r][c];
617+ button.setText("");
618+ }
619+ }
620+ }
621+
622+ /*
623+ * 西暦年選択メニューを表示する
624+ */
625+ private void showYearMenu(JComponent comp, int x, int y){
626+ if (!popupMenuEnabled)
627+ return;
628+
629+ // メニューの作成
630+ JPopupMenu menu = new JPopupMenu();
631+
632+ ActionListener al = new ActionListener(){
633+ @Override
634+ public void actionPerformed(ActionEvent e) {
635+ JMenuItem item = (JMenuItem) e.getSource();
636+
637+ Matcher ma = Pattern.compile("(\\d+)年").matcher(item.getText());
638+ if (ma.find()){
639+ setYearMonth(Integer.parseInt(ma.group(1)), getSelectedMonth());
640+ updateDayButtons();
641+ }
642+ }
643+
644+ };
645+
646+ Calendar cal = new GregorianCalendar();
647+
648+ int yearSel = getSelectedYear();
649+ for (int year=yearSel-6; year<=yearSel+6; year++){
650+ JMenuItem item = new JMenuItem(year + "年");
651+ if (year == cal.get(Calendar.YEAR)){
652+ Font f = item.getFont();
653+ item.setFont(f.deriveFont(f.getStyle()|Font.BOLD));
654+ }
655+ item.addActionListener(al);
656+ menu.add(item);
657+ }
658+
659+ menu.show(comp, x, y);
660+ }
661+
662+ /*
663+ * 月選択メニューを表示する
664+ */
665+ private void showMonthMenu(JComponent comp, int x, int y){
666+ if (!popupMenuEnabled)
667+ return;
668+
669+ // メニューの作成
670+ JPopupMenu menu = new JPopupMenu();
671+
672+ ActionListener al = new ActionListener(){
673+ @Override
674+ public void actionPerformed(ActionEvent e) {
675+ JMenuItem item = (JMenuItem) e.getSource();
676+
677+ Matcher ma = Pattern.compile("(\\d+)月").matcher(item.getText());
678+ if (ma.find()){
679+ setYearMonth(getSelectedYear(), Integer.parseInt(ma.group(1)));
680+ updateDayButtons();
681+ }
682+ }
683+
684+ };
685+
686+ Calendar cal = new GregorianCalendar();
687+ int yearSel = getSelectedYear();
688+ for (int month=1; month<=12; month++){
689+ JMenuItem item = new JMenuItem(month + "月");
690+ if (yearSel == cal.get(Calendar.YEAR) && month == cal.get(Calendar.MONTH)+1){
691+ Font f = item.getFont();
692+ item.setFont(f.deriveFont(f.getStyle()|Font.BOLD));
693+ }
694+ item.addActionListener(al);
695+ menu.add(item);
696+ }
697+
698+ menu.show(comp, x, y);
699+ }
700+
701+ /*
702+ * 選択中の年月を部品に反映する
703+ */
704+ private void setYearMonth(int year, int month){
705+ if (style == Style.Spinner){
706+ jSpinnerYear.setValue(year);
707+ jSpinnerMonth.setValue(month);
708+ }
709+ else{
710+ jLabelYearMonth.setText(String.format("%d年%d月", year, month));
711+
712+ Calendar cal = new GregorianCalendar();
713+ Font f = jLabelYearMonth.getFont();
714+ if (year == cal.get(Calendar.YEAR) && month == cal.get(Calendar.MONTH)+1){
715+ jLabelYearMonth.setFont(f.deriveFont(f.getStyle()|Font.BOLD));
716+ }
717+ else{
718+ jLabelYearMonth.setFont(f.deriveFont(f.getStyle()&~Font.BOLD));
719+ }
720+ }
721+ }
722+
723+ /*
724+ * 選択中の西暦年を取得する
725+ */
726+ private int getSelectedYear(){
727+ if (style == Style.Spinner)
728+ return (int)jSpinnerYear.getValue();
729+ else{
730+ Matcher ma = Pattern.compile("(\\d+)年(\\d+)月").matcher(jLabelYearMonth.getText());
731+ return ma.find() ? Integer.parseInt(ma.group(1)) : 2000;
732+ }
733+ }
734+
735+ /*
736+ * 選択中の月を取得する
737+ */
738+ private int getSelectedMonth(){
739+ if (style == Style.Spinner)
740+ return (int)jSpinnerMonth.getValue();
741+ else{
742+ Matcher ma = Pattern.compile("(\\d+)年(\\d+)月").matcher(jLabelYearMonth.getText());
743+ return ma.find() ? Integer.parseInt(ma.group(2)) : 1;
744+ }
745+ }
746+
747+ /*
748+ * 指定された日が選択日かどうかを取得する
749+ */
750+ private boolean isSelectedDate(int year, int month, int day){
751+ if (calSelDate == null)
752+ return false;
753+
754+ return
755+ calSelDate.get(Calendar.YEAR) == year &&
756+ calSelDate.get(Calendar.MONTH) == month-1 &&
757+ calSelDate.get(Calendar.DATE) == day;
758+ }
759+
760+ /*
761+ * 指定された日が当日かどうかを取得する
762+ */
763+ private boolean isTodayDate(int year, int month, int day){
764+ Calendar cal = new GregorianCalendar();
765+
766+ return
767+ cal.get(Calendar.YEAR) == year &&
768+ cal.get(Calendar.MONTH) == month-1 &&
769+ cal.get(Calendar.DATE) == day;
770+ }
771+
772+ /*
773+ * 指定された日が有効かどうかを返す
774+ */
775+ private boolean isValidDate(int year, int month, int day){
776+ GregorianCalendar cal = new GregorianCalendar(year, month-1, day);
777+ long time = cal.getTimeInMillis();
778+
779+ if (calMinDate != null){
780+ long timeMin = calMinDate.getTimeInMillis();
781+ if (time < timeMin)
782+ return false;
783+ }
784+
785+ if (calMaxDate != null){
786+ long timeMax = calMaxDate.getTimeInMillis();
787+ if (time > timeMax)
788+ return false;
789+ }
790+
791+ return true;
792+ }
793+
794+ /*
795+ * 指定された日が祝日かどうかを返す
796+ */
797+ private boolean isHolidayDate(int year, int month, int day){
798+ if (listener != null)
799+ return listener.isHoliday(year, month, day);
800+ else
801+ return false;
802+ }
803+
804+ /*
805+ * YYYY/MM/DD形式からGregorianCalendarクラスを取得する
806+ */
807+ public static GregorianCalendar getCalendar(String date) {
808+ Matcher ma = Pattern.compile("^(\\d{4})/(\\d{1,2})/(\\d{1,2})$").matcher(date);
809+ if ( ! ma.find()) {
810+ return null;
811+ }
812+
813+ return new GregorianCalendar(
814+ Integer.valueOf(ma.group(1)),
815+ Integer.valueOf(ma.group(2))-1,
816+ Integer.valueOf(ma.group(3)),
817+ 0,
818+ 0,
819+ 0);
820+ }
821+}
\ No newline at end of file
--- /dev/null
+++ b/TinyBannavi/src/tainavi/JDateField.java
@@ -0,0 +1,459 @@
1+package tainavi;
2+
3+import java.awt.Color;
4+import java.awt.event.ActionEvent;
5+import java.awt.event.ActionListener;
6+import java.awt.event.KeyEvent;
7+import java.beans.PropertyChangeEvent;
8+import java.beans.PropertyChangeListener;
9+import java.time.Instant;
10+import java.util.Calendar;
11+import java.util.GregorianCalendar;
12+import java.util.regex.Matcher;
13+import java.util.regex.Pattern;
14+
15+import javax.accessibility.Accessible;
16+import javax.swing.AbstractAction;
17+import javax.swing.JComboBox;
18+import javax.swing.JComponent;
19+import javax.swing.JPopupMenu;
20+import javax.swing.JTextField;
21+import javax.swing.KeyStroke;
22+import javax.swing.event.DocumentEvent;
23+import javax.swing.event.DocumentListener;
24+import javax.swing.event.PopupMenuEvent;
25+import javax.swing.event.PopupMenuListener;
26+
27+/*
28+ * 日付入力部品
29+ */
30+public class JDateField extends JComboBox<String> implements CalendarListener{
31+ /*******************************************************************************
32+ * 定数
33+ ******************************************************************************/
34+
35+ /*******************************************************************************
36+ * 部品
37+ ******************************************************************************/
38+ private JTextField textField = null;
39+ private JPopupMenu popupOrg = null;
40+ private JPopupMenu popup = null;
41+ private JCalendar calendar = null;
42+
43+ /*******************************************************************************
44+ * 部品以外のメンバー
45+ ******************************************************************************/
46+ private JCalendar.Style style = JCalendar.Style.Button;
47+ private boolean allowNoYear = false;
48+ private GregorianCalendar calMinDate = null;
49+ private GregorianCalendar calMaxDate = null;
50+
51+ private Instant last_shown = null;
52+
53+ private CalendarListener listener = null;
54+
55+ /*******************************************************************************
56+ * コンストラクタ
57+ ******************************************************************************/
58+ public JDateField() {
59+ this(JCalendar.Style.Button);
60+ }
61+
62+ public JDateField(JCalendar.Style s){
63+ super();
64+
65+ style = s;
66+
67+ setEditable(true);
68+ setMaximumRowCount(0);
69+
70+ createComponents();
71+ }
72+
73+ /*******************************************************************************
74+ * 公開メソッド
75+ ******************************************************************************/
76+ /*
77+ * リスナーを登録する
78+ */
79+ public void setListener(CalendarListener l){
80+ listener = l;
81+ }
82+
83+ /*
84+ * ポップアップを表示する
85+ *
86+ * @see javax.swing.JComboBox#showPopup()
87+ */
88+ @Override
89+ public void showPopup(){
90+ setSelectedDate(getCalendar(getText()));
91+
92+ if (last_shown != null && Instant.now().minusMillis(100).isBefore(last_shown))
93+ return;
94+
95+ popup.show(this, 0, getBounds().height);
96+
97+ last_shown = Instant.now();
98+ }
99+
100+ /*
101+ * ポップアップを非表示にする
102+ *
103+ * @see javax.swing.JComboBox#hidePopup()
104+ */
105+ @Override
106+ public void hidePopup(){
107+ if (popup.isVisible())
108+ popup.setVisible(false);
109+ }
110+
111+ /*
112+ * MM/DDのみを許すかをセットする
113+ */
114+ public void setAllowNoYear(boolean b){
115+ allowNoYear = b;
116+ }
117+
118+ /*
119+ * アクションリスナーを追加する
120+ */
121+ @Override
122+ public void addActionListener(ActionListener al){
123+ textField.addActionListener(al);
124+ }
125+
126+ /*
127+ * 選択日をYYYY/MM/DD形式で指定する
128+ */
129+ public void setSelectedDate(String date){
130+ setSelectedDate(getCalendar(date));
131+ }
132+
133+ /*
134+ * 選択日をGregorianCalendarクラスで指定する
135+ */
136+ public void setSelectedDate(GregorianCalendar cal){
137+ cleanUpCalendarTime(cal);
138+ if (calendar != null)
139+ calendar.setSelectedDate(cal);
140+ }
141+
142+ /*
143+ * 有効な最小日をYYYY/MM/DD形式で指定する
144+ */
145+ public void setMinDate(String date){
146+ setMinDate(getCalendar(date));
147+ }
148+
149+ /*
150+ * 有効な最小日をGregorianCalendarクラスで指定する
151+ */
152+ public void setMinDate(GregorianCalendar cal){
153+ cleanUpCalendarTime(cal);
154+ calMinDate = cal;
155+ if (calendar != null)
156+ calendar.setMinDate(cal);
157+ }
158+
159+ /*
160+ * 有効な最大日をYYYY/MM/DD形式で指定する
161+ */
162+ public void setMaxDate(String date){
163+ setMaxDate(getCalendar(date));
164+ }
165+
166+ /*
167+ * 有効な最大日をGregorianCalendarクラスで指定する
168+ */
169+ public void setMaxDate(GregorianCalendar cal){
170+ cleanUpCalendarTime(cal);
171+ calMaxDate = cal;
172+ if (calendar != null)
173+ calendar.setMaxDate(cal);
174+ }
175+
176+ /*
177+ * テキストを取得する
178+ */
179+ public String getText(){
180+ return textField.getText();
181+ }
182+
183+ /*
184+ * テキストをセットする
185+ */
186+ public void setText(String s){
187+ setSelectedItem(s);
188+ setSelectedDate(getCalendar(s));
189+ }
190+
191+ /*
192+ * 有効な日付を持っているか
193+ */
194+ public boolean hasValidDate(){
195+ return isValidDate(textField.getText());
196+ }
197+
198+ /*******************************************************************************
199+ * コンポーネント
200+ ******************************************************************************/
201+ /*
202+ * コンポーネントを作成する
203+ */
204+ private void createComponents(){
205+ // オリジナルのポップアップを取得する
206+ Accessible accessible = this.getUI().getAccessibleChild(this, 0);
207+ if (accessible instanceof JPopupMenu ){
208+ popupOrg = (JPopupMenu) accessible;
209+ popupOrg.addPropertyChangeListener(pcl_popupOrg);
210+ }
211+
212+ // テキストフィールドを取得する
213+ try{
214+// buttonArrow = (JButton)this.getComponent(0);
215+ textField = (JTextField) this.getEditor().getEditorComponent();
216+ textField.getDocument().addDocumentListener(dl_textField);
217+ }
218+ catch(ClassCastException e){
219+
220+ }
221+
222+ // カレンダーのポップアップを作成する
223+ calendar = new JCalendar(style, false);
224+ calendar.setListener(this);
225+
226+ String ESCKEYACTION = "escape";
227+ calendar.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
228+ .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ESCKEYACTION);
229+ calendar.getActionMap().put(ESCKEYACTION, aa_calendarEscape);
230+
231+ popup = new JPopupMenu();
232+ popup.add(calendar);
233+ popup.addPopupMenuListener(pml_popup);
234+ }
235+
236+ /*******************************************************************************
237+ * リスナー
238+ ******************************************************************************/
239+ /*
240+ * オリジナルのポップアップのプロパティ変更リスナー
241+ */
242+ private PropertyChangeListener pcl_popupOrg = new PropertyChangeListener(){
243+ @Override
244+ public void propertyChange(PropertyChangeEvent evt) {
245+ // オリジナルが表示されたら強制的に非表示にして、カレンダーをポップアップする
246+ if (evt.getPropertyName().equals("visible") && evt.getNewValue() == Boolean.TRUE){
247+ popupOrg.setVisible(false);
248+ showPopup();
249+ }
250+ }
251+ };
252+
253+ /*
254+ * カレンダーのポップアップのリスナー
255+ */
256+ private PopupMenuListener pml_popup = new PopupMenuListener(){
257+ @Override
258+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
259+ }
260+
261+ @Override
262+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
263+ // 非表示になる時に時刻を保存する
264+ last_shown = Instant.now();
265+ }
266+
267+ @Override
268+ public void popupMenuCanceled(PopupMenuEvent e) {
269+ }
270+ };
271+
272+ /*
273+ * テキストフィールドの文書変更リスナー
274+ *
275+ * 文書が変更されたら前景色を更新してポップアップを非表示にする
276+ */
277+ private DocumentListener dl_textField = new DocumentListener(){
278+ @Override
279+ public void insertUpdate(DocumentEvent e) {
280+ updateTextFieldForeground();
281+ hidePopup();
282+ }
283+
284+ @Override
285+ public void removeUpdate(DocumentEvent e) {
286+ updateTextFieldForeground();
287+ hidePopup();
288+ }
289+
290+ @Override
291+ public void changedUpdate(DocumentEvent e) {
292+ updateTextFieldForeground();
293+ hidePopup();
294+ }
295+ };
296+
297+ /*
298+ * カレンダーの時刻変更リスナー
299+ *
300+ * @see tainavi.CalendarListener#notifyDateChange(tainavi.JCalendar, java.lang.String)
301+ */
302+ @Override
303+ public void notifyDateChange(JCalendar cal, String date) {
304+ // テキストをセットしてポップアップを非表示にする
305+ setText(date);
306+ hidePopup();
307+
308+ if (listener != null)
309+ listener.notifyDateChange(cal, date);
310+ }
311+
312+ /*
313+ * カレンダーの祝日取得リスナー
314+ * @see tainavi.CalendarListener#isHoliday(int, int, int)
315+ */
316+ @Override
317+ public boolean isHoliday(int year, int month, int day) {
318+ if (listener != null)
319+ return listener.isHoliday(year, month, day);
320+ else
321+ return false;
322+ }
323+
324+ /*
325+ * カレンダーをESCキーで抜けるための仮想アクション
326+ */
327+ private AbstractAction aa_calendarEscape = new AbstractAction(){
328+ @Override
329+ public void actionPerformed(ActionEvent e) {
330+ hidePopup();
331+ }
332+ };
333+
334+ /*******************************************************************************
335+ * 内部関数
336+ ******************************************************************************/
337+ /*
338+ * テキストフィールドの前景色を更新する
339+ */
340+ private void updateTextFieldForeground(){
341+ textField.setForeground(isValidDate((textField.getText())) ? Color.BLACK : Color.RED);
342+ }
343+
344+ /*
345+ * 指定された文字列が有効な日付かどうかを返す
346+ */
347+ private boolean isValidDate(String s){
348+ if (s == null)
349+ return false;
350+ if (s.isEmpty())
351+ return true;
352+
353+ // YYYY/MM/DD形式であること
354+ GregorianCalendar cal = getCalendar(s);
355+ if (cal == null)
356+ return false;
357+
358+ // 最小値、最大値の間にあること
359+ if (!isValidDateRange(cal))
360+ return false;
361+
362+ return true;
363+ }
364+
365+ /*
366+ * 指定された日が最小値、最大値の間にあるかを返す
367+ */
368+ private boolean isValidDateRange(GregorianCalendar cal){
369+ long time = cal.getTimeInMillis();
370+
371+ if (calMinDate != null){
372+ long timeMin = calMinDate.getTimeInMillis();
373+ if (time < timeMin)
374+ return false;
375+ }
376+
377+ if (calMaxDate != null){
378+ long timeMax = calMaxDate.getTimeInMillis();
379+ if (time > timeMax)
380+ return false;
381+ }
382+
383+ return true;
384+ }
385+
386+ /*
387+ * カレンダーの時分秒をクリアする
388+ */
389+ private void cleanUpCalendarTime(GregorianCalendar cal){
390+ if (cal == null)
391+ return;
392+
393+ cal.set(Calendar.HOUR, 0);
394+ cal.set(Calendar.MINUTE, 0);
395+ cal.set(Calendar.SECOND, 0);
396+ cal.set(Calendar.MILLISECOND, 0);
397+ }
398+
399+ /*
400+ * YYYY/MM/DD, MM/DD形式からGregorianCalendarクラスを取得する
401+ */
402+ private GregorianCalendar getCalendar(String date) {
403+ if (date == null)
404+ return null;
405+
406+ GregorianCalendar cal = new GregorianCalendar();
407+
408+ int year, month, day;
409+
410+ // YYYY/MM/DD形式
411+ Matcher ma = Pattern.compile("^(\\d{4})/(\\d{2})/(\\d{2})$").matcher(date);
412+ if ( ma.find()) {
413+ year = Integer.parseInt(ma.group(1));
414+ month = Integer.parseInt(ma.group(2));
415+ day = Integer.parseInt(ma.group(3));
416+ }
417+ else{
418+ if (!allowNoYear)
419+ return null;
420+
421+ // MM/DD形式
422+ Matcher mb = Pattern.compile("^(\\d{2})/(\\d{2})$").matcher(date);
423+ if (mb.find()){
424+ year = cal.get(Calendar.YEAR);
425+ month = Integer.parseInt(mb.group(1));
426+ day = Integer.parseInt(mb.group(2));
427+ }
428+ else
429+ return null;
430+ }
431+
432+ // 年、月、日の値が有効な範囲であること
433+ if (!isValidDate(year, month, day))
434+ return null;
435+
436+ cal.set(Calendar.YEAR, year);
437+ cal.set(Calendar.MONTH, month-1);
438+ cal.set(Calendar.DATE, day);
439+ cleanUpCalendarTime(cal);
440+
441+ return cal;
442+ }
443+
444+ /*
445+ * 年、月、日の値が有効な範囲であるかを返す
446+ */
447+ private boolean isValidDate(int year, int month, int day){
448+ if (year < 1900 || year > 2100)
449+ return false;
450+ if (month < 1 || month > 12)
451+ return false;
452+
453+ GregorianCalendar cal = new GregorianCalendar(year, month-1, 1, 0, 0, 0);
454+ if (day < 1 || day > cal.getActualMaximum(Calendar.DATE))
455+ return false;
456+
457+ return true;
458+ }
459+}
\ No newline at end of file
--- a/TinyBannavi/src/tainavi/JEscCancelDialog.java
+++ b/TinyBannavi/src/tainavi/JEscCancelDialog.java
@@ -10,23 +10,23 @@ import javax.swing.JDialog;
1010 import javax.swing.KeyStroke;
1111
1212 abstract class JEscCancelDialog extends JDialog {
13-
13+
1414 private static final long serialVersionUID = 1L;
1515
1616 //
17- abstract protected void doCancel();
18-
17+ abstract protected void doCancel();
18+
1919 //
2020 private static final String ESCKEYACTION = "escape";
21-
21+
2222 //
2323 public JEscCancelDialog() {
2424 super();
25-
26- InputMap imap = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
25+
26+ InputMap imap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
2727 imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ESCKEYACTION);
2828 getRootPane().getActionMap().put(ESCKEYACTION, new AbstractAction() {
29-
29+
3030 private static final long serialVersionUID = 1L;
3131
3232 @Override
--- /dev/null
+++ b/TinyBannavi/src/tainavi/JSearchWordComboBox.java
@@ -0,0 +1,67 @@
1+package tainavi;
2+
3+import java.awt.Component;
4+
5+import javax.swing.DefaultListCellRenderer;
6+import javax.swing.JComponent;
7+import javax.swing.JList;
8+
9+/*
10+ * 検索キーワード用コンボボックス
11+ */
12+public class JSearchWordComboBox extends JComboBoxWithPopup {
13+
14+ /*******************************************************************************
15+ * 定数
16+ ******************************************************************************/
17+
18+ /*******************************************************************************
19+ * 部品
20+ ******************************************************************************/
21+ private SearchWordList swlist = null;
22+
23+ /*******************************************************************************
24+ * コンストラクタ
25+ ******************************************************************************/
26+ @SuppressWarnings("unchecked")
27+ public JSearchWordComboBox(SearchWordList list) {
28+ super();
29+
30+ swlist = list;
31+
32+ this.setMaximumRowCount(32);
33+ this.setRenderer(new DefaultListCellRenderer() {
34+ @Override public Component getListCellRendererComponent(
35+ JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
36+ Component c = super.getListCellRendererComponent(
37+ list, value, index, isSelected, cellHasFocus);
38+ if (index > 0){
39+ SearchWordItem swi = swlist.getWordList().get(index-1);
40+ if (c instanceof JComponent) {
41+ ((JComponent) c).setToolTipText(swi.formatAsHTML(false));
42+ }
43+ }
44+ return c;
45+ }
46+ });
47+ }
48+
49+ /*******************************************************************************
50+ * static 公開メソッド
51+ ******************************************************************************/
52+ /*******************************************************************************
53+ * 公開メソッド
54+ ******************************************************************************/
55+
56+ /*******************************************************************************
57+ * コンポーネント
58+ ******************************************************************************/
59+
60+ /*******************************************************************************
61+ * リスナー
62+ ******************************************************************************/
63+
64+ /*******************************************************************************
65+ * 内部関数
66+ ******************************************************************************/
67+}
\ No newline at end of file
--- a/TinyBannavi/src/tainavi/ListedColumnInfoList.java
+++ b/TinyBannavi/src/tainavi/ListedColumnInfoList.java
@@ -52,6 +52,23 @@ public class ListedColumnInfoList extends ArrayList<ListColumnInfo> implements C
5252 }
5353
5454 /*
55+ * IDを指定してインデックスを取得する
56+ *
57+ * @param id 列のID
58+ * @return 列のインデックス
59+ */
60+ public int getIndexById(int id){
61+ int idx = 0;
62+ for (ListColumnInfo li : this){
63+ if (li.getId() == id)
64+ return idx;
65+ idx++;
66+ }
67+
68+ return -1;
69+ }
70+
71+ /*
5572 * JTableに指定する列名の配列を作成する
5673 */
5774 public String [] getColNames(){
--- a/TinyBannavi/src/tainavi/SearchKey.java
+++ b/TinyBannavi/src/tainavi/SearchKey.java
@@ -3,8 +3,10 @@ package tainavi;
33 import java.util.ArrayList;
44 import java.util.regex.Pattern;
55
6+import tainavi.TVProgram.ProgSubgenre;
67
7-public class SearchKey implements SearchItem {
8+
9+public class SearchKey implements SearchItem, Cloneable {
810
911 private String label;
1012
@@ -79,6 +81,15 @@ public class SearchKey implements SearchItem {
7981 }
8082 }
8183
84+ @Override
85+ public Object clone() {
86+ try {
87+ return super.clone();
88+ } catch (CloneNotSupportedException e) {
89+ throw new InternalError(e.toString());
90+ }
91+ }
92+
8293 private String target;
8394
8495 // s\t..:キーワード
@@ -101,6 +112,9 @@ public class SearchKey implements SearchItem {
101112 // 番組追跡表示あり
102113 private boolean showInStandby = true;
103114
115+ // リストのソート順 列名+1:ASC/2:DESC
116+ private String sortBy = null;
117+
104118 // 正規表現はプリコンパイルしておくべきだ!
105119 ArrayList<TargetId> alTarget = new ArrayList<TargetId>();
106120 ArrayList<Pattern> alKeyword_regex = new ArrayList<Pattern>();
@@ -139,6 +153,9 @@ public class SearchKey implements SearchItem {
139153 public void setShowInStandby(boolean b) { showInStandby = b; }
140154 public boolean getShowInStandby() { return showInStandby; }
141155
156+ public void setSortBy(String s){ sortBy = s; }
157+ public String getSortBy(){ return sortBy; }
158+
142159 // interface
143160
144161 @Override
@@ -152,4 +169,116 @@ public class SearchKey implements SearchItem {
152169 public ArrayList<ProgDetailList> getMatchedList() { return _matched; }
153170 @Override
154171 public boolean isMatched() { return _matched != null && _matched.size() != 0; }
172+ @Override
173+ public boolean equals(Object o){
174+ SearchKey k = (SearchKey)o;
175+
176+ return compare(k.getTarget(), getTarget()) &&
177+ compare(k.getKeyword(), getKeyword()) &&
178+ compare(k.getContain(), getContain()) &&
179+ compare(k.getInfection(), getInfection()) &&
180+ k.getCaseSensitive() == getCaseSensitive() &&
181+ k.getShowInStandby() == getShowInStandby();
182+ }
183+
184+ /*
185+ * nullと空文字列を同一視して文字列を比較する
186+ *
187+ * @param s1 文字列1
188+ * @param s2 文字列2
189+ * @return 同一の場合true
190+ */
191+ private boolean compare(String s1, String s2){
192+ if (s1 == null)
193+ s1 = "";
194+ if (s2 == null)
195+ s2 = "";
196+
197+ return s1.equals(s2);
198+ }
199+
200+ /*
201+ * 内容をHTML形式に整形する
202+ *
203+ * @param noTag 前後に<HTML>を付けない
204+ * @return 整形したテキスト
205+ */
206+ public String formatAsHTML(boolean noTag){
207+ if (getTarget() == null)
208+ return "";
209+
210+ StringBuilder sb = new StringBuilder(noTag ? "" : "<HTML");
211+
212+ String [] ts = getTarget().split("\t");
213+ String [] rs = getKeyword().split("\t");
214+ String [] cs = getContain().split("\t");
215+
216+ StringBuilder fsb = new StringBuilder("");
217+ String from = null;
218+ String to = null;
219+
220+ for (int n=0; n<ts.length; n++){
221+ switch(ts[n]){
222+ case "0": // TITLEANDDETAIL ("0", true, true, "番組名、内容に"),
223+ sb.append("キーワード:<B>" + rs[n] + "</B><BR>");
224+ break;
225+ case "1": // TITLE ("1", true, true, "番組名に"),
226+ sb.append("キーワード:<B>" + rs[n] + "</B> (番組名一致)<BR>");
227+ break;
228+ case "2": // DETAIL ("2", true, true, "番組内容に"),
229+ sb.append("キーワード:<B>" + rs[n] + "</B> (番組内容一致)<BR>");
230+ break;
231+ case "3": // CHANNEL ("3", true, true, "チャンネル名に"),
232+ sb.append("チャンネル名:<B>" + rs[n] + "</B><BR>");
233+ break;
234+ case "4": // GENRE ("4", false, true, "ジャンルに"),
235+ sb.append("ジャンル:<B>" + rs[n] + "</B><BR>");
236+ break;
237+ case "5": // NEW ("5", false, false, "新番組"),
238+ fsb.append("新");
239+ break;
240+ case "6": // LAST ("6", false, false, "最終回"),
241+ fsb.append("終");
242+ break;
243+ case "7": // REPEAT ("7", false, false, "再放送"),
244+ fsb.append("再");
245+ break;
246+ case "8": // FIRST ("8", false, false, "初回放送"),
247+ fsb.append("初");
248+ break;
249+ case "9": // LENGTH ("9", false, true, "番組長が"),
250+ if (cs[n].equals("0"))
251+ from = rs[n].trim();
252+ else if (cs[n].equals("1")){
253+ try{
254+ to = String.valueOf(Integer.parseInt(rs[n].trim())-1);
255+ }catch(NumberFormatException e){
256+ to = rs[n].trim();
257+ }
258+ }
259+ break;
260+ // STARTA ("10", false, true, "開始時刻(上限)が"),
261+ // STARTZ ("11", false, true, "開始時刻(下限)が"),
262+ case "12": // SPECIAL ("12", false, false, "特番"),
263+ fsb.append("特");
264+ break;
265+ case "15": // SUBGENRE ("15", false, true, "サブジャンルに"),
266+ ProgSubgenre subgenre = ProgSubgenre.get(rs[n]);
267+ sb.append("サブジャンル:<B>" + (subgenre != null ? subgenre.toString() : "") + "</B><BR>");
268+ break;
269+ }
270+ }
271+
272+ if (from != null || to != null){
273+ sb.append("番組長:<B>" + (from != null ? from + "分" : "") + "~" + (to != null ? to + "分" : "") + "</B><BR>");
274+ }
275+ if (fsb.length() > 0){
276+ sb.append("フラグ:<B>" + fsb.toString() + "</B><BR>");
277+ }
278+
279+ if (!noTag)
280+ sb.append("</HTML>");
281+
282+ return sb.toString();
283+ }
155284 }
--- a/TinyBannavi/src/tainavi/SearchProgram.java
+++ b/TinyBannavi/src/tainavi/SearchProgram.java
@@ -20,10 +20,10 @@ public class SearchProgram {
2020
2121 private String searchKeyFile = null;
2222 protected void setSearchKeyFile(String s) { searchKeyFile = s; }
23-
23+
2424 private String searchKeyLabel = null;
2525 protected void setSearchKeyLabel(String s) { searchKeyLabel = s; }
26-
26+
2727 // 設定ファイルに書き出し
2828 public boolean save() {
2929 System.out.println(searchKeyLabel+"設定を保存します: "+searchKeyFile);
@@ -33,7 +33,7 @@ public class SearchProgram {
3333 }
3434 return true;
3535 }
36-
36+
3737 // 設定ファイルから読み出し
3838 @SuppressWarnings("unchecked")
3939 public void load() {
@@ -55,21 +55,21 @@ public class SearchProgram {
5555 }
5656 }
5757 }
58-
58+
5959 // 検索用
6060 public ArrayList<SearchKey> getSearchKeys() {
6161 return(searchKeys);
6262 }
63-
63+
6464 // キーワード検索の追加
6565 public void add(SearchKey newkey) {
6666 searchKeys.add(newkey);
6767 }
68-
68+
6969 public void add(int index, SearchKey newkey) {
7070 searchKeys.add(index, newkey);
7171 }
72-
72+
7373 // キーワード検索の削除
7474 public void remove(String key) {
7575 for ( SearchKey k : searchKeys ) {
@@ -79,7 +79,7 @@ public class SearchProgram {
7979 }
8080 }
8181 }
82-
82+
8383 // キーワード検索の置き換え
8484 public SearchKey replace(SearchKey xk, SearchKey sk) {
8585 int index = searchKeys.indexOf(xk);
@@ -90,22 +90,25 @@ public class SearchProgram {
9090 }
9191 return null ;
9292 }
93-
93+
9494 // キーワード検索の整理
9595 public void compile(SearchKey keyword) {
96+ if (keyword == null || keyword.getTarget() == null)
97+ return;
98+
9699 //
97100 keyword.alTarget = new ArrayList<TargetId>();
98101 keyword.alKeyword_regex = new ArrayList<Pattern>();
99102 keyword.alKeyword = new ArrayList<String>();
100103 keyword.alContain = new ArrayList<String>();
101-
104+
102105 //
103106 ArrayList<String> sTtmp = new ArrayList<String>();
104107 ArrayList<String> mRtmp = new ArrayList<String>();
105108 ArrayList<String> sCtmp = new ArrayList<String>();
106-
109+
107110 Matcher ma = null;
108-
111+
109112 //
110113 ma = Pattern.compile("(.*?)\t").matcher(keyword.getTarget());
111114 while (ma.find()) {
@@ -121,7 +124,7 @@ public class SearchProgram {
121124 while (ma.find()) {
122125 sCtmp.add(String.valueOf(ma.group(1)));
123126 }
124-
127+
125128 // 「含む」を前に、「除く」を後に
126129 for (int x=0; x<sTtmp.size(); x++) {
127130 if (sCtmp.get(x).equals("0")) {
@@ -135,7 +138,7 @@ public class SearchProgram {
135138 }
136139 }
137140 private void setAlKeyword(SearchKey keyword, int x, String sT, String mR, String sC) {
138-
141+
139142 TargetId ti = TargetId.getTargetId(sT);
140143 keyword.alTarget.add(ti);
141144
@@ -159,7 +162,7 @@ public class SearchProgram {
159162 keyword.alKeyword_regex.add(null);
160163 break;
161164 }
162-
165+
163166 // 数値なターゲット
164167 switch (ti) {
165168 case LENGTH:
@@ -175,7 +178,7 @@ public class SearchProgram {
175178 keyword.alLength.add(0);
176179 break;
177180 }
178-
181+
179182 // 時刻なターゲット
180183 switch (ti) {
181184 case STARTA:
@@ -195,14 +198,14 @@ public class SearchProgram {
195198 keyword.alKeyword_plane.add(mR);
196199 break;
197200 }
198-
201+
199202 keyword.alKeyword.add(TraceProgram.replacePop(mR));
200203 keyword.alKeyword_pop.add(TraceProgram.replacePop(mR));
201204 keyword.alContain.add(sC);
202205 }
203-
204-
205-
206+
207+
208+
206209 //
207210 private static int compareDT(String target, String limit)
208211 {
@@ -217,10 +220,10 @@ public class SearchProgram {
217220 return 0;
218221 }
219222 }
220-
223+
221224 private static String matchedString = null;
222225 public static String getMatchedString() { return matchedString; }
223-
226+
224227 public static boolean isMatchKeyword(SearchKey keyword, String center, ProgDetailList tvd)
225228 {
226229 //
@@ -231,7 +234,7 @@ public class SearchProgram {
231234 //ArrayList<String> mRpop = keyword.alKeyword_pop;
232235 ArrayList<String> sC = keyword.alContain;
233236 ArrayList<Integer> sL = keyword.alLength;
234-
237+
235238 matchedString = null;
236239
237240 //
@@ -241,7 +244,7 @@ public class SearchProgram {
241244 //
242245 boolean isCurMatch = false;
243246 TargetId ti = sT.get(x);
244-
247+
245248 // 0:"番組名、内容に"
246249 // 1:"番組名に"
247250 if (ti == TargetId.TITLE || ti == TargetId.TITLEANDDETAIL) {
@@ -282,7 +285,7 @@ public class SearchProgram {
282285 }
283286 }
284287 }
285-
288+
286289 // 0:"番組名、内容に"
287290 // 2:"番組内容に"
288291 if (ti == TargetId.DETAIL || ti == TargetId.TITLEANDDETAIL) {
@@ -301,8 +304,10 @@ public class SearchProgram {
301304
302305 switch ( ti ) {
303306 case CHANNEL: // 3
307+ if (center == null)
308+ center = "";
304309 if (keyword.getCaseSensitive() == false) {
305- mx = mRe.get(x).matcher(center);
310+ mx = mRe.get(x).matcher(TraceProgram.replacePop(center));
306311 if (mx.find()) {
307312 isCurMatch = true;
308313 }
@@ -313,7 +318,7 @@ public class SearchProgram {
313318 }
314319 }
315320 break;
316-
321+
317322 case GENRE: //4,15
318323 ProgGenre gr = ProgGenre.get(mRp.get(x));
319324 if ( gr != null ) {
@@ -326,7 +331,7 @@ public class SearchProgram {
326331 isCurMatch = tvd.isEqualsGenre(null, sgr);
327332 }
328333 break;
329-
334+
330335 case NEW: // 5,6,7,8,12,13
331336 if (tvd.flag == ProgFlags.NEW) {
332337 isCurMatch = true;
@@ -406,7 +411,7 @@ public class SearchProgram {
406411 }
407412 }
408413 break;
409-
414+
410415 case LENGTH: // 9
411416 if (tvd.length >= sL.get(x)) {
412417 isCurMatch = true;
@@ -429,12 +434,12 @@ public class SearchProgram {
429434 isCurMatch = true;
430435 }
431436 break;
432-
437+
433438 default:
434439 break;
435440 }
436-
437-
441+
442+
438443 //
439444 if (keyword.getCondition().equals("0")) {
440445 // 0:次のすべての条件に一致
@@ -455,16 +460,16 @@ public class SearchProgram {
455460 }
456461 else if (sC.get(x).equals("1") && isCurMatch == true) {
457462 // 1:"を含む番組を除く"
458-
463+
459464 // ★★★ 特殊動作注意!番ナビスレ Part.11 No.274付近参照 ★★★
460465 // 「含む ∪ 含む ∪ 含まない ∪ 含まない」ではなく
461466 // 「( 含む ∪ 含む ) ∩ ~(含まない ∪ 含まない)」となる
462-
467+
463468 return(false);
464469 }
465470 }
466471 }
467-
472+
468473 if (keyword.getCondition().equals("0")) {
469474 // 0:次のすべての条件に一致で、すべての条件に適合した場合
470475 return(true);
@@ -474,9 +479,9 @@ public class SearchProgram {
474479 return(isOrMatch);
475480 }
476481 }
477-
478-
479-
482+
483+
484+
480485 // コンストラクタ
481486 public SearchProgram() {
482487 setSearchKeyFile("env"+File.separator+"keyword.xml");
--- a/TinyBannavi/src/tainavi/SearchWordItem.java
+++ b/TinyBannavi/src/tainavi/SearchWordItem.java
@@ -1,5 +1,8 @@
11 package tainavi;
22
3+import java.util.regex.Matcher;
4+import java.util.regex.Pattern;
5+
36 /**
47 * <P>ツールバーの検索キーワードを保持するクラスです。
58 * @since 3.22.18β+1.10
@@ -9,36 +12,178 @@ public class SearchWordItem implements Cloneable {
912 @Override
1013 public Object clone() {
1114 try {
12- return super.clone();
15+ SearchWordItem swi = (SearchWordItem)super.clone();
16+ if (search != null)
17+ swi.search = (SearchKey)this.search.clone();
18+
19+ return swi;
1320 } catch (CloneNotSupportedException e) {
1421 throw new InternalError(e.toString());
1522 }
1623 }
1724
1825 private String keyword;
26+ private SearchKey search;
27+ private String label;
28+ private String from;
29+ private String to;
1930 private int count;
2031 private String last_used_time;
2132
2233 public SearchWordItem(){
2334 this.keyword = null;
35+ this.search = null;
36+ this.label = null;
37+ this.from = null;
38+ this.to = null;
2439 this.count = 0;
2540 this.last_used_time = null;
2641 }
2742
28- public SearchWordItem(String s) {
29- this.keyword = s;
43+ public SearchWordItem(String keyword) {
44+ this(keyword, keyword, null, null, null);
45+ }
46+
47+ public SearchWordItem(String label, String keyword, SearchKey search, String from, String to) {
48+ this.label = label;
49+ this.keyword = keyword;
50+ this.search = search;
51+ this.from = from;
52+ this.to = to;
3053 this.count = 1;
3154 this.last_used_time = CommonUtils.getDateTimeYMD(0);
3255 }
3356
57+ /*
58+ * 内容を比較する
59+ *
60+ * @param si 検索キーワード
61+ * @param fi 日付の下限
62+ * @param ti 日付の上限
63+ * @return 同一の場合trueを返す
64+ */
65+ public boolean equals(String si, SearchKey ki, String fi, String ti){
66+ return
67+ compare(si, keyword) &&
68+ compare(fi, from) &&
69+ compare(ti, to) &&
70+ compare(ki, search);
71+ }
72+
73+ /*
74+ * nullと空文字列を同一視して文字列を比較する
75+ * @param s1 文字列1
76+ * @param s2 文字列2
77+ * @return 同一の場合trueを返す
78+ */
79+ private boolean compare(String s1, String s2){
80+ if (s1 == null)
81+ s1 = "";
82+ if (s2 == null)
83+ s2 = "";
84+
85+ return s1.equals(s2);
86+ }
87+
88+ /*
89+ * 検索キーを比較する
90+ * @param s1 検索キー1
91+ * @param s2 検索キー2
92+ * @return 同一の場合trueを返す
93+ */
94+ private boolean compare(SearchKey s1, SearchKey s2){
95+ return (s1 == null && s2 == null) || (s1 != null && s2 != null && s1.equals(s2));
96+ }
97+
98+ /*
99+ * 内容をHTML形式で整形する
100+ *
101+ * @param noTag 前後に<HTML>タグを追加しない
102+ * @return 生成したテキスト
103+ */
104+ public String formatAsHTML(boolean noTag){
105+ StringBuilder sb = new StringBuilder(noTag ? "" : "<HTML>");
106+ String f = from;
107+ String t = to;
108+
109+ if (search != null){
110+ sb.append(search.formatAsHTML(true));
111+ }
112+
113+ // 過去ログ検索
114+ if (keyword != null){
115+ String str = keyword;
116+ Matcher ma = Pattern.compile("^(\\d\\d\\d\\d/)?(\\d\\d/\\d\\d)([  ]+((\\d\\d\\d\\d/)?\\d\\d/\\d\\d))?[  ]?(.*)$").matcher(str);
117+ if (ma.find()) {
118+ f = (ma.group(1) != null ? ma.group(1) : "") + ma.group(2);
119+ t = ma.group(4);
120+ str = ma.group(6);
121+ }
122+
123+ // オプションあり
124+ String option = "";
125+ Matcher mc = Pattern.compile("^(#title|#detail)?[  ]+(.*)$").matcher(str);
126+ if (mc.find()){
127+ if (mc.group(1).equals("#title"))
128+ option = "(番組名一致)";
129+ else if (mc.group(1).equals("#detail"))
130+ option = "(番組内容一致)";
131+ str = mc.group(2);
132+ }
133+
134+ if (str.length() > 0 && !sb.toString().contains("キーワード:"))
135+ sb.append("キーワード:<B>" + str + "</B> " + option + "<BR>");
136+ }
137+
138+ if ((f != null && !f.isEmpty()) || (t != null && !t.isEmpty())){
139+ sb.append("放送日:<B>" + (f != null ? f : "") + "~" + (t != null ? t : "") + "</B><BR>");
140+ }
141+
142+ sb.append("検索回数:" + count + "<BR>");
143+ sb.append("直近検索日時:" + formatLastUsedTime(last_used_time) + "<BR>");
144+
145+ if (!noTag)
146+ sb.append("</HTML>");
147+
148+ return sb.toString();
149+ }
150+
151+ /*
152+ * 最終検索日時を YYYY/MM/DD hh:mm:ss 形式に整形する
153+ *
154+ * @param time 最終検索日時
155+ * @return 整形したテキスト
156+ */
157+ private String formatLastUsedTime(String time){
158+ Matcher ma = Pattern.compile("(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})").matcher(time);
159+ if (ma.find()){
160+ return ma.group(1) + "/" + ma.group(2) + "/" + ma.group(3) + " " +
161+ ma.group(4) + ":" + ma.group(5) + ":" + ma.group(6);
162+ }
163+
164+ return "";
165+ }
166+
34167 public void notifyUse(){
35168 this.count++;
36169 this.last_used_time = CommonUtils.getDateTimeYMD(0);
37170 }
38171
172+ public void setLabel(String s){ this.label = s; }
173+ public String getLabel(){ return this.label != null ? this.label : this.keyword; }
174+
39175 public void setKeyword(String s){ this.keyword = s; }
40176 public String getKeyword() { return this.keyword; }
41177
178+ public void setSearchKey(SearchKey k){ this.search = k; }
179+ public SearchKey getSearchKey(){ return this.search; }
180+
181+ public void setFrom(String from){ this.from = from; }
182+ public String getFrom(){ return this.from; }
183+
184+ public void setTo(String to){ this.to = to; }
185+ public String getTo(){ return this.to; }
186+
42187 public void setCount(int n){ this.count = n; }
43188 public int getCount(){ return this.count; }
44189
--- a/TinyBannavi/src/tainavi/SearchWordList.java
+++ b/TinyBannavi/src/tainavi/SearchWordList.java
@@ -3,6 +3,8 @@ package tainavi;
33 import java.io.File;
44 import java.util.ArrayList;
55 import java.util.Comparator;
6+import java.util.regex.Matcher;
7+import java.util.regex.Pattern;
68
79 /*
810 * <P>ツールバーの検索キーワードのリストを保持するクラスです。
@@ -19,7 +21,7 @@ public class SearchWordList {
1921
2022 private final String MSGID = "[検索ワード] ";
2123 private final String ERRID = "[ERROR]"+MSGID;
22- private final int maxwords = 128;
24+ private final int MAXWORDS = 128;
2325
2426 /*
2527 * 部品
@@ -28,26 +30,77 @@ public class SearchWordList {
2830
2931 public int size() { return list.size(); }
3032
31- public boolean add(String s){
33+ /*
34+ * 検索キーワードを追加する
35+ *
36+ * @param s 検索キーワード
37+ * @param k 検索キー
38+ */
39+ public boolean add(String s, SearchKey k){
40+ return add(s, s, k, null, null);
41+ }
42+
43+ /*
44+ * 検索キーワードを追加する
45+ *
46+ * @param l0 検索キーワードの名称
47+ * @param s 検索キーワード
48+ * @param k 検索キー
49+ * @param f 日付の下限
50+ * @param t 日付の上限
51+ */
52+ public boolean add(String l0, String s, SearchKey k, String f, String t){
53+ if (s == null)
54+ return false;
55+
56+ // 同じ内容の検索キーワードがあったら検索回数を増やす
3257 for (SearchWordItem item : list){
33- if (item.getKeyword().equals(s)){
58+
59+ if (item.equals(s, k, f, t)){
3460 item.notifyUse();
3561 sortList();
3662 return true;
3763 }
3864 }
3965
40- SearchWordItem item = new SearchWordItem(s);
66+ // 名称未指定の場合
67+ if (l0.isEmpty() )
68+ l0 = "(キーワードなし)";
69+
70+ // (n)のサフィックスがある場合は取る
71+ int count=0;
72+ Matcher ma = Pattern.compile("^(.*)\\((\\d+)\\)$").matcher(l0);
73+ if (ma.find()){
74+ l0 = ma.group(1);
75+ count = Integer.parseInt(ma.group(2));
76+ }
77+
78+ String l = l0;
79+
80+ // 名称が被らなくなるまでサフィックスを増やす
81+ while(true){
82+ SearchWordItem item = getItemFromLabel(l);
83+ if (item == null)
84+ break;
85+
86+ count++;
87+ l = l0 + "(" + count + ")";
88+ }
89+
90+ // 追加する
91+ SearchWordItem item = new SearchWordItem(l, s, k, f, t);
4192 list.add(item);
4293 sortList();
4394
44- while (list.size() > maxwords){
95+ // 上限を超えたら古いものから削除する
96+ while (list.size() > MAXWORDS){
4597 list.remove(list.size()-1);
4698 }
4799
48100 return true;
49101 }
50102
103+
51104 /**
52105 * 検索ワードの一覧を返す
53106 */
@@ -87,6 +140,26 @@ public class SearchWordList {
87140 return true;
88141 }
89142
143+ /*
144+ * 名称から検索キーワードを取得する
145+ *
146+ * @param label 名称
147+ * @return 見つかった検索キーワード
148+ */
149+ public SearchWordItem getItemFromLabel(String label){
150+ for (int n=0; n<list.size(); n++){
151+ SearchWordItem item = list.get(n);
152+ if (item.getLabel().equals(label)){
153+ return item;
154+ }
155+ }
156+
157+ return null;
158+ }
159+
160+ /*
161+ * 検索回数、最終検索日時の降順にソートする
162+ */
90163 private class SearchWordComparator implements Comparator<SearchWordItem>{
91164 @Override
92165 public int compare(SearchWordItem p1, SearchWordItem p2) {
--- a/TinyBannavi/src/tainavi/VWSearchWordDialog.java
+++ b/TinyBannavi/src/tainavi/VWSearchWordDialog.java
@@ -9,38 +9,38 @@ import java.awt.event.ActionEvent;
99 import java.awt.event.ActionListener;
1010 import java.awt.event.ItemEvent;
1111 import java.awt.event.ItemListener;
12-import java.awt.event.KeyEvent;
13-import java.awt.event.KeyListener;
1412 import java.awt.event.MouseAdapter;
1513 import java.awt.event.MouseEvent;
1614 import java.awt.event.WindowEvent;
1715 import java.awt.event.WindowListener;
1816 import java.text.SimpleDateFormat;
1917 import java.time.Instant;
18+import java.util.Calendar;
19+import java.util.Date;
2020 import java.util.GregorianCalendar;
21+import java.util.List;
2122 import java.util.regex.Matcher;
2223 import java.util.regex.Pattern;
2324
24-import javax.swing.AbstractAction;
25-import javax.swing.InputMap;
2625 import javax.swing.JButton;
2726 import javax.swing.JCheckBox;
28-import javax.swing.JComponent;
2927 import javax.swing.JLabel;
3028 import javax.swing.JOptionPane;
3129 import javax.swing.JPanel;
3230 import javax.swing.JTextField;
33-import javax.swing.KeyStroke;
3431 import javax.swing.SpringLayout;
3532 import javax.swing.border.LineBorder;
3633 import javax.swing.event.DocumentEvent;
3734 import javax.swing.event.DocumentListener;
3835
36+import tainavi.TVProgram.ProgGenre;
37+import tainavi.TVProgram.ProgSubgenre;
38+
3939 /*
4040 * 検索文字列入力画面
4141 *
4242 */
43-public class VWSearchWordDialog extends JEscCancelDialog {
43+public class VWSearchWordDialog extends JEscCancelDialog{
4444
4545 private static final long serialVersionUID = 1L;
4646
@@ -49,20 +49,24 @@ public class VWSearchWordDialog extends JEscCancelDialog {
4949 ******************************************************************************/
5050
5151 // レイアウト関連
52-
53- private static final int PARTS_HEIGHT = 30;
52+ private static final int PARTS_HEIGHT = 25;
5453 private static final int SEP_WIDTH = 10;
55- private static final int SEP_HEIGHT = 10;
56- private static final int SEARCH_HEIGHT = 45;
54+ private static final int SEP_HEIGHT = 5;
55+ private static final int SEARCH_HEIGHT = 50;
56+ private static final int CLEAR_HEIGHT = 35;
5757
58- private static final int LABEL_WIDTH = 120;
58+ private static final int LABEL_WIDTH = 100;
5959
60- private static final int TEXT_WIDTH = 500;
60+ private static final int TEXT_WIDTH = 450;
6161 private static final int CHECKBOX_WIDTH = 150;
62- private static final int DATE_WIDTH = 120;
62+ private static final int DATE_WIDTH = 140;
63+ private static final int SAME_WIDTH = 60;
6364 private static final int AMONG_WIDTH = 30;
64- private static final int NOTE_WIDTH = 600;
65- private static final int NOTE_HEIGHT = PARTS_HEIGHT*2;
65+ private static final int CHANNEL_WIDTH = 450;
66+ private static final int GENRE_WIDTH = 210;
67+ private static final int SUBGENRE_WIDTH = 230;
68+ private static final int LEN_WIDTH = 90;
69+ private static final int FLAG_WIDTH = 60;
6670
6771 private static final int BUTTON_WIDTH = 120;
6872
@@ -78,7 +82,7 @@ public class VWSearchWordDialog extends JEscCancelDialog {
7882 private JPanel jPanel = null;
7983
8084 private JLabel jLabel_swlist = null;
81- private JComboBoxWithPopup jComboBox_swlist = null;
85+ private JSearchWordComboBox jComboBox_swlist = null;
8286 // private JTextField jTextField_swlist = null;
8387
8488 private JLabel jLabel_keyword = null;
@@ -90,42 +94,65 @@ public class VWSearchWordDialog extends JEscCancelDialog {
9094 private JCheckBox jCheckBox_filter = null;
9195
9296 private JLabel jLabel_period = null;
93- private JTextField jTextField_from = null;
97+ private JDateField jDateField_from = null;
9498 private JLabel jLabel_among = null;
95- private JTextField jTextField_to = null;
99+ private JDateField jDateField_to = null;
96100 private JButton jButton_same = null;
97101
102+ private JButton jButton_rename = null;
98103 private JButton jButton_delete = null;
99104 private JButton jButton_search = null;
100-// private JButton jButton_cancel = null;
101105 private JButton jButton_clear = null;
102- private JLabel jLabel_note = null;
106+
107+ private JLabel jLabel_channel = null;
108+ private JComboBoxWithPopup jComboBox_channel = null;
109+
110+ private JLabel jLabel_genre = null;
111+ private JComboBoxWithPopup jComboBox_genre = null;
112+ private JComboBoxWithPopup jComboBox_subgenre = null;
113+
114+ private JLabel jLabel_length = null;
115+ private JComboBoxWithPopup jComboBox_lenfrom = null;
116+ private JTextField jTextField_lenfrom = null;
117+ private JLabel jLabel_lenamong = null;
118+ private JComboBoxWithPopup jComboBox_lento = null;
119+ private JTextField jTextField_lento = null;
120+
121+ private JLabel jLabel_flag = null;
122+ private JCheckBox jCheckBox_new = null;
123+ private JCheckBox jCheckBox_final = null;
124+ private JCheckBox jCheckBox_repeat = null;
125+ private JCheckBox jCheckBox_first = null;
126+ private JCheckBox jCheckBox_special = null;
103127
104128 private JButton jButton_menu = null;
105129 private AbsToolBar jToolbar = null;
106130
107- // コンポーネント以外
131+ /*******************************************************************************
132+ * 部品以外のインスタンスメンバー
133+ ******************************************************************************/
108134 private Instant last_shown = null;
109135 private SearchWordList swlist = null;
110136 private boolean autoclose_enabled = true;
137+ private ChannelSort chsort = null;
111138
112139 /*******************************************************************************
113140 * コンストラクタ
114141 ******************************************************************************/
115-
116- public VWSearchWordDialog() {
142+ public VWSearchWordDialog(ChannelSort chs, SearchWordList list) {
117143
118144 super();
119145
146+ chsort = chs;
147+ swlist = list;
148+
120149 // this.setModal(true);
121-// this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); // 閉じるときはキャンセルボタンを使ってクレ
122- this.addWindowListener(wl_panel);
123- this.setAlwaysOnTop(true);
124- this.setContentPane(getJPanel());
125- this.setUndecorated(true);
126- this.pack();
127- this.setTitle("検索条件の編集");
128- this.setResizable(false);
150+ addWindowListener(wl_panel);
151+ setContentPane(getJPanel());
152+ setUndecorated(true);
153+ pack();
154+ setTitle("検索条件の編集");
155+ setResizable(false);
129156 }
130157
131158 /*******************************************************************************
@@ -134,24 +161,29 @@ public class VWSearchWordDialog extends JEscCancelDialog {
134161 /*
135162 * ダイアログを表示する
136163 */
137- public void open(AbsToolBar jtoolbar, JButton jbutton, SearchWordList list, String s){
164+ public void open(AbsToolBar jtoolbar, JButton jbutton, String s, SearchWordItem swi){
138165 jToolbar = jtoolbar;
139166 jButton_menu = jbutton;
140- swlist = list;
141167
142168 Instant now = Instant.now();
143169 if (last_shown != null && now.minusMillis(100).isBefore(last_shown))
144170 return;
145171
172+ // 放送日は7日先まで入力可とする
173+ GregorianCalendar cal = new GregorianCalendar();
174+ cal.add(Calendar.DATE, 7);
175+ jDateField_from.setMaxDate(cal);
176+ jDateField_to.setMaxDate(cal);
177+
178+ // 検索履歴を更新する
146179 updateKeywordComboBox();
147180 if (s != null){
148- jComboBox_swlist.setSelectedItem(s);
149-
150181 if (s.isEmpty()){
151- doClear();
182+// doClear();
152183 }
153184 else{
154- decodeKeyword(s);
185+ jComboBox_swlist.setSelectedItem(s);
186+ decodeKeyword(s, swi);
155187 }
156188 }
157189
@@ -171,6 +203,7 @@ public class VWSearchWordDialog extends JEscCancelDialog {
171203 jTextField_keyword.requestFocusInWindow();
172204 }
173205 else{
206+ // 画面を閉じる時のインスタントを取得する
174207 last_shown = Instant.now();
175208 }
176209
@@ -182,8 +215,6 @@ public class VWSearchWordDialog extends JEscCancelDialog {
182215 */
183216 public void setLocation(){
184217 Point pf = jToolbar.getLocationOnScreen();
185-// Rectangle rf = jToolbar.getBounds();
186-
187218 Point pm = jButton_menu.getLocationOnScreen();
188219 Rectangle rm = jButton_menu.getBounds();
189220
@@ -220,7 +251,7 @@ public class VWSearchWordDialog extends JEscCancelDialog {
220251 CommonSwingUtils.putComponentOn(jPanel, getJComboBox_swlist(), TEXT_WIDTH, PARTS_HEIGHT, x, y);
221252 x += TEXT_WIDTH + SEP_WIDTH*2;
222253 int xb = x;
223- CommonSwingUtils.putComponentOn(jPanel, getJButton_delete("削除"), BUTTON_WIDTH, PARTS_HEIGHT, xb, y);
254+ CommonSwingUtils.putComponentOn(jPanel, getJButton_rename("名称変更"), BUTTON_WIDTH, PARTS_HEIGHT, xb, y);
224255
225256 // 2行目
226257 y += PARTS_HEIGHT + SEP_HEIGHT;
@@ -228,7 +259,7 @@ public class VWSearchWordDialog extends JEscCancelDialog {
228259 CommonSwingUtils.putComponentOn(jPanel, getJLabel_keyword("キーワード"), LABEL_WIDTH, PARTS_HEIGHT, x, y);
229260 x += LABEL_WIDTH + SEP_WIDTH;
230261 CommonSwingUtils.putComponentOn(jPanel, getJTextField_keyword(), TEXT_WIDTH, PARTS_HEIGHT, x, y);
231- CommonSwingUtils.putComponentOn(jPanel, getJButton_search("検索"), BUTTON_WIDTH, SEARCH_HEIGHT, xb, y);
262+ CommonSwingUtils.putComponentOn(jPanel, getJButton_delete("削除"), BUTTON_WIDTH, PARTS_HEIGHT, xb, y);
232263
233264 // 3行目
234265 y += PARTS_HEIGHT + SEP_HEIGHT;
@@ -241,45 +272,71 @@ public class VWSearchWordDialog extends JEscCancelDialog {
241272 x += CHECKBOX_WIDTH;
242273 CommonSwingUtils.putComponentOn(jPanel, getJCheckBox_filter("絞り込み"), CHECKBOX_WIDTH, PARTS_HEIGHT, x, y);
243274 x += SEP_WIDTH;
244-// int yb2 = y;
245-// CommonSwingUtils.putComponentOn(jPanel, getJButton_cancel("閉じる"), BUTTON_WIDTH, PARTS_HEIGHT, xb, yb2);
246275
247276 // 4行目
248277 y += PARTS_HEIGHT + SEP_HEIGHT;
249278 x = SEP_WIDTH;
250- CommonSwingUtils.putComponentOn(jPanel, getJLabel_period("過去ログ期間※"), LABEL_WIDTH, PARTS_HEIGHT, x, y);
279+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_period("放送日"), LABEL_WIDTH, PARTS_HEIGHT, x, y);
251280 x += LABEL_WIDTH + SEP_WIDTH;
252- CommonSwingUtils.putComponentOn(jPanel, getJTextField_from(), DATE_WIDTH, PARTS_HEIGHT, x, y);
281+ CommonSwingUtils.putComponentOn(jPanel, getJDateField_from(), DATE_WIDTH, PARTS_HEIGHT, x, y);
253282 x += DATE_WIDTH;
254283 CommonSwingUtils.putComponentOn(jPanel, getJLabel_among("~"), AMONG_WIDTH, PARTS_HEIGHT, x, y);
255284 x += AMONG_WIDTH;
256- CommonSwingUtils.putComponentOn(jPanel, getJTextField_to(), DATE_WIDTH, PARTS_HEIGHT, x, y);
285+ CommonSwingUtils.putComponentOn(jPanel, getJDateField_to(), DATE_WIDTH, PARTS_HEIGHT, x, y);
257286 x += DATE_WIDTH;
258- CommonSwingUtils.putComponentOn(jPanel, getJButton_same("同値"), BUTTON_WIDTH, PARTS_HEIGHT, x, y);
287+ CommonSwingUtils.putComponentOn(jPanel, getJButton_same("同値"), SAME_WIDTH, PARTS_HEIGHT, x, y);
259288 x += BUTTON_WIDTH + SEP_WIDTH;
260- CommonSwingUtils.putComponentOn(jPanel, getJButton_clear("クリア"), BUTTON_WIDTH, PARTS_HEIGHT, xb, y);
289+ CommonSwingUtils.putComponentOn(jPanel, getJButton_search("検索"), BUTTON_WIDTH, SEARCH_HEIGHT, xb, y);
261290
262291 // 5行目
263- y += PARTS_HEIGHT-5;
292+ y += PARTS_HEIGHT + SEP_HEIGHT;
264293 x = SEP_WIDTH;
265- CommonSwingUtils.putComponentOn(jPanel, getJLabel_note(), NOTE_WIDTH, NOTE_HEIGHT, x, y);
266- y += NOTE_HEIGHT+SEP_HEIGHT;
294+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_channel("チャンネル名"), LABEL_WIDTH, PARTS_HEIGHT, x, y);
295+ x += LABEL_WIDTH + SEP_WIDTH;
296+ CommonSwingUtils.putComponentOn(jPanel, getJComboBox_channel(), CHANNEL_WIDTH, PARTS_HEIGHT, x, y);
267297
298+ // 6行目
299+ y += PARTS_HEIGHT + SEP_HEIGHT;
300+ x = SEP_WIDTH;
301+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_genre("ジャンル/サブ"), LABEL_WIDTH, PARTS_HEIGHT, x, y);
302+ x += LABEL_WIDTH + SEP_WIDTH;
303+ CommonSwingUtils.putComponentOn(jPanel, getJComboBox_genre(), GENRE_WIDTH, PARTS_HEIGHT, x, y);
304+ x += GENRE_WIDTH + SEP_WIDTH;
305+ CommonSwingUtils.putComponentOn(jPanel, getJComboBox_subgenre(), SUBGENRE_WIDTH, PARTS_HEIGHT, x, y);
306+
307+ // 7行目
308+ y += PARTS_HEIGHT + SEP_HEIGHT;
309+ x = SEP_WIDTH;
310+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_length("番組長(分)"), LABEL_WIDTH, PARTS_HEIGHT, x, y);
311+ x += LABEL_WIDTH + SEP_WIDTH;
312+ CommonSwingUtils.putComponentOn(jPanel, getJComboBox_lenfrom(), LEN_WIDTH, PARTS_HEIGHT, x, y);
313+ x += LEN_WIDTH;
314+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_lenamong("~"), AMONG_WIDTH, PARTS_HEIGHT, x, y);
315+ x += AMONG_WIDTH;
316+ CommonSwingUtils.putComponentOn(jPanel, getJComboBox_lento(), LEN_WIDTH, PARTS_HEIGHT, x, y);
317+ x += LEN_WIDTH;
318+ CommonSwingUtils.putComponentOn(jPanel, getJButton_clear("クリア"), BUTTON_WIDTH, CLEAR_HEIGHT, xb, y);
319+
320+ // 8行目
321+ y += PARTS_HEIGHT+SEP_HEIGHT;
322+ x = SEP_WIDTH;
323+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_flag("フラグ"), LABEL_WIDTH, PARTS_HEIGHT, x, y);
324+ x += LABEL_WIDTH + SEP_WIDTH;
325+ CommonSwingUtils.putComponentOn(jPanel, getJCheckBox_new("新"), FLAG_WIDTH, PARTS_HEIGHT, x, y);
326+ x += FLAG_WIDTH + SEP_WIDTH;
327+ CommonSwingUtils.putComponentOn(jPanel, getJCheckBox_final("終"), FLAG_WIDTH, PARTS_HEIGHT, x, y);
328+ x += FLAG_WIDTH + SEP_WIDTH;
329+ CommonSwingUtils.putComponentOn(jPanel, getJCheckBox_repeat("再"), FLAG_WIDTH, PARTS_HEIGHT, x, y);
330+ x += FLAG_WIDTH + SEP_WIDTH;
331+ CommonSwingUtils.putComponentOn(jPanel, getJCheckBox_first("初"), FLAG_WIDTH, PARTS_HEIGHT, x, y);
332+ x += FLAG_WIDTH + SEP_WIDTH;
333+ CommonSwingUtils.putComponentOn(jPanel, getJCheckBox_special("特"), FLAG_WIDTH, PARTS_HEIGHT, x, y);
334+ x += FLAG_WIDTH + SEP_WIDTH;
335+
336+ y += PARTS_HEIGHT+SEP_HEIGHT*2;
268337
269338 jPanel.setPreferredSize(new Dimension(PANEL_WIDTH, y));
270339 jPanel.setBorder(new LineBorder(Color.BLACK, 1));
271-
272- // ESCキーをショートカットに登録する(が何故か効かない)
273- String ESCKEYACTION = "escape";
274- InputMap imap = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
275- imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ESCKEYACTION);
276- getRootPane().getActionMap().put(ESCKEYACTION, new AbstractAction() {
277- private static final long serialVersionUID = 1L;
278- @Override
279- public void actionPerformed(ActionEvent e) {
280- doCancel();
281- }
282- });
283340 }
284341
285342 return jPanel;
@@ -298,19 +355,37 @@ public class VWSearchWordDialog extends JEscCancelDialog {
298355 /*
299356 * 「検索履歴」コンボボックス
300357 */
301- private JComboBoxWithPopup getJComboBox_swlist() {
358+ @SuppressWarnings("unchecked")
359+ private JSearchWordComboBox getJComboBox_swlist() {
302360 if (jComboBox_swlist == null) {
303- jComboBox_swlist = new JComboBoxWithPopup();
304- jComboBox_swlist.setMaximumRowCount(32);
361+ jComboBox_swlist = new JSearchWordComboBox(swlist);
305362 jComboBox_swlist.addItemListener(il_swlistSelected);
306-
307-// jTextField_swlist = ((JTextField)jComboBox_swlist.getEditor().getEditorComponent());
308363 }
309364
310365 return(jComboBox_swlist);
311366 }
312367
313368 /*
369+ * 「名称変更」ボタン
370+ */
371+ private JButton getJButton_rename(String s) {
372+ if (jButton_rename == null) {
373+ jButton_rename = new JButton();
374+ jButton_rename.setText(s);
375+
376+ jButton_rename.addMouseListener(new MouseAdapter() {
377+ @Override
378+ public void mouseClicked(MouseEvent e) {
379+ doRename();
380+ jTextField_keyword.requestFocusInWindow();
381+ updateControlStatus();
382+ }
383+ });
384+ }
385+ return(jButton_rename);
386+ }
387+
388+ /*
314389 * 「削除」ボタン
315390 */
316391 private JButton getJButton_delete(String s) {
@@ -348,7 +423,6 @@ public class VWSearchWordDialog extends JEscCancelDialog {
348423 if (jTextField_keyword == null) {
349424 jTextField_keyword = new JTextField();
350425 jTextField_keyword.addActionListener(al_search);
351- jTextField_keyword.addKeyListener(kl_escape);
352426 jTextField_keyword.getDocument().addDocumentListener(dl_documentChanged);
353427 }
354428
@@ -369,7 +443,8 @@ public class VWSearchWordDialog extends JEscCancelDialog {
369443 jButton_search.addMouseListener(new MouseAdapter() {
370444 @Override
371445 public void mouseClicked(MouseEvent e) {
372- doSearch();
446+ if (jButton_search.isEnabled())
447+ doSearch();
373448 }
374449 });
375450 }
@@ -422,23 +497,6 @@ public class VWSearchWordDialog extends JEscCancelDialog {
422497 }
423498
424499 /*
425- * 「キャンセル」ボタン
426- */
427-// private JButton getJButton_cancel(String s) {
428-// if (jButton_cancel == null) {
429-// jButton_cancel = new JButton(s);
430-// jButton_cancel.addMouseListener(new MouseAdapter() {
431-// @Override
432-// public void mouseClicked(MouseEvent e) {
433-// doCancel();
434-// }
435-// });
436-// }
437-//
438-// return(jButton_cancel);
439-// }
440-
441- /*
442500 * 「過去ログ期間」ラベル
443501 */
444502 private JLabel getJLabel_period(String s) {
@@ -451,16 +509,13 @@ public class VWSearchWordDialog extends JEscCancelDialog {
451509 /*
452510 * 「過去ログ期間開始」テキストフィールド
453511 */
454- private JTextField getJTextField_from() {
455- if (jTextField_from == null) {
456- jTextField_from = new JTextFieldWithPopup();
457-
458- jTextField_from.addActionListener(al_search);
459- jTextField_from.addKeyListener(kl_escape);
460- jTextField_from.getDocument().addDocumentListener(dl_documentChanged);
512+ private JDateField getJDateField_from() {
513+ if (jDateField_from == null) {
514+ String tt = "<html>YYYY/MM/DDかMM/DDの形式で指定してください。<br>YYYY/MM/DDで開始日のみ指定するとその日の過去ログが閲覧できます。</html>";
515+ jDateField_from = createDateField(tt);
461516 }
462517
463- return(jTextField_from);
518+ return(jDateField_from);
464519 }
465520
466521 /*
@@ -477,16 +532,13 @@ public class VWSearchWordDialog extends JEscCancelDialog {
477532 /*
478533 * 「過去ログ期間終了」テキストフィールド
479534 */
480- private JTextField getJTextField_to() {
481- if (jTextField_to == null) {
482- jTextField_to = new JTextFieldWithPopup();
483-
484- jTextField_to.addActionListener(al_search);
485- jTextField_to.addKeyListener(kl_escape);
486- jTextField_to.getDocument().addDocumentListener(dl_documentChanged);
535+ private JDateField getJDateField_to() {
536+ if (jDateField_to == null) {
537+ String tt = "YYYY/MM/DDかMM/DDの形式で指定してください。";
538+ jDateField_to = createDateField(tt);
487539 }
488540
489- return(jTextField_to);
541+ return(jDateField_to);
490542 }
491543
492544 /*
@@ -500,7 +552,7 @@ public class VWSearchWordDialog extends JEscCancelDialog {
500552 jButton_same.addMouseListener(new MouseAdapter() {
501553 @Override
502554 public void mouseClicked(MouseEvent e) {
503- jTextField_to.setText(jTextField_from.getText());
555+ jDateField_to.setText(jDateField_from.getText());
504556 }
505557 });
506558 }
@@ -508,18 +560,6 @@ public class VWSearchWordDialog extends JEscCancelDialog {
508560 }
509561
510562 /*
511- * 注釈ラベル
512- */
513- private JLabel getJLabel_note() {
514- if (jLabel_note == null) {
515- String s = "<html>※YYYY/MM/DDかMM/DDの形式で指定してください。<br>※YYYY/MM/DDで開始日のみ指定するとその日の過去ログが閲覧できます。</html>";
516- jLabel_note = new JLabel(s);
517-
518- }
519- return(jLabel_note);
520- }
521-
522- /*
523563 * 「クリア」ボタン
524564 */
525565 private JButton getJButton_clear(String s) {
@@ -539,6 +579,198 @@ public class VWSearchWordDialog extends JEscCancelDialog {
539579 return(jButton_clear);
540580 }
541581
582+ /*
583+ * 「チャンネル」ラベル
584+ */
585+ private JLabel getJLabel_channel(String s) {
586+ if (jLabel_channel == null) {
587+ jLabel_channel = new JLabel(s);
588+ }
589+ return(jLabel_channel);
590+ }
591+
592+ /*
593+ * 「チャンネル」コンボボックス
594+ */
595+ private JComboBoxWithPopup getJComboBox_channel() {
596+ if (jComboBox_channel == null) {
597+ jComboBox_channel = new JComboBoxWithPopup();
598+ jComboBox_channel.setMaximumRowCount(32);
599+
600+ jComboBox_channel.addItem("");
601+
602+ List<Center> list = chsort.getClst();
603+ for (Center c : list){
604+ jComboBox_channel.addItem(c.getCenter());
605+ }
606+ jComboBox_channel.addItemListener(il_channelSelected);
607+ }
608+
609+ return(jComboBox_channel);
610+ }
611+
612+ /*
613+ * 「ジャンル」ラベル
614+ */
615+ private JLabel getJLabel_genre(String s) {
616+ if (jLabel_genre == null) {
617+ jLabel_genre = new JLabel(s);
618+ }
619+ return(jLabel_genre);
620+ }
621+
622+ /*
623+ * 「ジャンル」コンボボックス
624+ */
625+ private JComboBoxWithPopup getJComboBox_genre() {
626+ if (jComboBox_genre == null) {
627+ jComboBox_genre = new JComboBoxWithPopup();
628+ jComboBox_genre.setMaximumRowCount(32);
629+
630+ jComboBox_genre.addItem("");
631+ for ( ProgGenre genre : ProgGenre.values()) {
632+ jComboBox_genre.addItem(genre.toString());
633+ }
634+
635+ jComboBox_genre.addItemListener(il_genreSelected);
636+ }
637+
638+ return(jComboBox_genre);
639+ }
640+
641+ /*
642+ * 「サブジャンル」コンボボックス
643+ */
644+ private JComboBoxWithPopup getJComboBox_subgenre() {
645+ if (jComboBox_subgenre == null) {
646+ jComboBox_subgenre = new JComboBoxWithPopup();
647+
648+ jComboBox_subgenre.addItem("");
649+ jComboBox_subgenre.setMaximumRowCount(32);
650+ jComboBox_subgenre.addItemListener(il_subgenreSelected);
651+ }
652+
653+ return(jComboBox_subgenre);
654+ }
655+
656+ /*
657+ * 「番組長」ラベル
658+ */
659+ private JLabel getJLabel_length(String s) {
660+ if (jLabel_length == null) {
661+ jLabel_length = new JLabel(s);
662+ }
663+ return(jLabel_length);
664+ }
665+
666+ /*
667+ * 「番組長下限」テキストフィールド
668+ */
669+ private JComboBoxWithPopup getJComboBox_lenfrom() {
670+ if (jComboBox_lenfrom == null) {
671+ jComboBox_lenfrom = createLenComboBox();
672+ jTextField_lenfrom = (JTextField) jComboBox_lenfrom.getEditor().getEditorComponent();
673+ jTextField_lenfrom.getDocument().addDocumentListener(dl_documentChanged);
674+ }
675+
676+ return(jComboBox_lenfrom);
677+ }
678+
679+ /*
680+ * 「~」ラベル
681+ */
682+ private JLabel getJLabel_lenamong(String s) {
683+ if (jLabel_lenamong == null) {
684+ jLabel_lenamong = new JLabel(s);
685+ jLabel_lenamong.setHorizontalAlignment(JLabel.CENTER);
686+ }
687+ return(jLabel_lenamong);
688+ }
689+
690+ /*
691+ * 「番組長上限」テキストフィールド
692+ */
693+ private JComboBoxWithPopup getJComboBox_lento() {
694+ if (jComboBox_lento == null) {
695+ jComboBox_lento = createLenComboBox();
696+ jTextField_lento = (JTextField) jComboBox_lento.getEditor().getEditorComponent();
697+// jTextField_lento.setHorizontalAlignment(JTextField.RIGHT);
698+ jTextField_lento.getDocument().addDocumentListener(dl_documentChanged);
699+ }
700+
701+ return(jComboBox_lento);
702+ }
703+
704+ /*
705+ * 「フラグ」ラベル
706+ */
707+ private JLabel getJLabel_flag(String s) {
708+ if (jLabel_flag == null) {
709+ jLabel_flag = new JLabel(s);
710+ }
711+ return(jLabel_flag);
712+ }
713+
714+ /*
715+ * 「新番組」チェックボックス
716+ */
717+ private JCheckBox getJCheckBox_new(String s) {
718+ if (jCheckBox_new == null) {
719+ jCheckBox_new = createFlagCheckBox(s);
720+ }
721+ return(jCheckBox_new);
722+ }
723+
724+ /*
725+ * 「最終回」チェックボックス
726+ */
727+ private JCheckBox getJCheckBox_final(String s) {
728+ if (jCheckBox_final == null) {
729+ jCheckBox_final = createFlagCheckBox(s);
730+ }
731+ return(jCheckBox_final);
732+ }
733+
734+ /*
735+ * 「再放送」チェックボックス
736+ */
737+ private JCheckBox getJCheckBox_repeat(String s) {
738+ if (jCheckBox_repeat == null) {
739+ jCheckBox_repeat = createFlagCheckBox(s);
740+ }
741+ return(jCheckBox_repeat);
742+ }
743+
744+ /*
745+ * 「初回放送」チェックボックス
746+ */
747+ private JCheckBox getJCheckBox_first(String s) {
748+ if (jCheckBox_first == null) {
749+ jCheckBox_first = createFlagCheckBox(s);
750+ }
751+ return(jCheckBox_first);
752+ }
753+
754+ /*
755+ * 「特番」チェックボックス
756+ */
757+ private JCheckBox getJCheckBox_special(String s) {
758+ if (jCheckBox_special == null) {
759+ jCheckBox_special = createFlagCheckBox(s);
760+ }
761+ return(jCheckBox_special);
762+ }
763+
764+ /*
765+ * 「フラグ」チェックボックスを生成する
766+ */
767+ private JCheckBox createFlagCheckBox(String s){
768+ JCheckBox checkBox = new JCheckBox();
769+ checkBox.setText(s);
770+ checkBox.addActionListener(al_check);
771+ return(checkBox);
772+ }
773+
542774 /*******************************************************************************
543775 * アクション
544776 ******************************************************************************/
@@ -547,12 +779,60 @@ public class VWSearchWordDialog extends JEscCancelDialog {
547779 */
548780 protected void doClear(){
549781 jTextField_keyword.setText("");
550- jTextField_from.setText("");
551- jTextField_to.setText("");
782+ jDateField_from.setText("");
783+ jDateField_to.setText("");
552784
553785 jCheckBox_title.setSelected(true);
554786 jCheckBox_detail.setSelected(true);
555787 jCheckBox_filter.setSelected(false);
788+
789+ jComboBox_channel.setSelectedItem("");
790+
791+ jComboBox_genre.setSelectedItem("");
792+ jComboBox_subgenre.setSelectedItem("");
793+
794+ jTextField_lenfrom.setText("");
795+ jTextField_lento.setText("");
796+
797+ jCheckBox_new.setSelected(false);
798+ jCheckBox_final.setSelected(false);
799+ jCheckBox_repeat.setSelected(false);
800+ jCheckBox_first.setSelected(false);
801+ jCheckBox_special.setSelected(false);
802+ }
803+
804+ /*
805+ * 履歴の名称を変更する
806+ */
807+ protected void doRename(){
808+ int no = jComboBox_swlist.getSelectedIndex();
809+ if (no < 1)
810+ return;
811+
812+ SearchWordItem swi = swlist.getWordList().get(no-1);
813+ String nameOld = swi.getLabel();
814+
815+ VWSearchWordRenameDialog dlg = new VWSearchWordRenameDialog();
816+
817+ autoclose_enabled = false;
818+ dlg.open(nameOld, swlist, this);
819+ autoclose_enabled = true;
820+
821+ String nameNew = dlg.getEditedName();
822+ if (nameNew == null)
823+ return;
824+
825+ swi.setLabel(nameNew);
826+ SearchKey search = createSearchKey();
827+ search.setLabel(nameNew);
828+ swi.setSearchKey(search);
829+ swlist.save();
830+
831+ updateKeywordComboBox();
832+
833+ jComboBox_swlist.setSelectedItem(nameNew);
834+
835+ jToolbar.updateKeywordComboBox();
556836 }
557837
558838 /*
@@ -569,10 +849,13 @@ public class VWSearchWordDialog extends JEscCancelDialog {
569849
570850 if (jComboBox_swlist.getItemCount() > no){
571851 jComboBox_swlist.setSelectedIndex(no);
572- decodeKeyword((String)jComboBox_swlist.getSelectedItem());
852+ SearchWordItem swi = swlist.getWordList().get(no-1);
853+ decodeKeyword((String)jComboBox_swlist.getSelectedItem(), swi);
573854 }
574855 else
575856 doClear();
857+
858+ jToolbar.updateKeywordComboBox();
576859 }
577860
578861 /*
@@ -591,18 +874,23 @@ public class VWSearchWordDialog extends JEscCancelDialog {
591874 protected boolean doSearch(){
592875 StringBuilder sb = new StringBuilder("");
593876
877+ // 履歴の名称
878+ String label = (String)jComboBox_swlist.getSelectedItem();
879+ // キーワード
594880 String keyword = jTextField_keyword.getText();
595881
596- String from = jTextField_from.getText();
597- if (from.length() > 0){
882+ // 放送日の下限
883+ String from = jDateField_from.getText();
884+ if (from != null && from.length() > 0){
598885 if (!checkDateFormat(from, true))
599886 return false;
600887
601888 sb.append(from);
602889 }
603890
604- String to = jTextField_to.getText();
605- if (to.length() > 0){
891+ // 放送日の上限
892+ String to = jDateField_to.getText();
893+ if (to != null && to.length() > 0){
606894 if (!checkDateFormat(to, true))
607895 return false;
608896
@@ -611,6 +899,14 @@ public class VWSearchWordDialog extends JEscCancelDialog {
611899 sb.append(to);
612900 }
613901
902+ // ログ表示モードの場合
903+ if (isLogViewMode()){
904+ jToolbar.keywordSearch(null, from, null, null, null, null, false);
905+ setVisible(false);
906+ return true;
907+ }
908+
909+ // 検索文字列を生成する
614910 if (sb.length() == 0 && jCheckBox_filter.isSelected())
615911 sb.append("@filter");
616912
@@ -633,18 +929,216 @@ public class VWSearchWordDialog extends JEscCancelDialog {
633929 sb.append(keyword);
634930 }
635931
636- jToolbar.toolbarSearch(sb.toString());
932+ // 検索キーを生成する
933+ SearchKey search = createSearchKey();
934+
935+ // 検索を実行する
936+ jToolbar.keywordSearch(label, sb.toString(), search, keyword, from, to, jCheckBox_filter.isSelected());
637937
638938 setVisible(false);
639939
640940 return true;
641941 }
642942
943+ /*******************************************************************************
944+ * リスナー
945+ ******************************************************************************/
946+ /*
947+ * ウインドウのオープン/クローズ等の変化
948+ */
949+ private final WindowListener wl_panel = new WindowListener(){
950+ @Override
951+ public void windowOpened(WindowEvent e) {
952+ }
953+
954+ @Override
955+ public void windowClosing(WindowEvent e) {
956+ }
957+
958+ @Override
959+ public void windowClosed(WindowEvent e) {
960+ }
961+
962+ @Override
963+ public void windowIconified(WindowEvent e) {
964+ if (autoclose_enabled)
965+ doCancel();
966+ }
967+
968+ @Override
969+ public void windowDeiconified(WindowEvent e) {
970+ }
971+
972+ @Override
973+ public void windowActivated(WindowEvent e) {
974+ }
975+
976+ @Override
977+ public void windowDeactivated(WindowEvent e) {
978+ if (autoclose_enabled)
979+ doCancel();
980+ }
981+ };
982+
983+ /*
984+ * 「検索履歴」コンボボックスの選択変更
985+ */
986+ private final ItemListener il_swlistSelected = new ItemListener() {
987+ @Override
988+ public void itemStateChanged(ItemEvent e) {
989+ switch (e.getStateChange()) {
990+ case ItemEvent.SELECTED:
991+ int no = jComboBox_swlist.getSelectedIndex();
992+ SearchWordItem swi = no < 1 ? null : swlist.getWordList().get(no-1);
993+ decodeKeyword((String)jComboBox_swlist.getSelectedItem(), swi);
994+ break;
995+ }
996+ }
997+ };
998+
999+ /*
1000+ * テキストフィールドでのEnterキー
1001+ */
1002+ private final ActionListener al_search = new ActionListener() {
1003+ @Override
1004+ public void actionPerformed(ActionEvent e) {
1005+ if (jButton_search.isEnabled())
1006+ doSearch();
1007+ }
1008+ };
1009+
1010+ /**
1011+ * テキストフィールドでの文書変更
1012+ */
1013+ private final DocumentListener dl_documentChanged = new DocumentListener(){
1014+ @Override
1015+ public void insertUpdate(DocumentEvent e) {
1016+ updateControlStatus();
1017+ }
1018+
1019+ @Override
1020+ public void removeUpdate(DocumentEvent e) {
1021+ updateControlStatus();
1022+ }
1023+
1024+ @Override
1025+ public void changedUpdate(DocumentEvent e) {
1026+ updateControlStatus();
1027+ }
1028+ };
1029+
1030+ /*
1031+ * カレンダーからのイベント
1032+ */
1033+ private final CalendarListener cl_dateField = new CalendarListener(){
1034+ @Override
1035+ public void notifyDateChange(JCalendar cal, String date) {
1036+ }
1037+
1038+ @Override
1039+ public boolean isHoliday(int year, int month, int day) {
1040+ return HolidayInfo.IsHoliday(year, month, day);
1041+ }
1042+ };
1043+
1044+ /*
1045+ * 「ジャンル」コンボボックスの選択変更
1046+ */
1047+ private final ItemListener il_genreSelected = new ItemListener() {
1048+ @Override
1049+ public void itemStateChanged(ItemEvent e) {
1050+ switch (e.getStateChange()) {
1051+ case ItemEvent.SELECTED:
1052+ updateSubgenreComboBox();
1053+ updateControlStatus();
1054+ break;
1055+ }
1056+ }
1057+ };
1058+
1059+ /*
1060+ * 「サブジャンル」コンボボックスの選択変更
1061+ */
1062+ private final ItemListener il_subgenreSelected = new ItemListener() {
1063+ @Override
1064+ public void itemStateChanged(ItemEvent e) {
1065+ switch (e.getStateChange()) {
1066+ case ItemEvent.SELECTED:
1067+ updateControlStatus();
1068+ break;
1069+ }
1070+ }
1071+ };
1072+
1073+ /*
1074+ * 「チャンネル」コンボボックスの選択変更
1075+ */
1076+ private final ItemListener il_channelSelected = new ItemListener() {
1077+ @Override
1078+ public void itemStateChanged(ItemEvent e) {
1079+ switch (e.getStateChange()) {
1080+ case ItemEvent.SELECTED:
1081+ updateControlStatus();
1082+ break;
1083+ }
1084+ }
1085+ };
1086+
1087+ /*
1088+ * チェックボックスでのクリック
1089+ */
1090+ private final ActionListener al_check = new ActionListener() {
1091+ @Override
1092+ public void actionPerformed(ActionEvent e) {
1093+ updateControlStatus();
1094+ }
1095+ };
1096+
1097+
1098+ /*******************************************************************************
1099+ * 内部関数
1100+ ******************************************************************************/
1101+ /*
1102+ * 日付フィールドを生成する
1103+ */
1104+ private JDateField createDateField(String tt){
1105+ JDateField field = new JDateField();
1106+ field.setAllowNoYear(true);
1107+ field.addActionListener(al_search);
1108+ field.setListener(cl_dateField);
1109+
1110+ JTextField textField = (JTextField) field.getEditor().getEditorComponent();
1111+ textField.getDocument().addDocumentListener(dl_documentChanged);
1112+ textField.setToolTipText(tt);
1113+
1114+ return field;
1115+ }
1116+
1117+ /*
1118+ * 「番組長」コンボボックスを生成する
1119+ */
1120+ private JComboBoxWithPopup createLenComboBox(){
1121+ JComboBoxWithPopup combo = new JComboBoxWithPopup();
1122+ combo.setEditable(true);
1123+ combo.setMaximumRowCount(16);
1124+
1125+ combo.addItem("");
1126+
1127+ int [] lens = {0, 5, 10, 15, 30, 45, 60, 90, 120, 180};
1128+ for (int len : lens){
1129+ combo.addItem(String.valueOf(len));
1130+ }
1131+
1132+ return combo;
1133+ }
1134+
6431135 /*
6441136 * 「検索履歴」コンボボックスを更新する
6451137 */
6461138 protected void updateKeywordComboBox(){
6471139 String str = (String)jComboBox_swlist.getSelectedItem();
1140+ if (str == null)
1141+ str = "";
6481142
6491143 jComboBox_swlist.removeAllItems();
6501144
@@ -655,7 +1149,7 @@ public class VWSearchWordDialog extends JEscCancelDialog {
6551149 if (num >= MAX_SEARCH_WORDS)
6561150 break;
6571151
658- jComboBox_swlist.addItem(item.getKeyword());
1152+ jComboBox_swlist.addItem(item.getLabel());
6591153 num++;
6601154 }
6611155
@@ -663,29 +1157,123 @@ public class VWSearchWordDialog extends JEscCancelDialog {
6631157 }
6641158
6651159 /*
1160+ * 「サブジャンル」コンボボックスを更新する
1161+ */
1162+ protected void updateSubgenreComboBox(){
1163+ String genre = (String)jComboBox_genre.getSelectedItem();
1164+
1165+ jComboBox_subgenre.removeAllItems();
1166+ jComboBox_subgenre.addItem("");
1167+
1168+ for ( ProgSubgenre subgenre : ProgSubgenre.values()) {
1169+ if (subgenre.getGenre().toString().equals(genre))
1170+ jComboBox_subgenre.addItem(subgenre.toString());
1171+ }
1172+ }
1173+
1174+ /*
6661175 * 部品のステータスを更新する
6671176 */
6681177 protected void updateControlStatus(){
669- String keyword = jTextField_keyword.getText();
670- String from = jTextField_from.getText();
671- String to = jTextField_to.getText();
672- boolean isSelected = jComboBox_swlist.getSelectedIndex() > 0;
673- boolean haskey = keyword.length() > 0;
1178+ String today = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
1179+ String from = jDateField_from.getText();
1180+ String to = jDateField_to.getText();
1181+ boolean swlsel = jComboBox_swlist.getSelectedIndex() > 0;
6741182 boolean hasrange = from.length() > 0 || to.length() > 0;
6751183 boolean hasfrom = from.length() == 10 && to.length() == 0;
676- boolean passlog = !haskey && hasfrom;
677- boolean passkey = hasrange;
678-
679- jButton_delete.setEnabled(isSelected);
680- jButton_search.setEnabled(haskey || hasfrom);
681- jButton_search.setText(passlog ? "過去ログ閲覧" : passkey ? "過去ログ検索" : "検索");
1184+ boolean logview = isLogViewMode();
1185+ boolean passlog = logview && hasfrom && from.compareTo(today) < 0;
1186+ boolean passkey = hasrange && from.compareTo(today) < 0 && to.compareTo(today) < 0;
1187+
1188+ jButton_rename.setEnabled(swlsel);
1189+ jButton_delete.setEnabled(swlsel);
1190+ jButton_search.setEnabled(isSearchEnabled());
1191+ jButton_search.setText(passlog ? "過去ログ閲覧" : logview ? "番組表閲覧" : passkey ? "過去ログ検索" : "検索");
6821192 jButton_same.setEnabled(hasrange);
6831193
6841194 jCheckBox_filter.setEnabled(!hasrange);
685- jTextField_to.setEnabled(from.length() > 0);
1195+ jDateField_to.setEnabled(from.length() > 0);
1196+ }
1197+
1198+ /*
1199+ * ログ閲覧モードかを返す
1200+ */
1201+ private boolean isLogViewMode(){
1202+ if (!jTextField_keyword.getText().isEmpty())
1203+ return false;
1204+ if (jDateField_from.getText().isEmpty()){
1205+ return false;
1206+ }
1207+ if (!jDateField_to.getText().isEmpty()){
1208+ return false;
1209+ }
1210+ if (!jComboBox_channel.getSelectedItem().toString().isEmpty())
1211+ return false;
1212+ if (!jComboBox_genre.getSelectedItem().toString().isEmpty())
1213+ return false;
1214+
1215+ if (!jTextField_lenfrom.getText().isEmpty())
1216+ return false;
1217+ if (!jTextField_lento.getText().isEmpty())
1218+ return false;
6861219
687- jTextField_from.setForeground(checkDateFormat(from, false) ? Color.BLACK : Color.RED);
688- jTextField_to.setForeground(checkDateFormat(to, false) ? Color.BLACK : Color.RED);
1220+ if (jCheckBox_new.isSelected())
1221+ return false;
1222+ if (jCheckBox_final.isSelected())
1223+ return false;
1224+ if (jCheckBox_repeat.isSelected())
1225+ return false;
1226+ if (jCheckBox_first.isSelected())
1227+ return false;
1228+ if (jCheckBox_special.isSelected())
1229+ return false;
1230+
1231+ return true;
1232+ }
1233+
1234+ /*
1235+ * 検索ボタンが有効かどうかを返す
1236+ */
1237+ private boolean isSearchEnabled(){
1238+ if (!jDateField_from.getText().isEmpty()){
1239+ if (!jDateField_from.hasValidDate())
1240+ return false;
1241+ }
1242+
1243+ if (!jDateField_to.getText().isEmpty()){
1244+ if (!jDateField_to.hasValidDate())
1245+ return false;
1246+ }
1247+
1248+ if (!jTextField_keyword.getText().isEmpty())
1249+ return true;
1250+ if (!jComboBox_channel.getSelectedItem().toString().isEmpty())
1251+ return true;
1252+ if (!jComboBox_genre.getSelectedItem().toString().isEmpty())
1253+ return true;
1254+
1255+ if (!jTextField_lenfrom.getText().isEmpty())
1256+ return true;
1257+ if (!jTextField_lento.getText().isEmpty())
1258+ return true;
1259+
1260+ if (jCheckBox_new.isSelected())
1261+ return true;
1262+ if (jCheckBox_final.isSelected())
1263+ return true;
1264+ if (jCheckBox_repeat.isSelected())
1265+ return true;
1266+ if (jCheckBox_first.isSelected())
1267+ return true;
1268+ if (jCheckBox_special.isSelected())
1269+ return true;
1270+
1271+ // ログ開始日のみ指定の場合
1272+ if (!jDateField_from.getText().isEmpty() && jDateField_to.getText().isEmpty()){
1273+ return true;
1274+ }
1275+
1276+ return false;
6891277 }
6901278
6911279 /*
@@ -701,6 +1289,8 @@ public class VWSearchWordDialog extends JEscCancelDialog {
7011289 String today = null;
7021290 String date = null;
7031291 GregorianCalendar c = new GregorianCalendar();
1292+ c.add(Calendar.DATE, 8);
1293+
7041294 if (syear == null){
7051295 today = new SimpleDateFormat("MM/dd").format(c.getTime());
7061296 date = String.format("%02d/%02d", month, day);
@@ -765,37 +1355,39 @@ public class VWSearchWordDialog extends JEscCancelDialog {
7651355 /*
7661356 * キーワードをデコードする
7671357 */
768- private void decodeKeyword(String str){
1358+ private void decodeKeyword(String str, SearchWordItem swi){
7691359 if (str == null || str.isEmpty())
7701360 return;
7711361
772- _decodeKeyword(str);
1362+ _decodeKeyword(str, swi);
7731363 updateControlStatus();
774-
775- jPanel.updateUI();
7761364 }
7771365
7781366 /*
7791367 * キーワードのデコードを実行する
7801368 */
781- private void _decodeKeyword(String str){
1369+ private void _decodeKeyword(String str, SearchWordItem swi){
7821370 doClear();
7831371
1372+ String keyword = swi.getKeyword();
1373+ if (keyword != null)
1374+ str = keyword;
1375+
7841376 // 過去ログ閲覧
7851377 if (str.matches("^\\d\\d\\d\\d/\\d\\d/\\d\\d$")) {
786- jTextField_keyword.setText(str);
1378+ jDateField_from.setText(str);
7871379 return;
7881380 }
7891381
7901382 // 過去ログ検索
791- Matcher ma = Pattern.compile("^(\\d\\d\\d\\d/)?(\\d\\d/\\d\\d)([  ]+((\\d\\d\\d\\d/)?\\d\\d/\\d\\d))?[  ]+(.*)$").matcher(str);
1383+ Matcher ma = Pattern.compile("^(\\d\\d\\d\\d/)?(\\d\\d/\\d\\d)([  ]+((\\d\\d\\d\\d/)?\\d\\d/\\d\\d))?[  ]?(.*)$").matcher(str);
7921384 if (ma.find()) {
7931385 String from = (ma.group(1) != null ? ma.group(1) : "") + ma.group(2);
7941386 String to = ma.group(4);
7951387 str = ma.group(6);
7961388
797- jTextField_from.setText(from);
798- jTextField_to.setText(to);
1389+ jDateField_from.setText(from);
1390+ jDateField_to.setText(to);
7991391 }
8001392
8011393 // 絞り込みあり
@@ -817,112 +1409,244 @@ public class VWSearchWordDialog extends JEscCancelDialog {
8171409 jTextField_keyword.setText(str);
8181410 }
8191411
1412+ // 検索履歴あり
1413+ if (swi != null){
1414+ String from = swi.getFrom();
1415+ String to = swi.getTo();
1416+ SearchKey key = swi.getSearchKey();
1417+
1418+ if (from != null)
1419+ jDateField_from.setText(from);
1420+ if (to != null)
1421+ jDateField_to.setText(to);
1422+ if (key != null)
1423+ decodeSearchKey(key);
1424+ }
1425+
8201426 updateControlStatus();
8211427 jTextField_keyword.requestFocusInWindow();
8221428 }
8231429
824- /*******************************************************************************
825- * リスナー
826- ******************************************************************************/
8271430 /*
828- * ウインドウのオープン/クローズ等の変化
1431+ * 検索キーを生成する
8291432 */
830- private final WindowListener wl_panel = new WindowListener(){
831- @Override
832- public void windowOpened(WindowEvent e) {
833- }
1433+ private SearchKey createSearchKey(){
1434+ SearchKey sk = new SearchKey();
8341435
835- @Override
836- public void windowClosing(WindowEvent e) {
837- }
1436+ StringBuilder label = new StringBuilder("");
1437+ String tStr = "";
1438+ String rStr = "";
1439+ String cStr = "";
1440+ boolean hasData = false;
8381441
839- @Override
840- public void windowClosed(WindowEvent e) {
1442+ // TITLEANDDETAIL ("0", true, true, "番組名、内容に"),
1443+ String keyword = jTextField_keyword.getText();
1444+ if (!keyword.isEmpty()){
1445+ if (jCheckBox_title.isSelected() && jCheckBox_detail.isSelected()){
1446+ tStr += "0\t";
1447+ rStr += keyword + "\t";
1448+ cStr += "0\t";
1449+ hasData = true;
1450+ }
1451+ // TITLE ("1", true, true, "番組名に"),
1452+ else if (jCheckBox_title.isSelected()){
1453+ tStr += "1\t";
1454+ rStr += keyword + "\t";
1455+ cStr += "0\t";
1456+ hasData = true;
1457+ }
1458+ // DETAIL ("2", true, true, "番組内容に"),
1459+ else if (jCheckBox_detail.isSelected()){
1460+ tStr += "2\t";
1461+ rStr += keyword + "\t";
1462+ cStr += "0\t";
1463+ hasData = true;
1464+ }
1465+ label.append(keyword);
8411466 }
842-
843- @Override
844- public void windowIconified(WindowEvent e) {
845- if (autoclose_enabled)
846- doCancel();
1467+ else
1468+ label.append("(キーワードなし)");
1469+
1470+ // CHANNEL ("3", true, true, "チャンネル名に"),
1471+ String ch = (String)jComboBox_channel.getSelectedItem();
1472+ if (!ch.isEmpty()){
1473+ tStr += "3\t";
1474+ rStr += ch + "\t";
1475+ cStr += "0\t";
1476+ label.append("+" + ch);
1477+ hasData = true;
8471478 }
8481479
849- @Override
850- public void windowDeiconified(WindowEvent e) {
1480+ // GENRE ("4", false, true, "ジャンルに"),
1481+ String genre = (String)jComboBox_genre.getSelectedItem();
1482+ if (!genre.isEmpty()){
1483+ tStr += "4\t";
1484+ rStr += genre + "\t";
1485+ cStr += "0\t";
1486+ label.append("+" + genre);
1487+ hasData = true;
8511488 }
852-
853- @Override
854- public void windowActivated(WindowEvent e) {
1489+ // SUBGENRE ("15", false, true, "サブジャンルに"),
1490+ String subgenre = (String)jComboBox_subgenre.getSelectedItem();
1491+ if (!subgenre.isEmpty()){
1492+ tStr += "15\t";
1493+ rStr += genre + " - " + subgenre + "\t";
1494+ cStr += "0\t";
1495+ label.append("+" + subgenre);
1496+ hasData = true;
8551497 }
8561498
857- @Override
858- public void windowDeactivated(WindowEvent e) {
859- if (autoclose_enabled)
860- doCancel();
1499+ // NEW ("5", false, false, "新番組"),
1500+ if (jCheckBox_new.isSelected()){
1501+ tStr += "5\t";
1502+ rStr += "\t";
1503+ cStr += "0\t";
1504+ label.append("+新");
1505+ hasData = true;
8611506 }
862- };
863-
864- /*
865- * 「検索履歴」コンボボックスの項目変更
866- */
867- private final ItemListener il_swlistSelected = new ItemListener() {
868-
869- @Override
870- public void itemStateChanged(ItemEvent e) {
871- switch (e.getStateChange()) {
872- case ItemEvent.SELECTED:
873- decodeKeyword((String)jComboBox_swlist.getSelectedItem());
874- break;
875- }
1507+ // LAST ("6", false, false, "最終回"),
1508+ if (jCheckBox_final.isSelected()){
1509+ tStr += "6\t";
1510+ rStr += "\t";
1511+ cStr += "0\t";
1512+ label.append("+終");
1513+ hasData = true;
8761514 }
877- };
878-
879- /*
880- * テキストフィールドでのEnterキー
881- */
882- private final ActionListener al_search = new ActionListener() {
883- @Override
884- public void actionPerformed(ActionEvent e) {
885- if (jButton_search.isEnabled())
886- doSearch();
1515+ // REPEAT ("7", false, false, "再放送"),
1516+ if (jCheckBox_repeat.isSelected()){
1517+ tStr += "7\t";
1518+ rStr += "\t";
1519+ cStr += "0\t";
1520+ label.append("+再");
1521+ hasData = true;
8871522 }
888- };
889-
890- /**
891- * テキストフィールドでの文書変更
892- */
893- private final DocumentListener dl_documentChanged = new DocumentListener(){
894- @Override
895- public void insertUpdate(DocumentEvent e) {
896- updateControlStatus();
1523+ // FIRST ("8", false, false, "初回放送"),
1524+ if (jCheckBox_first.isSelected()){
1525+ tStr += "8\t";
1526+ rStr += "\t";
1527+ cStr += "0\t";
1528+ label.append("+初");
1529+ hasData = true;
1530+ }
1531+ // LENGTH ("9", false, true, "番組長が"),
1532+ String lenfrom = jTextField_lenfrom.getText();
1533+ if (!lenfrom.isEmpty()){
1534+ tStr += "9\t";
1535+ rStr += lenfrom + " \t";
1536+ cStr += "0\t";
1537+ label.append("+" + lenfrom + "分以上");
1538+ hasData = true;
8971539 }
8981540
899- @Override
900- public void removeUpdate(DocumentEvent e) {
901- updateControlStatus();
1541+ String lento = jTextField_lento.getText();
1542+ if (!lento.isEmpty()){
1543+ tStr += "9\t";
1544+ rStr += String.valueOf(Integer.parseInt(lento)+1) + " \t";
1545+ cStr += "1\t";
1546+ label.append("+" + lento + "分以下");
1547+ hasData = true;
9021548 }
9031549
904- @Override
905- public void changedUpdate(DocumentEvent e) {
906- updateControlStatus();
1550+ // STARTA ("10", false, true, "開始時刻(上限)が"),
1551+ // STARTZ ("11", false, true, "開始時刻(下限)が"),
1552+ // SPECIAL ("12", false, false, "特番"),
1553+ if (jCheckBox_special.isSelected()){
1554+ tStr += "12\t";
1555+ rStr += "\t";
1556+ cStr += "0\t";
1557+ label.append("+特");
1558+ hasData = true;
9071559 }
908- };
1560+ // NOSCRUMBLE ("13", false, false, "無料放送"),
1561+ // STARTDATETIME ("14", true, true, "開始日時に"),
1562+ // LIVE ("16", false, false, "生放送"),
1563+ // BILINGUAL ("17", false, false, "二か国語放送"),
1564+ // STANDIN ("18", false, false, "吹替放送"),
1565+ // RATING ("19", false, false, "視聴制限"),
1566+ // MULTIVOICE ("20", false, false, "副音声/コメンタリ"),
1567+
1568+ if (!hasData)
1569+ return null;
1570+
1571+ sk.setLabel(label.toString());
1572+ sk.setTarget(tStr); // compile後は順番が変わるので残すことにする
1573+ sk.setKeyword(rStr); // 同上
1574+ sk.setContain(cStr); // 同上
1575+
1576+ sk.setCondition("0");
1577+ sk.setInfection("0");
1578+ sk.setOkiniiri("0");
1579+ sk.setCaseSensitive(false);
1580+ sk.setShowInStandby(false);
1581+
1582+ new SearchProgram().compile(sk);
1583+
1584+ return sk;
1585+ }
9091586
9101587 /*
911- * テキストフィールドでのESCキー押下
1588+ * 検索キーをデコードする
9121589 */
913- private final KeyListener kl_escape = new KeyListener(){
914- @Override
915- public void keyTyped(KeyEvent e) {
916- }
1590+ private void decodeSearchKey(SearchKey key){
1591+ if (key == null || key.getTarget() == null)
1592+ return;
9171593
918- @Override
919- public void keyPressed(KeyEvent e) {
920- if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
921- doCancel();
922- }
1594+ String [] ts = key.getTarget().split("\t");
1595+ String [] rs = key.getKeyword().split("\t");
1596+ String [] cs = key.getContain().split("\t");
9231597
924- @Override
925- public void keyReleased(KeyEvent e) {
1598+ jTextField_keyword.setText("");
1599+
1600+ for (int n=0; n<ts.length; n++){
1601+// System.out.println("ts[" + n +"]=" + ts[n]);
1602+
1603+ switch(ts[n]){
1604+ case "0": // TITLEANDDETAIL ("0", true, true, "番組名、内容に"),
1605+ case "1": // TITLE ("1", true, true, "番組名に"),
1606+ case "2": // DETAIL ("2", true, true, "番組内容に"),
1607+ jTextField_keyword.setText(rs[n]);
1608+ jCheckBox_title.setSelected(!ts[n].equals("2"));
1609+ jCheckBox_detail.setSelected(!ts[n].equals("1"));
1610+ break;
1611+ case "3": // CHANNEL ("3", true, true, "チャンネル名に"),
1612+ jComboBox_channel.setSelectedItem(rs[n]);
1613+ break;
1614+ case "4": // GENRE ("4", false, true, "ジャンルに"),
1615+ jComboBox_genre.setSelectedItem(rs[n]);
1616+ break;
1617+ case "5": // NEW ("5", false, false, "新番組"),
1618+ jCheckBox_new.setSelected(true);
1619+ break;
1620+ case "6": // LAST ("6", false, false, "最終回"),
1621+ jCheckBox_final.setSelected(true);
1622+ break;
1623+ case "7": // REPEAT ("7", false, false, "再放送"),
1624+ jCheckBox_repeat.setSelected(true);
1625+ break;
1626+ case "8": // FIRST ("8", false, false, "初回放送"),
1627+ jCheckBox_first.setSelected(true);
1628+ break;
1629+ case "9": // LENGTH ("9", false, true, "番組長が"),
1630+ if (cs[n].equals("0"))
1631+ jTextField_lenfrom.setText(rs[n].trim());
1632+ else if (cs[n].equals("1")){
1633+ try{
1634+ jTextField_lento.setText(String.valueOf(Integer.parseInt(rs[n].trim())-1));
1635+ }catch(NumberFormatException e){
1636+ jTextField_lento.setText(rs[n].trim());
1637+ }
1638+ }
1639+ break;
1640+ // STARTA ("10", false, true, "開始時刻(上限)が"),
1641+ // STARTZ ("11", false, true, "開始時刻(下限)が"),
1642+ case "12": // SPECIAL ("12", false, false, "特番"),
1643+ jCheckBox_special.setSelected(true);
1644+ break;
1645+ case "15": // SUBGENRE ("15", false, true, "サブジャンルに"),
1646+ ProgSubgenre subgenre = ProgSubgenre.get(rs[n]);
1647+ jComboBox_subgenre.setSelectedItem(subgenre != null ? subgenre.toString() : "");
1648+ break;
1649+ }
9261650 }
927- };
1651+ }
9281652 }
--- /dev/null
+++ b/TinyBannavi/src/tainavi/VWSearchWordRenameDialog.java
@@ -0,0 +1,289 @@
1+package tainavi;
2+
3+import java.awt.Color;
4+import java.awt.Component;
5+import java.awt.Dimension;
6+import java.awt.Rectangle;
7+import java.awt.event.ActionEvent;
8+import java.awt.event.ActionListener;
9+import java.awt.event.MouseAdapter;
10+import java.awt.event.MouseEvent;
11+
12+import javax.swing.JButton;
13+import javax.swing.JLabel;
14+import javax.swing.JPanel;
15+import javax.swing.JTextField;
16+import javax.swing.SpringLayout;
17+import javax.swing.border.LineBorder;
18+import javax.swing.event.DocumentEvent;
19+import javax.swing.event.DocumentListener;
20+
21+/*
22+ * 検索履歴名称変更画面
23+ *
24+ */
25+public class VWSearchWordRenameDialog extends JEscCancelDialog{
26+
27+ private static final long serialVersionUID = 1L;
28+
29+ /*******************************************************************************
30+ * 定数
31+ ******************************************************************************/
32+
33+ // レイアウト関連
34+
35+ private static final int PARTS_HEIGHT = 25;
36+ private static final int SEP_WIDTH = 10;
37+ private static final int SEP_HEIGHT = 5;
38+
39+ private static final int LABEL_WIDTH = 100;
40+ private static final int TEXT_WIDTH = 450;
41+
42+ private static final int BUTTON_WIDTH = 120;
43+
44+ private static final int PANEL_WIDTH = SEP_WIDTH+LABEL_WIDTH+SEP_WIDTH+TEXT_WIDTH+SEP_WIDTH*2+BUTTON_WIDTH+SEP_WIDTH;
45+
46+ /*******************************************************************************
47+ * 部品
48+ ******************************************************************************/
49+
50+ private JPanel jPanel = null;
51+
52+ private JLabel jLabel_name = null;
53+ private JTextField jTextField_name = null;
54+ private JLabel jLabel_message = null;
55+
56+ private JButton jButton_ok = null;
57+ private JButton jButton_cancel = null;
58+
59+ /*******************************************************************************
60+ * 部品以外のインスタンスメンバー
61+ ******************************************************************************/
62+ private SearchWordList swlist = null;
63+ private String name_edited = null;
64+
65+ /*******************************************************************************
66+ * コンストラクタ
67+ ******************************************************************************/
68+ public VWSearchWordRenameDialog() {
69+
70+ super();
71+
72+ this.setModal(true);
73+ setContentPane(getJPanel());
74+ pack();
75+ setTitle("検索条件の名称変更");
76+ setResizable(false);
77+ }
78+
79+ /*******************************************************************************
80+ * 公開メソッド
81+ ******************************************************************************/
82+ /*
83+ * ダイアログを表示する
84+ */
85+ public void open(String s, SearchWordList list, Component comp){
86+ swlist = list;
87+
88+ jTextField_name.setText(s);
89+
90+ this.setLocationRelativeTo(comp);
91+
92+ setVisible(true);
93+ }
94+
95+ /*
96+ * 入力した名称を取得する
97+ *
98+ */
99+ public String getEditedName(){
100+ return name_edited;
101+ }
102+
103+ /*
104+ * ダイアログを位置決めする
105+ */
106+ public void setPosition(int x, int y) {
107+ Rectangle r = this.getBounds();
108+ r.x = x;
109+ r.y = y;
110+ this.setBounds(r);
111+ }
112+
113+ /*******************************************************************************
114+ * コンポーネント
115+ ******************************************************************************/
116+ /*
117+ * パネル全体
118+ */
119+ private JPanel getJPanel() {
120+ if (jPanel == null) {
121+ jPanel = new JPanel();
122+
123+ jPanel.setLayout(new SpringLayout());
124+
125+ // 1行目
126+ int y = SEP_HEIGHT;
127+ int x = SEP_WIDTH;
128+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_name("検索条件名称"), LABEL_WIDTH, PARTS_HEIGHT, x, y);
129+ x += LABEL_WIDTH + SEP_WIDTH;
130+ CommonSwingUtils.putComponentOn(jPanel, getJTextField_name(), TEXT_WIDTH, PARTS_HEIGHT, x, y);
131+ x += TEXT_WIDTH + SEP_WIDTH*2;
132+ int xb = x;
133+ CommonSwingUtils.putComponentOn(jPanel, getJButton_ok("OK"), BUTTON_WIDTH, PARTS_HEIGHT, xb, y);
134+ y += PARTS_HEIGHT + SEP_HEIGHT;
135+
136+ // 2行目
137+ x = SEP_WIDTH + LABEL_WIDTH + SEP_WIDTH;
138+ CommonSwingUtils.putComponentOn(jPanel, getJLabel_message(), TEXT_WIDTH, PARTS_HEIGHT, x, y);
139+
140+ CommonSwingUtils.putComponentOn(jPanel, getJButton_cancel("キャンセル"), BUTTON_WIDTH, PARTS_HEIGHT, xb, y);
141+ y += PARTS_HEIGHT + SEP_HEIGHT;
142+
143+ y += SEP_HEIGHT;
144+
145+ jPanel.setPreferredSize(new Dimension(PANEL_WIDTH, y));
146+ jPanel.setBorder(new LineBorder(Color.BLACK, 1));
147+ }
148+
149+ return jPanel;
150+ }
151+
152+ /*
153+ * 「キーワード名称」ラベル
154+ */
155+ private JLabel getJLabel_name(String s) {
156+ if (jLabel_name == null) {
157+ jLabel_name = new JLabel(s);
158+ }
159+ return(jLabel_name);
160+ }
161+
162+ /*
163+ * 「キーワード名称」テキストフィールド
164+ */
165+ private JTextField getJTextField_name() {
166+ if (jTextField_name == null) {
167+ jTextField_name = new JTextField();
168+ jTextField_name.addActionListener(al_search);
169+ jTextField_name.getDocument().addDocumentListener(dl_documentChanged);
170+ }
171+
172+ return(jTextField_name);
173+ }
174+
175+ /*
176+ * 「メッセージ」ラベル
177+ */
178+ private JLabel getJLabel_message() {
179+ if (jLabel_message == null) {
180+ jLabel_message = new JLabel("すでに使用されている名称です。");
181+ jLabel_message.setForeground(Color.RED);
182+ }
183+ return(jLabel_message);
184+ }
185+
186+ /*
187+ * 「OK」ボタン
188+ */
189+ private JButton getJButton_ok(String s) {
190+ if (jButton_ok == null) {
191+ jButton_ok = new JButton(s);
192+ jButton_ok.addMouseListener(new MouseAdapter() {
193+ @Override
194+ public void mouseClicked(MouseEvent e) {
195+ if (jButton_ok.isEnabled())
196+ doRename();
197+ }
198+ });
199+ }
200+
201+ return(jButton_ok);
202+ }
203+
204+ /*
205+ * 「キャンセル」ボタン
206+ */
207+ private JButton getJButton_cancel(String s) {
208+ if (jButton_cancel == null) {
209+ jButton_cancel = new JButton(s);
210+ jButton_cancel.addMouseListener(new MouseAdapter() {
211+ @Override
212+ public void mouseClicked(MouseEvent e) {
213+ doCancel();
214+ }
215+ });
216+ }
217+
218+ return(jButton_cancel);
219+ }
220+
221+ /*******************************************************************************
222+ * アクション
223+ ******************************************************************************/
224+ /*
225+ * 履歴の名称を変更する
226+ */
227+ protected void doRename(){
228+ name_edited = jTextField_name.getText();
229+ setVisible(false);
230+ }
231+
232+ /*
233+ * キャンセルする
234+ *
235+ * @see tainavi.JEscCancelDialog#doCancel()
236+ */
237+ @Override
238+ protected void doCancel() {
239+ setVisible(false);
240+ }
241+
242+ /*******************************************************************************
243+ * リスナー
244+ ******************************************************************************/
245+ /*
246+ * テキストフィールドでのEnterキー
247+ */
248+ private final ActionListener al_search = new ActionListener() {
249+ @Override
250+ public void actionPerformed(ActionEvent e) {
251+ if (jButton_ok.isEnabled())
252+ doRename();
253+ }
254+ };
255+
256+ /**
257+ * テキストフィールドでの文書変更
258+ */
259+ private final DocumentListener dl_documentChanged = new DocumentListener(){
260+ @Override
261+ public void insertUpdate(DocumentEvent e) {
262+ updateControlStatus();
263+ }
264+
265+ @Override
266+ public void removeUpdate(DocumentEvent e) {
267+ updateControlStatus();
268+ }
269+
270+ @Override
271+ public void changedUpdate(DocumentEvent e) {
272+ updateControlStatus();
273+ }
274+ };
275+
276+ /*******************************************************************************
277+ * 内部関数
278+ ******************************************************************************/
279+ /*
280+ * 部品のステータスを更新する
281+ */
282+ protected void updateControlStatus(){
283+ String name = jTextField_name.getText();
284+ boolean used = swlist.getItemFromLabel(name) != null;
285+
286+ jLabel_message.setVisible(used);
287+ jButton_ok.setEnabled(name.length() > 0 && !used);
288+ }
289+}
--- a/TinyBannavi/src/tainavi/VWTraceKeyDialog.java
+++ b/TinyBannavi/src/tainavi/VWTraceKeyDialog.java
@@ -10,7 +10,6 @@ import javax.swing.JButton;
1010 import javax.swing.JCheckBox;
1111 import javax.swing.JComboBox;
1212 import javax.swing.JComponent;
13-import javax.swing.JDialog;
1413 import javax.swing.JLabel;
1514 import javax.swing.JOptionPane;
1615 import javax.swing.JPanel;
@@ -21,31 +20,50 @@ import javax.swing.event.ChangeEvent;
2120 import javax.swing.event.ChangeListener;
2221
2322 /***
24- *
23+ *
2524 * 番組追跡検索の設定のクラス
26- *
25+ *
2726 */
2827
2928 public class VWTraceKeyDialog extends JEscCancelDialog {
3029
3130 private static final long serialVersionUID = 1L;
3231
32+ /*******************************************************************************
33+ * 定数
34+ ******************************************************************************/
35+
36+ // レイアウト関連
37+ private static final int PARTS_HEIGHT = 25;
38+ private static final int SEP_WIDTH = 10;
39+ private static final int SEP_HEIGHT = 5;
40+ private static final int LABEL_WIDTH = 125;
41+ private static final int TITLE_WIDTH = 400;
42+ private static final int ITEM_WIDTH = 200;
43+ private static final int BUTTON_WIDTH = 100;
44+
45+ /*******************************************************************************
46+ * 部品以外のインスタンスメンバー
47+ ******************************************************************************/
3348 private TraceProgram xKeys = null;
3449 private TraceKey xKey = null;
3550 private ProgDetailList xTvd = null;
36-
51+
3752 private boolean reg = false;
38-
53+
3954 public String getNewLabel() { return TraceProgram.getNewLabel(jTextField_title.getText(),jTextField_channel.getText()); }
40-
41- private ArrayList<String> okiniiri_items = new ArrayList<String>();
55+
56+ private ArrayList<String> okiniiri_items = new ArrayList<String>();
4257 public void clean_okiniiri_items() { okiniiri_items.clear(); }
4358 public void add_okiniiri_item(String s) { okiniiri_items.add(s); }
4459
4560 // キーワード検索の設定ウィンドウのコンポーネント
46-
61+
62+ /*******************************************************************************
63+ * 部品
64+ ******************************************************************************/
4765 private JPanel jPanel = null;
48-
66+
4967 private JButton jButton_title = null;
5068 private JTextField jTextField_title = null;
5169 private JLabel jLabel_channel = null;
@@ -63,14 +81,49 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
6381
6482 // ほげほげ
6583
84+ /*******************************************************************************
85+ * コンストラクタ
86+ ******************************************************************************/
87+ public VWTraceKeyDialog(int x, int y) {
88+
89+ super();
90+
91+ //
92+ reg = false;
93+
94+ // 初期設定
95+ clean_okiniiri_items();
96+ for (String okini : TVProgram.OKINIIRI) {
97+ add_okiniiri_item(okini);
98+ }
99+
100+ //
101+ this.setModal(true);
102+ this.setContentPane(getJPanel());
103+ // タイトルバーの高さも考慮する必要がある
104+ Dimension d = getJPanel().getPreferredSize();
105+ this.pack();
106+ this.setBounds(
107+ x,
108+ y,
109+ d.width+(this.getInsets().left+this.getInsets().right),
110+ d.height+(this.getInsets().top+this.getInsets().bottom));
111+ this.setResizable(false);
112+ //
113+ this.setTitle("番組追跡の設定");
114+ }
115+
116+ /*******************************************************************************
117+ * 公開メソッド
118+ ******************************************************************************/
66119 public boolean isRegistered() { return reg; }
67-
120+
68121 public void open(TraceProgram sKeys, ProgDetailList tvd, int threshold) {
69-
122+
70123 xKeys = sKeys;
71124 xKey = null;
72125 xTvd = tvd;
73-
126+
74127 jTextField_title.setText(tvd.title);
75128 jTextField_title.setCaretPosition(0);
76129 jTextField_channel.setText(tvd.center);
@@ -78,16 +131,16 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
78131 jComboBox_okiniiri.setSelectedItem(TVProgram.OKINIIRI[0]);
79132 jCheckBox_disableRepeat.setSelected(false);
80133 jCheckBox_showLatestOnly.setSelected(false);
81-
134+
82135 jButton_title.setEnabled(true);
83136 }
84-
137+
85138 public void reopen(String label, TraceProgram sKeys) {
86-
139+
87140 xKeys = sKeys;
88141 xKey = null;
89142 xTvd = null;
90-
143+
91144 for (TraceKey k : xKeys.getTraceKeys()) {
92145 if (k._getLabel().equals(label)) {
93146 // 操作対象をみつけた
@@ -95,7 +148,7 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
95148 break;
96149 }
97150 }
98-
151+
99152 jTextField_title.setText(xKey.getTitle());
100153 jTextField_title.setCaretPosition(0);
101154 jTextField_channel.setText(xKey.getCenter());
@@ -103,73 +156,80 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
103156 jComboBox_okiniiri.setSelectedItem(xKey.getOkiniiri());
104157 jCheckBox_disableRepeat.setSelected(xKey.getDisableRepeat());
105158 jCheckBox_showLatestOnly.setSelected(xKey.getShowLatestOnly());
106-
159+
107160 jButton_title.setEnabled(false);
108161 }
109-
162+
163+ @Override
164+ protected void doCancel() {
165+ dispose();
166+ }
167+
168+ /*******************************************************************************
169+ * コンポーネント
170+ ******************************************************************************/
110171 //
111172 private JPanel getJPanel() {
112173 if (jPanel == null) {
113174 jPanel = new JPanel();
114175
115176 jPanel.setLayout(new SpringLayout());
116-
117- int lw = 90;
118- int iw = 200;
119- int ix = 10+lw+10;
120- int y = 10;
121- _getJComponent(jPanel, getJButton_title("番組タイトル"), lw, 25, 10, y);
122- _getJComponent(jPanel, getJTextField_title(), iw, 25, ix, y);
123-
124- y += 30;
125- _getJComponent(jPanel, getJLabel_channel("チャンネル名"), lw, 25, 10, y);
126- _getJComponent(jPanel, getJTextField_channel(), iw, 25, ix, y);
127-
128- y += 30;
129- _getJComponent(jPanel, getJLabel_fazzyThreshold("あいまい閾値"), lw, 25, 10, y);
130- _getJComponent(jPanel, getJSlider_fazzyThreshold(), iw, 25, ix, y);
131-
132- y += 30;
133- _getJComponent(jPanel, getJLabel_okiniiri("お気に入り度"), lw, 25, 10, y);
134- _getJComponent(jPanel, getJComboBox_okiniiri(), iw, 25, ix, y);
135-
136- y += 30;
137- _getJComponent(jPanel, getJLabel_disableRepeat("再放送を除く"), lw, 25, 10, y);
138- _getJComponent(jPanel, getJCheckBox_disableRepeat(), iw, 25, ix, y);
139-
140- y += 30;
141- _getJComponent(jPanel, getJLabel_showLatestOnly("リピート放送を検出"), lw, 25, 10, y);
142- _getJComponent(jPanel, getJCheckBox_showLatestOnly(), iw, 25, ix, y);
143-
144- int wd = 10+lw+10+iw+20;
145-
146- y += 50;
147- int bw = 75;
148- _getJComponent(jPanel, getJButton_label("登録"), bw, 25, (wd/2)-bw-5, y);
149- _getJComponent(jPanel, getJButton_cancel("キャンセル"), bw, 25, (wd/2)+5, y);
150-
151- y += 30;
152-
153- Dimension d = new Dimension(wd,y+10);
177+
178+ int lw = LABEL_WIDTH;
179+ int iw = ITEM_WIDTH;
180+ int ix = SEP_WIDTH+lw+SEP_WIDTH;
181+ int y = SEP_HEIGHT;
182+ int x = SEP_WIDTH;
183+ _getJComponent(jPanel, getJButton_title("番組タイトル"), lw, PARTS_HEIGHT, x, y);
184+ _getJComponent(jPanel, getJTextField_title(), TITLE_WIDTH, PARTS_HEIGHT, ix, y);
185+
186+ y += PARTS_HEIGHT+SEP_HEIGHT;
187+ _getJComponent(jPanel, getJLabel_channel("チャンネル名"), lw, PARTS_HEIGHT, x, y);
188+ _getJComponent(jPanel, getJTextField_channel(), iw, PARTS_HEIGHT, ix, y);
189+
190+ y += PARTS_HEIGHT+SEP_HEIGHT;
191+ _getJComponent(jPanel, getJLabel_fazzyThreshold("あいまい閾値"), lw, PARTS_HEIGHT, x, y);
192+ _getJComponent(jPanel, getJSlider_fazzyThreshold(), iw, PARTS_HEIGHT, ix, y);
193+
194+ y += PARTS_HEIGHT+SEP_HEIGHT;
195+ _getJComponent(jPanel, getJLabel_okiniiri("お気に入り度"), lw, PARTS_HEIGHT, x, y);
196+ _getJComponent(jPanel, getJComboBox_okiniiri(), iw, PARTS_HEIGHT, ix, y);
197+
198+ y += PARTS_HEIGHT+SEP_HEIGHT;
199+ _getJComponent(jPanel, getJLabel_disableRepeat("再放送を除く"), lw, PARTS_HEIGHT, x, y);
200+ _getJComponent(jPanel, getJCheckBox_disableRepeat(), iw, PARTS_HEIGHT, ix, y);
201+
202+ y += PARTS_HEIGHT+SEP_HEIGHT;
203+ _getJComponent(jPanel, getJLabel_showLatestOnly("リピート放送を検出"), lw, PARTS_HEIGHT, x, y);
204+ _getJComponent(jPanel, getJCheckBox_showLatestOnly(), iw, PARTS_HEIGHT, ix, y);
205+
206+ int wd = SEP_WIDTH+LABEL_WIDTH+SEP_WIDTH+TITLE_WIDTH+SEP_WIDTH;
207+
208+ y += PARTS_HEIGHT+SEP_HEIGHT*2;
209+ int bw = BUTTON_WIDTH;
210+ _getJComponent(jPanel, getJButton_label("登録"), bw, PARTS_HEIGHT, (wd/2)-bw-SEP_WIDTH, y);
211+ _getJComponent(jPanel, getJButton_cancel("キャンセル"), bw, PARTS_HEIGHT, (wd/2)+SEP_WIDTH, y);
212+
213+ y += PARTS_HEIGHT+SEP_HEIGHT;
214+
215+ Dimension d = new Dimension(wd, y);
154216 jPanel.setPreferredSize(d);
155217 }
156218 return jPanel;
157219 }
158-
220+
159221 private void _getJComponent(JPanel p, JComponent c, int width, int height, int x, int y) {
160222 c.setPreferredSize(new Dimension(width, height));
161223 ((SpringLayout)p.getLayout()).putConstraint(SpringLayout.NORTH, c, y, SpringLayout.NORTH, p);
162224 ((SpringLayout)p.getLayout()).putConstraint(SpringLayout.WEST, c, x, SpringLayout.WEST, p);
163225 p.add(c);
164226 }
165-
166-
167-
227+
168228 //
169229 private JButton getJButton_title(String s) {
170230 if (jButton_title == null) {
171231 jButton_title = new JButton(s);
172-
232+
173233 jButton_title.addActionListener(new ActionListener() {
174234 @Override
175235 public void actionPerformed(ActionEvent e) {
@@ -181,7 +241,7 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
181241 }
182242 return(jButton_title);
183243 }
184-
244+
185245 //
186246 private JTextField getJTextField_title() {
187247 if (jTextField_title == null) {
@@ -189,7 +249,7 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
189249 }
190250 return(jTextField_title);
191251 }
192-
252+
193253 //
194254 private JLabel getJLabel_channel(String s) {
195255 if (jLabel_channel == null) {
@@ -204,7 +264,7 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
204264 }
205265 return(jTextField_channel);
206266 }
207-
267+
208268 //
209269 private JLabel getJLabel_fazzyThreshold(String s) {
210270 if (jLabel_fazzyThreshold == null) {
@@ -215,18 +275,18 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
215275 private JSlider getJSlider_fazzyThreshold() {
216276 if (jSlider_fazzyThreshold == null) {
217277 jSlider_fazzyThreshold = new JSlider(1,99);
218-
278+
219279 jSlider_fazzyThreshold.addChangeListener(new ChangeListener() {
220280 @Override
221281 public void stateChanged(ChangeEvent e) {
222-
223- jLabel_fazzyThreshold.setText("あいまい閾値"+jSlider_fazzyThreshold.getValue());
282+
283+ jLabel_fazzyThreshold.setText("あいまい閾値("+jSlider_fazzyThreshold.getValue() +")");
224284 }
225285 });
226286 }
227287 return(jSlider_fazzyThreshold);
228288 }
229-
289+
230290 //
231291 private JLabel getJLabel_okiniiri(String s) {
232292 if (jLabel_okiniiri == null) {
@@ -238,7 +298,7 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
238298 if (jComboBox_okiniiri == null) {
239299 jComboBox_okiniiri = new JComboBox();
240300 jComboBox_okiniiri.setEditable(false);
241-
301+
242302 DefaultComboBoxModel aModel = new DefaultComboBoxModel();
243303 jComboBox_okiniiri.setModel(aModel);
244304 for (String k : okiniiri_items) {
@@ -247,7 +307,7 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
247307 }
248308 return(jComboBox_okiniiri);
249309 }
250-
310+
251311 //
252312 private JLabel getJLabel_disableRepeat(String s) {
253313 if (jLabel_disableRepeat == null) {
@@ -261,7 +321,7 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
261321 }
262322 return(jCheckBox_disableRepeat);
263323 }
264-
324+
265325 //
266326 private JLabel getJLabel_showLatestOnly(String s) {
267327 if (jLabel_showLatestOnly == null) {
@@ -275,13 +335,13 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
275335 }
276336 return(jCheckBox_showLatestOnly);
277337 }
278-
338+
279339 //
280340 private JButton getJButton_label(String s) {
281341 if (jButton_label == null) {
282342 jButton_label = new JButton();
283343 jButton_label.setText(s);
284-
344+
285345 jButton_label.addActionListener(new ActionListener() {
286346 @Override
287347 public void actionPerformed(ActionEvent e) {
@@ -294,12 +354,32 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
294354 }
295355 return(jButton_label);
296356 }
297-
357+
358+ //
359+ private JButton getJButton_cancel(String s) {
360+ if (jButton_cancel == null) {
361+ jButton_cancel = new JButton();
362+ jButton_cancel.setText(s);
363+
364+ jButton_cancel.addActionListener(new ActionListener() {
365+
366+ @Override
367+ public void actionPerformed(ActionEvent e) {
368+ doCancel();
369+ }
370+ });
371+ }
372+ return jButton_cancel;
373+ }
374+
375+ /*******************************************************************************
376+ * 内部関数
377+ ******************************************************************************/
298378 private boolean addToTraceKeyList() {
299379 if (jTextField_title.getText().equals("")) {
300380 return false;
301381 }
302-
382+
303383 // 重複登録を許さない
304384 for (TraceKey k : xKeys.getTraceKeys()) {
305385 if ( k != xKey && k._getLabel().equals(getNewLabel()) ) {
@@ -307,13 +387,13 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
307387 return false;
308388 }
309389 }
310-
390+
311391 if ( xKey == null ) {
312392 // 新規登録の場合はエントリがないので作成する
313393 xKey = new TraceKey();
314394 xKeys.add(xKey);
315395 }
316-
396+
317397 xKey.setLabel(getNewLabel());
318398 xKey.setCenter(jTextField_channel.getText());
319399 xKey.setTitle(jTextField_title.getText().trim());
@@ -322,62 +402,10 @@ public class VWTraceKeyDialog extends JEscCancelDialog {
322402 xKey.setOkiniiri((String) jComboBox_okiniiri.getSelectedItem());
323403 xKey.setDisableRepeat(jCheckBox_disableRepeat.isSelected());
324404 xKey.setShowLatestOnly(jCheckBox_showLatestOnly.isSelected());
325-
405+
326406 reg = xKeys.save();
327-
328- return reg;
329- }
330-
331- //
332- private JButton getJButton_cancel(String s) {
333- if (jButton_cancel == null) {
334- jButton_cancel = new JButton();
335- jButton_cancel.setText(s);
336-
337- jButton_cancel.addActionListener(new ActionListener() {
338407
339- @Override
340- public void actionPerformed(ActionEvent e) {
341- doCancel();
342- }
343- });
344- }
345- return jButton_cancel;
346- }
347-
348- @Override
349- protected void doCancel() {
350- dispose();
408+ return reg;
351409 }
352-
353-
354- // コンストラクタ
355- public VWTraceKeyDialog(int x, int y) {
356-
357- super();
358410
359- //
360- reg = false;
361-
362- // 初期設定
363- clean_okiniiri_items();
364- for (String okini : TVProgram.OKINIIRI) {
365- add_okiniiri_item(okini);
366- }
367-
368- //
369- this.setModal(true);
370- this.setContentPane(getJPanel());
371- // タイトルバーの高さも考慮する必要がある
372- Dimension d = getJPanel().getPreferredSize();
373- this.pack();
374- this.setBounds(
375- x,
376- y,
377- d.width+(this.getInsets().left+this.getInsets().right),
378- d.height+(this.getInsets().top+this.getInsets().bottom));
379- this.setResizable(false);
380- //
381- this.setTitle("番組追跡の設定");
382- }
383411 }
--- a/TinyBannavi/src/tainavi/VersionInfo.java
+++ b/TinyBannavi/src/tainavi/VersionInfo.java
@@ -5,7 +5,7 @@ import java.util.regex.Pattern;
55
66
77 public class VersionInfo {
8- private static final String Version = "タイニー番組ナビゲータ for DBR-T2007 3.22.18β+1.12.18";
8+ private static final String Version = "タイニー番組ナビゲータ for DBR-T2007 3.22.18β+1.12.19";
99
1010 private static final String OSname = System.getProperty("os.name");
1111 private static final String OSvers = System.getProperty("os.version");
--- a/TinyBannavi/src/tainavi/Viewer.java
+++ b/TinyBannavi/src/tainavi/Viewer.java
@@ -1955,7 +1955,7 @@ public class Viewer extends JFrame implements ChangeListener,TickTimerListener,H
19551955 }
19561956 }
19571957 else if ( mainWindow.getSelectedTab() == MWinTab.TITLED ) {
1958- titled.redrawListByKeywordFilter(search, kStr);
1958+ titled.redrawListByKeywordFilter(search, kStr, sStr);
19591959 }
19601960 else {
19611961 if ( search != null ) {
@@ -1996,13 +1996,16 @@ public class Viewer extends JFrame implements ChangeListener,TickTimerListener,H
19961996 timer_now.pause();
19971997
19981998 if (swdialog == null)
1999- swdialog = new VWSearchWordDialog();
1999+ swdialog = new VWSearchWordDialog(getChannelSort(), swlist);
20002000
20012001 if (swdialog.isVisible()){
20022002 swdialog.setVisible(false);
20032003 }
2004- else
2005- swdialog.open(this, jbutton, swlist, jTextField_keyword.getText());
2004+ else{
2005+ int no = jComboBox_keyword.getSelectedIndex();
2006+ SearchWordItem swi= no < 1 ? null : swlist.getWordList().get(no-1);
2007+ swdialog.open(this, jbutton, jTextField_keyword.getText(), swi);
2008+ }
20062009
20072010 timer_now.start();
20082011 }
@@ -2247,6 +2250,11 @@ public class Viewer extends JFrame implements ChangeListener,TickTimerListener,H
22472250 recorded.redrawListByKeyword(null,null);
22482251 }
22492252 }
2253+ else if ( mainWindow.isTabSelected(MWinTab.TITLED) ) {
2254+ if ( e.getCause() == CancelEvent.Cause.TOOLBAR_SEARCH ) {
2255+ titled.redrawListByKeywordFilter(null,null,null);
2256+ }
2257+ }
22502258 }
22512259
22522260 /**
@@ -5148,6 +5156,10 @@ public class Viewer extends JFrame implements ChangeListener,TickTimerListener,H
51485156
51495157 // ChannelConvert
51505158 chconv.load();
5159+
5160+ // 祝日情報
5161+ if (env.getUseHolidayCSV())
5162+ HolidayInfo.Load(env.getHolidayFetchURL(), env.getHolidayFetchInterval());
51515163 }
51525164
51535165 // 二重起動チェック