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.base.TethysUIIconId;
26 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollIcon;
27 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollItem;
28 import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
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 TethysUIIconId pId) {
725
726 return addItem(pValue, pValue.toString(), TethysUISwingUtils.getIconAtSize(pId, TethysUICoreScrollMenu.ICON_SIZE));
727 }
728
729 @Override
730 public TethysUIScrollItem<T> addItem(final T pValue,
731 final TethysUIIcon pGraphic) {
732
733 return addItem(pValue, pValue.toString(), pGraphic);
734 }
735
736 @Override
737 public TethysUIScrollItem<T> addNullItem(final String pName) {
738
739 return addItem(null, pName, null);
740 }
741
742 @Override
743 public TethysUIScrollItem<T> addNullItem(final String pName,
744 final TethysUIIcon pGraphic) {
745
746 return addItem(null, pName, pGraphic);
747 }
748
749 @Override
750 public TethysUIScrollItem<T> addItem(final T pValue,
751 final String pName,
752 final TethysUIIcon pGraphic) {
753
754 if (theDialog != null
755 && theDialog.isVisible()) {
756 throw new IllegalStateException();
757 }
758
759
760 final TethysUISwingScrollItem<T> myItem = new TethysUISwingScrollItem<>(this, pValue, pName, pGraphic);
761
762
763 theMenuItems.add(myItem);
764 needReBuild = true;
765 return myItem;
766 }
767
768 @Override
769 public TethysUIScrollSubMenu<T> addSubMenu(final String pName) {
770
771 return addSubMenu(pName, null);
772 }
773
774 @Override
775 public TethysUIScrollSubMenu<T> addSubMenu(final String pName,
776 final TethysUIIcon pGraphic) {
777
778 if (theDialog != null
779 && theDialog.isVisible()) {
780 throw new IllegalStateException();
781 }
782
783
784 final TethysUISwingScrollSubMenu<T> myMenu = new TethysUISwingScrollSubMenu<>(this, pName, pGraphic);
785
786
787 theMenuItems.add(myMenu);
788 needReBuild = true;
789 return myMenu;
790 }
791
792 @Override
793 public TethysUIScrollToggle<T> addToggleItem(final T pValue) {
794
795 return addToggleItem(pValue, pValue.toString());
796 }
797
798 @Override
799 public TethysUIScrollToggle<T> addToggleItem(final T pValue,
800 final String pName) {
801
802 if (theDialog != null
803 && theDialog.isVisible()) {
804 throw new IllegalStateException();
805 }
806
807
808 final TethysUISwingScrollToggle<T> myItem = new TethysUISwingScrollToggle<>(this, pValue, pName);
809
810
811 theMenuItems.add(myItem);
812 needReBuild = true;
813 return myItem;
814 }
815
816
817
818
819 void closeChildren() {
820
821 if (theActiveMenu != null) {
822 theActiveMenu.hideMenu();
823 }
824 theActiveMenu = null;
825 }
826
827
828
829
830
831
832 void scrollToIndex(final int pIndex) {
833
834 showIndex(pIndex);
835
836
837 if (theParentMenu != null) {
838 theParentMenu.scrollToMenu();
839 }
840 }
841
842
843
844
845
846
847 private void showIndex(final int pIndex) {
848
849 final int myCount = theMenuItems.size();
850 if (pIndex < 0
851 || pIndex >= myCount) {
852 return;
853 }
854
855
856 if (pIndex < theFirstIndex) {
857
858 requestScroll(pIndex - theFirstIndex);
859 return;
860 }
861
862
863 final int myLastIndex = theFirstIndex
864 + theMaxDisplayItems
865 - 1;
866 if (myLastIndex < pIndex) {
867
868 requestScroll(pIndex - myLastIndex);
869 }
870 }
871
872
873
874
875 private void determineSize() {
876
877 if (!needReBuild) {
878 return;
879 }
880
881
882 if (!theMenuItems.isEmpty()) {
883
884 final int myCount = theMenuItems.size();
885 final int myScroll = Math.min(theMaxDisplayItems, myCount);
886
887
888 theActiveItems.removeAll();
889 theContainer.removeAll();
890 theContainer.add(theActiveItems, BorderLayout.CENTER);
891
892
893 if (myScroll == myCount) {
894
895 for (int i = 0; i < myCount; i++) {
896
897 theActiveItems.add(theMenuItems.get(i).getPanel());
898 }
899
900
901 theDialog.pack();
902
903
904 theMenuSize = new Dimension(theDialog.getWidth(), theDialog.getHeight());
905
906
907 } else {
908
909 theContainer.add(theUpItem.getPanel(), BorderLayout.PAGE_START);
910 theContainer.add(theDownItem.getPanel(), BorderLayout.PAGE_END);
911 theUpItem.getPanel().setEnabled(true);
912 theDownItem.getPanel().setEnabled(true);
913
914
915 for (final TethysUISwingScrollElement myItem : theMenuItems) {
916
917 theActiveItems.add(myItem.getPanel());
918 }
919
920
921 theDialog.pack();
922 final int myWidth = theDialog.getWidth();
923
924
925 theActiveItems.removeAll();
926
927
928 if (theFirstIndex < 0) {
929 theFirstIndex = 0;
930 }
931
932
933 int myMaxIndex = theFirstIndex
934 + myScroll;
935 if (myMaxIndex > myCount) {
936
937 theFirstIndex = myCount
938 - myScroll;
939 myMaxIndex = myCount;
940 }
941
942
943 for (int i = theFirstIndex; i < myMaxIndex; i++) {
944
945 theActiveItems.add(theMenuItems.get(i).getPanel());
946 }
947
948
949 theDialog.pack();
950 final int myHeight = theDialog.getHeight();
951
952
953 theUpItem.getPanel().setEnabled(theFirstIndex > 0);
954 theDownItem.getPanel().setEnabled(myMaxIndex < myCount);
955
956
957 theMenuSize = new Dimension(myWidth, myHeight);
958
959
960 theDialog.setPreferredSize(theMenuSize);
961 theDialog.pack();
962 }
963
964
965 } else {
966 theMenuSize = null;
967 }
968
969
970 needReBuild = false;
971 }
972
973
974
975
976 protected void scrollPlusOne() {
977
978 if (!needReBuild) {
979
980 final int myCount = theMenuItems.size();
981
982
983 closeChildren();
984
985
986 theUpItem.getPanel().setEnabled(true);
987
988
989 theActiveItems.remove(0);
990
991
992 final int myLast = theFirstIndex + theMaxDisplayItems;
993 final TethysUISwingScrollElement myItem = theMenuItems.get(myLast);
994 myItem.setActive(false);
995 theActiveItems.add(myItem.getPanel());
996
997
998 theDownItem.getPanel().setEnabled(myLast + 1 < myCount);
999
1000
1001 theDialog.pack();
1002 }
1003
1004
1005 theFirstIndex++;
1006 }
1007
1008
1009
1010
1011 protected void scrollMinusOne() {
1012
1013 if (!needReBuild) {
1014
1015 closeChildren();
1016
1017
1018 theDownItem.getPanel().setEnabled(true);
1019
1020
1021 theActiveItems.remove(theMaxDisplayItems - 1);
1022
1023
1024 final TethysUISwingScrollElement myItem = theMenuItems.get(theFirstIndex - 1);
1025 myItem.setActive(false);
1026 theActiveItems.add(myItem.getPanel(), 0);
1027
1028
1029 theUpItem.getPanel().setEnabled(theFirstIndex > 1);
1030
1031
1032 theDialog.pack();
1033 }
1034
1035
1036 theFirstIndex--;
1037 }
1038
1039
1040
1041
1042
1043
1044 void requestScroll(final int pDelta) {
1045
1046 if (pDelta > 0) {
1047
1048 final int myCount = theMenuItems.size();
1049 final int mySpace = myCount - theFirstIndex - theMaxDisplayItems;
1050 int myScroll = Math.min(mySpace, pDelta);
1051
1052
1053 while (myScroll-- > 0) {
1054
1055 scrollPlusOne();
1056 }
1057
1058
1059 } else if (theFirstIndex > 0) {
1060
1061 int myScroll = Math.min(theFirstIndex, -pDelta);
1062
1063
1064 while (myScroll-- > 0) {
1065
1066 scrollMinusOne();
1067 }
1068 }
1069 }
1070
1071
1072
1073
1074 public abstract static class TethysUISwingScrollElement {
1075
1076
1077
1078 private static final int DEFAULT_ROWHEIGHT = 16;
1079
1080
1081
1082
1083 private final JPanel thePanel;
1084
1085
1086
1087
1088 private final JLabel theLabel;
1089
1090
1091
1092
1093 private final JLabel theIcon;
1094
1095
1096
1097
1098 private Color theBaseColor;
1099
1100
1101
1102
1103
1104
1105
1106 private TethysUISwingScrollElement(final String pName,
1107 final TethysUIIcon pGraphic) {
1108
1109 thePanel = new JPanel();
1110
1111
1112 thePanel.setLayout(new BorderLayout());
1113
1114
1115 theLabel = new JLabel();
1116 theLabel.setHorizontalAlignment(SwingConstants.LEFT);
1117 theLabel.setText(pName);
1118 theLabel.setMaximumSize(new Dimension(Integer.MAX_VALUE, DEFAULT_ROWHEIGHT));
1119
1120
1121 theIcon = new JLabel();
1122 theIcon.setIcon(TethysUISwingIcon.getIcon(pGraphic));
1123 final Dimension myDim = new Dimension(TethysUIConstant.DEFAULT_ICONSIZE, DEFAULT_ROWHEIGHT);
1124 theIcon.setMinimumSize(myDim);
1125 theIcon.setPreferredSize(myDim);
1126 theIcon.setMaximumSize(myDim);
1127
1128
1129 thePanel.add(theIcon, BorderLayout.LINE_START);
1130 thePanel.add(theLabel, BorderLayout.CENTER);
1131 }
1132
1133
1134
1135
1136
1137
1138 private TethysUISwingScrollElement(final TethysUIIcon pGraphic) {
1139
1140 thePanel = new JPanel();
1141
1142
1143 theLabel = null;
1144
1145
1146 theIcon = new JLabel();
1147 theIcon.setIcon(TethysUISwingIcon.getIcon(pGraphic));
1148 theIcon.setHorizontalAlignment(SwingConstants.CENTER);
1149
1150
1151 thePanel.add(theIcon);
1152 }
1153
1154
1155
1156
1157
1158
1159 protected JPanel getPanel() {
1160 return thePanel;
1161 }
1162
1163
1164
1165
1166
1167
1168 public String getText() {
1169 return theLabel.getText();
1170 }
1171
1172
1173
1174
1175
1176
1177 protected void setIcon(final TethysUIIcon pGraphic) {
1178 theIcon.setIcon(TethysUISwingIcon.getIcon(pGraphic));
1179 }
1180
1181
1182
1183
1184
1185
1186 protected void setActive(final boolean pActive) {
1187 if (pActive) {
1188 if (theBaseColor == null) {
1189 theBaseColor = thePanel.getBackground();
1190 thePanel.setBackground(COLOR_BACKGROUND);
1191 }
1192 } else {
1193 if (theBaseColor != null) {
1194 thePanel.setBackground(theBaseColor);
1195 theBaseColor = null;
1196 }
1197 }
1198 }
1199
1200
1201
1202
1203 protected void addMenuIcon() {
1204 final JLabel myLabel = new JLabel(TethysUISwingArrowIcon.RIGHT);
1205 thePanel.add(myLabel, BorderLayout.LINE_END);
1206 }
1207 }
1208
1209
1210
1211
1212
1213
1214 protected static class TethysUISwingScrollItem<T>
1215 extends TethysUISwingScrollElement
1216 implements TethysUIScrollItem<T> {
1217
1218
1219
1220 private final TethysUISwingScrollMenu<T> theContext;
1221
1222
1223
1224
1225 private final int theIndex;
1226
1227
1228
1229
1230 private final T theValue;
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240 protected TethysUISwingScrollItem(final TethysUISwingScrollMenu<T> pContext,
1241 final T pValue,
1242 final String pName,
1243 final TethysUIIcon pGraphic) {
1244
1245 super(pName, pGraphic);
1246
1247
1248 theContext = pContext;
1249 theValue = pValue;
1250
1251
1252 theIndex = theContext.getItemCount();
1253
1254
1255 getPanel().addMouseListener(new MouseAdapter() {
1256 @Override
1257 public void mouseEntered(final MouseEvent e) {
1258 handleMouseEntered();
1259 setActive(true);
1260 }
1261
1262 @Override
1263 public void mouseExited(final MouseEvent e) {
1264 setActive(false);
1265 }
1266
1267 @Override
1268 public void mouseClicked(final MouseEvent e) {
1269 setActive(false);
1270 handleMouseClicked();
1271 }
1272 });
1273 }
1274
1275
1276
1277
1278 void handleMouseClicked() {
1279 theContext.setSelectedItem(this);
1280 }
1281
1282
1283
1284
1285 void handleMouseEntered() {
1286 theContext.handleActiveItem(this);
1287 }
1288
1289 @Override
1290 public T getValue() {
1291 return theValue;
1292 }
1293
1294 @Override
1295 public void scrollToItem() {
1296 theContext.scrollToIndex(theIndex);
1297 }
1298 }
1299
1300
1301
1302
1303
1304
1305 private static final class TethysUISwingScrollToggle<T>
1306 extends TethysUISwingScrollItem<T>
1307 implements TethysUIScrollToggle<T> {
1308
1309
1310
1311 private boolean isSelected;
1312
1313
1314
1315
1316
1317
1318
1319
1320 TethysUISwingScrollToggle(final TethysUISwingScrollMenu<T> pContext,
1321 final T pValue,
1322 final String pName) {
1323
1324 super(pContext, pValue, pName, null);
1325 }
1326
1327 @Override
1328 public boolean isSelected() {
1329 return isSelected;
1330 }
1331
1332 @Override
1333 public void setSelected(final boolean pSelected) {
1334 isSelected = pSelected;
1335 setIcon(isSelected
1336 ? TethysUISwingUtils.getIconAtSize(TethysUIScrollIcon.CHECKMARK, TethysUIConstant.DEFAULT_ICONSIZE)
1337 : null);
1338 }
1339
1340 @Override
1341 public void toggleSelected() {
1342 setSelected(!isSelected);
1343 }
1344 }
1345
1346
1347
1348
1349
1350
1351 public static final class TethysUISwingScrollSubMenu<T>
1352 extends TethysUISwingScrollElement
1353 implements TethysUIScrollSubMenu<T> {
1354
1355
1356
1357 private final TethysUISwingScrollMenu<T> theContext;
1358
1359
1360
1361
1362 private final int theIndex;
1363
1364
1365
1366
1367 private final TethysUISwingScrollMenu<T> theSubMenu;
1368
1369
1370
1371
1372
1373
1374
1375
1376 TethysUISwingScrollSubMenu(final TethysUISwingScrollMenu<T> pContext,
1377 final String pName,
1378 final TethysUIIcon pGraphic) {
1379
1380 super(pName, pGraphic);
1381
1382
1383 theContext = pContext;
1384
1385
1386 theSubMenu = new TethysUISwingScrollMenu<>(this);
1387
1388
1389 theIndex = theContext.getItemCount();
1390
1391
1392 addMenuIcon();
1393
1394
1395 getPanel().addMouseListener(new MouseAdapter() {
1396 @Override
1397 public void mouseEntered(final MouseEvent e) {
1398 handleMouseEntered();
1399 }
1400
1401 @Override
1402 public void mouseExited(final MouseEvent e) {
1403 setActive(false);
1404 }
1405 });
1406 }
1407
1408
1409
1410
1411 void handleMouseEntered() {
1412
1413 theContext.handleActiveMenu(this);
1414 setActive(true);
1415
1416
1417 theSubMenu.ensureSubDialog(this);
1418 theSubMenu.showMenuAtPosition(getPanel(), SwingConstants.RIGHT);
1419 }
1420
1421
1422
1423
1424
1425
1426 TethysUISwingScrollMenu<T> getContext() {
1427 return theContext;
1428 }
1429
1430 @Override
1431 public TethysUISwingScrollMenu<T> getSubMenu() {
1432 return theSubMenu;
1433 }
1434
1435
1436
1437
1438
1439
1440 int getIndex() {
1441 return theIndex;
1442 }
1443
1444
1445
1446
1447 void hideMenu() {
1448 theSubMenu.closeMenu();
1449 }
1450
1451
1452
1453
1454
1455
1456 private JDialog getDialog() {
1457 return theContext.getDialog();
1458 }
1459
1460
1461
1462
1463
1464
1465
1466 private void setMaxDisplayItems(final int pMaxDisplayItems) {
1467
1468 theSubMenu.setMaxDisplayItems(pMaxDisplayItems);
1469 }
1470
1471
1472
1473
1474 void scrollToMenu() {
1475 theContext.scrollToIndex(theIndex);
1476 }
1477 }
1478
1479
1480
1481
1482 private final class ScrollControl
1483 extends TethysUISwingScrollElement {
1484
1485
1486
1487 private final int theIncrement;
1488
1489
1490
1491
1492 private TimerTask theTimerTask;
1493
1494
1495
1496
1497
1498
1499
1500 ScrollControl(final TethysUIIcon pIcon,
1501 final int pIncrement) {
1502
1503 super(pIcon);
1504
1505
1506 theIncrement = pIncrement;
1507
1508
1509 getPanel().addMouseListener(new MouseAdapter() {
1510 @Override
1511 public void mouseClicked(final MouseEvent e) {
1512 processScroll();
1513 }
1514
1515 @Override
1516 public void mouseEntered(final MouseEvent e) {
1517 processMouseEnter();
1518 }
1519
1520 @Override
1521 public void mouseExited(final MouseEvent e) {
1522 processMouseExit();
1523 }
1524 });
1525 }
1526
1527
1528
1529
1530 void processScroll() {
1531
1532 requestScroll(theIncrement);
1533 }
1534
1535
1536
1537
1538 void processMouseEnter() {
1539
1540 if (theTimerTask != null) {
1541 theTimerTask.cancel();
1542 }
1543
1544
1545 closeChildren();
1546
1547
1548 clearActiveItem();
1549 setActive(true);
1550
1551
1552 theTimerTask = new TimerTask() {
1553 @Override
1554 public void run() {
1555 SwingUtilities.invokeLater(ScrollControl.this::processScroll);
1556 }
1557 };
1558
1559
1560 getTimer().schedule(theTimerTask, TethysUICoreScrollMenu.INITIAL_SCROLLDELAY, TethysUICoreScrollMenu.REPEAT_SCROLLDELAY);
1561 }
1562
1563
1564
1565
1566 void processMouseExit() {
1567
1568 if (theTimerTask != null) {
1569 theTimerTask.cancel();
1570 theTimerTask = null;
1571 }
1572 setActive(false);
1573 }
1574 }
1575 }