* Bug 16633: now Listbox callback can be triggered by item click
[scilab.git] / scilab / modules / gui / src / java / org / scilab / modules / gui / bridge / listbox / SwingScilabListBox.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2007 - INRIA - Vincent Couvert
4  * Copyright (C) 2007 - INRIA - Marouane BEN JELLOUL
5  * Copyright (C) 2010-2011 - DIGITEO - Vincent COUVERT
6  *
7  * Copyright (C) 2012 - 2016 - Scilab Enterprises
8  *
9  * This file is hereby licensed under the terms of the GNU GPL v2.0,
10  * pursuant to article 5.3.4 of the CeCILL v.2.1.
11  * This file was originally licensed under the terms of the CeCILL v2.1,
12  * and continues to be available under such terms.
13  * For more information, see the COPYING file which you should have received
14  * along with this program.
15  *
16  */
17
18 package org.scilab.modules.gui.bridge.listbox;
19
20 import static org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties.__GO_UI_BACKGROUNDCOLOR__;
21 import static org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties.__GO_UI_LISTBOXTOP__;
22 import static org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties.__GO_UI_MAX__;
23 import static org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties.__GO_UI_MIN__;
24 import static org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties.__GO_UI_STRING_COLNB__;
25 import static org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties.__GO_UI_STRING__;
26 import static org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties.__GO_UI_VALUE__;
27
28 import java.awt.Color;
29 import java.awt.Component;
30 import java.awt.Font;
31 import java.awt.event.AdjustmentEvent;
32 import java.awt.event.AdjustmentListener;
33 import java.io.IOException;
34 import java.awt.event.MouseAdapter;
35 import java.awt.event.MouseEvent;
36
37 import javax.swing.DefaultListModel;
38 import javax.swing.Icon;
39 import javax.swing.JLabel;
40 import javax.swing.JList;
41 import javax.swing.JScrollPane;
42 import javax.swing.ListCellRenderer;
43 import javax.swing.ListSelectionModel;
44 import javax.swing.UIManager;
45 import javax.swing.border.Border;
46 import javax.swing.event.ListSelectionEvent;
47 import javax.swing.event.ListSelectionListener;
48
49 import org.scilab.modules.commons.gui.FindIconHelper;
50 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
51 import org.scilab.modules.gui.SwingViewObject;
52 import org.scilab.modules.gui.SwingViewWidget;
53 import org.scilab.modules.gui.events.callback.CommonCallBack;
54 import org.scilab.modules.gui.menubar.MenuBar;
55 import org.scilab.modules.gui.textbox.TextBox;
56 import org.scilab.modules.gui.toolbar.ToolBar;
57 import org.scilab.modules.gui.utils.ColorBox;
58 import org.scilab.modules.gui.utils.Position;
59 import org.scilab.modules.gui.utils.PositionConverter;
60 import org.scilab.modules.gui.utils.ScilabRelief;
61 import org.scilab.modules.gui.utils.ScilabSwingUtilities;
62 import org.scilab.modules.gui.utils.Size;
63 import org.scilab.modules.gui.utils.SwingScilabListItem;
64 import org.scilab.modules.gui.widget.Widget;
65
66 /**
67  * Swing implementation for Scilab ListBox in GUIs
68  * @author Vincent COUVERT
69  * @author Marouane BEN JELLOUL
70  */
71 public class SwingScilabListBox extends JScrollPane implements SwingViewObject, Widget {
72
73     private static final long serialVersionUID = 3507396207331058895L;
74
75     private static final int COLORS_COEFF = 255;
76
77     private Integer uid;
78
79     private Border defaultBorder = null;
80
81     private CommonCallBack callback;
82
83     private ListSelectionListener listListener;
84
85     private AdjustmentListener adjustmentListener;
86
87     /**
88      * the JList we use
89      */
90     private JList list;
91
92     private ListCellRenderer defaultRenderer = null;
93     private ListCellRenderer listRenderer = null;
94
95     /**
96      * Constructor
97      */
98     public SwingScilabListBox() {
99         super();
100         getViewport().add(getList());
101         defaultRenderer = getList().getCellRenderer();
102
103         listRenderer = new ListCellRenderer() {
104             public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
105                 JLabel label = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
106
107                 if (value instanceof SwingScilabListItem) {
108                     SwingScilabListItem item = (SwingScilabListItem) value;
109                     label.setText(item.toString());
110                     label.setIcon(item.getIcon());
111
112                     if (isSelected == false && item.getBackground() != null) {
113                         label.setBackground(item.getBackground());
114                     }
115
116                     if (isSelected == false && item.getForeground() != null) {
117                         label.setForeground(item.getForeground());
118                     }
119                 } else {
120                     label.setText("");
121                     label.setIcon(null);
122                 }
123                 return label;
124             }
125         };
126
127         getList().setCellRenderer(listRenderer);
128
129         listListener = new ListSelectionListener() {
130             public void valueChanged(ListSelectionEvent e) {
131
132                 //value not ready
133                 if (e.getValueIsAdjusting()) {
134                     return;
135                 }
136
137                 // Scilab indices in Value begin at 1 and Java indices begin at 0
138                 int[] javaIndices = getList().getSelectedIndices().clone();
139                 Double[] scilabIndices = new Double[javaIndices.length];
140                 for (int i = 0; i < getList().getSelectedIndices().length; i++) {
141                     scilabIndices[i] = (double) javaIndices[i] + 1;
142                 }
143
144                 GraphicController.getController().setProperty(uid, __GO_UI_VALUE__, scilabIndices);
145                 if (callback != null && getList().getSelectionMode() == ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
146                     callback.actionPerformed(null);
147                 }
148             }
149         };
150         getList().addListSelectionListener(listListener);
151
152          getList().addMouseListener(new MouseAdapter() {
153             public void mouseReleased(MouseEvent evt) {
154                 if (callback != null && getList().getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
155                     callback.actionPerformed(null);
156                 }
157             }
158         });
159
160         adjustmentListener = new AdjustmentListener() {
161             public void adjustmentValueChanged(AdjustmentEvent arg0) {
162                 int listboxtopValue = getList().getUI().locationToIndex(getList(), getViewport().getViewPosition()) + 1;
163                 GraphicController.getController().setProperty(uid, __GO_UI_LISTBOXTOP__, new Integer[] { listboxtopValue });
164             }
165         };
166         getVerticalScrollBar().addAdjustmentListener(adjustmentListener);
167     }
168
169     /**
170      * To get the Background color of the element.
171      * @return color the Color
172      */
173     public Color getBackground() {
174         return getList().getBackground();
175     }
176
177     /**
178      * To get the Font of the element.
179      * @return font the Font
180      */
181     public Font getFont() {
182         return getList().getFont();
183     }
184
185     /**
186      * To get the Foreground color of the element.
187      * @return color the Color
188      */
189     public Color getForeground() {
190         return getList().getForeground();
191     }
192
193     /**
194      * To set the Background color of the element.
195      * @param color the Color
196      */
197     public void setListBackground(Color color) {
198         getList().setBackground(color);
199     }
200
201     /**
202      * To set the Font of the element.
203      * @param font the Font
204      */
205     public void setFont(Font font) {
206         getList().setFont(font);
207     }
208
209     /**
210      * To set the Foreground color of the element.
211      * @param color the Color
212      */
213     public void setForeground(Color color) {
214         getList().setForeground(color);
215     }
216
217     /**
218      * Draws a swing Scilab tab
219      * @see org.scilab.modules.gui.uielement.UIElement#draw()
220      */
221     public void draw() {
222         this.setVisible(true);
223         this.doLayout();
224     }
225
226     /**
227      * Gets the dimensions (width and height) of a swing Scilab tab
228      * @return the dimensions of the tab
229      * @see org.scilab.modules.gui.uielement.UIElement#getDims()
230      */
231     public Size getDims() {
232         return new Size(getWidth(), getHeight());
233     }
234
235     /**
236      * Gets the position (X-coordinate and Y-coordinate) of a swing Scilab tab
237      * @return the position of the tab
238      * @see org.scilab.modules.gui.uielement.UIElement#getPosition()
239      */
240     public Position getPosition() {
241         return PositionConverter.javaToScilab(getLocation(), getSize(), getParent());
242     }
243
244     /**
245      * Sets the dimensions (width and height) of a swing Scilab tab
246      * @param newSize the dimensions we want to set to the tab
247      * @see org.scilab.modules.gui.uielement.UIElement#setDims(org.scilab.modules.gui.utils.Size)
248      */
249     public void setDims(Size newSize) {
250         setSize(newSize.getWidth(), newSize.getHeight());
251     }
252
253     /**
254      * Sets the position (X-coordinate and Y-coordinate) of a swing Scilab tab
255      * @param newPosition the position we want to set to the tab
256      * @see org.scilab.modules.gui.uielement.UIElement#setPosition(org.scilab.modules.gui.utils.Position)
257      */
258     public void setPosition(Position newPosition) {
259         Position javaPosition = PositionConverter.scilabToJava(newPosition, getDims(), getParent());
260         setLocation(javaPosition.getX(), javaPosition.getY());
261     }
262
263     /**
264      * Sets the visibility status of an UIElement
265      * @param newVisibleState the visibility status we want to set for the
266      * UIElement (true if the UIElement is visible, false if not)
267      */
268     public void setVisible(boolean newVisibleState) {
269         super.setVisible(newVisibleState);
270         list.setVisible(newVisibleState);
271     }
272
273     /**
274      * Sets the enable status of an UIElement
275      * @param newEnableState the enable status we want to set for the UIElement
276      * (true if the UIElement is enabled, false if not)
277      */
278     public void setEnabled(boolean newEnableState) {
279         if (newEnableState == isEnabled()) {
280             return;
281         }
282
283         super.setEnabled(newEnableState);
284         getList().setEnabled(newEnableState);
285         if (newEnableState) {
286             if (listListener != null) {
287                 getList().addListSelectionListener(listListener);
288             }
289         } else {
290             if (listListener != null) {
291                 getList().removeListSelectionListener(listListener);
292             }
293         }
294     }
295
296     /**
297      * Add a callback to the CheckBox
298      * @param cb the callback to set.
299      */
300     public void setCallback(CommonCallBack cb) {
301         this.callback = cb;
302     }
303
304     /**
305      * Setter for MenuBar
306      * @param menuBarToAdd the MenuBar associated to the Tab.
307      */
308     public void addMenuBar(MenuBar menuBarToAdd) {
309         /* Unimplemented for ListBoxes */
310         throw new UnsupportedOperationException();
311     }
312
313     /**
314      * Setter for ToolBar
315      * @param toolBarToAdd the ToolBar associated to the Tab.
316      */
317     public void addToolBar(ToolBar toolBarToAdd) {
318         /* Unimplemented for ListBoxes */
319         throw new UnsupportedOperationException();
320     }
321
322     /**
323      * Getter for MenuBar
324      * @return MenuBar: the MenuBar associated to the Tab.
325      */
326     public MenuBar getMenuBar() {
327         /* Unimplemented for ListBoxes */
328         throw new UnsupportedOperationException();
329     }
330
331     /**
332      * Getter for ToolBar
333      * @return ToolBar: the ToolBar associated to the Tab.
334      */
335     public ToolBar getToolBar() {
336         /* Unimplemented for ListBoxes */
337         throw new UnsupportedOperationException();
338     }
339
340     /**
341      * Get the first item text
342      * @return the items
343      * @see org.scilab.modules.gui.widget.Widget#getText()
344      */
345     public String getText() {
346         /* Unimplemented for ListBoxes */
347         throw new UnsupportedOperationException();
348     }
349
350     /**
351      * Get the text of all the list items
352      * @return the items
353      * @see org.scilab.modules.gui.listbox.ListBox#getAllItemsText()
354      */
355     public String[] getAllItemsText() {
356         String[] retValue = new String[getList().getModel().getSize()];
357         for (int i = 0; i < getList().getModel().getSize(); i++) {
358             retValue[i] = getList().getModel().getElementAt(i).toString();
359         }
360         return retValue;
361     }
362
363     /**
364      * Get the number of items in the list
365      * @return the number of items
366      * @see org.scilab.modules.gui.listbox.ListBox#getNumberOfItems()
367      */
368     public int getNumberOfItems() {
369         return getList().getModel().getSize();
370     }
371
372     /**
373      * Set the text of the list items
374      * @param text the text of the items
375      * @see org.scilab.modules.gui.widget.Widget#setText(java.lang.String)
376      */
377     public void setText(String text) {
378         DefaultListModel model = new DefaultListModel();
379         model.addElement(text);
380         getList().setModel(model);
381         revalidate();
382     }
383
384     /**
385      * Set the text of the list items
386      * @param text the text of the items
387      * @see org.scilab.modules.gui.widget.Widget#setText(java.lang.String)
388      */
389     public void setText(String[] text) {
390         DefaultListModel model = new DefaultListModel();
391
392         // check numbers of columns
393         GraphicController controller = GraphicController.getController();
394         Integer nbCol = (Integer) controller.getProperty(getId(), __GO_UI_STRING_COLNB__);
395
396         /* Remove the listener to avoid the callback to be executed */
397         getList().removeListSelectionListener(listListener);
398
399         boolean tryColorBox = true;
400         boolean tryColor = true;
401         boolean tryIcon = true;
402         int nbRow = text.length / nbCol;
403
404         for (int i = 0; i < nbRow; i++) {
405             Icon icon = null;
406             String str = null;
407             Color background = null;
408             Color foreground = null;
409
410             //4 cols :
411             // - 1st icon or colorBox
412             // - 2nd text
413             // - 3rd BG
414             // - 4th FG
415
416             //3 cols :  2 cases
417             // - 1st icon or colorBox
418             // - 2nd text
419             // - 3rd BG
420             //or
421             // - 1st text
422             // - 2nd BG
423             // - 3rd FG
424
425             //2 cols : 2 cases
426             // - 1st icon or colorBox
427             // - 2nd text
428             //or
429             // - 1st text
430             // - 2nd BG
431
432             if (tryColorBox) { //color
433                 try {
434                     //format #FFFFFF
435                     if (text[i].startsWith("#") == false) {
436                         throw new NumberFormatException();
437                     }
438
439                     Color color = Color.decode(text[i]);
440                     icon = ColorBox.createColorBox(16, 16, color);
441                 } catch (NumberFormatException e) {
442                     tryColorBox = false;
443                     model.clear();
444                     //restart loop with icon
445                     i = -1;
446                     continue;
447                 }
448             }
449
450             if (tryIcon) {
451                 try {
452                     icon = FindIconHelper.loadIcon(text[i]);
453                 } catch (IOException e) {
454                     tryIcon = false;
455                     model.clear();
456                     //restart loop with text only
457                     i = -1;
458                     continue;
459                 }
460             }
461
462             if (tryColor) {
463                 try {
464                     int colIndex = 0;
465                     if (tryColorBox || tryIcon) {
466                         colIndex = 1;
467                     }
468
469                     str = text[(nbRow * colIndex) + i];
470                     if (nbCol > (1 + colIndex)) {
471                         if (text[nbRow * (1 + colIndex) + i].startsWith("#") == false) {
472                             throw new NumberFormatException();
473                         }
474
475                         background = Color.decode(text[nbRow * (1 + colIndex) + i]);
476                         if (nbCol > (2 + colIndex)) {
477                             if (text[nbRow * (2 + colIndex) + i].startsWith("#") == false) {
478                                 throw new NumberFormatException();
479                             }
480                             foreground = Color.decode(text[nbRow * (2 + colIndex) + i]);
481                         }
482                     }
483
484                     //add item in list box
485                     model.addElement(new SwingScilabListItem(str, icon, background, foreground));
486                 } catch (NumberFormatException e) {
487                     tryColor = false;
488                     model.clear();
489                     //restart loop with text only
490                     i = -1;
491                     continue;
492                 }
493             } else { //text only
494                 for (int j = 0; j < nbCol; j++) {
495                     model.addElement(new SwingScilabListItem(text[nbRow * j + i], icon, background, foreground));
496                 }
497             }
498         }
499
500         //reset selected index
501         getList().setSelectedIndex(-1);
502         getList().setModel(model);
503         invalidate();
504         //take care to add listener BEFORE set Property to avoid multiple remove and multiple add
505         getList().addListSelectionListener(listListener);
506     }
507
508     public void setEmptyText() {
509         setText(new String[] {});
510     }
511
512     /**
513      * Set the horizontal alignment for the ListBox text
514      * @param alignment the value for the alignment (See ScilabAlignment.java)
515      */
516     public void setHorizontalAlignment(String alignment) {
517         // Nothing to do here
518     }
519
520     /**
521      * Set the vertical alignment for the ListBox text
522      * @param alignment the value for the alignment (See ScilabAlignment.java)
523      */
524     public void setVerticalAlignment(String alignment) {
525         // Nothing to do here
526     }
527
528     /**
529      * Set if more than one item can be selected in a ListBox
530      * @param status true if multiple selection is enabled
531      */
532     public void setMultipleSelectionEnabled(boolean status) {
533         if (status) {
534             getList().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
535         } else {
536             getList().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
537         }
538     }
539
540     /**
541      * Set the selected indices of the ListBox
542      * @param indices the indices of the items to be selected
543      */
544     public void setSelectedIndices(int[] indices) {
545         // Scilab indices in Value begin at 1 and Java indices begin at 0
546         int[] javaIndices = indices.clone();
547         for (int i = 0; i < javaIndices.length; i++) {
548             javaIndices[i] = javaIndices[i] - 1;
549         }
550
551         /* Remove the listener to avoid the callback to be executed */
552         if (listListener != null) {
553             getList().removeListSelectionListener(listListener);
554         }
555
556         getList().setSelectedIndices(javaIndices);
557
558         /* Put back the listener */
559         if (listListener != null) {
560             getList().addListSelectionListener(listListener);
561         }
562     }
563
564     /**
565      * Get the selected indices of the ListBox
566      * @return the indices of the items selected
567      */
568     public int[] getSelectedIndices() {
569         // Scilab indices in Value begin at 1 and Java indices begin at 0
570         int[] javaIndices = getList().getSelectedIndices().clone();
571         int[] scilabIndices = javaIndices.clone();
572         for (int i = 0; i < getList().getSelectedIndices().length; i++) {
573             scilabIndices[i] = scilabIndices[i] + 1;
574         }
575         return scilabIndices;
576     }
577
578     /**
579      * Get the number of items selected in the ListBox
580      * @return the number of items selected
581      */
582     public int getSelectionSize() {
583         return getList().getSelectedIndices().length;
584     }
585
586     /**
587      * Get or create the list component
588      * @return the list
589      */
590     private JList getList() {
591         if (list == null) {
592             list = new JList();
593             list.setLayoutOrientation(JList.VERTICAL);
594             list.setModel(new DefaultListModel());
595         }
596         return list;
597     }
598
599     /**
600      * Set the Relief of the ListBox
601      * @param reliefType the type of the relief to set (See ScilabRelief.java)
602      */
603     public void setRelief(String reliefType) {
604         if (defaultBorder == null) {
605             defaultBorder = getBorder();
606         }
607         setBorder(ScilabRelief.getBorderFromRelief(reliefType, defaultBorder));
608     }
609
610     /**
611      * Destroy the ListBox
612      */
613     public void destroy() {
614         ScilabSwingUtilities.removeFromParent(this);
615     }
616
617     /**
618      * Setter for InfoBar
619      * @param infoBarToAdd the InfoBar associated to the ListBox.
620      */
621     public void addInfoBar(TextBox infoBarToAdd) {
622         /* Unimplemented for ListBoxes */
623         throw new UnsupportedOperationException();
624     }
625
626     /**
627      * Getter for InfoBar
628      * @return the InfoBar associated to the ListBox.
629      */
630     public TextBox getInfoBar() {
631         /* Unimplemented for ListBoxes */
632         throw new UnsupportedOperationException();
633     }
634
635     /**
636      * Adjusts the view so that the element given by index is displayed at the
637      * top of the ListBox.
638      * @param index the index of the element to be displayed at the top of the
639      * ListBox.
640      */
641     public void setListBoxTop(int index) {
642         getVerticalScrollBar().removeAdjustmentListener(adjustmentListener);
643         if (index > 0 && index != getListBoxTop() && getList().getModel().getSize() != 0) {
644             getViewport().setViewPosition(getList().getUI().indexToLocation(getList(), index - 1));
645             doLayout();
646         }
647         getVerticalScrollBar().addAdjustmentListener(adjustmentListener);
648     }
649
650     /**
651      * Gets the index of the element displayed at the top of the ListBox
652      * @return the index of the element displayed at the top of the ListBox
653      */
654     public int getListBoxTop() {
655         return getList().getUI().locationToIndex(getList(), getViewport().getViewPosition()) + 1;
656     }
657
658     /**
659      * Set the UID
660      * @param id the UID
661      */
662     public void setId(Integer id) {
663         uid = id;
664     }
665
666     /**
667      * Get the UID
668      * @return the UID
669      */
670     public Integer getId() {
671         return uid;
672     }
673
674     /**
675      * Generic update method
676      * @param property property name
677      * @param value property value
678      */
679     public void update(int property, Object value) {
680         GraphicController controller = GraphicController.getController();
681         switch (property) {
682             case __GO_UI_VALUE__: {
683                 Double[] indexes = (Double[]) value;
684                 int[] index = new int[indexes.length];
685                 for (int i = 0; i < indexes.length; i++) {
686                     index[i] = indexes[i].intValue();
687                 }
688                 setSelectedIndices(index);
689                 break;
690             }
691             case __GO_UI_BACKGROUNDCOLOR__: {
692                 Double[] allColors = ((Double[]) value);
693                 if (allColors[0] != -1) {
694                     setListBackground(new Color((int) (allColors[0] * COLORS_COEFF), (int) (allColors[1] * COLORS_COEFF), (int) (allColors[2] * COLORS_COEFF)));
695                 } else {
696                     resetBackground();
697                 }
698                 break;
699             }
700             case __GO_UI_STRING__: {
701                 // Listboxes manage string vectors
702                 setText((String[]) value);
703                 break;
704             }
705             case __GO_UI_MAX__: {
706                 Double maxValue = ((Double) value);
707                 // Enable/Disable multiple selection
708                 double minValue = (Double) controller.getProperty(uid, __GO_UI_MIN__);
709                 setMultipleSelectionEnabled(maxValue - minValue > 1);
710                 break;
711             }
712             case __GO_UI_MIN__: {
713                 Double minValue = ((Double) value);
714                 // Enable/Disable multiple selection
715                 Double maxValue = (Double) controller.getProperty(uid, __GO_UI_MAX__);
716                 setMultipleSelectionEnabled(maxValue - minValue > 1);
717                 break;
718             }
719             case __GO_UI_LISTBOXTOP__: {
720                 Integer[] listboxtopValue = ((Integer[]) value);
721                 if (listboxtopValue.length > 0) {
722                     setListBoxTop(listboxtopValue[0]);
723                 }
724                 break;
725             }
726             default: {
727                 SwingViewWidget.update(this, property, value);
728                 break;
729             }
730         }
731     }
732
733     public void resetBackground() {
734         Color color = (Color) UIManager.getLookAndFeelDefaults().get("List.background");
735         if (color != null) {
736             getList().setBackground(color);
737         }
738     }
739
740     public void resetForeground() {
741         Color color = (Color) UIManager.getLookAndFeelDefaults().get("List.foreground");
742         if (color != null) {
743             getList().setForeground(color);
744         }
745     }
746 }