Mercurial > projects > dwt-addons
diff dwtx/jface/text/AbstractInformationControl.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | c4fb132a086c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/text/AbstractInformationControl.d Sat Aug 23 19:10:48 2008 +0200 @@ -0,0 +1,736 @@ +/******************************************************************************* + * Copyright (c) 2008 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.jface.text.AbstractInformationControl; + +import dwt.dwthelper.utils; + + + + +import dwt.DWT; +import dwt.events.DisposeListener; +import dwt.events.FocusEvent; +import dwt.events.FocusListener; +import dwt.events.MouseAdapter; +import dwt.events.MouseEvent; +import dwt.events.MouseMoveListener; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.graphics.Color; +import dwt.graphics.Cursor; +import dwt.graphics.Font; +import dwt.graphics.FontData; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.layout.FillLayout; +import dwt.layout.GridData; +import dwt.layout.GridLayout; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Display; +import dwt.widgets.Event; +import dwt.widgets.Label; +import dwt.widgets.Listener; +import dwt.widgets.Shell; +import dwt.widgets.Slider; +import dwt.widgets.ToolBar; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.ListenerList; +import dwtx.jface.action.ToolBarManager; +import dwtx.jface.resource.JFaceResources; +import dwtx.jface.util.Geometry; + + +/** + * An abstract information control that can show content inside a shell. + * The information control can be created in two styles: + * <ul> + * <li>non-resizable tooltip with optional status</li> + * <li>resizable tooltip with optional tool bar</li> + * </ul> + * Additionally it can present either a status line containing a status text or + * a toolbar containing toolbar buttons. + * <p> + * Subclasses must either override {@link IInformationControl#setInformation(String)} + * or implement {@link IInformationControlExtension2}. + * They should also extend {@link #computeTrim()} if they create a content area + * with additional trim (e.g. scrollbars) and override {@link #getInformationPresenterControlCreator()}. + * </p> + * + * @since 3.4 + */ +public abstract class AbstractInformationControl : IInformationControl, IInformationControlExtension, IInformationControlExtension3, IInformationControlExtension4, IInformationControlExtension5 { + + /** The information control's shell. */ + private final Shell fShell; + /** Composite containing the content created by subclasses. */ + private final Composite fContentComposite; + /** Whether the information control is resizable. */ + private final bool fResizable; + + /** Composite containing the status line content or <code>null</code> if none. */ + private Composite fStatusComposite; + /** Separator between content and status line or <code>null</code> if none. */ + private Label fSeparator; + /** Label in the status line or <code>null</code> if none. */ + private Label fStatusLabel; + /** The toolbar manager used by the toolbar or <code>null</code> if none. */ + private final ToolBarManager fToolBarManager; + /** Status line toolbar or <code>null</code> if none. */ + private ToolBar fToolBar; + + /** Listener for shell activation and deactivation. */ + private Listener fShellListener; + /** All focus listeners registered to this information control. */ + private ListenerList fFocusListeners= new ListenerList(ListenerList.IDENTITY); + + /** Size constraints, x is the maxWidth and y is the maxHeight, or <code>null</code> if not set. */ + private Point fSizeConstraints; + /** The size of the resize handle if already set, -1 otherwise */ + private int fResizeHandleSize; + + /** + * Creates an abstract information control with the given shell as parent. + * The control will not be resizable and optionally show a status line with + * the given status field text. + * <p> + * <em>Important: Subclasses are required to call {@link #create()} at the end of their constructor.</em> + * </p> + * + * @param parentShell the parent of this control's shell + * @param statusFieldText the text to be used in the status field or <code>null</code> to hide the status field + */ + public AbstractInformationControl(Shell parentShell, String statusFieldText) { + this(parentShell, DWT.TOOL | DWT.ON_TOP, statusFieldText, null); + } + + /** + * Creates an abstract information control with the given shell as parent. + * The control will be resizable and optionally show a tool bar managed by + * the given tool bar manager. + * <p> + * <em>Important: Subclasses are required to call {@link #create()} at the end of their constructor.</em> + * </p> + * + * @param parentShell the parent of this control's shell + * @param toolBarManager the manager or <code>null</code> if toolbar is not desired + */ + public AbstractInformationControl(Shell parentShell, ToolBarManager toolBarManager) { + this(parentShell, DWT.TOOL | DWT.ON_TOP | DWT.RESIZE, null, toolBarManager); + } + + /** + * Creates an abstract information control with the given shell as parent. + * <p> + * <em>Important: Subclasses are required to call {@link #create()} at the end of their constructor.</em> + * </p> + * + * @param parentShell the parent of this control's shell + * @param isResizable <code>true</code> if the control should be resizable + */ + public AbstractInformationControl(Shell parentShell, bool isResizable) { + this(parentShell, DWT.TOOL | DWT.ON_TOP | (isResizable ? DWT.RESIZE : 0), null, null); + } + + /** + * Creates an abstract information control with the given shell as parent. + * The given shell style is used for the shell (NO_TRIM will be removed to make sure there's a border). + * <p> + * The control will optionally show either a status line or a tool bar. + * At most one of <code>toolBarManager</code> or <code>statusFieldText</code> can be non-null. + * </p> + * <p> + * <strong>Important:</strong>: Subclasses are required to call {@link #create()} at the end of their constructor. + * </p> + * + * @param parentShell the parent of this control's shell + * @param shellStyle style of this control's shell + * @param statusFieldText the text to be used in the status field or <code>null</code> to hide the status field + * @param toolBarManager the manager or <code>null</code> if toolbar is not desired + * + * @deprecated clients should use one of the public constructors + */ + AbstractInformationControl(Shell parentShell, int shellStyle, final String statusFieldText, final ToolBarManager toolBarManager) { + Assert.isTrue(statusFieldText is null || toolBarManager is null); + fResizeHandleSize= -1; + fToolBarManager= toolBarManager; + + if ((shellStyle & DWT.NO_TRIM) !is 0) + shellStyle&= ~(DWT.NO_TRIM | DWT.SHELL_TRIM); // make sure we get the OS border but no other trims + + fResizable= (shellStyle & DWT.RESIZE) !is 0; // on GTK, Shell removes DWT.RESIZE if DWT.ON_TOP is set + fShell= new Shell(parentShell, shellStyle); + Display display= fShell.getDisplay(); + Color foreground= display.getSystemColor(DWT.COLOR_INFO_FOREGROUND); + Color background= display.getSystemColor(DWT.COLOR_INFO_BACKGROUND); + setColor(fShell, foreground, background); + + GridLayout layout= new GridLayout(1, false); + layout.marginHeight= 0; + layout.marginWidth= 0; + layout.verticalSpacing= 0; + fShell.setLayout(layout); + + fContentComposite= new Composite(fShell, DWT.NONE); + fContentComposite.setLayoutData(new GridData(DWT.FILL, DWT.FILL, true, true)); + fContentComposite.setLayout(new FillLayout()); + setColor(fContentComposite, foreground, background); + + createStatusComposite(statusFieldText, toolBarManager, foreground, background); + } + + private void createStatusComposite(final String statusFieldText, final ToolBarManager toolBarManager, Color foreground, Color background) { + if (toolBarManager is null && statusFieldText is null) + return; + + fStatusComposite= new Composite(fShell, DWT.NONE); + GridData gridData= new GridData(DWT.FILL, DWT.BOTTOM, true, false); + fStatusComposite.setLayoutData(gridData); + GridLayout statusLayout= new GridLayout(1, false); + statusLayout.marginHeight= 0; + statusLayout.marginWidth= 0; + statusLayout.verticalSpacing= 1; + fStatusComposite.setLayout(statusLayout); + + fSeparator= new Label(fStatusComposite, DWT.SEPARATOR | DWT.HORIZONTAL); + fSeparator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + if (statusFieldText !is null) { + createStatusLabel(statusFieldText, foreground, background); + } else { + createToolBar(toolBarManager); + } + } + + private void createStatusLabel(final String statusFieldText, Color foreground, Color background) { + fStatusLabel= new Label(fStatusComposite, DWT.RIGHT); + fStatusLabel.setLayoutData(new GridData(DWT.FILL, DWT.CENTER, true, false)); + fStatusLabel.setText(statusFieldText); + + FontData[] fontDatas= JFaceResources.getDialogFont().getFontData(); + for (int i= 0; i < fontDatas.length; i++) { + fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10); + } + fStatusLabel.setFont(new Font(fStatusLabel.getDisplay(), fontDatas)); + + fStatusLabel.setForeground(fStatusLabel.getDisplay().getSystemColor(DWT.COLOR_WIDGET_DARK_SHADOW)); + fStatusLabel.setBackground(background); + setColor(fStatusComposite, foreground, background); + } + + private void createToolBar(ToolBarManager toolBarManager) { + final Composite bars= new Composite(fStatusComposite, DWT.NONE); + bars.setLayoutData(new GridData(DWT.FILL, DWT.FILL, false, false)); + + GridLayout layout= new GridLayout(3, false); + layout.marginHeight= 0; + layout.marginWidth= 0; + layout.horizontalSpacing= 0; + layout.verticalSpacing= 0; + bars.setLayout(layout); + + fToolBar= toolBarManager.createControl(bars); + GridData gd= new GridData(DWT.BEGINNING, DWT.BEGINNING, false, false); + fToolBar.setLayoutData(gd); + + Composite spacer= new Composite(bars, DWT.NONE); + gd= new GridData(DWT.FILL, DWT.FILL, true, true); + gd.widthHint= 0; + gd.heightHint= 0; + spacer.setLayoutData(gd); + + addMoveSupport(spacer); + addResizeSupportIfNecessary(bars); + } + + private void addResizeSupportIfNecessary(final Composite bars) { + // XXX: workarounds for + // - https://bugs.eclipse.org/bugs/show_bug.cgi?id=219139 : API to add resize grip / grow box in lower right corner of shell + // - https://bugs.eclipse.org/bugs/show_bug.cgi?id=23980 : platform specific shell resize behavior + String platform= DWT.getPlatform(); + final bool isWin= platform.equals("win32"); //$NON-NLS-1$ + if (!isWin && !platform.equals("gtk")) //$NON-NLS-1$ + return; + + final Canvas resizer= new Canvas(bars, DWT.NONE); + + int size= getResizeHandleSize(bars); + + GridData data= new GridData(DWT.END, DWT.END, false, true); + data.widthHint= size; + data.heightHint= size; + resizer.setLayoutData(data); + resizer.addPaintListener(new PaintListener() { + public void paintControl(PaintEvent e) { + Point s= resizer.getSize(); + int x= s.x - 2; + int y= s.y - 2; + int min= Math.min(x, y); + if (isWin) { + // draw dots + e.gc.setBackground(resizer.getDisplay().getSystemColor(DWT.COLOR_WIDGET_HIGHLIGHT_SHADOW)); + int end= min - 1; + for (int i= 0; i <= 2; i++) + for (int j= 0; j <= 2 - i; j++) + e.gc.fillRectangle(end - 4 * i, end - 4 * j, 2, 2); + end--; + e.gc.setBackground(resizer.getDisplay().getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW)); + for (int i= 0; i <= 2; i++) + for (int j= 0; j <= 2 - i; j++) + e.gc.fillRectangle(end - 4 * i, end - 4 * j, 2, 2); + + } else { + // draw diagonal lines + e.gc.setForeground(resizer.getDisplay().getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW)); + for (int i= 1; i < min; i+= 4) { + e.gc.drawLine(i, y, x, i); + } + e.gc.setForeground(resizer.getDisplay().getSystemColor(DWT.COLOR_WIDGET_HIGHLIGHT_SHADOW)); + for (int i= 2; i < min; i+= 4) { + e.gc.drawLine(i, y, x, i); + } + } + } + }); + + resizer.setCursor(new Cursor(resizer.getDisplay(), DWT.CURSOR_SIZESE)); + MouseAdapter resizeSupport= new MouseAdapter() { + private MouseMoveListener fResizeListener; + + public void mouseDown(MouseEvent e) { + Point shellSize= fShell.getSize(); + final int shellX= shellSize.x; + final int shellY= shellSize.y; + Point mouseLoc= resizer.toDisplay(e.x, e.y); + final int mouseX= mouseLoc.x; + final int mouseY= mouseLoc.y; + fResizeListener= new MouseMoveListener() { + public void mouseMove(MouseEvent e2) { + Point mouseLoc2= resizer.toDisplay(e2.x, e2.y); + int dx= mouseLoc2.x - mouseX; + int dy= mouseLoc2.y - mouseY; + setSize(shellX + dx, shellY + dy); + } + }; + resizer.addMouseMoveListener(fResizeListener); + } + + public void mouseUp(MouseEvent e) { + resizer.removeMouseMoveListener(fResizeListener); + fResizeListener= null; + } + }; + resizer.addMouseListener(resizeSupport); + } + + private int getResizeHandleSize(Composite parent) { + if (fResizeHandleSize is -1) { + Slider sliderV= new Slider(parent, DWT.VERTICAL); + Slider sliderH= new Slider(parent, DWT.HORIZONTAL); + int width= sliderV.computeSize(DWT.DEFAULT, DWT.DEFAULT).x; + int height= sliderH.computeSize(DWT.DEFAULT, DWT.DEFAULT).y; + sliderV.dispose(); + sliderH.dispose(); + fResizeHandleSize= Math.min(width, height); + } + + return fResizeHandleSize; + } + + /** + * Adds support to move the shell by dragging the given control. + * + * @param control the control that can be used to move the shell + */ + private void addMoveSupport(final Control control) { + MouseAdapter moveSupport= new MouseAdapter() { + private MouseMoveListener fMoveListener; + + public void mouseDown(MouseEvent e) { + Point shellLoc= fShell.getLocation(); + final int shellX= shellLoc.x; + final int shellY= shellLoc.y; + Point mouseLoc= control.toDisplay(e.x, e.y); + final int mouseX= mouseLoc.x; + final int mouseY= mouseLoc.y; + fMoveListener= new MouseMoveListener() { + public void mouseMove(MouseEvent e2) { + Point mouseLoc2= control.toDisplay(e2.x, e2.y); + int dx= mouseLoc2.x - mouseX; + int dy= mouseLoc2.y - mouseY; + fShell.setLocation(shellX + dx, shellY + dy); + } + }; + control.addMouseMoveListener(fMoveListener); + } + + public void mouseUp(MouseEvent e) { + control.removeMouseMoveListener(fMoveListener); + fMoveListener= null; + } + }; + control.addMouseListener(moveSupport); + } + + /** + * Utility to set the foreground and the background color of the given + * control + * + * @param control the control to modify + * @param foreground the color to use for the foreground + * @param background the color to use for the background + */ + private static void setColor(Control control, Color foreground, Color background) { + control.setForeground(foreground); + control.setBackground(background); + } + + /** + * The shell of the popup window. + * + * @return the shell used for the popup window + */ + protected final Shell getShell() { + return fShell; + } + + /** + * The toolbar manager used to manage the toolbar, or <code>null</code> if + * no toolbar is shown. + * + * @return the tool bar manager or <code>null</code> + */ + protected final ToolBarManager getToolBarManager() { + return fToolBarManager; + } + + /** + * Creates the content of this information control. Subclasses must call + * this method at the end of their constructor(s). + */ + protected final void create() { + createContent(fContentComposite); + } + + /** + * Creates the content of the popup window. + * <p> + * Implementors will usually take over {@link Composite#getBackground()} and + * {@link Composite#getForeground()} from <code>parent</code>. + * </p> + * <p> + * Implementors are expected to consider {@link #isResizable()}: If + * <code>true</code>, they should show scrollbars if their content may + * exceed the size of the information control. If <code>false</code>, + * they should never show scrollbars. + * </p> + * <p> + * The given <code>parent</code> comes with a {@link FillLayout}. + * Subclasses may set a different layout. + * </p> + * + * @param parent the container of the content + */ + protected abstract void createContent(Composite parent); + + /** + * Sets the information to be presented by this information control. + * <p> + * The default implementation does nothing. Subclasses must either override this method + * or implement {@link IInformationControlExtension2}. + * + * @param information the information to be presented + * + * @see dwtx.jface.text.IInformationControl#setInformation(java.lang.String) + */ + public void setInformation(String information) { + + } + + /** + * Returns whether the information control is resizable. + * + * @return <code>true</code> if the information control is resizable, + * <code>false</code> if it is not resizable. + */ + public bool isResizable() { + return fResizable; + } + + /* + * @see IInformationControl#setVisible(bool) + */ + public void setVisible(bool visible) { + if (fShell.isVisible() is visible) + return; + + fShell.setVisible(visible); + } + + /* + * @see IInformationControl#dispose() + */ + public void dispose() { + if (fShell !is null && !fShell.isDisposed()) + fShell.dispose(); + } + + /* + * @see IInformationControl#setSize(int, int) + */ + public void setSize(int width, int height) { + fShell.setSize(width, height); + } + + /* + * @see IInformationControl#setLocation(Point) + */ + public void setLocation(Point location) { + fShell.setLocation(location); + } + + /* + * @see IInformationControl#setSizeConstraints(int, int) + */ + public void setSizeConstraints(int maxWidth, int maxHeight) { + fSizeConstraints= new Point(maxWidth, maxHeight); + } + + /** + * Returns the size constraints. + * + * @return the size constraints or <code>null</code> if not set + * @see #setSizeConstraints(int, int) + */ + protected final Point getSizeConstraints() { + return fSizeConstraints !is null ? Geometry.copy(fSizeConstraints) : null; + } + + /* + * @see IInformationControl#computeSizeHint() + */ + public Point computeSizeHint() { + // XXX: Verify whether this is a good default implementation. If yes, document it. + Point constrains= getSizeConstraints(); + if (constrains is null) + return fShell.computeSize(DWT.DEFAULT, DWT.DEFAULT, true); + + return fShell.computeSize(constrains.x, constrains.y, true); + } + + /** + * Computes the trim (status text and tool bar are considered as trim). + * Subclasses can extend this method to add additional trim (e.g. scroll + * bars for resizable information controls). + * + * @see dwtx.jface.text.IInformationControlExtension3#computeTrim() + */ + public Rectangle computeTrim() { + Rectangle trim= fShell.computeTrim(0, 0, 0, 0); + + if (fStatusComposite !is null) + trim.height+= fStatusComposite.computeSize(DWT.DEFAULT, DWT.DEFAULT).y; + + return trim; + } + + /* + * @see dwtx.jface.text.IInformationControlExtension3#getBounds() + */ + public Rectangle getBounds() { + return fShell.getBounds(); + } + + /** + * {@inheritDoc} + * <p> + * The default implementation always returns <code>false</code>. + * </p> + * @see dwtx.jface.text.IInformationControlExtension3#restoresLocation() + */ + public bool restoresLocation() { + return false; + } + + /** + * {@inheritDoc} + * <p> + * The default implementation always returns <code>false</code>. + * </p> + * @see dwtx.jface.text.IInformationControlExtension3#restoresSize() + */ + public bool restoresSize() { + return false; + } + + /* + * @see IInformationControl#addDisposeListener(DisposeListener) + */ + public void addDisposeListener(DisposeListener listener) { + fShell.addDisposeListener(listener); + } + + /* + * @see IInformationControl#removeDisposeListener(DisposeListener) + */ + public void removeDisposeListener(DisposeListener listener) { + fShell.removeDisposeListener(listener); + } + + /* + * @see IInformationControl#setForegroundColor(Color) + */ + public void setForegroundColor(Color foreground) { + fContentComposite.setForeground(foreground); + } + + /* + * @see IInformationControl#setBackgroundColor(Color) + */ + public void setBackgroundColor(Color background) { + fContentComposite.setBackground(background); + } + + /** + * {@inheritDoc} + * This method is not intended to be overridden by subclasses. + */ + public bool isFocusControl() { + return fShell.getDisplay().getActiveShell() is fShell; + } + + /** + * This default implementation sets the focus on the popup shell. + * Subclasses can override or extend. + * + * @see IInformationControl#setFocus() + */ + public void setFocus() { + bool focusTaken= fShell.setFocus(); + if (!focusTaken) + fShell.forceFocus(); + } + + /** + * {@inheritDoc} + * This method is not intended to be overridden by subclasses. + */ + public void addFocusListener(final FocusListener listener) { + if (fFocusListeners.isEmpty()) { + fShellListener= new Listener() { + + public void handleEvent(Event event) { + Object[] listeners= fFocusListeners.getListeners(); + for (int i= 0; i < listeners.length; i++) { + FocusListener focusListener= (FocusListener)listeners[i]; + if (event.type is DWT.Activate) { + focusListener.focusGained(new FocusEvent(event)); + } else { + focusListener.focusLost(new FocusEvent(event)); + } + } + } + }; + fShell.addListener(DWT.Deactivate, fShellListener); + fShell.addListener(DWT.Activate, fShellListener); + } + fFocusListeners.add(listener); + } + + /** + * {@inheritDoc} + * This method is not intended to be overridden by subclasses. + */ + public void removeFocusListener(FocusListener listener) { + fFocusListeners.remove(listener); + if (fFocusListeners.isEmpty()) { + fShell.removeListener(DWT.Activate, fShellListener); + fShell.removeListener(DWT.Deactivate, fShellListener); + fShellListener= null; + } + } + + /** + * Sets the text of the status field. + * <p> + * The default implementation currently only updates the status field when + * the popup shell is not visible. The status field can currently only be + * shown if the information control has been created with a non-null status + * field text. + * </p> + * + * @param statusFieldText the text to be used in the optional status field + * or <code>null</code> if the status field should be hidden + * + * @see dwtx.jface.text.IInformationControlExtension4#setStatusText(java.lang.String) + */ + public void setStatusText(String statusFieldText) { + if (fStatusLabel !is null && ! getShell().isVisible()) { + if (statusFieldText is null ) { + fStatusComposite.setVisible(false); + } else { + fStatusLabel.setText(statusFieldText); + fStatusComposite.setVisible(true); + } + } + } + + /* + * @see dwtx.jface.text.IInformationControlExtension5#containsControl(dwt.widgets.Control) + */ + public bool containsControl(Control control) { + do { + if (control is fShell) + return true; + if (control instanceof Shell) + return false; + control= control.getParent(); + } while (control !is null); + return false; + } + + /* + * @see dwtx.jface.text.IInformationControlExtension5#isVisible() + */ + public bool isVisible() { + return fShell !is null && !fShell.isDisposed() && fShell.isVisible(); + } + + /** + * {@inheritDoc} + * This default implementation returns <code>null</code>. Subclasses may override. + */ + public IInformationControlCreator getInformationPresenterControlCreator() { + return null; + } + + /** + * Computes the size constraints based on the + * {@link JFaceResources#getDialogFont() dialog font}. Subclasses can + * override or extend. + * + * @see dwtx.jface.text.IInformationControlExtension5#computeSizeConstraints(int, int) + */ + public Point computeSizeConstraints(int widthInChars, int heightInChars) { + GC gc= new GC(fContentComposite); + gc.setFont(JFaceResources.getDialogFont()); + int width= gc.getFontMetrics().getAverageCharWidth(); + int height= gc.getFontMetrics().getHeight(); + gc.dispose(); + + return new Point(widthInChars * width, heightInChars * height); + } + +}