2 * period viewer with tab access
4 * License : The MIT License
5 * Copyright(c) 2008 olyutorskii
8 package jp.sfjp.jindolf.view;
10 import java.awt.Component;
11 import java.awt.event.ActionListener;
12 import java.util.ArrayList;
13 import java.util.List;
14 import java.util.Objects;
15 import javax.swing.BorderFactory;
16 import javax.swing.JComponent;
17 import javax.swing.JPanel;
18 import javax.swing.JScrollPane;
19 import javax.swing.JTabbedPane;
20 import javax.swing.SwingConstants;
21 import javax.swing.border.Border;
22 import jp.sfjp.jindolf.data.DialogPref;
23 import jp.sfjp.jindolf.data.Period;
24 import jp.sfjp.jindolf.data.Village;
25 import jp.sfjp.jindolf.glyph.AnchorHitListener;
26 import jp.sfjp.jindolf.glyph.Discussion;
27 import jp.sfjp.jindolf.glyph.FontInfo;
30 * タブを用いて村情報と各Periodを切り替え表示するためのコンポーネント。
32 * <p>村情報タブのビューはVillageInfoPanel、
33 * PeriodタブのビューはPeriodViewが担当する。
35 * <p>PeriodViewの描画下請Discussionへのアクセス、
36 * およびフォント管理、会話描画設定を提供する。
38 @SuppressWarnings("serial")
39 public final class TabBrowser extends JTabbedPane{
41 private Village village;
43 private final VillageInfoPanel villageInfo = new VillageInfoPanel();
45 private FontInfo fontInfo;
46 private DialogPref dialogPref;
52 * <p>村が指定されていない状態のタブパネルを生成する。
57 setTabPlacement(SwingConstants.TOP);
58 // Mac Aqua L&F ignore WRAP_TAB_LAYOUT
59 setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
61 JComponent infoPane = decorateVillageInfo();
62 addTab("村情報", infoPane);
75 private JComponent decorateVillageInfo(){
76 Border border = BorderFactory.createEmptyBorder(5, 5, 5, 5);
77 this.villageInfo.setBorder(border);
78 JScrollPane result = new JScrollPane(this.villageInfo);
85 * <p>Periodタブは全て消え村情報タブのみになる。
87 private void initTab(){
91 selectVillageInfoTab();
100 * 指定した数のPeriodが収まるよう必要十分なタブ数を用意する。
102 * @param periods Periodの数(エピローグを含む)
104 private void modifyTabCount(int periods){ // TODO 0でも大丈夫?
105 int maxPeriodDays = periods - 1;
108 int lastTabIndex = getTabCount() - 1;
109 if(tabIndexToPeriodDays(lastTabIndex) >= maxPeriodDays) break;
110 String tabTitle = "";
111 Component dummy = new JPanel();
112 addTab(tabTitle, dummy);
115 for(;;){ // 長ければ余分なタブ削除
116 int lastTabIndex = getTabCount() - 1;
117 if(tabIndexToPeriodDays(lastTabIndex) <= maxPeriodDays) break;
118 remove(lastTabIndex);
125 * Period日付指定からタブインデックス値への変換。
127 * <p>エピローグ(0日目)のタブインデックスは1。
128 * 1日目Periodのタブインデックスは2。
130 * @param days Period日付指定
131 * @return タブインデックス。存在しないタブの場合は負の値。
133 public int periodDaysToTabIndex(int days){
134 if(days < 0) return -1;
135 int tabIndex = days+1;
136 if(tabIndex >= getTabCount()) return -1;
141 * タブインデックス値からPeriod日付指定への変換。
143 * <p>エピローグタブのPeriod日付は0。
144 * 1日目PeriodタブのPeriod日付は1。
146 * @param tabIndex タブインデックス
147 * @return Period日付指定。存在しないタブの場合は負の値。
149 private int tabIndexToPeriodDays(int tabIndex){
150 if(tabIndex < 0) return -1;
151 if(tabIndex >= getTabCount()) return -1;
152 int days = tabIndex - 1;
161 public Village getVillage(){
168 * <p>村のPeriod数に応じてタブの数は変化する。
170 * @param village 新しい村
172 public final void setVillage(Village village){
173 Village oldVillage = this.village;
174 if(oldVillage != null && village != oldVillage){
175 oldVillage.unloadPeriods();
178 this.village = village;
179 if(this.village == null){
184 if(this.village != oldVillage){
185 selectVillageInfoTab();
190 int periodNum = this.village.getPeriodSize();
191 modifyTabCount(periodNum);
193 for(int periodDays = 0; periodDays < periodNum; periodDays++){
194 Period period = this.village.getPeriod(periodDays);
195 PeriodView periodView = buildPeriodView(period);
197 int tabIndex = periodDaysToTabIndex(periodDays);
198 setComponentAt(tabIndex, periodView);
200 String caption = period.getCaption();
201 setTitleAt(tabIndex, caption);
211 * 村情報閲覧用のコンポーネントを更新する。
213 private void updateVillageInfo(){
214 Village target = getVillage();
215 this.villageInfo.updateVillage(target);
222 private void selectVillageInfoTab(){
228 * PeriodViewインスタンスを生成する。
230 * <p>フォント設定、会話表示設定、各種リスナの設定が行われる。
232 * @param period Period
233 * @return PeriodViewインスタンス
235 private PeriodView buildPeriodView(Period period){
236 Objects.nonNull(period);
240 result = new PeriodView(period);
241 result.setFontInfo(this.fontInfo);
242 result.setDialogPref(this.dialogPref);
244 Discussion discussion = result.getDiscussion();
245 for(ActionListener listener : getActionListeners()){
246 discussion.addActionListener(listener);
248 for(AnchorHitListener listener : getAnchorHitListeners()){
249 discussion.addAnchorHitListener(listener);
258 * @return PeriodView の List
260 public List<PeriodView> getPeriodViewList(){
261 int tabCount = getTabCount();
262 List<PeriodView> result = new ArrayList<>(tabCount - 1);
264 for(int tabIndex = 1; tabIndex < tabCount; tabIndex++){
265 Component component = getComponent(tabIndex);
266 PeriodView periodView = (PeriodView) component;
267 result.add(periodView);
274 * 指定したタブインデックスに関連付けられたPeriodViewを返す。
276 * <p>Periodに関係ないタブが指定されたらnullを返す。
278 * @param tabIndex タブインデックス
279 * @return 指定されたPeriodView
281 public PeriodView getPeriodView(int tabIndex){
282 if(tabIndexToPeriodDays(tabIndex) < 0) return null;
283 if(tabIndex >= getTabCount()) return null;
285 Component component = getComponentAt(tabIndex);
286 if( ! (component instanceof PeriodView) ) return null;
287 PeriodView periodView = (PeriodView) component;
293 * 現在タブ選択中のPeriodViewを返す。
295 * <p>Periodに関係ないタブが選択されていたらnullを返す。
297 * @return 現在選択中のPeriodView
299 public PeriodView currentPeriodView(){
300 int tabIndex = getSelectedIndex();
301 PeriodView result = getPeriodView(tabIndex);
306 * 指定したタブインデックスに関連付けられたDiscussionを返す。
308 * <p>Periodに関係ないタブが指定されたらnullを返す。
310 * @param tabIndex タブインデックス
311 * @return 指定されたDiscussion
313 private Discussion getDiscussion(int tabIndex){
314 PeriodView periodView = getPeriodView(tabIndex);
315 if(periodView == null) return null;
317 Discussion result = periodView.getDiscussion();
322 * 現在タブ選択中のDiscussionを返す。
324 * <p>Periodに関係ないタブが選択されていたらnullを返す。
326 * @return 現在選択中のDiscussion
328 public Discussion currentDiscussion(){
329 int tabIndex = getSelectedIndex();
330 Discussion result = getDiscussion(tabIndex);
337 * <p>設定は各PeriodViewに委譲される。
339 * @param fontInfo フォント
341 public void setFontInfo(FontInfo fontInfo){
342 Objects.nonNull(fontInfo);
343 this.fontInfo = fontInfo;
345 getPeriodViewList().forEach(periodView -> {
346 periodView.setFontInfo(this.fontInfo);
355 * <p>設定は各PeriodViewに委譲される。
357 * @param dialogPref 発言表示設定
359 public void setDialogPref(DialogPref dialogPref){
360 Objects.nonNull(dialogPref);
361 this.dialogPref = dialogPref;
363 getPeriodViewList().forEach(periodView -> {
364 periodView.setDialogPref(this.dialogPref);
371 * ActionListenerを追加する。
373 * <p>配下のDiscussionへもリスナは登録される。
375 * @param listener リスナー
377 public void addActionListener(ActionListener listener){
378 this.listenerList.add(ActionListener.class, listener);
380 getPeriodViewList().stream()
381 .map(PeriodView::getDiscussion)
382 .forEach(discussion ->{
383 discussion.addActionListener(listener);
390 * ActionListenerを削除する。
392 * @param listener リスナー
394 public void removeActionListener(ActionListener listener){
395 this.listenerList.remove(ActionListener.class, listener);
397 getPeriodViewList().stream()
398 .map(PeriodView::getDiscussion)
399 .forEach(discussion ->{
400 discussion.removeActionListener(listener);
407 * ActionListenerを列挙する。
409 * @return すべてのActionListener
411 public ActionListener[] getActionListeners(){
412 return getListeners(ActionListener.class);
416 * AnchorHitListenerを追加する。
418 * <p>配下のDiscussionへもリスナは登録される。
420 * @param listener リスナー
422 public void addAnchorHitListener(AnchorHitListener listener){
423 this.listenerList.add(AnchorHitListener.class, listener);
425 getPeriodViewList().stream()
426 .map(PeriodView::getDiscussion)
427 .forEach(discussion -> {
428 discussion.addAnchorHitListener(listener);
435 * AnchorHitListenerを削除する。
437 * @param listener リスナー
439 public void removeAnchorHitListener(AnchorHitListener listener){
440 this.listenerList.remove(AnchorHitListener.class, listener);
442 getPeriodViewList().stream()
443 .map(PeriodView::getDiscussion)
444 .forEach(discussion -> {
445 discussion.removeAnchorHitListener(listener);
452 * AnchorHitListenerを列挙する。
454 * @return すべてのAnchorHitListener
456 public AnchorHitListener[] getAnchorHitListeners(){
457 return getListeners(AnchorHitListener.class);