blob: ccd12c7f2d41734db8a2a296563eccb4e5ac635e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2010 Tasktop Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Xiaoyang Guan - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.trac.wiki.editor;
import java.util.Iterator;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.commons.net.AuthenticationType;
import org.eclipse.mylyn.commons.ui.CommonImages;
import org.eclipse.mylyn.internal.tasks.ui.editors.RepositoryTextViewerConfiguration;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.internal.trac.core.TracCorePlugin;
import org.eclipse.mylyn.internal.trac.core.model.TracWikiPage;
import org.eclipse.mylyn.internal.trac.ui.editor.TracRenderingEngine;
import org.eclipse.mylyn.internal.trac.wiki.TracWikiPlugin;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.ui.TasksUiImages;
import org.eclipse.mylyn.tasks.ui.editors.AbstractRenderingEngine;
import org.eclipse.mylyn.tasks.ui.editors.BrowserFormPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.LocationAdapter;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.events.IExpansionListener;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess;
import org.eclipse.ui.texteditor.MarkerAnnotationPreferences;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
/**
* @author Xiaoyang Guan
*/
public class TracWikiPageEditor extends FormEditor {
public static final String ID_EDITOR = "org.eclipse.mylyn.trac.ui.editor.wikipage";
private TaskRepository repository;
private TracWikiPage page;
private final WikiSourceEditor wikiSourceEditor;
private final BrowserFormPage browserPage;
private class WikiSourceEditor extends FormPage {
private static final String ID = "org.eclipse.mylyn.trac.ui.editor.wikisource";
private static final String TITLE = "Wiki Page Source";
private static final String LABEL_PREVIEW = "Page Preview";
private static final String LABEL_SOURCE = "Page Source";
private static final String LABEL_BUTTON_PREVIEW = "Preview";
private static final String LABEL_BUTTON_SUBMIT = "Submit";
private static final int PREVIEW_BROWSER_HEIGHT = 10 * 14;
private static final int DEFAULT_WIDTH = 79 * 7; // 500;
private ScrolledForm form;
private FormToolkit toolkit;
private Composite editorComposite;
private Section previewSection;
private Browser previewBrowser;
private TextViewer sourceEditor;
private Button previewButton;
private Button submitButton;
protected boolean isDirty;
public WikiSourceEditor(FormEditor editor) {
super(editor, ID, TITLE);
}
@Override
protected void createFormContent(IManagedForm managedForm) {
super.createFormContent(managedForm);
form = managedForm.getForm();
toolkit = managedForm.getToolkit();
editorComposite = form.getBody();
GridLayout editorLayout = new GridLayout();
editorComposite.setLayout(editorLayout);
editorComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
createPreviewSection(editorComposite);
createSourceSection(editorComposite);
}
private void createPreviewSection(Composite parent) {
previewSection = toolkit.createSection(parent, ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE);
previewSection.setText(LABEL_PREVIEW);
previewSection.setExpanded(false);
previewSection.setLayout(new GridLayout());
previewSection.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
previewSection.addExpansionListener(new IExpansionListener() {
public void expansionStateChanging(ExpansionEvent e) {
form.reflow(true);
}
public void expansionStateChanged(ExpansionEvent e) {
form.reflow(true);
}
});
Composite container = toolkit.createComposite(previewSection);
previewSection.setClient(container);
container.setLayout(new GridLayout());
container.setLayoutData(new GridData(GridData.FILL_BOTH));
previewBrowser = addBrowser(container, SWT.NONE);
previewBrowser.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
GridData previewBrowserData = new GridData(GridData.FILL_BOTH);
previewBrowserData.heightHint = PREVIEW_BROWSER_HEIGHT;
previewBrowser.setLayoutData(previewBrowserData);
toolkit.paintBordersFor(container);
}
private void createSourceSection(Composite parent) {
Section section = toolkit.createSection(parent, ExpandableComposite.TITLE_BAR);
section.setText(LABEL_SOURCE);
section.setExpanded(true);
section.setLayout(new GridLayout());
section.setLayoutData(new GridData(GridData.FILL_BOTH));
section.addExpansionListener(new IExpansionListener() {
public void expansionStateChanging(ExpansionEvent e) {
form.reflow(true);
}
public void expansionStateChanged(ExpansionEvent e) {
form.reflow(true);
}
});
Composite container = toolkit.createComposite(section);
section.setClient(container);
container.setLayout(new GridLayout());
container.setLayoutData(new GridData(GridData.FILL_BOTH));
sourceEditor = new SourceViewer(container, null, SWT.FLAT | SWT.MULTI | SWT.WRAP | SWT.V_SCROLL);
sourceEditor.getControl().setLayoutData(new GridData(GridData.FILL_BOTH));
sourceEditor.getControl().setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
sourceEditor.setEditable(true);
sourceEditor.getTextWidget().addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
if (!isDirty()) {
markDirty(true);
setSubmitEnabled(true);
}
}
});
StyledText styledText = sourceEditor.getTextWidget();
GridDataFactory.fillDefaults().hint(DEFAULT_WIDTH, SWT.DEFAULT).grab(true, true).applyTo(styledText);
IAnnotationAccess annotationAccess = new DefaultMarkerAnnotationAccess();
final SourceViewerDecorationSupport support = new SourceViewerDecorationSupport(
(SourceViewer) sourceEditor, null, annotationAccess, EditorsUI.getSharedTextColors());
Iterator<?> e = new MarkerAnnotationPreferences().getAnnotationPreferences().iterator();
while (e.hasNext()) {
support.setAnnotationPreference((AnnotationPreference) e.next());
}
support.install(EditorsUI.getPreferenceStore());
styledText.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
support.uninstall();
}
});
TextSourceViewerConfiguration viewerConfig = new RepositoryTextViewerConfiguration(repository, null, true);
((SourceViewer) sourceEditor).configure(viewerConfig);
Document document = new Document(page.getContent());
((SourceViewer) sourceEditor).setDocument(document, new AnnotationModel());
createActionsLayout(container);
toolkit.paintBordersFor(container);
}
private void createActionsLayout(Composite parent) {
Composite buttonComposite = toolkit.createComposite(parent);
GridLayout buttonLayout = new GridLayout();
buttonLayout.numColumns = 2;
buttonComposite.setLayout(buttonLayout);
buttonComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
addActionButtons(buttonComposite);
}
private void addActionButtons(Composite buttonComposite) {
previewButton = toolkit.createButton(buttonComposite, LABEL_BUTTON_PREVIEW, SWT.NONE);
GridData previewButtonData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
previewButtonData.widthHint = 100;
previewButton.setLayoutData(previewButtonData);
previewButton.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
previewSection.setExpanded(true);
setText(previewBrowser, "loading preview...");
previewWiki(previewBrowser, sourceEditor.getTextWidget().getText());
}
});
submitButton = toolkit.createButton(buttonComposite, LABEL_BUTTON_SUBMIT, SWT.NONE);
GridData submitButtonData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
submitButtonData.widthHint = 100;
submitButton.setImage(CommonImages.getImage(TasksUiImages.REPOSITORY_SUBMIT));
submitButton.setLayoutData(submitButtonData);
submitButton.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
InputDialog commentDialog = new InputDialog(null, "Page Comment",
"Enter a Comment for the page (or cancel the submit):", null, null);
// cancel the comment dialog will cancel the submit
if (commentDialog.open() == Window.OK) {
page.getPageInfo().setComment(commentDialog.getValue());
boolean isAnonymouse = repository.getCredentials(AuthenticationType.REPOSITORY) == null;
page.getPageInfo().setAuthor(isAnonymouse ? "anonymous" : repository.getUserName());
page.setContent(sourceEditor.getTextWidget().getText());
submitToRepository();
}
}
});
setSubmitEnabled(false);
}
/**
* Copied from AbstractRepositoryTaskEditor
*
* @param enabled
*/
private void setSubmitEnabled(boolean enabled) {
if (submitButton != null && !submitButton.isDisposed()) {
submitButton.setEnabled(enabled);
if (enabled) {
submitButton.setToolTipText("Submit to " + repository.getRepositoryUrl());
}
}
}
public void submitToRepository() {
class SubmitPageJob extends Job {
private IStatus jobStatus;
public SubmitPageJob() {
super("upload wiki page");
}
public IStatus getStatus() {
return jobStatus;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
monitor.beginTask("Uploading wiki page", IProgressMonitor.UNKNOWN);
TracCorePlugin.getDefault()
.getConnector()
.getWikiHandler()
.postWikiPage(repository, page, monitor);
jobStatus = Status.OK_STATUS;
} catch (CoreException e) {
TasksUiInternal.displayStatus("Submit failed", e.getStatus());
jobStatus = e.getStatus();
} finally {
monitor.done();
}
return Status.OK_STATUS;
}
}
final SubmitPageJob submitJob = new SubmitPageJob();
submitJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
// refresh editor only if uploading the wiki page succeeded
if (submitJob.getStatus().isOK()) {
updateWikiPage();
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
public void run() {
if (browserPage.getBrowser() != null) {
browserPage.getBrowser().refresh();
}
setActivePage(BrowserFormPage.ID_EDITOR);
markDirty(false);
setSubmitEnabled(false);
}
});
}
}
});
submitJob.schedule();
}
@Override
public boolean isDirty() {
return isDirty;
}
protected void markDirty(boolean dirty) {
isDirty = dirty;
getManagedForm().dirtyStateChanged();
}
/*====== Copied/modified from AbstractRepositoryTaskEditor ======*/
private boolean ignoreLocationEvents = false;
private Browser addBrowser(Composite parent, int style) {
Browser browser = new Browser(parent, style);
// intercept links to open tasks in rich editor and urls in separate browser
browser.addLocationListener(new LocationAdapter() {
@Override
public void changing(LocationEvent event) {
// ignore events that are caused by manually setting the contents of the browser
if (ignoreLocationEvents) {
return;
}
if (event.location != null) {
event.doit = false;
// TODO: add open link support when this editor is moved into ..mylyn.tasks.ui.editors
//IHyperlink link = new TaskUrlHyperlink(
// new Region(0, 0)/* a fake region just to make constructor happy */, event.location);
//link.open();
}
}
});
return browser;
}
private void setText(Browser browser, String html) {
try {
ignoreLocationEvents = true;
browser.setText((html != null) ? html : "");
} finally {
ignoreLocationEvents = false;
}
}
private void previewWiki(final Browser browser, String sourceText) {
final class PreviewWikiJob extends Job {
private final String sourceText;
private String htmlText;
private IStatus jobStatus;
public PreviewWikiJob(String sourceText) {
super("Formatting Wiki Text");
if (sourceText == null) {
throw new IllegalArgumentException("source text must not be null");
}
this.sourceText = sourceText;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
AbstractRenderingEngine htmlRenderingEngine = new TracRenderingEngine();
jobStatus = Status.OK_STATUS;
try {
htmlText = htmlRenderingEngine.renderAsHtml(repository, sourceText, monitor);
} catch (CoreException e) {
jobStatus = e.getStatus();
}
return Status.OK_STATUS;
}
public String getHtmlText() {
return htmlText;
}
public IStatus getStatus() {
return jobStatus;
}
}
final PreviewWikiJob job = new PreviewWikiJob(sourceText);
job.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(final IJobChangeEvent event) {
if (!form.isDisposed()) {
if (job.getStatus().isOK()) {
getPartControl().getDisplay().asyncExec(new Runnable() {
public void run() {
WikiSourceEditor.this.setText(browser, job.getHtmlText());
//parentEditor.setMessage(null, IMessageProvider.NONE);
}
});
} else {
getPartControl().getDisplay().asyncExec(new Runnable() {
public void run() {
//parentEditor.setMessage(job.getStatus().getMessage(), IMessageProvider.ERROR);
}
});
}
}
super.done(event);
}
});
job.setUser(true);
job.schedule();
}
}
public TracWikiPageEditor() {
super();
wikiSourceEditor = new WikiSourceEditor(this);
browserPage = new BrowserFormPage(this, "Browser") {
@Override
protected String getUrl() {
return ((TracWikiPageEditorInput) getEditorInput()).getPageUrl();
}
};
}
protected void initializeEditor() {
TracWikiPageEditorInput editorInput = (TracWikiPageEditorInput) getEditorInput();
page = editorInput.getPage();
repository = editorInput.getRepository();
}
@Override
protected void addPages() {
initializeEditor();
try {
addPage(wikiSourceEditor);
addPage(browserPage);
setPartName(getEditorInput().getName());
setActivePage(BrowserFormPage.ID_EDITOR);
} catch (PartInitException e) {
StatusHandler.fail(new Status(IStatus.ERROR, TracWikiPlugin.ID_PLUGIN,
"Cannot create Trac Wiki page editor pages"));
}
}
@Override
public void doSave(IProgressMonitor monitor) {
MessageDialog.openInformation(getSite().getShell(), "Changes cannot be saved",
"Offline editting on wiki pages not supported yet.");
monitor.setCanceled(true);
}
@Override
public void doSaveAs() {
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
protected void updateWikiPage() {
Job updatePageJob = new Job("Update wiki page") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
monitor.beginTask("Downloading wiki page", IProgressMonitor.UNKNOWN);
TracWikiPage newPage = TracCorePlugin.getDefault()
.getConnector()
.getWikiHandler()
.getWikiPage(repository, page.getPageInfo().getPageName(), monitor);
if (newPage != null) {
((TracWikiPageEditorInput) getEditorInput()).setPage(newPage);
} else {
TasksUiInternal.displayStatus("Download failed", new Status(IStatus.ERROR,
TracCorePlugin.ID_PLUGIN, "Unable to retrieve wiki page "
+ page.getPageInfo().getPageName()));
}
} catch (CoreException e) {
TasksUiInternal.displayStatus("Download failed", e.getStatus());
} finally {
monitor.done();
}
return Status.OK_STATUS;
}
};
updatePageJob.schedule();
}
}