/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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/legal/epl-v10.html
*
* 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.google.dart.tools.designer.editor;
import com.google.common.collect.Lists;
import com.google.dart.tools.ui.web.html.HtmlEditor;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.part.MultiPageEditorPart;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.wb.internal.core.DesignerPlugin;
import org.eclipse.wb.internal.core.editor.DesignComposite;
import org.eclipse.wb.internal.core.preferences.IPreferenceConstants;
import org.eclipse.wb.internal.core.utils.execution.ExecutionUtils;
import org.eclipse.wb.internal.core.utils.execution.RunnableEx;
import org.eclipse.wb.internal.core.utils.external.ExternalFactoriesHelper;
import org.eclipse.wb.internal.core.utils.ui.GridDataFactory;
import org.eclipse.wb.internal.core.utils.ui.GridLayoutFactory;
import org.eclipse.wb.internal.core.utils.ui.TabFolderDecorator;
import org.eclipse.wb.internal.core.utils.ui.UiUtils;
import org.eclipse.wb.internal.core.views.IDesignCompositeProvider;
import java.util.List;
/**
* Editor for any XML based UI.
*
* @author scheglov_ke
* @coverage XML.editor
*/
public abstract class AbstractXmlEditor extends MultiPageEditorPart implements
IDesignCompositeProvider {
private static final String CONTEXT_ID = "com.google.dart.tools.designer.editorScope";
protected HtmlEditor m_xmlEditor;
private SourcePage m_sourcePage;
private XmlDesignPage m_designPage;
private final List<IXmlEditorPage> m_additionalPages = Lists.newArrayList();
private IXmlEditorPage m_activePage;
// private String m_cleanSource;
private Control m_partControl;
////////////////////////////////////////////////////////////////////////////
//
// Constructor
//
////////////////////////////////////////////////////////////////////////////
public AbstractXmlEditor() {
DesignerPlugin.configurePreEditor();
}
////////////////////////////////////////////////////////////////////////////
//
// Life cycle
//
////////////////////////////////////////////////////////////////////////////
@Override
public void dispose() {
// dispose pages
m_sourcePage.dispose();
m_designPage.dispose();
for (IXmlEditorPage page : m_additionalPages) {
page.dispose();
}
// clear page references (sometimes top level editor leaks)
{
m_activePage = null;
m_sourcePage = null;
m_designPage = null;
m_additionalPages.clear();
}
// continue
super.dispose();
}
@Override
protected void setInput(IEditorInput input) {
super.setInput(input);
initializeTitle(input);
}
private void initializeTitle(IEditorInput input) {
if (input != null) {
String title = input.getName();
setPartName(title);
}
}
////////////////////////////////////////////////////////////////////////////
//
// EditorPart
//
////////////////////////////////////////////////////////////////////////////
@Override
public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException {
if (!(editorInput instanceof IFileEditorInput)) {
throw new PartInitException("Invalid Input: Must be IFileEditorInput");
}
super.init(site, editorInput);
}
////////////////////////////////////////////////////////////////////////////
//
// Access
//
////////////////////////////////////////////////////////////////////////////
/**
* @return the {@link IDocument} from XML editor.
*/
public final IDocument getDocument() {
return m_xmlEditor.getDocument();
}
/**
* @return the {@link SourcePage} which is used as "Source" page.
*/
public final SourcePage getSourcePage() {
return m_sourcePage;
}
/**
* @return the {@link XmlDesignPage} which is used as "Design" page.
*/
public final XmlDesignPage getDesignPage() {
return m_designPage;
}
/**
* @return the top level {@link Control} of this {@link AbstractXmlEditor} part.
*/
public Control getPartControl() {
return m_partControl;
}
////////////////////////////////////////////////////////////////////////////
//
// IAdaptable
//
////////////////////////////////////////////////////////////////////////////
@Override
@SuppressWarnings("rawtypes")
public Object getAdapter(Class adapter) {
if (ITextEditor.class.isAssignableFrom(adapter)) {
return m_xmlEditor;
}
return super.getAdapter(adapter);
}
////////////////////////////////////////////////////////////////////////////
//
// IDesignCompositeProvider
//
////////////////////////////////////////////////////////////////////////////
@Override
public DesignComposite getDesignComposite() {
return m_designPage.getDesignComposite();
}
////////////////////////////////////////////////////////////////////////////
//
// Save
//
////////////////////////////////////////////////////////////////////////////
@Override
public void doSave(IProgressMonitor monitor) {
// rememberSourceContent();
m_xmlEditor.doSave(monitor);
if (m_splitRefreshStrategy.shouldOnSave()) {
m_designPage.updateGEF();
}
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
@Override
public void doSaveAs() {
}
////////////////////////////////////////////////////////////////////////////
//
// Utils
//
////////////////////////////////////////////////////////////////////////////
/**
* @return <code>true</code> "Source" page should be first.
*/
private boolean isSourceFirst() {
int layout = DesignerPlugin.getPreferences().getInt(IPreferenceConstants.P_EDITOR_LAYOUT);
return layout == IPreferenceConstants.V_EDITOR_LAYOUT_PAGES_SOURCE
|| layout == IPreferenceConstants.V_EDITOR_LAYOUT_SPLIT_HORIZONTAL_SOURCE
|| layout == IPreferenceConstants.V_EDITOR_LAYOUT_SPLIT_VERTICAL_SOURCE;
}
/**
* @return <code>true</code> if "page mode".
*/
private boolean isPagesMode() {
int layout = DesignerPlugin.getPreferences().getInt(IPreferenceConstants.P_EDITOR_LAYOUT);
return layout == IPreferenceConstants.V_EDITOR_LAYOUT_PAGES_SOURCE
|| layout == IPreferenceConstants.V_EDITOR_LAYOUT_PAGES_DESIGN;
}
/**
* @return <code>true</code> "split mode".
*/
private boolean isSplitMode() {
int layout = DesignerPlugin.getPreferences().getInt(IPreferenceConstants.P_EDITOR_LAYOUT);
return layout == IPreferenceConstants.V_EDITOR_LAYOUT_SPLIT_HORIZONTAL_DESIGN
|| layout == IPreferenceConstants.V_EDITOR_LAYOUT_SPLIT_HORIZONTAL_SOURCE
|| layout == IPreferenceConstants.V_EDITOR_LAYOUT_SPLIT_VERTICAL_DESIGN
|| layout == IPreferenceConstants.V_EDITOR_LAYOUT_SPLIT_VERTICAL_SOURCE;
}
/**
* @return <code>true</code> horizontal "split mode".
*/
private boolean isSplitModeHorizontal() {
int layout = DesignerPlugin.getPreferences().getInt(IPreferenceConstants.P_EDITOR_LAYOUT);
return layout == IPreferenceConstants.V_EDITOR_LAYOUT_SPLIT_HORIZONTAL_DESIGN
|| layout == IPreferenceConstants.V_EDITOR_LAYOUT_SPLIT_HORIZONTAL_SOURCE;
}
////////////////////////////////////////////////////////////////////////////
//
// Pages
//
////////////////////////////////////////////////////////////////////////////
private Composite m_splitSourceContainer;
private SashForm m_splitSashForm;
private final IRefreshStrategy m_splitRefreshStrategy = new IRefreshStrategy() {
@Override
public boolean shouldImmediately() {
return false;
}
@Override
public boolean shouldWithDelay() {
return getPreferenceDelay() > 0;
}
@Override
public boolean shouldOnSave() {
return getPreferenceDelay() <= 0;
}
@Override
public int getDelay() {
int delay = getPreferenceDelay();
return Math.max(delay, 250);
}
private int getPreferenceDelay() {
IPreferenceStore preferences = DesignerPlugin.getPreferences();
return preferences.getInt(IPreferenceConstants.P_EDITOR_LAYOUT_SYNC_DELAY);
}
};
@Override
protected Composite createPageContainer(Composite parent) {
m_partControl = parent;
parent = super.createPageContainer(parent);
if (isPagesMode()) {
return parent;
} else {
int style = isSplitModeHorizontal() ? SWT.HORIZONTAL : SWT.VERTICAL;
m_splitSashForm = new SashForm(parent, style);
return m_splitSashForm;
}
}
@Override
protected void createPages() {
// decorate CTabFolder
{
CTabFolder tabFolder = (CTabFolder) getContainer();
TabFolderDecorator.decorate(this, tabFolder);
}
// do create pages
if (isSourceFirst()) {
createPageXml();
createPageDesign(1);
m_sourcePage.setPageIndex(0);
m_designPage.setPageIndex(1);
} else {
createPageXml();
createPageDesign(0);
m_designPage.getControl().moveAbove(null);
m_sourcePage.setPageIndex(1);
m_designPage.setPageIndex(0);
}
createAdditionalPages();
// activate page
activateEditorContext();
// tweak for "split mode"
if (isSplitMode()) {
createPageSplit();
}
}
/**
* Updates UI for "split mode".
*/
private void createPageSplit() {
trackSourceActivation();
// prepare Composite for "Source" control
{
m_splitSourceContainer = new Composite(m_splitSashForm, SWT.NONE);
// layout and separator
if (isSplitModeHorizontal()) {
GridLayoutFactory.create(m_splitSourceContainer).columns(2).noMargins().noSpacing();
{
LineControl separator = new LineControl(m_splitSourceContainer, SWT.VERTICAL);
GridDataFactory.create(separator).grabV().fillV();
}
} else {
GridLayoutFactory.create(m_splitSourceContainer).columns(1).noMargins().noSpacing();
{
LineControl separator = new LineControl(m_splitSourceContainer, SWT.HORIZONTAL);
GridDataFactory.create(separator).grabH().fillH();
}
}
// sash
m_splitSashForm.setWeights(new int[] {60, 40});
}
// move "Source" control to "sash"
int sourceIndex = m_sourcePage.getPageIndex();
Control control = getControl(sourceIndex);
{
control.setParent(m_splitSourceContainer);
control.setVisible(true);
GridDataFactory.create(control).grab().fill();
}
// if "Source" first, then move it
if (isSourceFirst()) {
control.moveAbove(null);
m_splitSourceContainer.moveAbove(null);
}
// remove "Source" tab
{
CTabFolder tabFolder = (CTabFolder) getContainer();
// dispose "Source" item
CTabItem item = tabFolder.getItem(sourceIndex);
item.dispose();
// show "Design" tab
tabFolder.setSelection(0);
// if only one tab, don't show tabs
if (tabFolder.getItems().length == 1) {
tabFolder.setTabHeight(0);
}
}
// "Design" is always active
{
m_designPage.setActive(true);
m_designPage.forceDocumentListener();
m_designPage.setRefreshStrategy(m_splitRefreshStrategy);
m_designPage.setActive(false);
}
// activate "Source"
pageChange(m_sourcePage.getPageIndex());
m_xmlEditor.setFocus();
}
private void trackSourceActivation() {
final Display display = Display.getDefault();
display.addFilter(SWT.MouseDown, new Listener() {
@Override
public void handleEvent(Event event) {
// editor was disposed
if (m_sourcePage == null) {
display.removeFilter(SWT.MouseDown, this);
return;
}
// track location in "source" or "design"
Composite sourceControl = (Composite) m_sourcePage.getControl();
Composite designControl = getContainer();
if (UiUtils.isChildOf(sourceControl, event.widget)) {
int pageIndex = m_sourcePage.getPageIndex();
pageChange(pageIndex);
m_designPage.setRefreshStrategy(m_splitRefreshStrategy);
}
if (UiUtils.isChildOf(designControl, event.widget)) {
int pageIndex = m_designPage.getPageIndex();
pageChange(pageIndex);
m_designPage.setRefreshStrategy(IRefreshStrategy.IMMEDIATELY);
}
}
});
}
@Override
public int getActivePage() {
int pageIndex = super.getActivePage();
if (isSplitMode() && m_sourcePage != null) {
int sourcePageIndex = m_sourcePage.getPageIndex();
if (m_activePage == m_sourcePage) {
return sourcePageIndex;
}
if (pageIndex >= sourcePageIndex) {
pageIndex++;
}
}
return pageIndex;
}
@Override
protected int getPageCount() {
int pageCount = super.getPageCount();
if (isSplitMode() && m_sourcePage != null) {
boolean isSourceExtracted = !UiUtils.isChildOf(getContainer(), m_sourcePage.getControl());
if (isSourceExtracted) {
pageCount++;
}
}
return pageCount;
}
@Override
protected Control getControl(int pageIndex) {
if (isSplitMode() && m_sourcePage != null) {
int sourcePageIndex = m_sourcePage.getPageIndex();
if (pageIndex == sourcePageIndex) {
return m_sourcePage.getControl();
}
if (pageIndex > sourcePageIndex) {
pageIndex--;
}
}
return super.getControl(pageIndex);
}
@Override
protected IEditorPart getEditor(int pageIndex) {
if (isSplitMode() && m_sourcePage != null) {
int sourcePageIndex = m_sourcePage.getPageIndex();
if (pageIndex == sourcePageIndex) {
return m_xmlEditor;
}
if (pageIndex > sourcePageIndex) {
pageIndex--;
}
}
return super.getEditor(pageIndex);
}
/**
* Activates context of our XML editor.
*/
private void activateEditorContext() {
IContextService contextService = (IContextService) getSite().getService(IContextService.class);
if (contextService != null) {
contextService.activateContext(CONTEXT_ID);
}
}
/**
* Creates "XML Source" page of multi-page editor.
*/
private void createPageXml() {
ExecutionUtils.runLog(new RunnableEx() {
@Override
public void run() throws Exception {
m_xmlEditor = createEditorXml();
int pageIndex = addPage(m_xmlEditor, getEditorInput());
Control control = getControl(pageIndex);
// create XML page
m_sourcePage = new SourcePage(m_xmlEditor, control);
m_sourcePage.initialize(AbstractXmlEditor.this);
m_sourcePage.setPageIndex(pageIndex);
// configure page tab
setPageText(pageIndex, m_sourcePage.getName());
setPageImage(pageIndex, m_sourcePage.getImage());
// // track changes and update dirty flag
// trackDirty();
}
});
}
/**
* @return the {@link ITextEditor} to use as HTML editor.
*/
protected HtmlEditor createEditorXml() {
return new HtmlEditor();
}
/**
* Creates "Design" page of multi-page editor.
*/
private void createPageDesign(int pageIndex) {
m_designPage = createDesignPage();
addPage(pageIndex, m_designPage);
}
/**
* Create additional pages.
*/
private void createAdditionalPages() {
List<IXmlEditorPageFactory> factories = ExternalFactoriesHelper.getElementsInstances(
IXmlEditorPageFactory.class,
"org.eclipse.wb.core.xml.XMLEditorPageFactories",
"factory");
for (IXmlEditorPageFactory factory : factories) {
factory.createPages(this, m_additionalPages);
}
// initialize created pages
for (IXmlEditorPage page : m_additionalPages) {
page.initialize(this);
addPage(page);
}
}
/**
* Add {@link IXmlEditorPage} page to this editor.
*/
private void addPage(IXmlEditorPage page) {
int index = getPageCount();
addPage(index, page);
}
/**
* Add {@link IXmlEditorPage} page to this editor.
*/
private void addPage(int pageIndex, IXmlEditorPage page) {
page.initialize(this);
// create/add control
Control control = page.createControl(getContainer());
addPage(pageIndex, control);
page.setPageIndex(pageIndex);
// presentation
setPageText(pageIndex, page.getName());
setPageImage(pageIndex, page.getImage());
}
/**
* @return the {@link XmlDesignPage} to be used as "Design" page.
*/
protected abstract XmlDesignPage createDesignPage();
// ////////////////////////////////////////////////////////////////////////////
// //
// // Dirty flag tracking
// //
// ////////////////////////////////////////////////////////////////////////////
// /**
// * WST SSE has bug with undo/redo and "dirty" flag. So, we need to track "dirty" flag manually.
// * <p>
// * https://bugs.eclipse.org/bugs/show_bug.cgi?id=138100
// */
// private void trackDirty() {
// rememberSourceContent();
// getDocument().addDocumentListener(new IDocumentListener() {
// @Override
// public void documentChanged(DocumentEvent event) {
// firePropertyChange(PROP_DIRTY);
// }
//
// @Override
// public void documentAboutToBeChanged(DocumentEvent event) {
// }
// });
// }
//
// private void rememberSourceContent() {
// m_cleanSource = getDocument().get();
// }
//
// @Override
// public boolean isDirty() {
// return !getDocument().get().equals(m_cleanSource);
// }
////////////////////////////////////////////////////////////////////////////
//
// Page access
//
////////////////////////////////////////////////////////////////////////////
@Override
protected void pageChange(int pageIndex) {
// tweak page index
if (isSplitMode() && pageIndex >= m_sourcePage.getPageIndex()) {
String callerMethodName = Thread.currentThread().getStackTrace()[2].getMethodName();
boolean isUserPageChangeRequest = callerMethodName.equals("widgetSelected");
if (isUserPageChangeRequest) {
pageIndex++;
}
}
// prepare new active page
IXmlEditorPage activePage = m_activePage;
if (pageIndex == m_sourcePage.getPageIndex()) {
activePage = m_sourcePage;
} else if (pageIndex == m_designPage.getPageIndex()) {
activePage = m_designPage;
} else {
for (IXmlEditorPage page : m_additionalPages) {
if (pageIndex == page.getPageIndex()) {
activePage = page;
break;
}
}
}
// do nothing, if this page is already active
// else this will cause loosing "..." button clicking in properties table
if (activePage == m_activePage) {
return;
}
// deactivate active page
if (m_activePage != null) {
m_activePage.setActive(false);
m_activePage = null;
}
// activate new active page
m_activePage = activePage;
// We use "fake" item because we can not override private getItem() method.
// Currently (20110623) this item is not used for anything useful for WindowBuilder.
{
CTabItem fakeItem = isSplitMode() ? new CTabItem((CTabFolder) getContainer(), SWT.NONE)
: null;
try {
super.pageChange(pageIndex);
} finally {
if (fakeItem != null) {
fakeItem.dispose();
}
}
}
// activate new active page
m_activePage = activePage;
if (m_activePage != null) {
m_activePage.setActive(true);
}
}
/**
* Switches between "Source" and "Design" pages.
*/
public void switchSourceDesign() {
if (m_activePage == m_sourcePage) {
setActivePage(m_designPage);
} else {
setActivePage(m_sourcePage);
}
}
/**
* Shows "XML Source" page.
*/
public void showSource() {
if (m_activePage != m_sourcePage) {
setActivePage(m_sourcePage);
}
}
/**
* Shows "Design" page.
*/
public void showDesign() {
if (m_activePage != m_designPage) {
setActivePage(m_designPage);
}
}
/**
* Shows given {@link IXmlEditorPage}.
*/
private void setActivePage(IXmlEditorPage page) {
int pageIndex = page.getPageIndex();
if (isPagesMode()) {
setActivePage(pageIndex);
} else {
pageChange(pageIndex);
}
page.getControl().setFocus();
}
/**
* Moves cursor to given position in "XML Source" editor.
*/
public void showSourcePosition(final int position) {
ExecutionUtils.runLogLater(new RunnableEx() {
@Override
public void run() throws Exception {
m_xmlEditor.selectAndReveal(position, 0);
}
});
}
}