/* * 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: *
* 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
* 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.
*
* 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();
}
}
}