4
* Created on January 13, 2007, 12:42 PM
6
* Copyright 2006-2007 Nigel Hughes
8
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
9
* in compliance with the License. You may obtain a copy of the License at http://www.apache.org/
10
* licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
12
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
13
* governing permissions and limitations under the License.
16
package org.pushingpixels.lafwidget.contrib.blogofbug.swing.components;
19
import java.awt.event.*;
21
import java.security.InvalidParameterException;
25
import javax.swing.event.*;
27
import org.pushingpixels.lafwidget.contrib.blogofbug.swing.borders.ImageBorder;
28
import org.pushingpixels.lafwidget.contrib.blogofbug.swing.layout.OffsetCaroselLayout;
32
* Shows a carousel offset to the left with a menu of actions on the right.
35
public class JCarouselMenu extends GradientPanel implements ListSelectionListener,MouseListener,KeyListener, ChangeListener, MouseWheelListener{
37
* The carousel used and drawn on the left.
39
private JCarosel carousel;
41
* A JList with the menu items in
45
* The scroll pane the menu is in
47
private JScrollPane menuScroll;
49
* The model for the action menu
51
private DefaultListModel menuModel=new DefaultListModel();
53
* Linked list of the items in the menu
55
private LinkedList<MenuItem> menuItems=new LinkedList<MenuItem>();
57
* A hashtable connecting the actions to the components in the carousel
59
private Map<Component, MenuItem> menuMap = new HashMap<Component, MenuItem>();
61
* The last item selected
63
private int lastSelection = -1;
66
* The button that is drawn when it is possible to scroll up
68
private UpDownButton upButton = new UpDownButton("Up");
70
* The button shown when you can scroll down
72
private UpDownButton downButton = new UpDownButton("Down");
75
* Creates a new instance of JCarouselMenu
76
* @param border The border to use to draw items in the menu
78
public JCarouselMenu(ImageBorder border) {
79
carousel = new JCarosel();
80
carousel.setLayout(new OffsetCaroselLayout(carousel));
81
carousel.setBackground(null);
82
carousel.setOpaque(false);
83
carousel.setContentWidth(256);
85
super.setLayout(new GridLayout(1,2));
88
upButton.setForeground(Color.WHITE);
89
downButton.setForeground(Color.WHITE);
91
JPanel menuPanel = new JPanel();
92
menuPanel.setBackground(null);
93
menuPanel.setOpaque(false);
94
menuPanel.setLayout(new GridBagLayout());
95
GridBagConstraints gbc = new GridBagConstraints();
98
menuScroll = new JScrollPane(menu, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
99
menuScroll.getViewport().setOpaque(false);
100
menuScroll.setBorder(null);
101
menuScroll.getViewport().addChangeListener(this);
102
menu.setModel(menuModel);
103
menu.setCellRenderer(new CarouselListCellRenderer(border));
104
menu.setBackground(null);
105
menu.setOpaque(false);
106
menu.addListSelectionListener(this);
107
menuScroll.setOpaque(true);
108
menuScroll.setBackground(Color.BLACK);
109
menuScroll.setBorder(BorderFactory.createEmptyBorder());
114
gbc.fill=GridBagConstraints.HORIZONTAL;
115
menuPanel.add(upButton,gbc);
119
gbc.fill=GridBagConstraints.BOTH;
120
menuPanel.add(menuScroll,gbc);
124
gbc.fill=GridBagConstraints.HORIZONTAL;
125
menuPanel.add(downButton,gbc);
126
menu.addMouseListener(this);
127
menu.addKeyListener(this);
129
//Don't want it to listen to itself...
130
carousel.removeMouseWheelListener(carousel);
131
carousel.addMouseWheelListener(this);
132
menu.addMouseWheelListener(this);
133
menuScroll.addMouseWheelListener(this);
134
menuPanel.addMouseWheelListener(this);
136
super.add(menuPanel);
140
* Creates a new instance
142
public JCarouselMenu(){
143
this(new ImageBorder(JCarouselMenu.class.getResource("/com/blogofbug/swing/borders/images/menu_highlight.png"),new Insets(10,12,16,12)));
147
* Sets the color the up and down buttons are drawn
148
* @param color The desired color
150
public void setUpDownColor(Color color){
151
upButton.setForeground(color);
152
downButton.setForeground(color);
156
* Returns the list part of the carousel menu
158
* @return The JList object
160
public JList getList(){
165
* Sets the selected item in the menu
166
* @param i The index of the item to select
168
public void setSelectedIndex(int i){
169
menu.setSelectedIndex(i);
173
* Adds a component to the carousel menu that will be brought into view when the user clicks
174
* on the associated item
175
* @param component The component
176
* @param label The text to appear in the menu
177
* @return The created component
179
public Component add(Component component,String label){
180
carousel.add(label,component);
181
MenuItem item = new MenuItem(component,label,null);
182
menuItems.addLast(item);
183
menuModel.addElement(item);
184
menuMap.put(component, item);
185
component.removeMouseListener(carousel);
190
* Removes a component from the menu
191
* @param component The component to remove
194
public void remove(Component component) {
195
carousel.remove(component);
196
MenuItem menuItem = menuMap.remove(component);
197
if (menuItem != null) {
198
menuItems.remove(menuItem);
199
menuModel.removeElement(menuItem);
205
* Adds an image to the menu.
206
* @deprecated Use add(Image, String) instead
207
* @param image The image
208
* @param label The text
209
* @param width Prefered width
210
* @param height Prefered height
211
* @return The created component
213
public Component add(Image image, String label, int width, int height) {
214
Component comp = carousel.add(image, null);
215
MenuItem item = new MenuItem(comp, label, null);
216
menuItems.addLast(item);
217
menuModel.addElement(item);
218
comp.removeMouseListener(carousel);
219
menuMap.put(comp, item);
225
* Adds an image (with a label) and returns the component created to represent them
226
* @param image The image to display
227
* @param label The label to show
228
* @return The component created
230
public Component add(Image image, String label) {
231
Component comp = carousel.add(image, null);
232
MenuItem item = new MenuItem(comp, label, null);
233
menuItems.addLast(item);
234
menuModel.addElement(item);
235
comp.removeMouseListener(carousel);
236
menuMap.put(comp, item);
241
* Adds an action to the menu
242
* @deprecated Use add(imageURL) instead
243
* @param action The action to add
244
* @param width The width
245
* @param height The height
246
* @return The created component
248
public Component add(Action action, int width, int height){
249
URL url = (URL) action.getValue(AbstractCarouselMenuAction.ACTION_IMAGE_URL);
251
throw new InvalidParameterException("Supplied action does not have Image URL key (AbstractCarouselMenuAction.ACTION_IMAGE_URL)"
254
Component comp = carousel.add(url.toString());
255
MenuItem item = new MenuItem(comp,(String) action.getValue(Action.SHORT_DESCRIPTION),action);
256
menuItems.addLast(item);
257
menuMap.put(comp, item);
258
menuModel.addElement(item);
259
comp.removeMouseListener(carousel);
264
* Adds an action to the list, creating a menu item and a carousel entry
265
* @param action The action to add
266
* @return The resultant component
268
public Component add(Action action){
269
URL url = (URL) action.getValue(AbstractCarouselMenuAction.ACTION_IMAGE_URL);
271
throw new InvalidParameterException("Supplied action does not have Image URL key (AbstractCarouselMenuAction.ACTION_IMAGE_URL)"
274
Component comp = carousel.add(url.toString());
275
MenuItem item = new MenuItem(comp,(String) action.getValue(Action.SHORT_DESCRIPTION),action);
276
menuItems.addLast(item);
277
menuMap.put(comp, item);
278
menuModel.addElement(item);
279
comp.removeMouseListener(carousel);
284
* Adds an image (through a URL) to the menu
285
* @deprecated Use add(imageURL, label) instead
286
* @param imageURL URL of the image
287
* @param label Text message
289
* @param height height
290
* @return The created component
292
public Component add(String imageURL, String label, int width, int height){
293
Component comp = carousel.add(imageURL);
294
MenuItem item = new MenuItem(comp,label,null);
295
menuMap.put(comp, item);
296
menuItems.addLast(item);
297
menuModel.addElement(item);
298
comp.removeMouseListener(carousel);
303
* Adds an image based on the imageURL and a text label, returning the component that is created as a result
304
* @param imageURL The URL of the image
305
* @param label Text label to be shown in the menu
306
* @return The created component
308
public Component add(String imageURL, String label){
309
Component comp = carousel.add(imageURL);
310
MenuItem item = new MenuItem(comp,label,null);
311
menuMap.put(comp, item);
312
menuItems.addLast(item);
313
menuModel.addElement(item);
314
comp.removeMouseListener(carousel);
319
* Return the preferred size of the component
320
* @return The prefered dimensions of the component
323
public Dimension getPreferredSize() {
324
Dimension size = super.getPreferredSize();
330
* Detect when the list selection changes, and respond by updating the state
331
* of the two "arrow" buttons. Contributed by Sebastian Charpentier.
332
* @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
333
* @param e The state changed event
336
public void stateChanged(ChangeEvent e) {
337
// Check if the scroll bar is at the top or at the bottom
338
// Note: It's a trick, I don't know if this is the best/correct way to handle that
339
// We show the "go up" arrow if were not at the beginning
340
JViewport viewport = menuScroll.getViewport();
341
int yPos = (int)viewport.getViewPosition().getY();
342
upButton.setDoPaint(yPos > 0);
343
// We show the "go down" arrow if were not at the end (having the view as down as we could)
344
downButton.setDoPaint((yPos + viewport.getExtentSize().getHeight()) != menu.getHeight());
349
* Detect when the list selection changes, and respond by rotating the carousel to show
351
* @param listSelectionEvent The list selection change event
354
public void valueChanged(ListSelectionEvent listSelectionEvent) {
355
MenuItem item = (MenuItem) menu.getSelectedValue();
360
carousel.bringToFront(item.carouselComponent);
364
* Launch the action associated with the currently selected list item
367
protected void processAction(){
368
MenuItem item = (MenuItem) menu.getSelectedValue();
372
if (item.action==null){
375
item.action.actionPerformed(new ActionEvent(this,ActionEvent.ACTION_PERFORMED,item.label));
379
* Look to see if an item in the list is double clicked, and launch the action if it is
380
* @param mouseEvent The mouse event
383
public void mouseClicked(MouseEvent mouseEvent) {
384
if (mouseEvent.getClickCount()==2){
391
* @param mouseEvent The mouse event
394
public void mousePressed(MouseEvent mouseEvent) { }
398
* @param mouseEvent The mouse event
401
public void mouseReleased(MouseEvent mouseEvent) {}
405
* @param mouseEvent The mouse event
408
public void mouseEntered(MouseEvent mouseEvent) {}
412
* @param mouseEvent The mouse event
415
public void mouseExited(MouseEvent mouseEvent) {}
419
* @param keyEvent The key event
422
public void keyTyped(KeyEvent keyEvent) { }
425
* Listen for key events, when we see one that looks like it should wrap, set up the lastSelection variable to
426
* trigger a change on release of the key
427
* @param keyEvent The key event
430
public void keyPressed(KeyEvent keyEvent) {
431
switch (keyEvent.getKeyCode()){
432
case KeyEvent.VK_ENTER:
436
if (menu.getSelectedIndex()==0){
437
this.lastSelection = menuModel.size()-1;
439
this.lastSelection = -1;
442
case KeyEvent.VK_DOWN:
443
if (menu.getSelectedIndex()==menuModel.size()-1){
444
this.lastSelection = 0;
446
this.lastSelection = -1;
454
* Sets the image border used to draw around the items in the menu
455
* @param imageBorder The desired image border
457
public void setCellImageBorder(ImageBorder imageBorder){
458
CarouselListCellRenderer renderer = (CarouselListCellRenderer) menu.getCellRenderer();
460
renderer.setImageBorder(imageBorder);
464
* Specifies the list cell renderer used to draw the items in the menu
465
* @param cellRenderer The list cell renderer
467
public void setCellRenderer(ListCellRenderer cellRenderer){
468
menu.setCellRenderer(cellRenderer);
472
* If the wrap-around has detected the need to wrap, sets the selection to the value
473
* calculated when the key was first pressed.
474
* @param keyEvent The key event
477
public void keyReleased(KeyEvent keyEvent) {
478
if (lastSelection!=-1){
479
menu.setSelectedIndex(lastSelection);
480
menu.ensureIndexIsVisible(lastSelection);
486
* Moves the selected menu up or down when the mouse wheel scrolls
487
* @param mouseWheelEvent The mouse wheel event
490
public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent) {
491
if (mouseWheelEvent.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
492
int amount = mouseWheelEvent.getWheelRotation();
496
if (menu.getSelectedIndex()==0){
497
lastSelection = menuModel.size()-1;
499
lastSelection = menu.getSelectedIndex()-1;
502
if (menu.getSelectedIndex()==menuModel.size()-1){
505
lastSelection = menu.getSelectedIndex()+1;
509
final int indexToSelect = lastSelection;
510
SwingUtilities.invokeLater(new Runnable() {
513
menu.setSelectedIndex(indexToSelect);
514
menu.ensureIndexIsVisible(indexToSelect);
522
* Sets icons to use to show the up and down buttons
523
* @param upIcon The icon to use for up
524
* @param downIcon The icon to use for down
526
public void setUpDownIcons(Icon upIcon, Icon downIcon) {
527
upButton.setIcon(upIcon);
528
downButton.setIcon(downIcon);
532
* Allows the background color to the menu (left side) to be set
533
* @param color Sets the background color to the menu
535
public void setMenuScrollColor(Color color) {
536
this.menuScroll.setBackground(color);
540
* ListCellRenderer for the Carousel uses an image border to draw a nice border around the menu item when it is selected
543
protected class CarouselListCellRenderer extends JLabel implements ListCellRenderer{
544
ImageBorder imageBorder;
546
* Creates a new list cell renderer for the menu with the specified image border
547
* @param border The border to use
549
public CarouselListCellRenderer(ImageBorder border){
550
imageBorder = border;
551
setBorder(imageBorder);
555
* Allows the setting of the image border
556
* @param border The border to use
558
public void setImageBorder(ImageBorder border){
559
imageBorder = border;
560
setBorder(imageBorder);
564
* Sets up the component for stamping
565
* @param jList The list
566
* @param object The object being drawn
567
* @param i The index of the object
568
* @param isSelected If the object is selected
569
* @param cellHasFocus Does the cell have the focus
570
* @return The object to use to stamp the list item
573
public Component getListCellRendererComponent(JList jList, Object object, int i, boolean isSelected, boolean cellHasFocus) {
574
MenuItem item = (MenuItem) object;
578
imageBorder.setPaintBorder(false);
581
imageBorder.setPaintBorder(false);
584
setForeground(Color.WHITE);
590
* Our image border can paint a center as well as a surround. Call paint center if we want it to do this.
591
* @param g The graphcis context
594
public void paintComponent(Graphics g){
595
imageBorder.paintCenter((Graphics2D)g,this);
596
super.paintComponent(g);
600
* I want it to be wider than it needs to be
601
* @return The desired width of the cell
604
public Dimension getPreferredSize() {
605
Dimension d = super.getPreferredSize();
612
* A menu item inside the carousel
614
public class MenuItem{
616
* The component inside the caroulse
618
protected Component carouselComponent;
622
protected String label;
624
* An associated action
626
protected Action action;
629
* Creates a new instance of the menu item
630
* @param component The component to use
631
* @param label The text label
632
* @param action The associated action
634
public MenuItem(Component component, String label,Action action){
636
carouselComponent = component;
637
this.action = action;
641
* Retreives the label associated with the entry
644
public String getLabel() {
649
* Gets the action associated with the entry
650
* @return The action associated with the entry
652
public Action getAction() {
657
* Gets the component in the carousel associated with the entry
658
* @return The component
660
public Component getCarouselComponent() {
661
return carouselComponent;
667
* This class represents the up and down buttons that allow the scrolling through the menu when it is too big to fit in the avaiable space
669
private class UpDownButton extends JLabel implements MouseListener{
671
* True if they should be painted
673
private boolean doPaint = true;
676
* Creates the up down button
677
* @param text Test, ignored
679
public UpDownButton(String text){
681
addMouseListener(this);
682
setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
686
* Controls if the button should paint itself or not
687
* @param shouldPaint True if it should, false if it shouldn't
689
public void setDoPaint(boolean shouldPaint){
690
doPaint = shouldPaint;
695
* Paint the component
696
* @param g The graphics context
699
public void paintComponent(Graphics g){
701
Icon icon = this.getIcon();
703
int centerX = getWidth()
704
- (getInsets().left + getInsets().right);
705
centerX = getInsets().left + centerX / 2;
706
int centerY = getHeight()
707
- (getInsets().top + getInsets().bottom);
708
centerY = getInsets().top + centerY / 2;
709
icon.paintIcon(this, g, centerX - icon.getIconWidth() / 2,
710
centerY - icon.getIconHeight() / 2);
712
Graphics2D g2 = (Graphics2D) g;
713
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
714
g.setColor(getForeground());
715
int centerX = getWidth()-(getInsets().left+getInsets().right);
716
centerX = getInsets().left + centerX/2;
717
int height = getHeight()-(getInsets().top+getInsets().bottom);
718
int width = height*2;
719
if ("Up".equals(getText())){
720
g.fillPolygon(new int[]{centerX-width,centerX,centerX+width},new int[]{height,getInsets().top,height},3);
722
g.fillPolygon(new int[]{centerX-width,centerX,centerX+width},new int[]{getInsets().top,height,getInsets().top},3);
731
* Listens for a mouse click and scroll up or down in the menu when it gets one
732
* @param mouseEvent The mouse event
735
public void mouseClicked(MouseEvent mouseEvent) {
739
if (mouseEvent.getClickCount()==1){
740
int height = menu.getCellBounds(menu.getSelectedIndex(),menu.getSelectedIndex()).height;
741
if (getText().equals("Up")) {
742
setSelectedIndex(menu.getSelectedIndex()-1);
743
Point pos = menuScroll.getViewport().getViewPosition();
745
menuScroll.getViewport().setViewPosition(pos);
746
} else if (getText().equals("Down")) {
747
setSelectedIndex(menu.getSelectedIndex()+1);
748
Point pos = menuScroll.getViewport().getViewPosition();
750
menuScroll.getViewport().setViewPosition(pos);
757
* @param mouseEvent The mouse event
760
public void mousePressed(MouseEvent mouseEvent) {
765
* @param mouseEvent The mouse event
768
public void mouseReleased(MouseEvent mouseEvent) {
773
* @param mouseEvent The mouse event
776
public void mouseEntered(MouseEvent mouseEvent) {
781
* @param mouseEvent The mouse event
784
public void mouseExited(MouseEvent mouseEvent) {