blob: f076305a1cbe315ea085611bdf379a1821e47ed4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2014 Tasktop Technologies and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.tasks.ui.editors;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.util.Date;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.mylyn.commons.ui.CommonUiUtil;
import org.eclipse.mylyn.commons.ui.PlatformUiUtil;
import org.eclipse.mylyn.commons.ui.compatibility.CommonThemes;
import org.eclipse.mylyn.commons.workbench.editors.CommonTextSupport;
import org.eclipse.mylyn.commons.workbench.forms.CommonFormUtil;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.internal.tasks.ui.wizards.NewAttachmentWizardDialog;
import org.eclipse.mylyn.internal.tasks.ui.wizards.TaskAttachmentWizard.Mode;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentSource;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper;
import org.eclipse.mylyn.tasks.ui.editors.AbstractTaskEditorPage;
import org.eclipse.mylyn.tasks.ui.editors.TaskFormPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
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.swt.widgets.Menu;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Scrollable;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.IFormPage;
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.forms.widgets.SharedScrolledComposite;
import org.eclipse.ui.internal.WorkbenchPage;
import org.eclipse.ui.internal.forms.widgets.FormUtil;
/**
* @author Steffen Pingel
* @author Leo Dos Santos
*/
public class EditorUtil {
private static final int PAGE_H_SCROLL_INCREMENT = 64;
public static final int HEADER_COLUMN_MARGIN = 6;
// public static final String DATE_FORMAT = "yyyy-MM-dd";
//
// public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
static final String KEY_MARKER = "marker"; //$NON-NLS-1$
static final String KEY_TEXT_VIEWER = "textViewer"; //$NON-NLS-1$
public static final int MAXIMUM_HEIGHT = 140;
public static final int MAXIMUM_WIDTH = 300;
// XXX why is this required?
public static final Font TEXT_FONT = JFaceResources.getDefaultFont();
public static final String KEY_TOGGLE_TO_MAXIMIZE_ACTION = "maximizeAction"; //$NON-NLS-1$
static boolean canDoGlobalAction(String actionId, TextViewer textViewer) {
if (actionId.equals(ActionFactory.CUT.getId())) {
return textViewer.canDoOperation(ITextOperationTarget.CUT);
} else if (actionId.equals(ActionFactory.COPY.getId())) {
return textViewer.canDoOperation(ITextOperationTarget.COPY);
} else if (actionId.equals(ActionFactory.PASTE.getId())) {
return textViewer.canDoOperation(ITextOperationTarget.PASTE);
} else if (actionId.equals(ActionFactory.DELETE.getId())) {
return textViewer.canDoOperation(ITextOperationTarget.DELETE);
} else if (actionId.equals(ActionFactory.UNDO.getId())) {
return textViewer.canDoOperation(ITextOperationTarget.UNDO);
} else if (actionId.equals(ActionFactory.REDO.getId())) {
return textViewer.canDoOperation(ITextOperationTarget.REDO);
} else if (actionId.equals(ActionFactory.SELECT_ALL.getId())) {
return textViewer.canDoOperation(ITextOperationTarget.SELECT_ALL);
}
return false;
}
/**
* @deprecated use {@link CommonTextSupport#canPerformAction(String, Control)} instead
*/
@Deprecated
public static boolean canPerformAction(String actionId, Control focusControl) {
return CommonTextSupport.canPerformAction(actionId, focusControl);
}
/**
* @deprecated use {@link CommonTextSupport#doAction(String, Control)} instead
*/
@Deprecated
public static void doAction(String actionId, Control focusControl) {
CommonTextSupport.doAction(actionId, focusControl);
}
private static Control findControl(Composite composite, String key) {
if (!composite.isDisposed()) {
for (Control child : composite.getChildren()) {
if (key.equals(getMarker(child))) {
return child;
}
if (child instanceof Composite) {
Control found = findControl((Composite) child, key);
if (found != null) {
return found;
}
}
}
}
return null;
}
/**
* Scroll to a specified piece of text
*
* @param control
* The StyledText to scroll to
*/
public static void focusOn(ScrolledForm form, Control control) {
int pos = 0;
control.setEnabled(true);
control.setFocus();
control.forceFocus();
while (control != null && control != form.getBody()) {
pos += control.getLocation().y;
control = control.getParent();
}
pos = pos - 60; // form.getOrigin().y;
if (!form.getBody().isDisposed()) {
form.setOrigin(0, pos);
}
}
static DateFormat getDateFormat() {
return DateFormat.getDateInstance(DateFormat.MEDIUM);
}
static String formatDate(Date date) {
return getDateFormat().format(date);
}
static String formatDateTime(Date date) {
return getDateTimeFormat().format(date);
}
static DateFormat getDateTimeFormat() {
return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
}
public static Control getFocusControl(IFormPage page) {
if (page == null) {
return null;
}
IManagedForm form = page.getManagedForm();
if (form == null) {
return null;
}
Control control = form.getForm();
if (control == null || control.isDisposed()) {
return null;
}
Display display = control.getDisplay();
Control focusControl = display.getFocusControl();
if (focusControl == null || focusControl.isDisposed()) {
return null;
}
return focusControl;
}
public static String getMarker(Widget widget) {
return (String) widget.getData(KEY_MARKER);
}
/**
* @deprecated use {@link CommonTextSupport#getTextViewer(Widget)} instead
*/
@Deprecated
public static TextViewer getTextViewer(Widget widget) {
return CommonTextSupport.getTextViewer(widget);
}
public static NewAttachmentWizardDialog openNewAttachmentWizard(final AbstractTaskEditorPage page, Mode mode,
AbstractTaskAttachmentSource source) {
TaskAttributeMapper mapper = page.getModel().getTaskData().getAttributeMapper();
TaskAttribute attribute = mapper.createTaskAttachment(page.getModel().getTaskData());
final NewAttachmentWizardDialog dialog = TasksUiInternal.openNewAttachmentWizard(page.getSite().getShell(),
page.getTaskRepository(), page.getTask(), attribute, mode, source);
dialog.getShell().addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
if (dialog.getReturnCode() == Window.OK) {
page.getTaskEditor().refreshPages();
}
}
});
return dialog;
}
/**
* Selects the given object in the editor.
*
* @param o
* The object to be selected.
* @param highlight
* Whether or not the object should be highlighted.
*/
public static boolean reveal(ScrolledForm form, String key) {
Control control = findControl(form.getBody(), key);
if (control != null) {
// expand all children
if (control instanceof ExpandableComposite) {
ExpandableComposite ex = (ExpandableComposite) control;
if (!ex.isExpanded()) {
CommonFormUtil.setExpanded(ex, true);
}
}
// expand all parents of control
Composite comp = control.getParent();
while (comp != null) {
if (comp instanceof Section) {
if (!((Section) comp).isExpanded()) {
((Section) comp).setExpanded(true);
}
} else if (comp instanceof ExpandableComposite) {
ExpandableComposite ex = (ExpandableComposite) comp;
if (!ex.isExpanded()) {
CommonFormUtil.setExpanded(ex, true);
}
// HACK: This is necessary
// due to a bug in SWT's ExpandableComposite.
// 165803: Expandable bars should expand when clicking anywhere
// https://bugs.eclipse.org/bugs/show_bug.cgi?taskId=165803
if (ex.getData() != null && ex.getData() instanceof Composite) {
((Composite) ex.getData()).setVisible(true);
}
}
comp = comp.getParent();
}
focusOn(form, control);
}
return true;
}
/**
* @deprecated Use {@link CommonUiUtil#setEnabled(Composite,boolean)} instead
*/
@Deprecated
public static void setEnabledState(Composite composite, boolean enabled) {
CommonUiUtil.setEnabled(composite, enabled);
}
public static void setMarker(Widget widget, String text) {
widget.setData(KEY_MARKER, text);
}
/**
* @deprecated use {@link CommonTextSupport#setTextViewer(Widget, TextViewer)} instead
*/
@Deprecated
public static void setTextViewer(Widget widget, TextViewer textViewer) {
CommonTextSupport.setTextViewer(widget, textViewer);
}
/**
* Programmatically expand the provided ExpandableComposite, using reflection to fire the expansion listeners (see
* bug#70358)
*
* @param comp
* @deprecated Use {@link CommonFormUtil#setExpanded(ExpandableComposite,boolean)} instead
*/
@Deprecated
public static void toggleExpandableComposite(boolean expanded, ExpandableComposite comp) {
CommonFormUtil.setExpanded(comp, expanded);
}
/**
* @deprecated Use {@link CommonFormUtil#disableScrollingOnFocus(ScrolledForm)} instead
*/
@Deprecated
public static void disableScrollingOnFocus(ScrolledForm form) {
CommonFormUtil.disableScrollingOnFocus(form);
}
/**
* @deprecated Use {@link CommonFormUtil#ensureVisible(Control)} instead
*/
@Deprecated
public static void ensureVisible(Control control) {
CommonFormUtil.ensureVisible(control);
}
// copied from Section.reflow()
public static void reflow(Control control) {
Composite c = control.getParent();
while (c != null) {
c.setRedraw(false);
c = c.getParent();
if (c instanceof SharedScrolledComposite) {
break;
}
}
c = control.getParent();
while (c != null) {
c.layout(true);
c = c.getParent();
if (c instanceof SharedScrolledComposite) {
((SharedScrolledComposite) c).reflow(true);
break;
}
}
c = control.getParent();
while (c != null) {
c.setRedraw(true);
c = c.getParent();
if (c instanceof SharedScrolledComposite) {
break;
}
}
}
public static Composite getLayoutAdvisor(AbstractTaskEditorPage page) {
Composite layoutAdvisor = page.getEditorComposite();
do {
layoutAdvisor = layoutAdvisor.getParent();
} while (!(layoutAdvisor instanceof CTabFolder || layoutAdvisor.getClass()
.getName()
.equals("org.eclipse.e4.ui.widgets.CTabFolder"))); //$NON-NLS-1$
return layoutAdvisor.getParent();
}
/**
* Recursively sets the menu of all children of <code>composite</code>.
*
* @deprecated Use {@link CommonUiUtil#setMenu(Composite,Menu)} instead
*/
@Deprecated
public static void setMenu(Composite composite, Menu menu) {
CommonUiUtil.setMenu(composite, menu);
}
/**
* @deprecated use {@link RowLayout#center} instead
*/
@Deprecated
public static void center(RowLayout rowLayout) {
try {
Field field = RowLayout.class.getDeclaredField("center"); //$NON-NLS-1$
field.set(rowLayout, Boolean.TRUE);
} catch (Throwable e) {
// ignore
}
}
public static Composite createBorder(Composite composite, final FormToolkit toolkit, boolean paintBorder) {
// create composite to hold rounded border
final Composite roundedBorder = toolkit.createComposite(composite);
if (paintBorder) {
roundedBorder.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
e.gc.setForeground(toolkit.getColors().getBorderColor());
Point size = roundedBorder.getSize();
e.gc.drawRoundRectangle(0, 2, size.x - 1, size.y - 5, 5, 5);
}
});
roundedBorder.setLayout(GridLayoutFactory.fillDefaults().margins(4, 6).create());
} else {
roundedBorder.setLayout(GridLayoutFactory.fillDefaults().margins(0, 6).create());
}
GridDataFactory.fillDefaults()
.align(SWT.FILL, SWT.BEGINNING)
.hint(EditorUtil.MAXIMUM_WIDTH, SWT.DEFAULT)
.grab(true, false)
.applyTo(roundedBorder);
return roundedBorder;
}
public static Composite createBorder(Composite composite, final FormToolkit toolkit) {
return createBorder(composite, toolkit, true);
}
public static Font setHeaderFontSizeAndStyle(Control text) {
GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(text);
// .hint(EditorUtil.MAXIMUM_WIDTH, SWT.DEFAULT)
float sizeFactor = 1.1f;
Font initialFont = text.getFont();
FontData[] fontData = initialFont.getFontData();
for (FontData element : fontData) {
element.setHeight((int) (element.getHeight() * sizeFactor));
element.setStyle(element.getStyle() | SWT.BOLD);
}
final Font textFont = new Font(text.getDisplay(), fontData);
text.setFont(textFont);
text.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
textFont.dispose();
}
});
Color color = PlatformUI.getWorkbench()
.getThemeManager()
.getCurrentTheme()
.getColorRegistry()
.get(CommonThemes.COLOR_COMPLETED);
text.setForeground(color);
return textFont;
}
public static GridData getTextControlLayoutData(TaskFormPage page, Control control, boolean expandVertically) {
final GridData gd = new GridData();
// wrap text at this margin, see comment below
int width = getEditorWidth(page);
// the goal is to make the text viewer as big as the text so it does not require scrolling when first drawn
// on screen
Point size = control.computeSize(width, SWT.DEFAULT, true);
gd.widthHint = EditorUtil.MAXIMUM_WIDTH;
gd.minimumWidth = EditorUtil.MAXIMUM_WIDTH;
gd.horizontalAlignment = SWT.FILL;
gd.grabExcessHorizontalSpace = true;
// limit height to be avoid dynamic resizing of the text widget:
// MAXIMUM_HEIGHT < height < MAXIMUM_HEIGHT * 3
//gd.minimumHeight = AbstractAttributeEditor.MAXIMUM_HEIGHT;
gd.heightHint = Math.min(Math.max(EditorUtil.MAXIMUM_HEIGHT, size.y), EditorUtil.MAXIMUM_HEIGHT * 3);
if (expandVertically) {
gd.verticalAlignment = SWT.FILL;
gd.grabExcessVerticalSpace = true;
}
return gd;
// shrink the text control if the editor width is reduced, otherwise the text field will always keep it's original
// width and will cause the editor to have a horizonal scroll bar
// composite.addControlListener(new ControlAdapter() {
// @Override
// public void controlResized(ControlEvent e) {
// int width = sectionComposite.getSize().x;
// Point size = descriptionTextViewer.getTextWidget().computeSize(width, SWT.DEFAULT, true);
// // limit width to parent widget
// gd.widthHint = width;
// // limit height to avoid dynamic resizing of the text widget
// gd.heightHint = Math.min(Math.max(DESCRIPTION_HEIGHT, size.y), DESCRIPTION_HEIGHT * 4);
// sectionComposite.layout();
// }
// });
}
private static int getEditorWidth(TaskFormPage page) {
int widthHint = 0;
if (page.getManagedForm() != null && page.getManagedForm().getForm() != null) {
widthHint = page.getManagedForm().getForm().getClientArea().width - 90;
}
if (widthHint <= 0 && page.getEditor().getEditorSite() != null
&& page.getEditor().getEditorSite().getPage() != null) {
// EditorAreaHelper editorManager = ((WorkbenchPage) page.getEditor().getEditorSite().getPage()).getEditorPresentation();
// if (editorManager != null && editorManager.getLayoutPart() != null) {
// widthHint = editorManager.getLayoutPart().getControl().getBounds().width - 90;
// }
// XXX e4.0 this does not work, the composite is always null
Composite composite = ((WorkbenchPage) page.getEditor().getEditorSite().getPage()).getClientComposite();
if (composite != null) {
widthHint = composite.getBounds().width - 90;
}
}
if (widthHint <= 0) {
widthHint = EditorUtil.MAXIMUM_WIDTH;
}
return widthHint;
}
public static GridLayout createSectionClientLayout() {
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
// leave 1px for borders
layout.marginTop = 2;
// spacing if a section is expanded
layout.marginBottom = 8;
return layout;
}
public static void initializeScrollbars(ScrolledForm form) {
// initialize scroll bars
ScrollBar hbar = form.getHorizontalBar();
if (hbar != null) {
hbar.setIncrement(PAGE_H_SCROLL_INCREMENT);
}
}
public static String removeColon(String label) {
label = label.trim();
return (label.endsWith(":")) ? label.substring(0, label.length() - 1) : label; //$NON-NLS-1$
}
public static void scroll(ScrolledComposite scomp, int xoffset, int yoffset) {
Point origin = scomp.getOrigin();
Point contentSize = scomp.getContent().getSize();
int xorigin = origin.x + xoffset;
int yorigin = origin.y + yoffset;
xorigin = Math.max(xorigin, 0);
xorigin = Math.min(xorigin, contentSize.x - 1);
yorigin = Math.max(yorigin, 0);
yorigin = Math.min(yorigin, contentSize.y - 1);
scomp.setOrigin(xorigin, yorigin);
}
public static void addScrollListener(final Scrollable textWidget) {
textWidget.addMouseWheelListener(new MouseWheelListener() {
public void mouseScrolled(MouseEvent event) {
ScrolledComposite form = FormUtil.getScrolledComposite(textWidget);
if (form != null) {
ScrollBar verticalBar = textWidget.getVerticalBar();
if (event.count < 0) {
// scroll form down
if (verticalBar == null
|| verticalBar.getSelection() + verticalBar.getThumb() == verticalBar.getMaximum()) {
EditorUtil.scroll(form, 0, form.getVerticalBar().getIncrement());
}
} else {
// scroll form up
if (verticalBar == null || verticalBar.getSelection() == verticalBar.getMinimum()) {
EditorUtil.scroll(form, 0, -form.getVerticalBar().getIncrement());
}
}
}
}
});
}
public static void addScrollListener(final CCombo combo) {
if (PlatformUiUtil.usesMouseWheelEventsForScrolling()) {
combo.addListener(SWT.MouseWheel, new Listener() {
public void handleEvent(Event event) {
if (event.count > 0) {
EditorUtil.handleScrollUp(combo, event);
} else if (event.count < 0) {
EditorUtil.handleScrollDown(combo, event);
}
}
});
} else {
combo.addListener(SWT.KeyDown, new Listener() {
public void handleEvent(Event event) {
if (event.keyCode == SWT.ARROW_UP) {
EditorUtil.handleScrollUp(combo, event);
} else if (event.keyCode == SWT.ARROW_DOWN) {
EditorUtil.handleScrollDown(combo, event);
}
}
});
}
}
private static void handleScrollUp(CCombo combo, Event event) {
if (combo.isFocusControl()) {
// could be a legitimate key event, let CCombo handle it
return;
}
ScrolledComposite form = FormUtil.getScrolledComposite(combo);
if (form != null) {
EditorUtil.scroll(form, 0, -form.getVerticalBar().getIncrement());
event.doit = false;
}
}
private static void handleScrollDown(CCombo combo, Event event) {
if (combo.isFocusControl()) {
// could be a legitimate key event, let CCombo handle it
return;
}
ScrolledComposite form = FormUtil.getScrolledComposite(combo);
if (form != null) {
EditorUtil.scroll(form, 0, form.getVerticalBar().getIncrement());
event.doit = false;
}
}
public static void setTitleBarForeground(ExpandableComposite composite, Color color) {
try {
Field field = ExpandableComposite.class.getDeclaredField("titleBarForeground"); //$NON-NLS-1$
field.setAccessible(true);
field.set(composite, color);
} catch (Exception e) {
composite.setTitleBarForeground(color);
}
}
}