1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package net.sourceforge.jarduino.gui;
18
19 import java.awt.BorderLayout;
20 import java.awt.Color;
21 import java.awt.Component;
22 import java.awt.Dimension;
23 import java.awt.Graphics;
24 import java.awt.Point;
25 import java.awt.event.FocusEvent;
26 import java.awt.event.FocusListener;
27 import java.awt.event.InputEvent;
28 import java.awt.event.KeyEvent;
29 import java.awt.event.KeyListener;
30 import java.awt.event.MouseAdapter;
31 import java.awt.event.MouseEvent;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.function.Consumer;
35 import java.util.function.Function;
36 import javax.swing.BorderFactory;
37 import javax.swing.BoxLayout;
38 import javax.swing.Icon;
39 import javax.swing.JButton;
40 import javax.swing.JComponent;
41 import javax.swing.JDialog;
42 import javax.swing.JFrame;
43 import javax.swing.JLabel;
44 import javax.swing.JPanel;
45 import javax.swing.JScrollBar;
46 import javax.swing.JScrollPane;
47 import javax.swing.JViewport;
48 import javax.swing.ScrollPaneConstants;
49 import javax.swing.SwingConstants;
50
51
52
53
54
55 public class ArduinoScrollButton<T> {
56
57
58
59 private static final Color COLOR_BACKGROUND = Color.decode("#5F9EA0");
60
61
62
63
64 private static final int INSET_SIZE = 5;
65
66
67
68
69 private static final int DEFAULT_MAXITEMS = 10;
70
71
72
73
74 private static final int MIN_MAXITEMS = 3;
75
76
77
78
79 private static final int DEFAULT_ROWHEIGHT = 16;
80
81
82
83
84 private static final int SCROLLBAR_WIDTH = 4;
85
86
87
88
89 private final JFrame theFrame;
90
91
92
93
94 private final JButton theButton;
95
96
97
98
99 private final List<T> theItems;
100
101
102
103
104 private final ArduinoScrollMenu theMenu;
105
106
107
108
109 private int theMaxItems;
110
111
112
113
114 private Color theHighlight;
115
116
117
118
119 private Function<T, String> theFormatter;
120
121
122
123
124 private Consumer<T> theConsumer;
125
126
127
128
129 private T theSelectedItem;
130
131
132
133
134 private T theActiveItem;
135
136
137
138
139
140 ArduinoScrollButton(final JFrame pFrame) {
141
142 theFrame = pFrame;
143 theMaxItems = DEFAULT_MAXITEMS;
144 theHighlight = COLOR_BACKGROUND;
145
146
147 theItems = new ArrayList<>();
148 theMenu = new ArduinoScrollMenu();
149
150
151 theButton = new JButton();
152 theButton.setIcon(ArduinoArrowIcon.DOWN);
153 theButton.setHorizontalAlignment(SwingConstants.CENTER);
154 theButton.setVerticalAlignment(SwingConstants.CENTER);
155 theButton.setHorizontalTextPosition(SwingConstants.LEFT);
156 theButton.addActionListener(e -> theMenu.showMenuBelow(theButton));
157
158
159 theFormatter = Object::toString;
160 }
161
162
163
164
165
166 public JComponent getComponent() {
167 return theButton;
168 }
169
170
171
172
173
174 public void setMaxDisplayItems(final int pMaxItems) {
175 theMaxItems = Math.max(MIN_MAXITEMS, pMaxItems);
176 }
177
178
179
180
181
182 public int getMaxDisplayItems() {
183 return theMaxItems;
184 }
185
186
187
188
189
190 public Color getHighlightColor() {
191 return theHighlight;
192 }
193
194
195
196
197
198 public void setHighlightColor(final Color pColor) {
199 theHighlight = pColor;
200 }
201
202
203
204
205
206 public void add(final T pItem) {
207 if (theItems.isEmpty()) {
208 setSelectedItem(pItem);
209 }
210 theItems.add(pItem);
211 }
212
213
214
215
216 public void removeAll() {
217 theItems.clear();
218 setSelectedItem(null);
219 }
220
221
222
223
224
225 public void setFormatter(final Function<T, String> pFormatter) {
226 theFormatter = pFormatter;
227 }
228
229
230
231
232
233 public void onSelect(final Consumer<T> pConsumer) {
234 theConsumer = pConsumer;
235 }
236
237
238
239
240
241 public void setSelectedItem(final T pItem) {
242
243 theSelectedItem = pItem;
244 theButton.setText(getText(pItem));
245
246
247 if (theConsumer != null && pItem != null) {
248 theConsumer.accept(pItem);
249 }
250
251
252 closeMenu();
253 }
254
255
256
257
258
259 public T getSelectedItem() {
260 return theSelectedItem;
261 }
262
263
264
265
266 void closeMenu() {
267 theMenu.closeMenu();
268 }
269
270
271
272
273
274
275 String getText(final T pItem) {
276 return pItem == null ? "Select" : theFormatter.apply(pItem);
277 }
278
279
280
281
282 private class ArduinoScrollMenu {
283
284
285
286 private JDialog theDialog;
287
288
289
290
291
292
293 private JPanel buildMenu() {
294
295 final JPanel myMenu = new JPanel();
296 myMenu.setLayout(new BoxLayout(myMenu, BoxLayout.Y_AXIS));
297 myMenu.setBorder(BorderFactory.createEmptyBorder(INSET_SIZE, INSET_SIZE, INSET_SIZE, INSET_SIZE));
298
299
300 for (T myItem : theItems) {
301
302 final ArduinoScrollableMenuElement myElement = new ArduinoScrollableMenuElement(myItem);
303 myMenu.add(myElement.getComponent());
304 }
305
306
307 return myMenu;
308 }
309
310
311
312
313 void closeMenu() {
314 if (isVisible()) {
315 theDialog.setVisible(false);
316 theDialog = null;
317 theActiveItem = null;
318 }
319 }
320
321
322
323
324
325
326 boolean isVisible() {
327 return theDialog != null && theDialog.isShowing();
328 }
329
330
331
332
333
334
335 public void showMenuBelow(final JComponent pComponent) {
336
337 if (isVisible()) {
338 return;
339 }
340
341
342 theDialog = new JDialog(theFrame, false);
343 theDialog.setUndecorated(true);
344
345
346 final ArduinoScrollListener myListener = new ArduinoScrollListener();
347 theDialog.addFocusListener(myListener);
348 theDialog.addKeyListener(myListener);
349
350
351 buildDialog(pComponent);
352
353
354 final Point myLoc = pComponent.getLocationOnScreen();
355 theDialog.setLocation(myLoc.x, myLoc.y
356 + pComponent.getHeight());
357 theDialog.setVisible(true);
358 }
359
360
361
362
363
364 private void buildDialog(final JComponent pComponent) {
365
366 final int myCount = theItems.size();
367 final int myScrollCount = Math.min(theMaxItems, myCount);
368
369
370 JComponent myPanel = buildMenu();
371
372
373 if (myScrollCount != myCount) {
374
375 final JScrollPane myScroll = new JScrollPane(myPanel);
376 myScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
377 final JViewport myView = myScroll.getViewport();
378 final Dimension myPrefSize = myView.getPreferredSize();
379 final int myLen = theMaxItems * DEFAULT_ROWHEIGHT + 1;
380 myView.setPreferredSize(new Dimension(myPrefSize.width + SCROLLBAR_WIDTH, myLen));
381 myPanel = myScroll;
382
383
384 final JScrollBar myBar = myScroll.getVerticalScrollBar();
385 myBar.setUnitIncrement(DEFAULT_ROWHEIGHT);
386
387
388 theDialog.addMouseWheelListener(InputEvent::consume);
389 }
390
391
392 final JPanel myMain = new JPanel(new BorderLayout());
393 myMain.add(myPanel, BorderLayout.CENTER);
394 myMain.setBorder(BorderFactory.createLineBorder(Color.BLACK));
395
396
397 theDialog.getContentPane().add(myMain);
398 final Dimension myPrefSize = myMain.getPreferredSize();
399 final int myWidth = Math.max(myPrefSize.width, pComponent.getWidth());
400 myMain.setPreferredSize(new Dimension(myWidth, myPrefSize.height));
401
402
403 theDialog.pack();
404 }
405 }
406
407
408
409
410 private class ArduinoScrollListener
411 implements FocusListener, KeyListener {
412 @Override
413 public void focusGained(final FocusEvent e) {
414
415 }
416
417 @Override
418 public void focusLost(final FocusEvent e) {
419 closeMenu();
420 }
421
422 @Override
423 public void keyTyped(final KeyEvent e) {
424
425 }
426
427 @Override
428 public void keyPressed(final KeyEvent e) {
429 switch (e.getKeyCode()) {
430 case KeyEvent.VK_ESCAPE:
431 closeMenu();
432 break;
433 case KeyEvent.VK_ENTER:
434 setSelectedItem(theActiveItem);
435 break;
436 default:
437 break;
438 }
439 }
440
441 @Override
442 public void keyReleased(final KeyEvent e) {
443
444 }
445 }
446
447
448
449
450 private class ArduinoScrollableMenuElement {
451
452
453
454 private final JLabel theLabel;
455
456
457
458
459 private final T theItem;
460
461
462
463
464 private Color theBaseColor;
465
466
467
468
469
470 ArduinoScrollableMenuElement(final T pItem) {
471
472 theItem = pItem;
473
474
475 theLabel = new JLabel();
476 theLabel.setOpaque(true);
477 theLabel.setHorizontalAlignment(SwingConstants.LEFT);
478 theLabel.setText(getText(pItem));
479 theLabel.setMaximumSize(new Dimension(Integer.MAX_VALUE, DEFAULT_ROWHEIGHT));
480
481
482 theLabel.addMouseListener(new MouseAdapter() {
483 @Override
484 public void mouseEntered(final MouseEvent e) {
485 handleMouseEntered();
486 setActive(true);
487 }
488
489 @Override
490 public void mouseExited(final MouseEvent e) {
491 setActive(false);
492 }
493
494 @Override
495 public void mouseClicked(final MouseEvent e) {
496 setActive(false);
497 handleMouseClicked();
498 }
499 });
500 }
501
502
503
504
505
506 JComponent getComponent() {
507 return theLabel;
508 }
509
510
511
512
513 void handleMouseEntered() {
514 theActiveItem = theItem;
515 }
516
517
518
519
520 void handleMouseClicked() {
521 setSelectedItem(theItem);
522 }
523
524
525
526
527
528 protected void setActive(final boolean pActive) {
529 if (pActive) {
530 if (theBaseColor == null) {
531 theBaseColor = theLabel.getBackground();
532 theLabel.setBackground(theHighlight);
533 }
534 } else if (theBaseColor != null) {
535 theLabel.setBackground(theBaseColor);
536 theBaseColor = null;
537 }
538 }
539 }
540
541
542
543
544 private enum ArduinoArrowIcon implements Icon {
545
546
547
548 UP(new Point(1, 9), new Point(5, 1), new Point(9, 9)),
549
550
551
552
553 DOWN(new Point(1, 1), new Point(5, 9), new Point(9, 1)),
554
555
556
557
558 LEFT(new Point(1, 5), new Point(9, 1), new Point(9, 9)),
559
560
561
562
563 RIGHT(new Point(1, 1), new Point(1, 9), new Point(9, 5));
564
565
566
567
568 private static final int ICON_SIZE = 10;
569
570
571
572
573 private final int theNumPoints;
574
575
576
577
578 private final transient int[] theXPoints;
579
580
581
582
583 private final transient int[] theYPoints;
584
585
586
587
588
589 ArduinoArrowIcon(final Point... pPoints) {
590
591 theNumPoints = pPoints.length;
592 theXPoints = new int[theNumPoints];
593 theYPoints = new int[theNumPoints];
594
595
596 for (int i = 0; i < theNumPoints; i++) {
597
598 theXPoints[i] = pPoints[i].x;
599 theYPoints[i] = pPoints[i].y;
600 }
601 }
602
603 @Override
604 public void paintIcon(final Component c,
605 final Graphics g,
606 final int x,
607 final int y) {
608
609 final Graphics g2 = g.create(x, y, ICON_SIZE, ICON_SIZE);
610
611
612 g2.setColor(Color.GRAY);
613 g2.drawPolygon(theXPoints, theYPoints, theNumPoints);
614
615
616 if (c.isEnabled()) {
617
618 g2.setColor(Color.BLACK);
619 g2.fillPolygon(theXPoints, theYPoints, theNumPoints);
620 }
621
622
623 g2.dispose();
624 }
625
626 @Override
627 public int getIconWidth() {
628 return ICON_SIZE;
629 }
630
631 @Override
632 public int getIconHeight() {
633 return ICON_SIZE;
634 }
635 }
636 }