1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.tethys.swing.menu;
18
19 import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
20 import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
21 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIArrowIconId;
22 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIConstant;
23 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
24 import io.github.tonywasher.joceanus.tethys.api.base.TethysUIIcon;
25 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollIcon;
26 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
27 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
28 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollSubMenu;
29 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollToggle;
30 import io.github.tonywasher.joceanus.tethys.core.menu.TethysUICoreScrollMenu;
31 import io.github.tonywasher.joceanus.tethys.swing.base.TethysUISwingArrowIcon;
32 import io.github.tonywasher.joceanus.tethys.swing.base.TethysUISwingIcon;
33 import io.github.tonywasher.joceanus.tethys.swing.base.TethysUISwingUtils;
34
35 import javax.swing.BorderFactory;
36 import javax.swing.BoxLayout;
37 import javax.swing.JDialog;
38 import javax.swing.JLabel;
39 import javax.swing.JOptionPane;
40 import javax.swing.JPanel;
41 import javax.swing.SwingConstants;
42 import javax.swing.SwingUtilities;
43 import java.awt.BorderLayout;
44 import java.awt.Color;
45 import java.awt.Component;
46 import java.awt.Dimension;
47 import java.awt.Point;
48 import java.awt.Rectangle;
49 import java.awt.event.FocusEvent;
50 import java.awt.event.FocusListener;
51 import java.awt.event.KeyEvent;
52 import java.awt.event.KeyListener;
53 import java.awt.event.MouseAdapter;
54 import java.awt.event.MouseEvent;
55 import java.awt.event.MouseWheelEvent;
56 import java.awt.event.MouseWheelListener;
57 import java.util.ArrayList;
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.Timer;
61 import java.util.TimerTask;
62
63
64
65
66
67
68
69
70 public class TethysUISwingScrollMenu<T>
71 implements TethysUIScrollMenu<T> {
72
73
74
75 private static final Color COLOR_BACKGROUND = Color.decode("#add8e6");
76
77
78
79
80 private Timer theTimer;
81
82
83
84
85 private final List<TethysUISwingScrollElement> theMenuItems;
86
87
88
89
90 private final JPanel theActiveItems;
91
92
93
94
95 private int theFirstIndex;
96
97
98
99
100 private int theMaxDisplayItems;
101
102
103
104
105 private final OceanusEventManager<TethysUIEvent> theEventManager;
106
107
108
109
110 private final ScrollControl theUpItem;
111
112
113
114
115 private final ScrollControl theDownItem;
116
117
118
119
120 private final JPanel theContainer;
121
122
123
124
125 private final TethysUISwingScrollSubMenu<T> theParentMenu;
126
127
128
129
130 private final TethysUISwingScrollMenu<T> theParentContext;
131
132
133
134
135 private JDialog theDialog;
136
137
138
139
140 private TethysUISwingScrollSubMenu<T> theActiveMenu;
141
142
143
144
145 private TethysUISwingScrollItem<T> theActiveItem;
146
147
148
149
150 private TethysUISwingScrollItem<T> theSelectedItem;
151
152
153
154
155 private boolean closeOnToggle;
156
157
158
159
160 private boolean needReBuild;
161
162
163
164
165 private Dimension theMenuSize;
166
167
168
169
170 TethysUISwingScrollMenu() {
171 this(TethysUICoreScrollMenu.DEFAULT_ITEMCOUNT);
172 }
173
174
175
176
177
178
179 TethysUISwingScrollMenu(final int pMaxDisplayItems) {
180 this(null, pMaxDisplayItems);
181 }
182
183
184
185
186
187
188 TethysUISwingScrollMenu(final TethysUISwingScrollSubMenu<T> pParent) {
189 this(pParent, pParent.getContext().getMaxDisplayItems());
190 }
191
192
193
194
195
196
197
198 TethysUISwingScrollMenu(final TethysUISwingScrollSubMenu<T> pParent,
199 final int pMaxDisplayItems) {
200
201 if (pMaxDisplayItems <= 0) {
202 throw new IllegalArgumentException(TethysUICoreScrollMenu.ERROR_MAXITEMS);
203 }
204
205
206 theMaxDisplayItems = pMaxDisplayItems;
207 theParentMenu = pParent;
208 theParentContext = theParentMenu == null
209 ? null
210 : theParentMenu.getContext();
211
212
213 needReBuild = true;
214
215
216 closeOnToggle = true;
217
218
219 theEventManager = new OceanusEventManager<>();
220
221
222 theUpItem = new ScrollControl(TethysUISwingArrowIcon.getIconForId(TethysUIArrowIconId.UP), -1);
223 theDownItem = new ScrollControl(TethysUISwingArrowIcon.getIconForId(TethysUIArrowIconId.DOWN), 1);
224
225
226 theMenuItems = new ArrayList<>();
227 theActiveItems = new JPanel();
228 theActiveItems.setLayout(new BoxLayout(theActiveItems, BoxLayout.Y_AXIS));
229 theActiveItems.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
230
231
232 theContainer = new JPanel();
233 theContainer.setLayout(new BorderLayout());
234 theContainer.setBorder(BorderFactory.createLineBorder(Color.lightGray));
235 }
236
237 @Override
238 public OceanusEventRegistrar<TethysUIEvent> getEventRegistrar() {
239 return theEventManager.getEventRegistrar();
240 }
241
242 @Override
243 public TethysUIScrollItem<T> getSelectedItem() {
244 return theSelectedItem;
245 }
246
247
248
249
250
251
252 public int getMaxDisplayItems() {
253 return theMaxDisplayItems;
254 }
255
256
257
258
259
260
261 Timer getTimer() {
262 return theTimer;
263 }
264
265
266
267
268
269
270 JDialog getDialog() {
271 return theDialog;
272 }
273
274 @Override
275 public void setCloseOnToggle(final boolean pCloseOnToggle) {
276
277 closeOnToggle = pCloseOnToggle;
278 }
279
280 @Override
281 public void setMaxDisplayItems(final int pMaxDisplayItems) {
282
283 if (pMaxDisplayItems <= 0) {
284 throw new IllegalArgumentException(TethysUICoreScrollMenu.ERROR_MAXITEMS);
285 }
286
287
288 if (theDialog != null
289 && theDialog.isVisible()) {
290 throw new IllegalStateException();
291 }
292
293
294 theMaxDisplayItems = pMaxDisplayItems;
295
296
297 final Iterator<TethysUISwingScrollElement> myIterator = theMenuItems.iterator();
298 while (myIterator.hasNext()) {
299 final TethysUISwingScrollElement myChild = myIterator.next();
300
301
302 if (myChild instanceof TethysUISwingScrollSubMenu) {
303
304 final TethysUISwingScrollSubMenu<?> mySubMenu = (TethysUISwingScrollSubMenu<?>) myChild;
305 mySubMenu.setMaxDisplayItems(pMaxDisplayItems);
306 }
307 }
308
309
310 needReBuild = true;
311 }
312
313
314
315
316
317
318
319 public void showMenuAtPosition(final Component pAnchor,
320 final int pSide) {
321
322 if (theDialog == null) {
323 createDialog(pAnchor);
324 }
325
326
327 determineSize();
328
329
330 if (theMenuSize != null) {
331
332 final Point myLocation = TethysUISwingUtils.obtainDisplayPoint(pAnchor, pSide, theMenuSize);
333
334
335 showMenuAtLocation(myLocation);
336 }
337 }
338
339
340
341
342
343
344
345 public void showMenuAtPosition(final Rectangle pAnchor,
346 final int pSide) {
347
348 if (theDialog == null) {
349 createDialog(null);
350 }
351
352
353 determineSize();
354
355
356 if (theMenuSize != null) {
357
358 final Point myLocation = TethysUISwingUtils.obtainDisplayPoint(pAnchor, pSide, theMenuSize);
359
360
361 showMenuAtLocation(myLocation);
362 }
363 }
364
365
366
367
368
369
370
371
372 public void showMenuAtPosition(final Component pAnchor,
373 final double pX,
374 final double pY) {
375
376 if (theDialog == null) {
377 createDialog(pAnchor);
378 }
379
380
381 determineSize();
382
383
384 if (theMenuSize != null) {
385
386 final Point myRequest = new Point((int) pX, (int) pY);
387 final Point myLocation = TethysUISwingUtils.obtainDisplayPoint(pAnchor, myRequest, theMenuSize);
388
389
390 showMenuAtLocation(myLocation);
391 }
392 }
393
394
395
396
397
398
399 private void showMenuAtLocation(final Point pLocation) {
400
401 theDialog.setLocation(pLocation);
402
403
404 showMenu();
405 }
406
407
408
409
410 private void showMenu() {
411
412 if (theParentMenu == null
413 && theTimer != null) {
414 theTimer.cancel();
415 }
416
417
418 theTimer = theParentMenu != null
419 ? theParentContext.getTimer()
420 : new Timer();
421 theSelectedItem = null;
422 theActiveMenu = null;
423 theActiveItem = null;
424
425
426 theDialog.setVisible(true);
427 }
428
429
430
431
432 private void closeOnFocusLoss() {
433
434 if (theParentContext != null) {
435 theParentContext.closeOnFocusLoss();
436 }
437
438
439 closeMenu();
440 }
441
442
443
444
445 void closeMenu() {
446
447 closeChildren();
448
449
450 if (theDialog != null) {
451
452 theDialog.setVisible(false);
453 theDialog.getContentPane().removeAll();
454 theDialog = null;
455 }
456
457
458 if (theParentMenu == null
459 && theTimer != null) {
460 theTimer.cancel();
461 }
462 theTimer = null;
463 }
464
465
466
467
468
469
470 void ensureSubDialog(final TethysUISwingScrollSubMenu<T> pOwner) {
471
472 if (theDialog == null) {
473
474 theDialog = new JDialog(pOwner.getDialog(), false);
475 initDialog();
476 }
477 }
478
479
480
481
482
483
484 private void createDialog(final Component pOwner) {
485
486 theDialog = new JDialog(JOptionPane.getFrameForComponent(pOwner), false);
487 initDialog();
488 }
489
490
491
492
493 private void initDialog() {
494
495 theDialog.setUndecorated(true);
496 theDialog.getContentPane().add(theContainer);
497 needReBuild = true;
498
499
500 final ScrollListener myListener = new ScrollListener();
501 theDialog.addFocusListener(myListener);
502 theDialog.addKeyListener(myListener);
503 theDialog.addMouseWheelListener(myListener);
504 }
505
506
507
508
509 private final class ScrollListener
510 implements FocusListener, KeyListener, MouseWheelListener {
511 @Override
512 public void focusGained(final FocusEvent e) {
513
514 }
515
516 @Override
517 public void focusLost(final FocusEvent e) {
518 handleFocusLost();
519 }
520
521 @Override
522 public void keyTyped(final KeyEvent e) {
523
524 }
525
526 @Override
527 public void keyPressed(final KeyEvent e) {
528 switch (e.getKeyCode()) {
529 case KeyEvent.VK_ESCAPE:
530 handleEscapeKey();
531 break;
532 case KeyEvent.VK_ENTER:
533 handleEnterKey();
534 break;
535 default:
536 break;
537 }
538 }
539
540 @Override
541 public void keyReleased(final KeyEvent e) {
542
543 }
544
545 @Override
546 public void mouseWheelMoved(final MouseWheelEvent e) {
547
548 requestScroll(e.getWheelRotation());
549 e.consume();
550 }
551 }
552
553
554
555
556 void handleFocusLost() {
557
558 if (theActiveMenu == null) {
559
560 if (theParentMenu == null) {
561 theEventManager.fireEvent(TethysUIEvent.WINDOWCLOSED);
562 }
563
564
565 if (theDialog != null
566 && theDialog.isShowing()) {
567 closeOnFocusLoss();
568 }
569 }
570 }
571
572
573
574
575
576
577 void setSelectedItem(final TethysUISwingScrollItem<T> pItem) {
578
579 if (theParentContext != null) {
580
581 theParentContext.setSelectedItem(pItem);
582
583
584 } else {
585
586 boolean doCloseMenu = true;
587
588
589 theSelectedItem = pItem;
590 if (theSelectedItem instanceof TethysUIScrollToggle) {
591 final TethysUIScrollToggle<?> myItem = (TethysUIScrollToggle<?>) theSelectedItem;
592 myItem.toggleSelected();
593 doCloseMenu = closeOnToggle;
594 }
595
596
597 if (doCloseMenu) {
598
599 closeMenu();
600 }
601
602
603 theEventManager.fireEvent(TethysUIEvent.NEWVALUE, theSelectedItem);
604 }
605 }
606
607
608
609
610 void handleEscapeKey() {
611
612 if (theParentContext != null) {
613
614 theParentContext.handleEscapeKey();
615
616
617 } else {
618
619 theEventManager.fireEvent(TethysUIEvent.WINDOWCLOSED);
620
621
622 closeMenu();
623 }
624 }
625
626
627
628
629 void handleEnterKey() {
630
631 if (theActiveItem != null) {
632
633 setSelectedItem(theActiveItem);
634 }
635 }
636
637
638
639
640 void clearActiveItem() {
641 theActiveItem = null;
642 }
643
644
645
646
647
648
649 void handleActiveItem(final TethysUISwingScrollItem<T> pItem) {
650
651 closeChildren();
652
653
654 theActiveItem = pItem;
655 }
656
657
658
659
660
661
662 void handleActiveMenu(final TethysUISwingScrollSubMenu<T> pMenu) {
663
664 theActiveItem = null;
665
666
667 if (theActiveMenu != null
668 && theActiveMenu.getIndex() != pMenu.getIndex()) {
669 theActiveMenu.hideMenu();
670 }
671
672
673 theActiveMenu = pMenu;
674 }
675
676 @Override
677 public void removeAllItems() {
678
679 if (theDialog != null
680 && theDialog.isVisible()) {
681 theDialog.setVisible(false);
682 }
683
684
685 theMenuItems.clear();
686 theFirstIndex = 0;
687 needReBuild = true;
688
689
690 theSelectedItem = null;
691 }
692
693 @Override
694 public boolean isEmpty() {
695
696 return theMenuItems.isEmpty();
697 }
698
699
700
701
702
703
704 protected int getItemCount() {
705
706 return theMenuItems.size();
707 }
708
709 @Override
710 public TethysUIScrollItem<T> addItem(final T pValue) {
711
712 return addItem(pValue, pValue.toString(), null);
713 }
714
715 @Override
716 public TethysUIScrollItem<T> addItem(final T pValue,
717 final String pName) {
718
719 return addItem(pValue, pName, null);
720 }
721
722 @Override
723 public TethysUIScrollItem<T> addItem(final T pValue,
724 final TethysUIIcon pGraphic) {
725
726 return addItem(pValue, pValue.toString(), pGraphic);
727 }
728
729 @Override
730 public TethysUIScrollItem<T> addNullItem(final String pName) {
731
732 return addItem(null, pName, null);
733 }
734
735 @Override
736 public TethysUIScrollItem<T> addNullItem(final String pName,
737 final TethysUIIcon pGraphic) {
738
739 return addItem(null, pName, pGraphic);
740 }
741
742 @Override
743 public TethysUIScrollItem<T> addItem(final T pValue,
744 final String pName,
745 final TethysUIIcon pGraphic) {
746
747 if (theDialog != null
748 && theDialog.isVisible()) {
749 throw new IllegalStateException();
750 }
751
752
753 final TethysUISwingScrollItem<T> myItem = new TethysUISwingScrollItem<>(this, pValue, pName, pGraphic);
754
755
756 theMenuItems.add(myItem);
757 needReBuild = true;
758 return myItem;
759 }
760
761 @Override
762 public TethysUIScrollSubMenu<T> addSubMenu(final String pName) {
763
764 return addSubMenu(pName, null);
765 }
766
767 @Override
768 public TethysUIScrollSubMenu<T> addSubMenu(final String pName,
769 final TethysUIIcon pGraphic) {
770
771 if (theDialog != null
772 && theDialog.isVisible()) {
773 throw new IllegalStateException();
774 }
775
776
777 final TethysUISwingScrollSubMenu<T> myMenu = new TethysUISwingScrollSubMenu<>(this, pName, pGraphic);
778
779
780 theMenuItems.add(myMenu);
781 needReBuild = true;
782 return myMenu;
783 }
784
785 @Override
786 public TethysUIScrollToggle<T> addToggleItem(final T pValue) {
787
788 return addToggleItem(pValue, pValue.toString());
789 }
790
791 @Override
792 public TethysUIScrollToggle<T> addToggleItem(final T pValue,
793 final String pName) {
794
795 if (theDialog != null
796 && theDialog.isVisible()) {
797 throw new IllegalStateException();
798 }
799
800
801 final TethysUISwingScrollToggle<T> myItem = new TethysUISwingScrollToggle<>(this, pValue, pName);
802
803
804 theMenuItems.add(myItem);
805 needReBuild = true;
806 return myItem;
807 }
808
809
810
811
812 void closeChildren() {
813
814 if (theActiveMenu != null) {
815 theActiveMenu.hideMenu();
816 }
817 theActiveMenu = null;
818 }
819
820
821
822
823
824
825 void scrollToIndex(final int pIndex) {
826
827 showIndex(pIndex);
828
829
830 if (theParentMenu != null) {
831 theParentMenu.scrollToMenu();
832 }
833 }
834
835
836
837
838
839
840 private void showIndex(final int pIndex) {
841
842 final int myCount = theMenuItems.size();
843 if (pIndex < 0
844 || pIndex >= myCount) {
845 return;
846 }
847
848
849 if (pIndex < theFirstIndex) {
850
851 requestScroll(pIndex - theFirstIndex);
852 return;
853 }
854
855
856 final int myLastIndex = theFirstIndex
857 + theMaxDisplayItems
858 - 1;
859 if (myLastIndex < pIndex) {
860
861 requestScroll(pIndex - myLastIndex);
862 }
863 }
864
865
866
867
868 private void determineSize() {
869
870 if (!needReBuild) {
871 return;
872 }
873
874
875 if (!theMenuItems.isEmpty()) {
876
877 final int myCount = theMenuItems.size();
878 final int myScroll = Math.min(theMaxDisplayItems, myCount);
879
880
881 theActiveItems.removeAll();
882 theContainer.removeAll();
883 theContainer.add(theActiveItems, BorderLayout.CENTER);
884
885
886 if (myScroll == myCount) {
887
888 for (int i = 0; i < myCount; i++) {
889
890 theActiveItems.add(theMenuItems.get(i).getPanel());
891 }
892
893
894 theDialog.pack();
895
896
897 theMenuSize = new Dimension(theDialog.getWidth(), theDialog.getHeight());
898
899
900 } else {
901
902 theContainer.add(theUpItem.getPanel(), BorderLayout.PAGE_START);
903 theContainer.add(theDownItem.getPanel(), BorderLayout.PAGE_END);
904 theUpItem.getPanel().setEnabled(true);
905 theDownItem.getPanel().setEnabled(true);
906
907
908 for (final TethysUISwingScrollElement myItem : theMenuItems) {
909
910 theActiveItems.add(myItem.getPanel());
911 }
912
913
914 theDialog.pack();
915 final int myWidth = theDialog.getWidth();
916
917
918 theActiveItems.removeAll();
919
920
921 if (theFirstIndex < 0) {
922 theFirstIndex = 0;
923 }
924
925
926 int myMaxIndex = theFirstIndex
927 + myScroll;
928 if (myMaxIndex > myCount) {
929
930 theFirstIndex = myCount
931 - myScroll;
932 myMaxIndex = myCount;
933 }
934
935
936 for (int i = theFirstIndex; i < myMaxIndex; i++) {
937
938 theActiveItems.add(theMenuItems.get(i).getPanel());
939 }
940
941
942 theDialog.pack();
943 final int myHeight = theDialog.getHeight();
944
945
946 theUpItem.getPanel().setEnabled(theFirstIndex > 0);
947 theDownItem.getPanel().setEnabled(myMaxIndex < myCount);
948
949
950 theMenuSize = new Dimension(myWidth, myHeight);
951
952
953 theDialog.setPreferredSize(theMenuSize);
954 theDialog.pack();
955 }
956
957
958 } else {
959 theMenuSize = null;
960 }
961
962
963 needReBuild = false;
964 }
965
966
967
968
969 protected void scrollPlusOne() {
970
971 if (!needReBuild) {
972
973 final int myCount = theMenuItems.size();
974
975
976 closeChildren();
977
978
979 theUpItem.getPanel().setEnabled(true);
980
981
982 theActiveItems.remove(0);
983
984
985 final int myLast = theFirstIndex + theMaxDisplayItems;
986 final TethysUISwingScrollElement myItem = theMenuItems.get(myLast);
987 myItem.setActive(false);
988 theActiveItems.add(myItem.getPanel());
989
990
991 theDownItem.getPanel().setEnabled(myLast + 1 < myCount);
992
993
994 theDialog.pack();
995 }
996
997
998 theFirstIndex++;
999 }
1000
1001
1002
1003
1004 protected void scrollMinusOne() {
1005
1006 if (!needReBuild) {
1007
1008 closeChildren();
1009
1010
1011 theDownItem.getPanel().setEnabled(true);
1012
1013
1014 theActiveItems.remove(theMaxDisplayItems - 1);
1015
1016
1017 final TethysUISwingScrollElement myItem = theMenuItems.get(theFirstIndex - 1);
1018 myItem.setActive(false);
1019 theActiveItems.add(myItem.getPanel(), 0);
1020
1021
1022 theUpItem.getPanel().setEnabled(theFirstIndex > 1);
1023
1024
1025 theDialog.pack();
1026 }
1027
1028
1029 theFirstIndex--;
1030 }
1031
1032
1033
1034
1035
1036
1037 void requestScroll(final int pDelta) {
1038
1039 if (pDelta > 0) {
1040
1041 final int myCount = theMenuItems.size();
1042 final int mySpace = myCount - theFirstIndex - theMaxDisplayItems;
1043 int myScroll = Math.min(mySpace, pDelta);
1044
1045
1046 while (myScroll-- > 0) {
1047
1048 scrollPlusOne();
1049 }
1050
1051
1052 } else if (theFirstIndex > 0) {
1053
1054 int myScroll = Math.min(theFirstIndex, -pDelta);
1055
1056
1057 while (myScroll-- > 0) {
1058
1059 scrollMinusOne();
1060 }
1061 }
1062 }
1063
1064
1065
1066
1067 public abstract static class TethysUISwingScrollElement {
1068
1069
1070
1071 private static final int DEFAULT_ROWHEIGHT = 16;
1072
1073
1074
1075
1076 private final JPanel thePanel;
1077
1078
1079
1080
1081 private final JLabel theLabel;
1082
1083
1084
1085
1086 private final JLabel theIcon;
1087
1088
1089
1090
1091 private Color theBaseColor;
1092
1093
1094
1095
1096
1097
1098
1099 private TethysUISwingScrollElement(final String pName,
1100 final TethysUIIcon pGraphic) {
1101
1102 thePanel = new JPanel();
1103
1104
1105 thePanel.setLayout(new BorderLayout());
1106
1107
1108 theLabel = new JLabel();
1109 theLabel.setHorizontalAlignment(SwingConstants.LEFT);
1110 theLabel.setText(pName);
1111 theLabel.setMaximumSize(new Dimension(Integer.MAX_VALUE, DEFAULT_ROWHEIGHT));
1112
1113
1114 theIcon = new JLabel();
1115 theIcon.setIcon(TethysUISwingIcon.getIcon(pGraphic));
1116 final Dimension myDim = new Dimension(TethysUIConstant.DEFAULT_ICONSIZE, DEFAULT_ROWHEIGHT);
1117 theIcon.setMinimumSize(myDim);
1118 theIcon.setPreferredSize(myDim);
1119 theIcon.setMaximumSize(myDim);
1120
1121
1122 thePanel.add(theIcon, BorderLayout.LINE_START);
1123 thePanel.add(theLabel, BorderLayout.CENTER);
1124 }
1125
1126
1127
1128
1129
1130
1131 private TethysUISwingScrollElement(final TethysUIIcon pGraphic) {
1132
1133 thePanel = new JPanel();
1134
1135
1136 theLabel = null;
1137
1138
1139 theIcon = new JLabel();
1140 theIcon.setIcon(TethysUISwingIcon.getIcon(pGraphic));
1141 theIcon.setHorizontalAlignment(SwingConstants.CENTER);
1142
1143
1144 thePanel.add(theIcon);
1145 }
1146
1147
1148
1149
1150
1151
1152 protected JPanel getPanel() {
1153 return thePanel;
1154 }
1155
1156
1157
1158
1159
1160
1161 public String getText() {
1162 return theLabel.getText();
1163 }
1164
1165
1166
1167
1168
1169
1170 protected void setIcon(final TethysUIIcon pGraphic) {
1171 theIcon.setIcon(TethysUISwingIcon.getIcon(pGraphic));
1172 }
1173
1174
1175
1176
1177
1178
1179 protected void setActive(final boolean pActive) {
1180 if (pActive) {
1181 if (theBaseColor == null) {
1182 theBaseColor = thePanel.getBackground();
1183 thePanel.setBackground(COLOR_BACKGROUND);
1184 }
1185 } else {
1186 if (theBaseColor != null) {
1187 thePanel.setBackground(theBaseColor);
1188 theBaseColor = null;
1189 }
1190 }
1191 }
1192
1193
1194
1195
1196 protected void addMenuIcon() {
1197 final JLabel myLabel = new JLabel(TethysUISwingArrowIcon.RIGHT);
1198 thePanel.add(myLabel, BorderLayout.LINE_END);
1199 }
1200 }
1201
1202
1203
1204
1205
1206
1207 protected static class TethysUISwingScrollItem<T>
1208 extends TethysUISwingScrollElement
1209 implements TethysUIScrollItem<T> {
1210
1211
1212
1213 private final TethysUISwingScrollMenu<T> theContext;
1214
1215
1216
1217
1218 private final int theIndex;
1219
1220
1221
1222
1223 private final T theValue;
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233 protected TethysUISwingScrollItem(final TethysUISwingScrollMenu<T> pContext,
1234 final T pValue,
1235 final String pName,
1236 final TethysUIIcon pGraphic) {
1237
1238 super(pName, pGraphic);
1239
1240
1241 theContext = pContext;
1242 theValue = pValue;
1243
1244
1245 theIndex = theContext.getItemCount();
1246
1247
1248 getPanel().addMouseListener(new MouseAdapter() {
1249 @Override
1250 public void mouseEntered(final MouseEvent e) {
1251 handleMouseEntered();
1252 setActive(true);
1253 }
1254
1255 @Override
1256 public void mouseExited(final MouseEvent e) {
1257 setActive(false);
1258 }
1259
1260 @Override
1261 public void mouseClicked(final MouseEvent e) {
1262 setActive(false);
1263 handleMouseClicked();
1264 }
1265 });
1266 }
1267
1268
1269
1270
1271 void handleMouseClicked() {
1272 theContext.setSelectedItem(this);
1273 }
1274
1275
1276
1277
1278 void handleMouseEntered() {
1279 theContext.handleActiveItem(this);
1280 }
1281
1282 @Override
1283 public T getValue() {
1284 return theValue;
1285 }
1286
1287 @Override
1288 public void scrollToItem() {
1289 theContext.scrollToIndex(theIndex);
1290 }
1291 }
1292
1293
1294
1295
1296
1297
1298 private static final class TethysUISwingScrollToggle<T>
1299 extends TethysUISwingScrollItem<T>
1300 implements TethysUIScrollToggle<T> {
1301
1302
1303
1304 private boolean isSelected;
1305
1306
1307
1308
1309
1310
1311
1312
1313 TethysUISwingScrollToggle(final TethysUISwingScrollMenu<T> pContext,
1314 final T pValue,
1315 final String pName) {
1316
1317 super(pContext, pValue, pName, null);
1318 }
1319
1320 @Override
1321 public boolean isSelected() {
1322 return isSelected;
1323 }
1324
1325 @Override
1326 public void setSelected(final boolean pSelected) {
1327 isSelected = pSelected;
1328 setIcon(isSelected
1329 ? TethysUISwingUtils.getIconAtSize(TethysUIScrollIcon.CHECKMARK, TethysUIConstant.DEFAULT_ICONSIZE)
1330 : null);
1331 }
1332
1333 @Override
1334 public void toggleSelected() {
1335 setSelected(!isSelected);
1336 }
1337 }
1338
1339
1340
1341
1342
1343
1344 public static final class TethysUISwingScrollSubMenu<T>
1345 extends TethysUISwingScrollElement
1346 implements TethysUIScrollSubMenu<T> {
1347
1348
1349
1350 private final TethysUISwingScrollMenu<T> theContext;
1351
1352
1353
1354
1355 private final int theIndex;
1356
1357
1358
1359
1360 private final TethysUISwingScrollMenu<T> theSubMenu;
1361
1362
1363
1364
1365
1366
1367
1368
1369 TethysUISwingScrollSubMenu(final TethysUISwingScrollMenu<T> pContext,
1370 final String pName,
1371 final TethysUIIcon pGraphic) {
1372
1373 super(pName, pGraphic);
1374
1375
1376 theContext = pContext;
1377
1378
1379 theSubMenu = new TethysUISwingScrollMenu<>(this);
1380
1381
1382 theIndex = theContext.getItemCount();
1383
1384
1385 addMenuIcon();
1386
1387
1388 getPanel().addMouseListener(new MouseAdapter() {
1389 @Override
1390 public void mouseEntered(final MouseEvent e) {
1391 handleMouseEntered();
1392 }
1393
1394 @Override
1395 public void mouseExited(final MouseEvent e) {
1396 setActive(false);
1397 }
1398 });
1399 }
1400
1401
1402
1403
1404 void handleMouseEntered() {
1405
1406 theContext.handleActiveMenu(this);
1407 setActive(true);
1408
1409
1410 theSubMenu.ensureSubDialog(this);
1411 theSubMenu.showMenuAtPosition(getPanel(), SwingConstants.RIGHT);
1412 }
1413
1414
1415
1416
1417
1418
1419 TethysUISwingScrollMenu<T> getContext() {
1420 return theContext;
1421 }
1422
1423 @Override
1424 public TethysUISwingScrollMenu<T> getSubMenu() {
1425 return theSubMenu;
1426 }
1427
1428
1429
1430
1431
1432
1433 int getIndex() {
1434 return theIndex;
1435 }
1436
1437
1438
1439
1440 void hideMenu() {
1441 theSubMenu.closeMenu();
1442 }
1443
1444
1445
1446
1447
1448
1449 private JDialog getDialog() {
1450 return theContext.getDialog();
1451 }
1452
1453
1454
1455
1456
1457
1458
1459 private void setMaxDisplayItems(final int pMaxDisplayItems) {
1460
1461 theSubMenu.setMaxDisplayItems(pMaxDisplayItems);
1462 }
1463
1464
1465
1466
1467 void scrollToMenu() {
1468 theContext.scrollToIndex(theIndex);
1469 }
1470 }
1471
1472
1473
1474
1475 private final class ScrollControl
1476 extends TethysUISwingScrollElement {
1477
1478
1479
1480 private final int theIncrement;
1481
1482
1483
1484
1485 private TimerTask theTimerTask;
1486
1487
1488
1489
1490
1491
1492
1493 ScrollControl(final TethysUIIcon pIcon,
1494 final int pIncrement) {
1495
1496 super(pIcon);
1497
1498
1499 theIncrement = pIncrement;
1500
1501
1502 getPanel().addMouseListener(new MouseAdapter() {
1503 @Override
1504 public void mouseClicked(final MouseEvent e) {
1505 processScroll();
1506 }
1507
1508 @Override
1509 public void mouseEntered(final MouseEvent e) {
1510 processMouseEnter();
1511 }
1512
1513 @Override
1514 public void mouseExited(final MouseEvent e) {
1515 processMouseExit();
1516 }
1517 });
1518 }
1519
1520
1521
1522
1523 void processScroll() {
1524
1525 requestScroll(theIncrement);
1526 }
1527
1528
1529
1530
1531 void processMouseEnter() {
1532
1533 if (theTimerTask != null) {
1534 theTimerTask.cancel();
1535 }
1536
1537
1538 closeChildren();
1539
1540
1541 clearActiveItem();
1542 setActive(true);
1543
1544
1545 theTimerTask = new TimerTask() {
1546 @Override
1547 public void run() {
1548 SwingUtilities.invokeLater(ScrollControl.this::processScroll);
1549 }
1550 };
1551
1552
1553 getTimer().schedule(theTimerTask, TethysUICoreScrollMenu.INITIAL_SCROLLDELAY, TethysUICoreScrollMenu.REPEAT_SCROLLDELAY);
1554 }
1555
1556
1557
1558
1559 void processMouseExit() {
1560
1561 if (theTimerTask != null) {
1562 theTimerTask.cancel();
1563 theTimerTask = null;
1564 }
1565 setActive(false);
1566 }
1567 }
1568 }