2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.api.AlignViewControllerGuiI;
24 import jalview.api.FeatureSettingsControllerI;
25 import jalview.api.SplitContainerI;
26 import jalview.controller.FeatureSettingsControllerGuiI;
27 import jalview.datamodel.AlignmentI;
28 import jalview.jbgui.GAlignFrame;
29 import jalview.jbgui.GSplitFrame;
30 import jalview.structure.StructureSelectionManager;
31 import jalview.util.MessageManager;
32 import jalview.util.Platform;
33 import jalview.viewmodel.AlignmentViewport;
35 import java.awt.Component;
36 import java.awt.Dimension;
37 import java.awt.event.ActionEvent;
38 import java.awt.event.ActionListener;
39 import java.awt.event.FocusEvent;
40 import java.awt.event.FocusListener;
41 import java.awt.event.KeyAdapter;
42 import java.awt.event.KeyEvent;
43 import java.awt.event.KeyListener;
44 import java.beans.PropertyVetoException;
45 import java.util.Arrays;
46 import java.util.List;
47 import java.util.Map.Entry;
49 import javax.swing.AbstractAction;
50 import javax.swing.InputMap;
51 import javax.swing.JComponent;
52 import javax.swing.JDesktopPane;
53 import javax.swing.JInternalFrame;
54 import javax.swing.JLayeredPane;
55 import javax.swing.JMenuItem;
56 import javax.swing.JPanel;
57 import javax.swing.JTabbedPane;
58 import javax.swing.KeyStroke;
59 import javax.swing.event.InternalFrameAdapter;
60 import javax.swing.event.InternalFrameEvent;
63 * An internal frame on the desktop that hosts a horizontally split view of
64 * linked DNA and Protein alignments. Additional views can be created in linked
65 * pairs, expanded to separate split frames, or regathered into a single frame.
67 * (Some) operations on each alignment are automatically mirrored on the other.
68 * These include mouseover (highlighting), sequence and column selection,
69 * sequence ordering and sorting, and grouping, colouring and sorting by tree.
74 public class SplitFrame extends GSplitFrame implements SplitContainerI
76 private static final int WINDOWS_INSETS_WIDTH = 28; // tbc
78 private static final int MAC_INSETS_WIDTH = 28;
80 private static final int WINDOWS_INSETS_HEIGHT = 50; // tbc
82 private static final int MAC_INSETS_HEIGHT = 50;
84 private static final int DESKTOP_DECORATORS_HEIGHT = 65;
86 private static final long serialVersionUID = 1L;
89 * geometry for Feature Settings Holder
91 private static final int FS_MIN_WIDTH = 400;
93 private static final int FS_MIN_HEIGHT = 400;
95 public SplitFrame(GAlignFrame top, GAlignFrame bottom)
102 * Initialise this frame.
104 protected void init()
106 getTopFrame().setSplitFrame(this);
107 getBottomFrame().setSplitFrame(this);
108 getTopFrame().setVisible(true);
109 getBottomFrame().setVisible(true);
111 ((AlignFrame) getTopFrame()).getViewport().setCodingComplement(
112 ((AlignFrame) getBottomFrame()).getViewport());
115 * estimate width and height of SplitFrame; this.getInsets() doesn't seem to
116 * give the full additional size (a few pixels short)
118 int widthFudge = Platform.isAMac() ? MAC_INSETS_WIDTH
119 : WINDOWS_INSETS_WIDTH;
120 int heightFudge = Platform.isAMac() ? MAC_INSETS_HEIGHT
121 : WINDOWS_INSETS_HEIGHT;
122 int width = ((AlignFrame) getTopFrame()).getWidth() + widthFudge;
123 int height = ((AlignFrame) getTopFrame()).getHeight()
124 + ((AlignFrame) getBottomFrame()).getHeight() + DIVIDER_SIZE
126 height = fitHeightToDesktop(height);
127 setSize(width, height);
131 addCloseFrameListener();
137 addCommandListeners();
141 * Reduce the height if too large to fit in the Desktop. Also adjust the
142 * divider location in proportion.
146 * @return original or reduced height
148 public int fitHeightToDesktop(int height)
150 // allow about 65 pixels for Desktop decorators on Windows
152 int newHeight = Math.min(height,
153 Desktop.instance.getHeight() - DESKTOP_DECORATORS_HEIGHT);
154 if (newHeight != height)
156 int oldDividerLocation = getDividerLocation();
157 setDividerLocation(oldDividerLocation * newHeight / height);
163 * Set the top and bottom frames to listen to each others Commands (e.g. Edit,
166 protected void addCommandListeners()
168 // TODO if CommandListener is only ever 1:1 for complementary views,
169 // may change broadcast pattern to direct messaging (more efficient)
170 final StructureSelectionManager ssm = StructureSelectionManager
171 .getStructureSelectionManager(Desktop.instance);
172 ssm.addCommandListener(((AlignFrame) getTopFrame()).getViewport());
173 ssm.addCommandListener(((AlignFrame) getBottomFrame()).getViewport());
177 * Do any tweaking and twerking of the layout wanted.
179 public void adjustLayout()
182 * Ensure sequence ids are the same width so sequences line up
184 int w1 = ((AlignFrame) getTopFrame()).getViewport().getIdWidth();
185 int w2 = ((AlignFrame) getBottomFrame()).getViewport().getIdWidth();
186 int w3 = Math.max(w1, w2);
189 ((AlignFrame) getTopFrame()).getViewport().setIdWidth(w3);
193 ((AlignFrame) getBottomFrame()).getViewport().setIdWidth(w3);
197 * Scale protein to either 1 or 3 times character width of dna
199 final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
200 final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
201 final AlignmentI topAlignment = topViewport.getAlignment();
202 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
203 AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
204 : (bottomAlignment.isNucleotide() ? bottomViewport : null);
205 AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
206 : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
207 if (protein != null && cdna != null)
209 int scale = protein.isScaleProteinAsCdna() ? 3 : 1;
210 protein.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
215 * Adjusts the divider for a sensible split of the real estate (for example,
216 * when many transcripts are shown with a single protein). This should only be
217 * called after the split pane has been laid out (made visible) so it has a
218 * height. The aim is to avoid unnecessary vertical scroll bars, while
219 * ensuring that at least 2 sequences are visible in each panel.
221 * Once laid out, the user may choose to customise as they wish, so this
222 * method is not called again after the initial layout.
224 protected void adjustInitialLayout()
226 AlignFrame topFrame = (AlignFrame) getTopFrame();
227 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
230 * recompute layout of top and bottom panels to reflect their
231 * actual (rather than requested) height
233 topFrame.alignPanel.adjustAnnotationHeight();
234 bottomFrame.alignPanel.adjustAnnotationHeight();
236 final AlignViewport topViewport = topFrame.viewport;
237 final AlignViewport bottomViewport = bottomFrame.viewport;
238 final AlignmentI topAlignment = topViewport.getAlignment();
239 final AlignmentI bottomAlignment = bottomViewport.getAlignment();
240 boolean topAnnotations = topViewport.isShowAnnotation();
241 boolean bottomAnnotations = bottomViewport.isShowAnnotation();
242 // TODO need number of visible sequences here, not #sequences - how?
243 int topCount = topAlignment.getHeight();
244 int bottomCount = bottomAlignment.getHeight();
245 int topCharHeight = topViewport.getViewStyle().getCharHeight();
246 int bottomCharHeight = bottomViewport.getViewStyle().getCharHeight();
249 * calculate the minimum ratio that leaves at least the height
250 * of two sequences (after rounding) visible in the top panel
252 int topPanelHeight = topFrame.getHeight();
253 int bottomPanelHeight = bottomFrame.getHeight();
254 int topSequencesHeight = topFrame.alignPanel.getSeqPanel().seqCanvas
256 int topPanelMinHeight = topPanelHeight
257 - Math.max(0, topSequencesHeight - 3 * topCharHeight);
258 double totalHeight = (double) topPanelHeight + bottomPanelHeight;
259 double minRatio = topPanelMinHeight / totalHeight;
262 * calculate the maximum ratio that leaves at least the height
263 * of two sequences (after rounding) visible in the bottom panel
265 int bottomSequencesHeight = bottomFrame.alignPanel.getSeqPanel().seqCanvas
267 int bottomPanelMinHeight = bottomPanelHeight
268 - Math.max(0, bottomSequencesHeight - 3 * bottomCharHeight);
269 double maxRatio = (totalHeight - bottomPanelMinHeight) / totalHeight;
272 * estimate ratio of (topFrameContent / bottomFrameContent)
274 int insets = Platform.isAMac() ? MAC_INSETS_HEIGHT
275 : WINDOWS_INSETS_HEIGHT;
276 // allow 3 'rows' for scale, scrollbar, status bar
277 int topHeight = insets + (3 + topCount) * topCharHeight
278 + (topAnnotations ? topViewport.calcPanelHeight() : 0);
279 int bottomHeight = insets + (3 + bottomCount) * bottomCharHeight
280 + (bottomAnnotations ? bottomViewport.calcPanelHeight() : 0);
281 double ratio = ((double) topHeight)
282 / (double) (topHeight + bottomHeight);
285 * limit ratio to avoid concealing all sequences
287 ratio = Math.min(ratio, maxRatio);
288 ratio = Math.max(ratio, minRatio);
289 setRelativeDividerLocation(ratio);
293 * Add a listener to tidy up when the frame is closed.
295 protected void addCloseFrameListener()
297 addInternalFrameListener(new InternalFrameAdapter()
300 public void internalFrameClosed(InternalFrameEvent evt)
308 * Add a key listener that delegates to whichever split component the mouse is
309 * in (or does nothing if neither).
311 protected void addKeyListener()
313 addKeyListener(new KeyAdapter()
317 public void keyPressed(KeyEvent e)
319 AlignFrame af = (AlignFrame) getFrameAtMouse();
322 * Intercept and override any keys here if wanted.
324 if (!overrideKey(e, af))
328 for (KeyListener kl : af.getKeyListeners())
337 public void keyReleased(KeyEvent e)
339 Component c = getFrameAtMouse();
342 for (KeyListener kl : c.getKeyListeners())
353 * Returns true if the key event is overriden and actioned (or ignored) here,
354 * else returns false, indicating it should be delegated to the AlignFrame's
357 * We can't handle Cmd-Key combinations here, instead this is done by
358 * overriding key bindings.
360 * @see addKeyOverrides
365 protected boolean overrideKey(KeyEvent e, AlignFrame af)
367 boolean actioned = false;
368 int keyCode = e.getKeyCode();
371 case KeyEvent.VK_DOWN:
372 if (e.isAltDown() || !af.viewport.cursorMode)
375 * Key down (or Alt-key-down in cursor mode) - move selected sequences
377 ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
378 ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
384 if (e.isAltDown() || !af.viewport.cursorMode)
387 * Key up (or Alt-key-up in cursor mode) - move selected sequences
389 ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
390 ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
401 * Set key bindings (recommended for Swing over key accelerators).
403 private void addKeyBindings()
405 overrideDelegatedKeyBindings();
407 overrideImplementedKeyBindings();
411 * Override key bindings with alternative action methods implemented in this
414 protected void overrideImplementedKeyBindings()
419 overrideExpandViews();
420 overrideGatherViews();
424 * Replace Cmd-W close view action with our version.
426 protected void overrideCloseView()
428 AbstractAction action;
430 * Ctrl-W / Cmd-W - close view or window
432 KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W,
433 jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
434 action = new AbstractAction()
437 public void actionPerformed(ActionEvent e)
439 closeView_actionPerformed();
442 overrideKeyBinding(key_cmdW, action);
446 * Replace Cmd-T new view action with our version.
448 protected void overrideNewView()
451 * Ctrl-T / Cmd-T open new view
453 KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T,
454 jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
455 AbstractAction action = new AbstractAction()
458 public void actionPerformed(ActionEvent e)
460 newView_actionPerformed();
463 overrideKeyBinding(key_cmdT, action);
467 * For now, delegates key events to the corresponding key accelerator for the
468 * AlignFrame that the mouse is in. Hopefully can be simplified in future if
469 * AlignFrame is changed to use key bindings rather than accelerators.
471 protected void overrideDelegatedKeyBindings()
473 if (getTopFrame() instanceof AlignFrame)
476 * Get all accelerator keys in the top frame (the bottom should be
477 * identical) and override each one.
479 for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
480 .getAccelerators().entrySet())
482 overrideKeyBinding(acc);
488 * Overrides an AlignFrame key accelerator with our version which delegates to
489 * the action listener in whichever frame has the mouse (and does nothing if
494 private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
496 final KeyStroke ks = acc.getKey();
497 InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
498 inputMap.put(ks, ks);
499 this.getActionMap().put(ks, new AbstractAction()
502 public void actionPerformed(ActionEvent e)
504 Component c = getFrameAtMouse();
505 if (c != null && c instanceof AlignFrame)
507 for (ActionListener a : ((AlignFrame) c).getAccelerators().get(ks)
508 .getActionListeners())
510 a.actionPerformed(null);
518 * Replace an accelerator key's action with the specified action.
522 protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
524 this.getActionMap().put(ks, action);
525 overrideMenuItem(ks, action);
529 * Create and link new views (with matching names) in both panes.
531 * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
532 * is a single split pane with each split holding multiple tabs which are
535 * TODO implement instead with a tabbed holder in the SplitView, each tab
536 * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
537 * some additional coding.
539 protected void newView_actionPerformed()
541 AlignFrame topFrame = (AlignFrame) getTopFrame();
542 AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
543 final boolean scaleProteinAsCdna = topFrame.viewport
544 .isScaleProteinAsCdna();
546 AlignmentPanel newTopPanel = topFrame.newView(null, true);
547 AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
550 * This currently (for the first new view only) leaves the top pane on tab 0
551 * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
552 * from the bottom back to the first frame. Next line is a fudge to work
553 * around this. TODO find a better way.
555 if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
557 topFrame.setDisplayedView(newTopPanel);
560 newBottomPanel.av.setViewName(newTopPanel.av.getViewName());
561 newTopPanel.av.setCodingComplement(newBottomPanel.av);
564 * These lines can be removed once scaleProteinAsCdna is added to element
565 * Viewport in jalview.xsd, as Jalview2XML.copyAlignPanel will then take
568 newTopPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
569 newBottomPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
572 * Line up id labels etc
576 final StructureSelectionManager ssm = StructureSelectionManager
577 .getStructureSelectionManager(Desktop.instance);
578 ssm.addCommandListener(newTopPanel.av);
579 ssm.addCommandListener(newBottomPanel.av);
583 * Close the currently selected view in both panes. If there is only one view,
584 * close this split frame.
586 protected void closeView_actionPerformed()
588 int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
595 AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
596 AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
598 ((AlignFrame) getTopFrame()).closeView(topPanel);
599 ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
604 * Close child frames and this split frame.
608 ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
609 ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
612 this.setClosed(true);
613 } catch (PropertyVetoException e)
620 * Replace AlignFrame 'expand views' action with SplitFrame version.
622 protected void overrideExpandViews()
624 KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
625 AbstractAction action = new AbstractAction()
628 public void actionPerformed(ActionEvent e)
630 expandViews_actionPerformed();
633 overrideMenuItem(key_X, action);
637 * Replace AlignFrame 'gather views' action with SplitFrame version.
639 protected void overrideGatherViews()
641 KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
642 AbstractAction action = new AbstractAction()
645 public void actionPerformed(ActionEvent e)
647 gatherViews_actionPerformed();
650 overrideMenuItem(key_G, action);
654 * Override the menu action associated with the keystroke in the child frames,
655 * replacing it with the given action.
660 private void overrideMenuItem(KeyStroke ks, AbstractAction action)
662 overrideMenuItem(ks, action, getTopFrame());
663 overrideMenuItem(ks, action, getBottomFrame());
667 * Override the menu action associated with the keystroke in one child frame,
668 * replacing it with the given action. Mwahahahaha.
674 private void overrideMenuItem(KeyStroke key, final AbstractAction action,
677 if (comp instanceof AlignFrame)
679 JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
682 for (ActionListener al : mi.getActionListeners())
684 mi.removeActionListener(al);
686 mi.addActionListener(new ActionListener()
689 public void actionPerformed(ActionEvent e)
691 action.actionPerformed(e);
699 * Expand any multiple views (which are always in pairs) into separate split
702 protected void expandViews_actionPerformed()
704 Desktop.instance.explodeViews(this);
708 * Gather any other SplitFrame views of this alignment back in as multiple
709 * (pairs of) views in this SplitFrame.
711 protected void gatherViews_actionPerformed()
713 Desktop.instance.gatherViews(this);
717 * Returns the alignment in the complementary frame to the one given.
720 public AlignmentI getComplement(Object alignFrame)
722 if (alignFrame == this.getTopFrame())
724 return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
726 else if (alignFrame == this.getBottomFrame())
728 return ((AlignFrame) getTopFrame()).viewport.getAlignment();
734 * Returns the title of the complementary frame to the one given.
737 public String getComplementTitle(Object alignFrame)
739 if (alignFrame == this.getTopFrame())
741 return ((AlignFrame) getBottomFrame()).getTitle();
743 else if (alignFrame == this.getBottomFrame())
745 return ((AlignFrame) getTopFrame()).getTitle();
751 * Set the 'other half' to hidden / revealed.
754 public void setComplementVisible(Object alignFrame, boolean show)
757 * Hiding the AlignPanel suppresses unnecessary repaints
759 if (alignFrame == getTopFrame())
761 ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
763 else if (alignFrame == getBottomFrame())
765 ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
767 super.setComplementVisible(alignFrame, show);
771 * return the AlignFrames held by this container
773 * @return { Top alignFrame (Usually CDS), Bottom AlignFrame (Usually
776 public List<AlignFrame> getAlignFrames()
779 .asList(new AlignFrame[]
780 { (AlignFrame) getTopFrame(), (AlignFrame) getBottomFrame() });
784 public AlignFrame getComplementAlignFrame(
785 AlignViewControllerGuiI alignFrame)
787 if (getTopFrame() == alignFrame)
789 return (AlignFrame) getBottomFrame();
791 if (getBottomFrame() == alignFrame)
793 return (AlignFrame) getTopFrame();
795 // we didn't know anything about this frame...
800 * Replace Cmd-F Find action with our version. This is necessary because the
801 * 'default' Finder searches in the first AlignFrame it finds. We need it to
802 * search in the half of the SplitFrame that has the mouse.
804 protected void overrideFind()
807 * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
809 KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F,
810 jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx(), false);
811 AbstractAction action = new AbstractAction()
814 public void actionPerformed(ActionEvent e)
816 Component c = getFrameAtMouse();
817 if (c != null && c instanceof AlignFrame)
819 AlignFrame af = (AlignFrame) c;
820 new Finder(af.viewport, af.alignPanel);
824 overrideKeyBinding(key_cmdF, action);
828 * Override to do nothing if triggered from one of the child frames
831 public void setSelected(boolean selected) throws PropertyVetoException
833 JDesktopPane desktopPane = getDesktopPane();
834 JInternalFrame fr = desktopPane == null ? null
835 : desktopPane.getSelectedFrame();
836 if (fr == getTopFrame() || fr == getBottomFrame())
839 * patch for JAL-3288 (deselecting top/bottom frame closes popup menu);
840 * it may be possible to remove this method in future
841 * if the underlying Java behaviour changes
849 super.setSelected(selected);
853 * holds the frame for feature settings, so Protein and DNA tabs can be managed
855 JInternalFrame featureSettingsUI;
857 JTabbedPane featureSettingsPanels;
860 public void addFeatureSettingsUI(
861 FeatureSettingsControllerGuiI featureSettings)
863 boolean showInternalFrame = false;
864 if (featureSettingsUI == null || featureSettingsPanels == null)
866 showInternalFrame = true;
867 featureSettingsPanels = new JTabbedPane();
868 featureSettingsUI = new JInternalFrame(
869 "Feature Settings for CDS and Protein Views");
870 featureSettingsPanels.setOpaque(true);
871 featureSettingsUI.setContentPane(featureSettingsPanels);
874 if (featureSettingsPanels
875 .indexOfTabComponent((Component) featureSettings) > -1)
877 // just show the feature settings !
878 featureSettingsPanels
879 .setSelectedComponent((Component) featureSettings);
882 // otherwise replace the dummy tab with the given feature settings
883 int pos = getAlignFrames().indexOf(featureSettings.getAlignframe());
884 // if pos==-1 then alignFrame isn't managed by this splitframe
887 featureSettingsPanels.removeTabAt(0);
888 featureSettingsPanels.insertTab("CDS", null,
889 (Component) featureSettings, "Feature Settings for DNA CDS",
894 featureSettingsPanels.removeTabAt(1);
895 featureSettingsPanels.insertTab("Protein", null,
896 (Component) featureSettings, "Feature Settings for Protein",
899 featureSettingsPanels.setSelectedComponent((Component) featureSettings);
900 if (showInternalFrame)
902 if (Platform.isAMac())
904 Desktop.addInternalFrame(featureSettingsUI,
905 MessageManager.getString(
906 "Feature Settings for CDS and Protein Views"),
911 Desktop.addInternalFrame(featureSettingsUI,
912 MessageManager.getString(
913 "Feature Settings for CDS and Protein Views"),
917 .setMinimumSize(new Dimension(FS_MIN_WIDTH, FS_MIN_HEIGHT));
919 featureSettingsUI.addInternalFrameListener(
920 new javax.swing.event.InternalFrameAdapter()
923 public void internalFrameClosed(
924 javax.swing.event.InternalFrameEvent evt)
926 for (int tab = 0; tab < featureSettingsPanels
929 FeatureSettingsControllerGuiI fsettings = (FeatureSettingsControllerGuiI) featureSettingsPanels
930 .getTabComponentAt(tab);
931 if (fsettings != null)
933 featureSettingsPanels.removeTabAt(tab);
934 fsettings.featureSettings_isClosed();
941 featureSettingsPanels = null;
944 featureSettingsUI.setLayer(JLayeredPane.PALETTE_LAYER);
949 * for materialising feature settings for a tab when clicked on
951 private FocusListener fl1 = new FocusListener()
955 public void focusLost(FocusEvent e)
957 // TODO Auto-generated method stub
962 public void focusGained(FocusEvent e)
964 int tab = featureSettingsPanels.getSelectedIndex();
965 getAlignFrames().get(tab).showFeatureSettingsUI();
970 * tab names for feature settings
972 private String[] tabName = new String[] {
973 MessageManager.getString("label.CDS"),
974 MessageManager.getString("label.Protein") };
977 * create placeholder tabs which materialise the feature settings for a given
978 * view. Also reinitialises any tabs containing stale feature settings
980 private void createDummyTabs()
982 for (int tabIndex = 0; tabIndex < 2; tabIndex++)
984 JPanel dummyTab = new JPanel();
985 dummyTab.addFocusListener(fl1);
986 featureSettingsPanels.addTab(tabName[tabIndex], dummyTab);
990 private void replaceWithDummyTab(FeatureSettingsControllerI toClose)
992 Component dummyTab = null;
993 for (int tabIndex = 0; tabIndex < 2; tabIndex++)
995 if (featureSettingsPanels.getTabCount() > tabIndex)
997 dummyTab = featureSettingsPanels.getTabComponentAt(tabIndex);
998 if (dummyTab instanceof FeatureSettingsControllerGuiI
999 && !dummyTab.isVisible())
1001 featureSettingsPanels.removeTabAt(tabIndex);
1002 // close the feature Settings tab
1003 ((FeatureSettingsControllerGuiI) dummyTab)
1004 .featureSettings_isClosed();
1005 // create a dummy tab in its place
1006 dummyTab = new JPanel();
1007 dummyTab.addFocusListener(fl1);
1008 featureSettingsPanels.insertTab(tabName[tabIndex], null, dummyTab,
1009 MessageManager.formatMessage(
1010 "label.sequence_feature_settings_for",
1019 public void closeFeatureSettings(
1020 FeatureSettingsControllerI featureSettings,
1021 boolean closeContainingFrame)
1023 if (featureSettingsUI != null)
1025 if (closeContainingFrame)
1029 featureSettingsUI.setClosed(true);
1030 } catch (Exception x)
1033 featureSettingsUI = null;
1037 replaceWithDummyTab(featureSettings);
1043 public boolean isFeatureSettingsOpen()
1045 return featureSettingsUI != null && !featureSettingsUI.isClosed();