JAL-3026 all tabbed panes use JavaScript option
[jalview.git] / src / jalview / jbgui / swing / JTabbedPane.java
1 package jalview.jbgui.swing;
2
3 import jalview.fts.core.GFTSPanel;
4
5 import java.awt.Component;
6 import java.awt.Dimension;
7 import java.awt.event.ItemEvent;
8 import java.awt.event.ItemListener;
9
10 import javax.swing.BoxLayout;
11 import javax.swing.Icon;
12 import javax.swing.JComboBox;
13 import javax.swing.JComponent;
14 import javax.swing.JPanel;
15 import javax.swing.ToolTipManager;
16 import javax.swing.event.ChangeEvent;
17 import javax.swing.event.ChangeListener;
18 import javax.swing.event.EventListenerList;
19
20 import javajs.util.Lst;
21
22 @SuppressWarnings("serial")
23 public class JTabbedPane extends JPanel
24 {
25
26   @SuppressWarnings("unused")
27   public static javax.swing.JTabbedPane createTabbedPane()
28   {
29     // BH 2018 coercing jalview.jbgui.swing.JTabbedPane() for now
30     if (/** @j2sNative false && */
31     true)
32     {
33       // Java
34       return new javax.swing.JTabbedPane();
35     }
36     // JavaScript
37     return (javax.swing.JTabbedPane) (Object) new jalview.jbgui.swing.JTabbedPane();
38   }
39
40   private JPanel pagePanel;
41
42   private JComboBox<String> tabs;
43
44   private Component visComp;
45   
46   /**
47    * Only one <code>ChangeEvent</code> is needed per <code>TabPane</code>
48    * instance since the
49    * event's only (read-only) state is the source property.  The source
50    * of events generated here is always "this".
51    */
52   protected transient ChangeEvent changeEvent = null;
53
54   
55   /** just faking a JTabbedPane here using a drop-down combo box and a JPanel
56   
57     This will take considerably more work to make it right in general.
58   */
59   
60   private JTabbedPane()
61   {
62     pagePanel = new JPanel();
63     pagePanel.setLayout(new BoxLayout(pagePanel, BoxLayout.PAGE_AXIS));
64     tabs = new JComboBox<String>();
65     tabs.setMaximumSize(new Dimension(150, 15));
66     tabs.addItemListener(new ItemListener()
67     {
68
69       @Override
70       public void itemStateChanged(ItemEvent e)
71       {
72         update(false);
73       }
74
75     });
76     setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
77     add(tabs);
78     add(pagePanel);
79   }
80
81   public void setTitleAt(int index, String title)
82   {
83     Page page = pages.get(index);
84     String oldTitle = page.title;
85     page.title = title;
86
87     if (oldTitle != title)
88     {
89       firePropertyChange("indexForTitle", -1, index);
90     }
91     page.updateDisplayedMnemonicIndex();
92     // if ((oldTitle != title) && (accessibleContext != null)) {
93     // accessibleContext.firePropertyChange(
94     // AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
95     // oldTitle, title);
96     // }
97     if (title == null || oldTitle == null || !title.equals(oldTitle))
98     {
99       revalidate();
100       repaint();
101     }
102   }
103
104   public String getTitleAt(int index)
105   {
106     return pages.get(index).title;
107   }
108
109   /**
110    * We pass <code>ModelChanged</code> events along to the listeners with
111    * the tabbedpane (instead of the model itself) as the event source.
112    */
113   protected class ModelListener implements ChangeListener {
114       @Override
115                               public void stateChanged(ChangeEvent e) {
116           fireStateChanged();
117       }
118   }
119
120   /**
121    * Subclasses that want to handle <code>ChangeEvents</code> differently
122    * can override this to return a subclass of <code>ModelListener</code> or
123    * another <code>ChangeListener</code> implementation.
124    *
125    * @see #fireStateChanged
126    */
127   protected ChangeListener createChangeListener() {
128       return new ModelListener();
129   }
130   
131   /**
132    * Adds a <code>ChangeListener</code> to this tabbedpane.
133    *
134    * @param l the <code>ChangeListener</code> to add
135    * @see #fireStateChanged
136    * @see #removeChangeListener
137    */
138   public void addChangeListener(ChangeListener l) {
139       listenerList.add(ChangeListener.class, l);
140   }
141
142   /**
143    * Removes a <code>ChangeListener</code> from this tabbedpane.
144    *
145    * @param l the <code>ChangeListener</code> to remove
146    * @see #fireStateChanged
147    * @see #addChangeListener
148    */
149   public void removeChangeListener(ChangeListener l) {
150       listenerList.remove(ChangeListener.class, l);
151   }
152
153   public void setSelectedComponent(JPanel structureTab)
154   {
155     // TODO Auto-generated method stub
156
157   }
158
159   /**
160    * Returns an array of all the <code>ChangeListener</code>s added
161    * to this <code>JTabbedPane</code> with <code>addChangeListener</code>.
162    *
163    * @return all of the <code>ChangeListener</code>s added or an empty
164    *         array if no listeners have been added
165    * @since 1.4
166    */
167   public ChangeListener[] getChangeListeners() {
168       return (ChangeListener[])listenerList.getListeners(
169               ChangeListener.class);
170   }
171
172   /**
173    * Sends a {@code ChangeEvent}, with this {@code JTabbedPane} as the source,
174    * to each registered listener. This method is called each time there is
175    * a change to either the selected index or the selected tab in the
176    * {@code JTabbedPane}. Usually, the selected index and selected tab change
177    * together. However, there are some cases, such as tab addition, where the
178    * selected index changes and the same tab remains selected. There are other
179    * cases, such as deleting the selected tab, where the index remains the
180    * same, but a new tab moves to that index. Events are fired for all of
181    * these cases.
182    *
183    * @see #addChangeListener
184    * @see EventListenerList
185    */
186   protected void fireStateChanged() {
187       /* --- Begin code to deal with visibility --- */
188
189       /* This code deals with changing the visibility of components to
190        * hide and show the contents for the selected tab. It duplicates
191        * logic already present in BasicTabbedPaneUI, logic that is
192        * processed during the layout pass. This code exists to allow
193        * developers to do things that are quite difficult to accomplish
194        * with the previous model of waiting for the layout pass to process
195        * visibility changes; such as requesting focus on the new visible
196        * component.
197        *
198        * For the average code, using the typical JTabbedPane methods,
199        * all visibility changes will now be processed here. However,
200        * the code in BasicTabbedPaneUI still exists, for the purposes
201        * of backward compatibility. Therefore, when making changes to
202        * this code, ensure that the BasicTabbedPaneUI code is kept in
203        * synch.
204        */
205
206       int selIndex = getSelectedIndex();
207
208       /* if the selection is now nothing */
209       if (selIndex < 0) {
210           /* if there was a previous visible component */
211           if (visComp != null && visComp.isVisible()) {
212               /* make it invisible */
213               visComp.setVisible(false);
214           }
215
216           /* now there's no visible component */
217           visComp = null;
218
219       /* else - the selection is now something */
220       } else {
221           /* Fetch the component for the new selection */
222           Component newComp = getComponentAt(selIndex);
223
224           /* if the new component is non-null and different */
225           if (newComp != null && newComp != visComp) {
226 //SwingJS X: Key Focus
227 //              boolean shouldChangeFocus = false;
228
229               /* Note: the following (clearing of the old visible component)
230                * is inside this if-statement for good reason: Tabbed pane
231                * should continue to show the previously visible component
232                * if there is no component for the chosen tab.
233                */
234
235               /* if there was a previous visible component */
236               if (visComp != null) {
237 //SwingJS X: Key Focus
238 //                  shouldChangeFocus =
239 //                      (SwingUtilities.findFocusOwner(visComp) != null);
240
241                   /* if it's still visible */
242                   if (visComp.isVisible()) {
243                       /* make it invisible */
244                       visComp.setVisible(false);
245                   }
246               }
247
248               if (!newComp.isVisible()) {
249                   newComp.setVisible(true);
250               }
251
252 //SwingJS X: Key Focus
253 //              if (shouldChangeFocus) {
254 //                  SwingUtilities2.tabbedPaneChangeFocusTo(newComp);
255 //              }
256
257               visComp = newComp;
258           } /* else - the visible component shouldn't changed */
259       }
260
261       /* --- End code to deal with visibility --- */
262
263       // Guaranteed to return a non-null array
264       Object[] listeners = listenerList.getListenerList();
265       // Process the listeners last to first, notifying
266       // those that are interested in this event
267       for (int i = listeners.length-2; i>=0; i-=2) {
268           if (listeners[i]==ChangeListener.class) {
269               // Lazily create the event:
270               if (changeEvent == null)
271                   changeEvent = new ChangeEvent(this);
272               ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
273           }
274       }
275   }
276
277   private class Page
278   {
279     public String title;
280
281     String tab;
282
283     JComponent component;
284
285     public void updateDisplayedMnemonicIndex()
286     {
287       // TODO Auto-generated method stub
288       
289     }
290   }
291
292   public Component getSelectedComponent()
293   {
294     int i = getSelectedIndex();
295     return (i < 0 ? null : getComponent(i));
296   }
297
298   
299   /**
300    * Returns the component at <code>index</code>.
301    *
302    * @param index  the index of the item being queried
303    * @return the <code>Component</code> at <code>index</code>
304    * @exception IndexOutOfBoundsException if index is out of range
305    *            (index < 0 || index >= tab count)
306    *
307    * @see #setComponentAt
308    */
309   public Component getComponentAt(int index) {
310       return pages.get(index).component;
311   }
312   
313   Lst<Page> pages = new Lst<Page>();
314
315   public Component add(String title, Component panel)
316   {
317     add(panel, title, pages.size());
318     return panel;
319   }
320
321   public void addTab(String name, Component panel)
322   {
323     add(panel, name, Integer.MAX_VALUE);
324   }
325   
326   public void add(Component panel, String title, int index)
327   {
328     addIndex((JComponent) panel, title, index);
329   }
330
331
332   private void addIndex(JComponent panel, String title, int index)
333   {
334     // improperly allowing for multiple instances of a panel
335     Page page = new Page();
336     page.component = panel;
337     page.tab = title;
338     panel.setVisible(true);
339     if (index < pages.size())
340     {
341       pages.get(index).component = panel;
342     }
343     else
344     {
345       pages.addLast(page);
346     }
347     update(true);
348  }
349
350   public void insertTab(String title, Icon icon, Component component,
351           String tip, int index)
352   {
353     // int newIndex = index;
354
355     addIndex((JComponent) component, title, index);
356     //
357     // // If component already exists, remove corresponding
358     // // tab so that new tab gets added correctly
359     // // Note: we are allowing component=null because of compatibility,
360     // // but we really should throw an exception because much of the
361     // // rest of the JTabbedPane implementation isn't designed to deal
362     // // with null components for tabs.
363     // int removeIndex = indexOfComponent(component);
364     // if (component != null && removeIndex != -1) {
365     // removeTabAt(removeIndex);
366     // if (newIndex > removeIndex) {
367     // newIndex--;
368     // }
369     // }
370     //
371     // int selectedIndex = getSelectedIndex();
372     //
373     // pages.add(
374     // newIndex,
375     // new Page(this, title != null? title : "", icon, null, component, tip));
376     //
377     //
378     // if (component != null) {
379     // addImpl(component, null, -1);
380     // component.setVisible(false);
381     // } else {
382     // firePropertyChange("indexForNullComponent", -1, index);
383     // }
384     //
385     // if (pages.size() == 1) {
386     // setSelectedIndex(0);
387     // }
388     //
389     // if (selectedIndex >= newIndex) {
390     // setSelectedIndexImpl(selectedIndex + 1, false);
391     // }
392     //
393     // if (!haveRegistered && tip != null) {
394     // ToolTipManager.sharedInstance().registerComponent(this);
395     // haveRegistered = true;
396     // }
397
398     // if (accessibleContext != null) {
399     // accessibleContext.firePropertyChange(
400     // AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
401     // null, component);
402     // }
403   }
404
405   private void update(boolean combo)
406   {
407     if (combo)
408     {
409       int i0 = tabs.getSelectedIndex();
410       tabs.removeAllItems();
411       for (int i = 0; i < pages.size(); i++)
412       {
413         tabs.addItem(pages.get(i).tab);
414       }
415       tabs.setSelectedIndex(i0 < 0 ? 0 : i0);
416     }
417     pagePanel.removeAll();
418     int selected = getSelectedIndex();
419     if (selected >= 0)
420     {// && bsEnabled.get(selected)) {
421       pagePanel.add(pages.get(selected).component);
422     }
423     revalidate();
424     repaint();
425   }
426
427   public void setComponentAt(int index, JPanel panel)
428   {
429     if (index < 0 || index >= pages.size())
430       throw new IndexOutOfBoundsException();
431     pages.get(index).component = panel;
432     update(false);
433   }
434
435   public int getTabCount()
436   {
437     return tabs.getItemCount();
438   }
439
440   public void setSelectedIndex(int index)
441   {
442     tabs.setSelectedIndex(index);
443     update(false);
444   }
445
446   public int getSelectedIndex()
447   {
448     return tabs.getSelectedIndex();
449   }
450
451   public void setEnabledAt(int index, boolean bEnable)
452   {
453     // bsEnabled.setBitTo(index, bEnable);
454     // if (!bEnable && index == getSelectedIndex()) {
455     // for (int i = index - 1; --i >= 0;) {
456     // if (bsEnabled.get(i)) {
457     // setSelectedIndex(i);
458     // return;
459     // }
460     // }
461     // }
462     // update(false, true);
463   }
464
465   public void remove(int n)
466   {
467     pages.removeItemAt(n);
468     tabs.removeItemAt(n);
469     update(false);
470     // bsEnabled.clearAll();
471     // bsEnabled.set(lastSelected);
472   }
473
474 }