JAL-2089 fix broken help link
[jalview.git] / src / jalview / gui / SplitFrame.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.gui;
22
23 import jalview.api.SplitContainerI;
24 import jalview.api.ViewStyleI;
25 import jalview.datamodel.AlignmentI;
26 import jalview.jbgui.GAlignFrame;
27 import jalview.jbgui.GSplitFrame;
28 import jalview.structure.StructureSelectionManager;
29 import jalview.util.Platform;
30 import jalview.viewmodel.AlignmentViewport;
31
32 import java.awt.Component;
33 import java.awt.Toolkit;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.awt.event.KeyAdapter;
37 import java.awt.event.KeyEvent;
38 import java.awt.event.KeyListener;
39 import java.beans.PropertyVetoException;
40 import java.util.Arrays;
41 import java.util.List;
42 import java.util.Map.Entry;
43
44 import javax.swing.AbstractAction;
45 import javax.swing.InputMap;
46 import javax.swing.JComponent;
47 import javax.swing.JMenuItem;
48 import javax.swing.KeyStroke;
49 import javax.swing.event.InternalFrameAdapter;
50 import javax.swing.event.InternalFrameEvent;
51
52 /**
53  * An internal frame on the desktop that hosts a horizontally split view of
54  * linked DNA and Protein alignments. Additional views can be created in linked
55  * pairs, expanded to separate split frames, or regathered into a single frame.
56  * <p>
57  * (Some) operations on each alignment are automatically mirrored on the other.
58  * These include mouseover (highlighting), sequence and column selection,
59  * sequence ordering and sorting, and grouping, colouring and sorting by tree.
60  * 
61  * @author gmcarstairs
62  *
63  */
64 public class SplitFrame extends GSplitFrame implements SplitContainerI
65 {
66   private static final int WINDOWS_INSETS_WIDTH = 28; // tbc
67
68   private static final int MAC_INSETS_WIDTH = 28;
69
70   private static final int WINDOWS_INSETS_HEIGHT = 50; // tbc
71
72   private static final int MAC_INSETS_HEIGHT = 50;
73   private static final int DESKTOP_DECORATORS_HEIGHT = 65;
74   private static final long serialVersionUID = 1L;
75
76   public SplitFrame(GAlignFrame top, GAlignFrame bottom)
77   {
78     super(top, bottom);
79     init();
80   }
81
82   /**
83    * Initialise this frame.
84    */
85   protected void init()
86   {
87     getTopFrame().setSplitFrame(this);
88     getBottomFrame().setSplitFrame(this);
89     getTopFrame().setVisible(true);
90     getBottomFrame().setVisible(true);
91
92     ((AlignFrame) getTopFrame()).getViewport().setCodingComplement(
93             ((AlignFrame) getBottomFrame()).getViewport());
94
95     /*
96      * estimate width and height of SplitFrame; this.getInsets() doesn't seem to
97      * give the full additional size (a few pixels short)
98      */
99     int widthFudge = Platform.isAMac() ? MAC_INSETS_WIDTH
100             : WINDOWS_INSETS_WIDTH;
101     int heightFudge = Platform.isAMac() ? MAC_INSETS_HEIGHT
102             : WINDOWS_INSETS_HEIGHT;
103     int width = ((AlignFrame) getTopFrame()).getWidth() + widthFudge;
104     int height = ((AlignFrame) getTopFrame()).getHeight()
105             + ((AlignFrame) getBottomFrame()).getHeight() + DIVIDER_SIZE
106             + heightFudge;
107     height = fitHeightToDesktop(height);
108     setSize(width, height);
109
110     adjustLayout();
111
112     addCloseFrameListener();
113
114     addKeyListener();
115
116     addKeyBindings();
117
118     addCommandListeners();
119   }
120
121   /**
122    * Reduce the height if too large to fit in the Desktop. Also adjust the
123    * divider location in proportion.
124    * 
125    * @param height
126    *          in pixels
127    * @return original or reduced height
128    */
129   public int fitHeightToDesktop(int height)
130   {
131     // allow about 65 pixels for Desktop decorators on Windows
132
133     int newHeight = Math.min(height, Desktop.instance.getHeight()
134             - DESKTOP_DECORATORS_HEIGHT);
135     if (newHeight != height)
136     {
137       int oldDividerLocation = getDividerLocation();
138       setDividerLocation(oldDividerLocation * newHeight / height);
139     }
140     return newHeight;
141   }
142
143   /**
144    * Set the top and bottom frames to listen to each others Commands (e.g. Edit,
145    * Order).
146    */
147   protected void addCommandListeners()
148   {
149     // TODO if CommandListener is only ever 1:1 for complementary views,
150     // may change broadcast pattern to direct messaging (more efficient)
151     final StructureSelectionManager ssm = StructureSelectionManager
152             .getStructureSelectionManager(Desktop.instance);
153     ssm.addCommandListener(((AlignFrame) getTopFrame()).getViewport());
154     ssm.addCommandListener(((AlignFrame) getBottomFrame()).getViewport());
155   }
156
157   /**
158    * Do any tweaking and twerking of the layout wanted.
159    */
160   public void adjustLayout()
161   {
162     /*
163      * Ensure sequence ids are the same width so sequences line up
164      */
165     int w1 = ((AlignFrame) getTopFrame()).getViewport().getIdWidth();
166     int w2 = ((AlignFrame) getBottomFrame()).getViewport().getIdWidth();
167     int w3 = Math.max(w1, w2);
168     if (w1 != w3)
169     {
170       ((AlignFrame) getTopFrame()).getViewport().setIdWidth(w3);
171     }
172     if (w2 != w3)
173     {
174       ((AlignFrame) getBottomFrame()).getViewport().setIdWidth(w3);
175     }
176
177     /*
178      * Scale protein to either 1 or 3 times character width of dna
179      */
180     final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
181     final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
182     final AlignmentI topAlignment = topViewport.getAlignment();
183     final AlignmentI bottomAlignment = bottomViewport.getAlignment();
184     AlignmentViewport cdna = topAlignment.isNucleotide() ? topViewport
185             : (bottomAlignment.isNucleotide() ? bottomViewport : null);
186     AlignmentViewport protein = !topAlignment.isNucleotide() ? topViewport
187             : (!bottomAlignment.isNucleotide() ? bottomViewport : null);
188     if (protein != null && cdna != null)
189     {
190       ViewStyleI vs = protein.getViewStyle();
191       int scale = vs.isScaleProteinAsCdna() ? 3 : 1;
192       vs.setCharWidth(scale * cdna.getViewStyle().getCharWidth());
193       protein.setViewStyle(vs);
194     }
195   }
196
197   /**
198    * Adjust the divider for a sensible split of the real estate (for example,
199    * when many transcripts are shown with a single protein). This should only be
200    * called after the split pane has been laid out (made visible) so it has a
201    * height.
202    */
203   protected void adjustDivider()
204   {
205     final AlignViewport topViewport = ((AlignFrame) getTopFrame()).viewport;
206     final AlignViewport bottomViewport = ((AlignFrame) getBottomFrame()).viewport;
207     final AlignmentI topAlignment = topViewport.getAlignment();
208     final AlignmentI bottomAlignment = bottomViewport.getAlignment();
209     boolean topAnnotations = topViewport.isShowAnnotation();
210     boolean bottomAnnotations = bottomViewport.isShowAnnotation();
211     // TODO need number of visible sequences here, not #sequences - how?
212     int topCount = topAlignment.getHeight();
213     int bottomCount = bottomAlignment.getHeight();
214     int topCharHeight = topViewport.getViewStyle().getCharHeight();
215     int bottomCharHeight = bottomViewport.getViewStyle().getCharHeight();
216
217     /*
218      * estimate ratio of (topFrameContent / bottomFrameContent)
219      */
220     int insets = Platform.isAMac() ? MAC_INSETS_HEIGHT
221             : WINDOWS_INSETS_HEIGHT;
222     // allow 3 'rows' for scale, scrollbar, status bar
223     int topHeight = insets + (3 + topCount) * topCharHeight
224             + (topAnnotations ? topViewport.calcPanelHeight() : 0);
225     int bottomHeight = insets + (3 + bottomCount) * bottomCharHeight
226             + (bottomAnnotations ? bottomViewport.calcPanelHeight() : 0);
227     double ratio = ((double) topHeight) / (topHeight + bottomHeight);
228
229     /*
230      * limit to 0.2 <= ratio <= 0.8 to avoid concealing all sequences
231      */
232     ratio = Math.min(ratio, 0.8d);
233     ratio = Math.max(ratio, 0.2d);
234     setRelativeDividerLocation(ratio);
235   }
236
237   /**
238    * Add a listener to tidy up when the frame is closed.
239    */
240   protected void addCloseFrameListener()
241   {
242     addInternalFrameListener(new InternalFrameAdapter()
243     {
244       @Override
245       public void internalFrameClosed(InternalFrameEvent evt)
246       {
247         close();
248       };
249     });
250   }
251
252   /**
253    * Add a key listener that delegates to whichever split component the mouse is
254    * in (or does nothing if neither).
255    */
256   protected void addKeyListener()
257   {
258     addKeyListener(new KeyAdapter()
259     {
260
261       @Override
262       public void keyPressed(KeyEvent e)
263       {
264         AlignFrame af = (AlignFrame) getFrameAtMouse();
265
266         /*
267          * Intercept and override any keys here if wanted.
268          */
269         if (!overrideKey(e, af))
270         {
271           if (af != null)
272           {
273             for (KeyListener kl : af.getKeyListeners())
274             {
275               kl.keyPressed(e);
276             }
277           }
278         }
279       }
280
281       @Override
282       public void keyReleased(KeyEvent e)
283       {
284         Component c = getFrameAtMouse();
285         if (c != null)
286         {
287           for (KeyListener kl : c.getKeyListeners())
288           {
289             kl.keyReleased(e);
290           }
291         }
292       }
293
294     });
295   }
296
297   /**
298    * Returns true if the key event is overriden and actioned (or ignored) here,
299    * else returns false, indicating it should be delegated to the AlignFrame's
300    * usual handler.
301    * <p>
302    * We can't handle Cmd-Key combinations here, instead this is done by
303    * overriding key bindings.
304    * 
305    * @see addKeyOverrides
306    * @param e
307    * @param af
308    * @return
309    */
310   protected boolean overrideKey(KeyEvent e, AlignFrame af)
311   {
312     boolean actioned = false;
313     int keyCode = e.getKeyCode();
314     switch (keyCode)
315     {
316     case KeyEvent.VK_DOWN:
317       if (e.isAltDown() || !af.viewport.cursorMode)
318       {
319         /*
320          * Key down (or Alt-key-down in cursor mode) - move selected sequences
321          */
322         ((AlignFrame) getTopFrame()).moveSelectedSequences(false);
323         ((AlignFrame) getBottomFrame()).moveSelectedSequences(false);
324         actioned = true;
325         e.consume();
326       }
327       break;
328     case KeyEvent.VK_UP:
329       if (e.isAltDown() || !af.viewport.cursorMode)
330       {
331         /*
332          * Key up (or Alt-key-up in cursor mode) - move selected sequences
333          */
334         ((AlignFrame) getTopFrame()).moveSelectedSequences(true);
335         ((AlignFrame) getBottomFrame()).moveSelectedSequences(true);
336         actioned = true;
337         e.consume();
338       }
339       break;
340     default:
341     }
342     return actioned;
343   }
344
345   /**
346    * Set key bindings (recommended for Swing over key accelerators).
347    */
348   private void addKeyBindings()
349   {
350     overrideDelegatedKeyBindings();
351
352     overrideImplementedKeyBindings();
353   }
354
355   /**
356    * Override key bindings with alternative action methods implemented in this
357    * class.
358    */
359   protected void overrideImplementedKeyBindings()
360   {
361     overrideFind();
362     overrideNewView();
363     overrideCloseView();
364     overrideExpandViews();
365     overrideGatherViews();
366   }
367
368   /**
369    * Replace Cmd-W close view action with our version.
370    */
371   protected void overrideCloseView()
372   {
373     AbstractAction action;
374     /*
375      * Ctrl-W / Cmd-W - close view or window
376      */
377     KeyStroke key_cmdW = KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
378             .getDefaultToolkit().getMenuShortcutKeyMask(), false);
379     action = new AbstractAction()
380     {
381       @Override
382       public void actionPerformed(ActionEvent e)
383       {
384         closeView_actionPerformed();
385       }
386     };
387     overrideKeyBinding(key_cmdW, action);
388   }
389
390   /**
391    * Replace Cmd-T new view action with our version.
392    */
393   protected void overrideNewView()
394   {
395     /*
396      * Ctrl-T / Cmd-T open new view
397      */
398     KeyStroke key_cmdT = KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit
399             .getDefaultToolkit().getMenuShortcutKeyMask(), false);
400     AbstractAction action = new AbstractAction()
401     {
402       @Override
403       public void actionPerformed(ActionEvent e)
404       {
405         newView_actionPerformed();
406       }
407     };
408     overrideKeyBinding(key_cmdT, action);
409   }
410
411   /**
412    * For now, delegates key events to the corresponding key accelerator for the
413    * AlignFrame that the mouse is in. Hopefully can be simplified in future if
414    * AlignFrame is changed to use key bindings rather than accelerators.
415    */
416   protected void overrideDelegatedKeyBindings()
417   {
418     if (getTopFrame() instanceof AlignFrame)
419     {
420       /*
421        * Get all accelerator keys in the top frame (the bottom should be
422        * identical) and override each one.
423        */
424       for (Entry<KeyStroke, JMenuItem> acc : ((AlignFrame) getTopFrame())
425               .getAccelerators().entrySet())
426       {
427         overrideKeyBinding(acc);
428       }
429     }
430   }
431
432   /**
433    * Overrides an AlignFrame key accelerator with our version which delegates to
434    * the action listener in whichever frame has the mouse (and does nothing if
435    * neither has).
436    * 
437    * @param acc
438    */
439   private void overrideKeyBinding(Entry<KeyStroke, JMenuItem> acc)
440   {
441     final KeyStroke ks = acc.getKey();
442     InputMap inputMap = this.getInputMap(JComponent.WHEN_FOCUSED);
443     inputMap.put(ks, ks);
444     this.getActionMap().put(ks, new AbstractAction()
445     {
446       @Override
447       public void actionPerformed(ActionEvent e)
448       {
449         Component c = getFrameAtMouse();
450         if (c != null && c instanceof AlignFrame)
451         {
452           for (ActionListener a : ((AlignFrame) c).getAccelerators()
453                   .get(ks).getActionListeners())
454           {
455             a.actionPerformed(null);
456           }
457         }
458       }
459     });
460   }
461
462   /**
463    * Replace an accelerator key's action with the specified action.
464    * 
465    * @param ks
466    */
467   protected void overrideKeyBinding(KeyStroke ks, AbstractAction action)
468   {
469     this.getActionMap().put(ks, action);
470     overrideMenuItem(ks, action);
471   }
472
473   /**
474    * Create and link new views (with matching names) in both panes.
475    * <p>
476    * Note this is _not_ multiple tabs, each hosting a split pane view, rather it
477    * is a single split pane with each split holding multiple tabs which are
478    * linked in pairs.
479    * <p>
480    * TODO implement instead with a tabbed holder in the SplitView, each tab
481    * holding a single JSplitPane. Would avoid a duplicated tab, at the cost of
482    * some additional coding.
483    */
484   protected void newView_actionPerformed()
485   {
486     AlignFrame topFrame = (AlignFrame) getTopFrame();
487     AlignFrame bottomFrame = (AlignFrame) getBottomFrame();
488     final boolean scaleProteinAsCdna = topFrame.viewport
489             .isScaleProteinAsCdna();
490
491     AlignmentPanel newTopPanel = topFrame.newView(null, true);
492     AlignmentPanel newBottomPanel = bottomFrame.newView(null, true);
493
494     /*
495      * This currently (for the first new view only) leaves the top pane on tab 0
496      * but the bottom on tab 1. This results from 'setInitialTabVisible' echoing
497      * from the bottom back to the first frame. Next line is a fudge to work
498      * around this. TODO find a better way.
499      */
500     if (topFrame.getTabIndex() != bottomFrame.getTabIndex())
501     {
502       topFrame.setDisplayedView(newTopPanel);
503     }
504
505     newBottomPanel.av.viewName = newTopPanel.av.viewName;
506     newTopPanel.av.setCodingComplement(newBottomPanel.av);
507
508     /*
509      * These lines can be removed once scaleProteinAsCdna is added to element
510      * Viewport in jalview.xsd, as Jalview2XML.copyAlignPanel will then take
511      * care of it
512      */
513     newTopPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
514     newBottomPanel.av.setScaleProteinAsCdna(scaleProteinAsCdna);
515
516     /*
517      * Line up id labels etc
518      */
519     adjustLayout();
520
521     final StructureSelectionManager ssm = StructureSelectionManager
522             .getStructureSelectionManager(Desktop.instance);
523     ssm.addCommandListener(newTopPanel.av);
524     ssm.addCommandListener(newBottomPanel.av);
525   }
526
527   /**
528    * Close the currently selected view in both panes. If there is only one view,
529    * close this split frame.
530    */
531   protected void closeView_actionPerformed()
532   {
533     int viewCount = ((AlignFrame) getTopFrame()).getAlignPanels().size();
534     if (viewCount < 2)
535     {
536       close();
537       return;
538     }
539
540     AlignmentPanel topPanel = ((AlignFrame) getTopFrame()).alignPanel;
541     AlignmentPanel bottomPanel = ((AlignFrame) getBottomFrame()).alignPanel;
542
543     ((AlignFrame) getTopFrame()).closeView(topPanel);
544     ((AlignFrame) getBottomFrame()).closeView(bottomPanel);
545
546   }
547
548   /**
549    * Close child frames and this split frame.
550    */
551   public void close()
552   {
553     ((AlignFrame) getTopFrame()).closeMenuItem_actionPerformed(true);
554     ((AlignFrame) getBottomFrame()).closeMenuItem_actionPerformed(true);
555     try
556     {
557       this.setClosed(true);
558     } catch (PropertyVetoException e)
559     {
560       // ignore
561     }
562   }
563
564   /**
565    * Replace AlignFrame 'expand views' action with SplitFrame version.
566    */
567   protected void overrideExpandViews()
568   {
569     KeyStroke key_X = KeyStroke.getKeyStroke(KeyEvent.VK_X, 0, false);
570     AbstractAction action = new AbstractAction()
571     {
572       @Override
573       public void actionPerformed(ActionEvent e)
574       {
575         expandViews_actionPerformed();
576       }
577     };
578     overrideMenuItem(key_X, action);
579   }
580
581   /**
582    * Replace AlignFrame 'gather views' action with SplitFrame version.
583    */
584   protected void overrideGatherViews()
585   {
586     KeyStroke key_G = KeyStroke.getKeyStroke(KeyEvent.VK_G, 0, false);
587     AbstractAction action = new AbstractAction()
588     {
589       @Override
590       public void actionPerformed(ActionEvent e)
591       {
592         gatherViews_actionPerformed();
593       }
594     };
595     overrideMenuItem(key_G, action);
596   }
597
598   /**
599    * Override the menu action associated with the keystroke in the child frames,
600    * replacing it with the given action.
601    * 
602    * @param ks
603    * @param action
604    */
605   private void overrideMenuItem(KeyStroke ks, AbstractAction action)
606   {
607     overrideMenuItem(ks, action, getTopFrame());
608     overrideMenuItem(ks, action, getBottomFrame());
609   }
610
611   /**
612    * Override the menu action associated with the keystroke in one child frame,
613    * replacing it with the given action. Mwahahahaha.
614    * 
615    * @param key
616    * @param action
617    * @param comp
618    */
619   private void overrideMenuItem(KeyStroke key, final AbstractAction action,
620           JComponent comp)
621   {
622     if (comp instanceof AlignFrame)
623     {
624       JMenuItem mi = ((AlignFrame) comp).getAccelerators().get(key);
625       if (mi != null)
626       {
627         for (ActionListener al : mi.getActionListeners())
628         {
629           mi.removeActionListener(al);
630         }
631         mi.addActionListener(new ActionListener()
632         {
633           @Override
634           public void actionPerformed(ActionEvent e)
635           {
636             action.actionPerformed(e);
637           }
638         });
639       }
640     }
641   }
642
643   /**
644    * Expand any multiple views (which are always in pairs) into separate split
645    * frames.
646    */
647   protected void expandViews_actionPerformed()
648   {
649     Desktop.instance.explodeViews(this);
650   }
651
652   /**
653    * Gather any other SplitFrame views of this alignment back in as multiple
654    * (pairs of) views in this SplitFrame.
655    */
656   protected void gatherViews_actionPerformed()
657   {
658     Desktop.instance.gatherViews(this);
659   }
660
661   /**
662    * Returns the alignment in the complementary frame to the one given.
663    */
664   @Override
665   public AlignmentI getComplement(Object alignFrame)
666   {
667     if (alignFrame == this.getTopFrame())
668     {
669       return ((AlignFrame) getBottomFrame()).viewport.getAlignment();
670     }
671     else if (alignFrame == this.getBottomFrame())
672     {
673       return ((AlignFrame) getTopFrame()).viewport.getAlignment();
674     }
675     return null;
676   }
677
678   /**
679    * Returns the title of the complementary frame to the one given.
680    */
681   @Override
682   public String getComplementTitle(Object alignFrame)
683   {
684     if (alignFrame == this.getTopFrame())
685     {
686       return ((AlignFrame) getBottomFrame()).getTitle();
687     }
688     else if (alignFrame == this.getBottomFrame())
689     {
690       return ((AlignFrame) getTopFrame()).getTitle();
691     }
692     return null;
693   }
694
695   /**
696    * Set the 'other half' to hidden / revealed.
697    */
698   @Override
699   public void setComplementVisible(Object alignFrame, boolean show)
700   {
701     /*
702      * Hiding the AlignPanel suppresses unnecessary repaints
703      */
704     if (alignFrame == getTopFrame())
705     {
706       ((AlignFrame) getBottomFrame()).alignPanel.setVisible(show);
707     }
708     else if (alignFrame == getBottomFrame())
709     {
710       ((AlignFrame) getTopFrame()).alignPanel.setVisible(show);
711     }
712     super.setComplementVisible(alignFrame, show);
713   }
714
715   /**
716    * return the AlignFrames held by this container
717    * 
718    * @return { Top alignFrame (Usually CDS), Bottom AlignFrame (Usually
719    *         Protein)}
720    */
721   public List<AlignFrame> getAlignFrames()
722   {
723     return Arrays.asList(new AlignFrame[] { (AlignFrame) getTopFrame(),
724         (AlignFrame) getBottomFrame() });
725   }
726   /**
727    * Replace Cmd-F Find action with our version. This is necessary because the
728    * 'default' Finder searches in the first AlignFrame it finds. We need it to
729    * search in the half of the SplitFrame that has the mouse.
730    */
731   protected void overrideFind()
732   {
733     /*
734      * Ctrl-F / Cmd-F open Finder dialog, 'focused' on the right alignment
735      */
736     KeyStroke key_cmdF = KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit
737             .getDefaultToolkit().getMenuShortcutKeyMask(), false);
738     AbstractAction action = new AbstractAction()
739     {
740       @Override
741       public void actionPerformed(ActionEvent e)
742       {
743         Component c = getFrameAtMouse();
744         if (c != null && c instanceof AlignFrame)
745         {
746           AlignFrame af = (AlignFrame) c;
747           new Finder(af.viewport, af.alignPanel);
748         }
749       }
750     };
751     overrideKeyBinding(key_cmdF, action);
752   }
753 }