/******************************************************************************* * Copyright (c) 2004, 2014 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: * 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); } } }