Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
     17 
     18 import com.android.annotations.NonNull;
     19 import com.android.annotations.Nullable;
     20 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     21 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
     22 import com.google.common.collect.Maps;
     23 
     24 import org.eclipse.ui.IEditorPart;
     25 import org.eclipse.ui.IEditorReference;
     26 import org.eclipse.ui.IPartListener2;
     27 import org.eclipse.ui.IPartService;
     28 import org.eclipse.ui.IViewReference;
     29 import org.eclipse.ui.IWorkbenchPage;
     30 import org.eclipse.ui.IWorkbenchPart;
     31 import org.eclipse.ui.IWorkbenchPartReference;
     32 import org.eclipse.ui.IWorkbenchWindow;
     33 
     34 import java.util.Map;
     35 
     36 /**
     37  * The {@link LayoutWindowCoordinator} keeps track of Eclipse window events (opening, closing,
     38  * fronting, etc) and uses this information to manage the propertysheet and outline
     39  * views such that they are always(*) showing:
     40  * <ul>
     41  *  <li> If the Property Sheet and Outline Eclipse views are showing, it does nothing.
     42  *       "Showing" means "is open", not necessary "is visible", e.g. in a tabbed view
     43  *       there could be a different view on top.
     44  *  <li> If just the outline is showing, then the property sheet is shown in a sashed
     45  *       pane below or to the right of the outline (depending on the dominant dimension
     46  *       of the window).
     47  *  <li> TBD: If just the property sheet is showing, should the outline be showed
     48  *       inside that window? Not yet done.
     49  *  <li> If the outline is *not* showing, then the outline is instead shown
     50  *       <b>inside</b> the editor area, in a right-docked view! This right docked view
     51  *       also includes the property sheet!
     52  *  <li> If the property sheet is not showing (which includes not showing in the outline
     53  *       view as well), then it will be shown inside the editor area, along with the outline
     54  *       which should also be there (since if the outline was showing outside the editor
     55  *       area, the property sheet would have docked there).
     56  *  <li> When the editor is maximized, then all views are temporarily hidden. In this
     57  *       case, the property sheet and outline will show up inside the editor.
     58  *       When the editor view is un-maximized, the view state will return to what it
     59  *       was before.
     60  * </ul>
     61  * </p>
     62  * There is one coordinator per workbench window, shared between all editors in that window.
     63  * <p>
     64  * TODO: Rename this class to AdtWindowCoordinator. It is used for more than just layout
     65  * window coordination now. For example, it's also used to dispatch {@code activated()} and
     66  * {@code deactivated()} events to all the XML editors, to ensure that key bindings are
     67  * properly dispatched to the right editors in Eclipse 4.x.
     68  */
     69 public class LayoutWindowCoordinator implements IPartListener2 {
     70     static final String PROPERTY_SHEET_PART_ID = "org.eclipse.ui.views.PropertySheet"; //$NON-NLS-1$
     71     static final String OUTLINE_PART_ID = "org.eclipse.ui.views.ContentOutline"; //$NON-NLS-1$
     72     /** The workbench window */
     73     private final IWorkbenchWindow mWindow;
     74     /** Is the Eclipse property sheet ViewPart open? */
     75     private boolean mPropertiesOpen;
     76     /** Is the Eclipse outline ViewPart open? */
     77     private boolean mOutlineOpen;
     78     /** Is the editor maximized? */
     79     private boolean mEditorMaximized;
     80     /**
     81      * Has the coordinator been initialized? We may have to delay initialization
     82      * and perform it lazily if the workbench window does not have an active
     83      * page when the coordinator is first started
     84      */
     85     private boolean mInitialized;
     86 
     87     /** Map from workbench windows to each layout window coordinator instance for that window */
     88     private static Map<IWorkbenchWindow, LayoutWindowCoordinator> sCoordinators =
     89             Maps.newHashMapWithExpectedSize(2);
     90 
     91     /**
     92      * Returns the coordinator for the given window.
     93      *
     94      * @param window the associated window
     95      * @param create whether to create the window if it does not already exist
     96      * @return the new coordinator, never null if {@code create} is true
     97      */
     98     @Nullable
     99     public static LayoutWindowCoordinator get(@NonNull IWorkbenchWindow window, boolean create) {
    100         synchronized (LayoutWindowCoordinator.class){
    101             LayoutWindowCoordinator coordinator = sCoordinators.get(window);
    102             if (coordinator == null && create) {
    103                 coordinator = new LayoutWindowCoordinator(window);
    104 
    105                 IPartService service = window.getPartService();
    106                 if (service != null) {
    107                     // What if the editor part is *already* open? How do I deal with that?
    108                     service.addPartListener(coordinator);
    109                 }
    110 
    111                 sCoordinators.put(window, coordinator);
    112             }
    113 
    114             return coordinator;
    115         }
    116     }
    117 
    118 
    119     /** Disposes this coordinator (when a window is closed) */
    120     public void dispose() {
    121         IPartService service = mWindow.getPartService();
    122         if (service != null) {
    123             service.removePartListener(this);
    124         }
    125 
    126         synchronized (LayoutWindowCoordinator.class){
    127             sCoordinators.remove(mWindow);
    128         }
    129     }
    130 
    131     /**
    132      * Returns true if the main editor window is maximized
    133      *
    134      * @return true if the main editor window is maximized
    135      */
    136     public boolean isEditorMaximized() {
    137         return mEditorMaximized;
    138     }
    139 
    140     private LayoutWindowCoordinator(@NonNull IWorkbenchWindow window) {
    141         mWindow = window;
    142 
    143         initialize();
    144     }
    145 
    146     private void initialize() {
    147         if (mInitialized) {
    148             return;
    149         }
    150 
    151         IWorkbenchPage activePage = mWindow.getActivePage();
    152         if (activePage == null) {
    153             return;
    154         }
    155 
    156         mInitialized = true;
    157 
    158         // Look up current state of the properties and outline windows (in case
    159         // they have already been opened before we added our part listener)
    160         IViewReference ref = findPropertySheetView(activePage);
    161         if (ref != null) {
    162             IWorkbenchPart part = ref.getPart(false /*restore*/);
    163             if (activePage.isPartVisible(part)) {
    164                 mPropertiesOpen = true;
    165             }
    166         }
    167         ref = findOutlineView(activePage);
    168         if (ref != null) {
    169             IWorkbenchPart part = ref.getPart(false /*restore*/);
    170             if (activePage.isPartVisible(part)) {
    171                 mOutlineOpen = true;
    172             }
    173         }
    174         if (!syncMaximizedState(activePage)) {
    175             syncActive();
    176         }
    177     }
    178 
    179     static IViewReference findPropertySheetView(IWorkbenchPage activePage) {
    180         return activePage.findViewReference(PROPERTY_SHEET_PART_ID);
    181     }
    182 
    183     static IViewReference findOutlineView(IWorkbenchPage activePage) {
    184         return activePage.findViewReference(OUTLINE_PART_ID);
    185     }
    186 
    187     /**
    188      * Checks the maximized state of the page and updates internal state if
    189      * necessary.
    190      * <p>
    191      * This is used in Eclipse 4.x, where the {@link IPartListener2} does not
    192      * fire {@link IPartListener2#partHidden(IWorkbenchPartReference)} when the
    193      * editor is maximized anymore (see issue
    194      * https://bugs.eclipse.org/bugs/show_bug.cgi?id=382120 for details).
    195      * Instead, the layout editor listens for resize events, and upon resize it
    196      * looks up the part state and calls this method to ensure that the right
    197      * maximized state is known to the layout coordinator.
    198      *
    199      * @param page the active workbench page
    200      * @return true if the state changed, false otherwise
    201      */
    202     public boolean syncMaximizedState(IWorkbenchPage page) {
    203         boolean maximized = isPageZoomed(page);
    204         if (mEditorMaximized != maximized) {
    205             mEditorMaximized = maximized;
    206             syncActive();
    207             return true;
    208         }
    209         return false;
    210     }
    211 
    212     private boolean isPageZoomed(IWorkbenchPage page) {
    213         IWorkbenchPartReference reference = page.getActivePartReference();
    214         if (reference != null && reference instanceof IEditorReference) {
    215             int state = page.getPartState(reference);
    216             boolean maximized = (state & IWorkbenchPage.STATE_MAXIMIZED) != 0;
    217             return maximized;
    218         }
    219 
    220         // If the active reference isn't the editor, then the editor can't be maximized
    221         return false;
    222     }
    223 
    224     /**
    225      * Syncs the given editor's view state such that the property sheet and or
    226      * outline are shown or hidden according to the visibility of the global
    227      * outline and property sheet views.
    228      * <p>
    229      * This is typically done when a layout editor is fronted. For view updates
    230      * when the view is already showing, the {@link LayoutWindowCoordinator}
    231      * will automatically handle the current fronted window.
    232      *
    233      * @param editor the editor to sync
    234      */
    235     private void sync(@Nullable GraphicalEditorPart editor) {
    236         if (editor == null) {
    237             return;
    238         }
    239         if (mEditorMaximized) {
    240             editor.showStructureViews(true /*outline*/, true /*properties*/, true /*layout*/);
    241         } else if (mOutlineOpen) {
    242             editor.showStructureViews(false /*outline*/, false /*properties*/, true /*layout*/);
    243             editor.getCanvasControl().getOutlinePage().setShowPropertySheet(!mPropertiesOpen);
    244         } else {
    245             editor.showStructureViews(true /*outline*/, !mPropertiesOpen /*properties*/,
    246                     true /*layout*/);
    247         }
    248     }
    249 
    250     private void sync(IWorkbenchPart part) {
    251         if (part instanceof AndroidXmlEditor) {
    252             LayoutEditorDelegate editor = LayoutEditorDelegate.fromEditor((IEditorPart) part);
    253             if (editor != null) {
    254                 sync(editor.getGraphicalEditor());
    255             }
    256         }
    257     }
    258 
    259     private void syncActive() {
    260         IWorkbenchPage activePage = mWindow.getActivePage();
    261         if (activePage != null) {
    262             IEditorPart editor = activePage.getActiveEditor();
    263             sync(editor);
    264         }
    265     }
    266 
    267     private void propertySheetClosed() {
    268         mPropertiesOpen = false;
    269         syncActive();
    270     }
    271 
    272     private void propertySheetOpened() {
    273         mPropertiesOpen = true;
    274         syncActive();
    275     }
    276 
    277     private void outlineClosed() {
    278         mOutlineOpen = false;
    279         syncActive();
    280     }
    281 
    282     private void outlineOpened() {
    283         mOutlineOpen = true;
    284         syncActive();
    285     }
    286 
    287     // ---- Implements IPartListener2 ----
    288 
    289     @Override
    290     public void partOpened(IWorkbenchPartReference partRef) {
    291         // We ignore partOpened() and partClosed() because these methods are only
    292         // called when a view is opened in the first perspective, and closed in the
    293         // last perspective. The outline is typically used in multiple perspectives,
    294         // so closing it in the Java perspective does *not* fire a partClosed event.
    295         // There is no notification for "part closed in perspective" (see issue
    296         // https://bugs.eclipse.org/bugs/show_bug.cgi?id=54559 for details).
    297         // However, the workaround we can use is to listen to partVisible() and
    298         // partHidden(). These will be called more often than we'd like (e.g.
    299         // when the tab order causes a view to be obscured), however, we can use
    300         // the workaround of looking up IWorkbenchPage.findViewReference(id) after
    301         // partHidden(), which will return null if the view is closed in the current
    302         // perspective. For partOpened, we simply look in partVisible() for whether
    303         // our flags tracking the view state have been initialized already.
    304     }
    305 
    306     @Override
    307     public void partClosed(IWorkbenchPartReference partRef) {
    308         // partClosed() doesn't get called when a window is closed unless it has
    309         // been closed in *all* perspectives. See partOpened() for more.
    310     }
    311 
    312     @Override
    313     public void partHidden(IWorkbenchPartReference partRef) {
    314         IWorkbenchPage activePage = mWindow.getActivePage();
    315         if (activePage == null) {
    316             return;
    317         }
    318         initialize();
    319 
    320         // See if this looks like the window was closed in this workspace
    321         // See partOpened() for an explanation.
    322         String id = partRef.getId();
    323         if (PROPERTY_SHEET_PART_ID.equals(id)) {
    324             if (activePage.findViewReference(id) == null) {
    325                 propertySheetClosed();
    326                 return;
    327             }
    328         } else if (OUTLINE_PART_ID.equals(id)) {
    329             if (activePage.findViewReference(id) == null) {
    330                 outlineClosed();
    331                 return;
    332             }
    333         }
    334 
    335         // Does this look like a window getting maximized?
    336         syncMaximizedState(activePage);
    337     }
    338 
    339     @Override
    340     public void partVisible(IWorkbenchPartReference partRef) {
    341         IWorkbenchPage activePage = mWindow.getActivePage();
    342         if (activePage == null) {
    343             return;
    344         }
    345         initialize();
    346 
    347         String id = partRef.getId();
    348         if (mEditorMaximized) {
    349             // Return to their non-maximized state
    350             mEditorMaximized = false;
    351             syncActive();
    352         }
    353 
    354         IWorkbenchPart part = partRef.getPart(false /*restore*/);
    355         sync(part);
    356 
    357         // See partOpened() for an explanation
    358         if (PROPERTY_SHEET_PART_ID.equals(id)) {
    359             if (!mPropertiesOpen) {
    360                 propertySheetOpened();
    361                 assert mPropertiesOpen;
    362             }
    363         } else if (OUTLINE_PART_ID.equals(id)) {
    364             if (!mOutlineOpen) {
    365                 outlineOpened();
    366                 assert mOutlineOpen;
    367             }
    368         }
    369     }
    370 
    371     @Override
    372     public void partInputChanged(IWorkbenchPartReference partRef) {
    373     }
    374 
    375     @Override
    376     public void partActivated(IWorkbenchPartReference partRef) {
    377         IWorkbenchPart part = partRef.getPart(false);
    378         if (part instanceof AndroidXmlEditor) {
    379             ((AndroidXmlEditor)part).activated();
    380         }
    381     }
    382 
    383     @Override
    384     public void partBroughtToTop(IWorkbenchPartReference partRef) {
    385     }
    386 
    387     @Override
    388     public void partDeactivated(IWorkbenchPartReference partRef) {
    389         IWorkbenchPart part = partRef.getPart(false);
    390         if (part instanceof AndroidXmlEditor) {
    391             ((AndroidXmlEditor)part).deactivated();
    392         }
    393     }
    394 }