| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/org/documents/epl-v10.php |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.ide.eclipse.adt.internal.editors.layout.gle2; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; |
| import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; |
| import com.google.common.collect.Maps; |
| |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IEditorReference; |
| import org.eclipse.ui.IPartListener2; |
| import org.eclipse.ui.IPartService; |
| import org.eclipse.ui.IViewReference; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchPartReference; |
| import org.eclipse.ui.IWorkbenchWindow; |
| |
| import java.util.Map; |
| |
| /** |
| * The {@link LayoutWindowCoordinator} keeps track of Eclipse window events (opening, closing, |
| * fronting, etc) and uses this information to manage the propertysheet and outline |
| * views such that they are always(*) showing: |
| * <ul> |
| * <li> If the Property Sheet and Outline Eclipse views are showing, it does nothing. |
| * "Showing" means "is open", not necessary "is visible", e.g. in a tabbed view |
| * there could be a different view on top. |
| * <li> If just the outline is showing, then the property sheet is shown in a sashed |
| * pane below or to the right of the outline (depending on the dominant dimension |
| * of the window). |
| * <li> TBD: If just the property sheet is showing, should the outline be showed |
| * inside that window? Not yet done. |
| * <li> If the outline is *not* showing, then the outline is instead shown |
| * <b>inside</b> the editor area, in a right-docked view! This right docked view |
| * also includes the property sheet! |
| * <li> If the property sheet is not showing (which includes not showing in the outline |
| * view as well), then it will be shown inside the editor area, along with the outline |
| * which should also be there (since if the outline was showing outside the editor |
| * area, the property sheet would have docked there). |
| * <li> When the editor is maximized, then all views are temporarily hidden. In this |
| * case, the property sheet and outline will show up inside the editor. |
| * When the editor view is un-maximized, the view state will return to what it |
| * was before. |
| * </ul> |
| * </p> |
| * There is one coordinator per workbench window, shared between all editors in that window. |
| * <p> |
| * TODO: Rename this class to AdtWindowCoordinator. It is used for more than just layout |
| * window coordination now. For example, it's also used to dispatch {@code activated()} and |
| * {@code deactivated()} events to all the XML editors, to ensure that key bindings are |
| * properly dispatched to the right editors in Eclipse 4.x. |
| */ |
| public class LayoutWindowCoordinator implements IPartListener2 { |
| static final String PROPERTY_SHEET_PART_ID = "org.eclipse.ui.views.PropertySheet"; //$NON-NLS-1$ |
| static final String OUTLINE_PART_ID = "org.eclipse.ui.views.ContentOutline"; //$NON-NLS-1$ |
| /** The workbench window */ |
| private final IWorkbenchWindow mWindow; |
| /** Is the Eclipse property sheet ViewPart open? */ |
| private boolean mPropertiesOpen; |
| /** Is the Eclipse outline ViewPart open? */ |
| private boolean mOutlineOpen; |
| /** Is the editor maximized? */ |
| private boolean mEditorMaximized; |
| /** |
| * Has the coordinator been initialized? We may have to delay initialization |
| * and perform it lazily if the workbench window does not have an active |
| * page when the coordinator is first started |
| */ |
| private boolean mInitialized; |
| |
| /** Map from workbench windows to each layout window coordinator instance for that window */ |
| private static Map<IWorkbenchWindow, LayoutWindowCoordinator> sCoordinators = |
| Maps.newHashMapWithExpectedSize(2); |
| |
| /** |
| * Returns the coordinator for the given window. |
| * |
| * @param window the associated window |
| * @param create whether to create the window if it does not already exist |
| * @return the new coordinator, never null if {@code create} is true |
| */ |
| @Nullable |
| public static LayoutWindowCoordinator get(@NonNull IWorkbenchWindow window, boolean create) { |
| synchronized (LayoutWindowCoordinator.class){ |
| LayoutWindowCoordinator coordinator = sCoordinators.get(window); |
| if (coordinator == null && create) { |
| coordinator = new LayoutWindowCoordinator(window); |
| |
| IPartService service = window.getPartService(); |
| if (service != null) { |
| // What if the editor part is *already* open? How do I deal with that? |
| service.addPartListener(coordinator); |
| } |
| |
| sCoordinators.put(window, coordinator); |
| } |
| |
| return coordinator; |
| } |
| } |
| |
| |
| /** Disposes this coordinator (when a window is closed) */ |
| public void dispose() { |
| IPartService service = mWindow.getPartService(); |
| if (service != null) { |
| service.removePartListener(this); |
| } |
| |
| synchronized (LayoutWindowCoordinator.class){ |
| sCoordinators.remove(mWindow); |
| } |
| } |
| |
| /** |
| * Returns true if the main editor window is maximized |
| * |
| * @return true if the main editor window is maximized |
| */ |
| public boolean isEditorMaximized() { |
| return mEditorMaximized; |
| } |
| |
| private LayoutWindowCoordinator(@NonNull IWorkbenchWindow window) { |
| mWindow = window; |
| |
| initialize(); |
| } |
| |
| private void initialize() { |
| if (mInitialized) { |
| return; |
| } |
| |
| IWorkbenchPage activePage = mWindow.getActivePage(); |
| if (activePage == null) { |
| return; |
| } |
| |
| mInitialized = true; |
| |
| // Look up current state of the properties and outline windows (in case |
| // they have already been opened before we added our part listener) |
| IViewReference ref = findPropertySheetView(activePage); |
| if (ref != null) { |
| IWorkbenchPart part = ref.getPart(false /*restore*/); |
| if (activePage.isPartVisible(part)) { |
| mPropertiesOpen = true; |
| } |
| } |
| ref = findOutlineView(activePage); |
| if (ref != null) { |
| IWorkbenchPart part = ref.getPart(false /*restore*/); |
| if (activePage.isPartVisible(part)) { |
| mOutlineOpen = true; |
| } |
| } |
| if (!syncMaximizedState(activePage)) { |
| syncActive(); |
| } |
| } |
| |
| static IViewReference findPropertySheetView(IWorkbenchPage activePage) { |
| return activePage.findViewReference(PROPERTY_SHEET_PART_ID); |
| } |
| |
| static IViewReference findOutlineView(IWorkbenchPage activePage) { |
| return activePage.findViewReference(OUTLINE_PART_ID); |
| } |
| |
| /** |
| * Checks the maximized state of the page and updates internal state if |
| * necessary. |
| * <p> |
| * This is used in Eclipse 4.x, where the {@link IPartListener2} does not |
| * fire {@link IPartListener2#partHidden(IWorkbenchPartReference)} when the |
| * editor is maximized anymore (see issue |
| * https://bugs.eclipse.org/bugs/show_bug.cgi?id=382120 for details). |
| * Instead, the layout editor listens for resize events, and upon resize it |
| * looks up the part state and calls this method to ensure that the right |
| * maximized state is known to the layout coordinator. |
| * |
| * @param page the active workbench page |
| * @return true if the state changed, false otherwise |
| */ |
| public boolean syncMaximizedState(IWorkbenchPage page) { |
| boolean maximized = isPageZoomed(page); |
| if (mEditorMaximized != maximized) { |
| mEditorMaximized = maximized; |
| syncActive(); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean isPageZoomed(IWorkbenchPage page) { |
| IWorkbenchPartReference reference = page.getActivePartReference(); |
| if (reference != null && reference instanceof IEditorReference) { |
| int state = page.getPartState(reference); |
| boolean maximized = (state & IWorkbenchPage.STATE_MAXIMIZED) != 0; |
| return maximized; |
| } |
| |
| // If the active reference isn't the editor, then the editor can't be maximized |
| return false; |
| } |
| |
| /** |
| * Syncs the given editor's view state such that the property sheet and or |
| * outline are shown or hidden according to the visibility of the global |
| * outline and property sheet views. |
| * <p> |
| * This is typically done when a layout editor is fronted. For view updates |
| * when the view is already showing, the {@link LayoutWindowCoordinator} |
| * will automatically handle the current fronted window. |
| * |
| * @param editor the editor to sync |
| */ |
| private void sync(@Nullable GraphicalEditorPart editor) { |
| if (editor == null) { |
| return; |
| } |
| if (mEditorMaximized) { |
| editor.showStructureViews(true /*outline*/, true /*properties*/, true /*layout*/); |
| } else if (mOutlineOpen) { |
| editor.showStructureViews(false /*outline*/, false /*properties*/, true /*layout*/); |
| editor.getCanvasControl().getOutlinePage().setShowPropertySheet(!mPropertiesOpen); |
| } else { |
| editor.showStructureViews(true /*outline*/, !mPropertiesOpen /*properties*/, |
| true /*layout*/); |
| } |
| } |
| |
| private void sync(IWorkbenchPart part) { |
| if (part instanceof AndroidXmlEditor) { |
| LayoutEditorDelegate editor = LayoutEditorDelegate.fromEditor((IEditorPart) part); |
| if (editor != null) { |
| sync(editor.getGraphicalEditor()); |
| } |
| } |
| } |
| |
| private void syncActive() { |
| IWorkbenchPage activePage = mWindow.getActivePage(); |
| if (activePage != null) { |
| IEditorPart editor = activePage.getActiveEditor(); |
| sync(editor); |
| } |
| } |
| |
| private void propertySheetClosed() { |
| mPropertiesOpen = false; |
| syncActive(); |
| } |
| |
| private void propertySheetOpened() { |
| mPropertiesOpen = true; |
| syncActive(); |
| } |
| |
| private void outlineClosed() { |
| mOutlineOpen = false; |
| syncActive(); |
| } |
| |
| private void outlineOpened() { |
| mOutlineOpen = true; |
| syncActive(); |
| } |
| |
| // ---- Implements IPartListener2 ---- |
| |
| @Override |
| public void partOpened(IWorkbenchPartReference partRef) { |
| // We ignore partOpened() and partClosed() because these methods are only |
| // called when a view is opened in the first perspective, and closed in the |
| // last perspective. The outline is typically used in multiple perspectives, |
| // so closing it in the Java perspective does *not* fire a partClosed event. |
| // There is no notification for "part closed in perspective" (see issue |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=54559 for details). |
| // However, the workaround we can use is to listen to partVisible() and |
| // partHidden(). These will be called more often than we'd like (e.g. |
| // when the tab order causes a view to be obscured), however, we can use |
| // the workaround of looking up IWorkbenchPage.findViewReference(id) after |
| // partHidden(), which will return null if the view is closed in the current |
| // perspective. For partOpened, we simply look in partVisible() for whether |
| // our flags tracking the view state have been initialized already. |
| } |
| |
| @Override |
| public void partClosed(IWorkbenchPartReference partRef) { |
| // partClosed() doesn't get called when a window is closed unless it has |
| // been closed in *all* perspectives. See partOpened() for more. |
| } |
| |
| @Override |
| public void partHidden(IWorkbenchPartReference partRef) { |
| IWorkbenchPage activePage = mWindow.getActivePage(); |
| if (activePage == null) { |
| return; |
| } |
| initialize(); |
| |
| // See if this looks like the window was closed in this workspace |
| // See partOpened() for an explanation. |
| String id = partRef.getId(); |
| if (PROPERTY_SHEET_PART_ID.equals(id)) { |
| if (activePage.findViewReference(id) == null) { |
| propertySheetClosed(); |
| return; |
| } |
| } else if (OUTLINE_PART_ID.equals(id)) { |
| if (activePage.findViewReference(id) == null) { |
| outlineClosed(); |
| return; |
| } |
| } |
| |
| // Does this look like a window getting maximized? |
| syncMaximizedState(activePage); |
| } |
| |
| @Override |
| public void partVisible(IWorkbenchPartReference partRef) { |
| IWorkbenchPage activePage = mWindow.getActivePage(); |
| if (activePage == null) { |
| return; |
| } |
| initialize(); |
| |
| String id = partRef.getId(); |
| if (mEditorMaximized) { |
| // Return to their non-maximized state |
| mEditorMaximized = false; |
| syncActive(); |
| } |
| |
| IWorkbenchPart part = partRef.getPart(false /*restore*/); |
| sync(part); |
| |
| // See partOpened() for an explanation |
| if (PROPERTY_SHEET_PART_ID.equals(id)) { |
| if (!mPropertiesOpen) { |
| propertySheetOpened(); |
| assert mPropertiesOpen; |
| } |
| } else if (OUTLINE_PART_ID.equals(id)) { |
| if (!mOutlineOpen) { |
| outlineOpened(); |
| assert mOutlineOpen; |
| } |
| } |
| } |
| |
| @Override |
| public void partInputChanged(IWorkbenchPartReference partRef) { |
| } |
| |
| @Override |
| public void partActivated(IWorkbenchPartReference partRef) { |
| IWorkbenchPart part = partRef.getPart(false); |
| if (part instanceof AndroidXmlEditor) { |
| ((AndroidXmlEditor)part).activated(); |
| } |
| } |
| |
| @Override |
| public void partBroughtToTop(IWorkbenchPartReference partRef) { |
| } |
| |
| @Override |
| public void partDeactivated(IWorkbenchPartReference partRef) { |
| IWorkbenchPart part = partRef.getPart(false); |
| if (part instanceof AndroidXmlEditor) { |
| ((AndroidXmlEditor)part).deactivated(); |
| } |
| } |
| } |