Mercurial > projects > dwt-addons
changeset 75:5d489b9f966c
Fix continue porting
line wrap: on
line diff
--- a/dwtx/jface/viewers/deferred/package.html Thu May 22 20:08:10 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> -<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> - <meta name="Author" content="IBM"> - <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]"> - <title>Package-level Javadoc</title> -</head> -<body> -Provides a framework for viewers that handle deferred contents. -<h2> -Package Specification</h2> -<p>The deferred viewers are viewers that can handle concurrent updates from a - variety of Threads.<br> - -</body> -</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/AbstractFormPart.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.AbstractFormPart; + +import dwtx.ui.forms.IFormPart; +import dwtx.ui.forms.IManagedForm; + +import dwt.dwthelper.utils; + +/** + * AbstractFormPart implements IFormPart interface and can be used as a + * convenient base class for concrete form parts. If a method contains + * code that must be called, look for instructions to call 'super' + * when overriding. + * + * @see dwtx.ui.forms.widgets.Section + * @since 3.0 + */ +public abstract class AbstractFormPart : IFormPart { + private IManagedForm managedForm; + private bool dirty = false; + private bool stale = true; + /** + * @see dwtx.ui.forms.IFormPart#initialize(dwtx.ui.forms.IManagedForm) + */ + public void initialize(IManagedForm form) { + this.managedForm = form; + } + /** + * Returns the form that manages this part. + * + * @return the managed form + */ + public IManagedForm getManagedForm() { + return managedForm; + } + /** + * Disposes the part. Subclasses should override to release any system + * resources. + */ + public void dispose() { + } + /** + * Commits the part. Subclasses should call 'super' when overriding. + * + * @param onSave + * <code>true</code> if the request to commit has arrived as a + * result of the 'save' action. + */ + public void commit(bool onSave) { + dirty = false; + } + /** + * Sets the overall form input. Subclases may elect to override the method + * and adjust according to the form input. + * + * @param input + * the form input object + * @return <code>false</code> + */ + public bool setFormInput(Object input) { + return false; + } + /** + * Instructs the part to grab keyboard focus. + */ + public void setFocus() { + } + /** + * Refreshes the section after becoming stale (falling behind data in the + * model). Subclasses must call 'super' when overriding this method. + */ + public void refresh() { + stale = false; + // since we have refreshed, any changes we had in the + // part are gone and we are not dirty + dirty = false; + } + /** + * Marks the part dirty. Subclasses should call this method as a result of + * user interaction with the widgets in the section. + */ + public void markDirty() { + dirty = true; + managedForm.dirtyStateChanged(); + } + /** + * Tests whether the part is dirty i.e. its widgets have state that is + * newer than the data in the model. + * + * @return <code>true</code> if the part is dirty, <code>false</code> + * otherwise. + */ + public bool isDirty() { + return dirty; + } + /** + * Tests whether the part is stale i.e. its widgets have state that is + * older than the data in the model. + * + * @return <code>true</code> if the part is stale, <code>false</code> + * otherwise. + */ + public bool isStale() { + return stale; + } + /** + * Marks the part stale. Subclasses should call this method as a result of + * model notification that indicates that the content of the section is no + * longer in sync with the model. + */ + public void markStale() { + stale = true; + managedForm.staleStateChanged(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/DetailsPart.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,338 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.ui.forms.DetailsPart; + +import dwtx.ui.forms.IFormPart; +import dwtx.ui.forms.IPartSelectionListener; +import dwtx.ui.forms.IManagedForm; +import dwtx.ui.forms.IDetailsPageProvider; +import dwtx.ui.forms.IDetailsPage; + +import dwt.DWT; +import dwt.custom.BusyIndicator; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwtx.jface.viewers.ISelection; +import dwtx.jface.viewers.IStructuredSelection; +import dwtx.ui.forms.widgets.ScrolledPageBook; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Runnable; +import tango.util.collection.HashMap; + +/** + * This managed form part handles the 'details' portion of the + * 'master/details' block. It has a page book that manages pages + * of details registered for the current selection. + * <p>By default, details part accepts any number of pages. + * If dynamic page provider is registered, this number may + * be excessive. To avoid running out of steam (by creating + * a large number of pages with widgets on each), maximum + * number of pages can be set to some reasonable value (e.g. 10). + * When this number is reached, old pages (those created first) + * will be removed and disposed as new ones are added. If + * the disposed pages are needed again after that, they + * will be created again. + * + * @since 3.0 + */ +public final class DetailsPart : IFormPart, IPartSelectionListener { + private IManagedForm managedForm; + private ScrolledPageBook pageBook; + private IFormPart masterPart; + private IStructuredSelection currentSelection; + private HashMap!(Object,Object) pages; + private IDetailsPageProvider pageProvider; + private int pageLimit=Integer.MAX_VALUE; + + private static class PageBag { + private static int counter; + private int ticket; + private IDetailsPage page; + private bool fixed; + + public this(IDetailsPage page, bool fixed) { + this.page= page; + this.fixed = fixed; + this.ticket = ++counter; + } + public int getTicket() { + return ticket; + } + public IDetailsPage getPage() { + return page; + } + public void dispose() { + page.dispose(); + page=null; + } + public bool isDisposed() { + return page is null; + } + public bool isFixed() { + return fixed; + } + public static int getCurrentTicket() { + return counter; + } + } +/** + * Creates a details part by wrapping the provided page book. + * @param mform the parent form + * @param pageBook the page book to wrap + */ + public this(IManagedForm mform, ScrolledPageBook pageBook) { + this.pageBook = pageBook; + pages = new HashMap!(Object,Object); + initialize(mform); + } +/** + * Creates a new details part in the provided form by creating + * the page book. + * @param mform the parent form + * @param parent the composite to create the page book in + * @param style the style for the page book + */ + public this(IManagedForm mform, Composite parent, int style) { + this(mform, mform.getToolkit().createPageBook(parent, style|DWT.V_SCROLL|DWT.H_SCROLL)); + } +/** + * Registers the details page to be used for all the objects of + * the provided object class. + * @param objectClass an object of type 'java.lang.Class' to be used + * as a key for the provided page + * @param page the page to show for objects of the provided object class + */ + public void registerPage(Object objectClass, IDetailsPage page) { + registerPage(objectClass, page, true); + } + + private void registerPage(Object objectClass, IDetailsPage page, bool fixed) { + pages.add(objectClass, new PageBag(page, fixed)); + page.initialize(managedForm); + } +/** + * Sets the dynamic page provider. The dynamic provider can return + * different pages for objects of the same class based on their state. + * @param provider the provider to use + */ + public void setPageProvider(IDetailsPageProvider provider) { + this.pageProvider = provider; + } +/** + * Commits the part by committing the current page. + * @param onSave <code>true</code> if commit is requested as a result + * of the 'save' action, <code>false</code> otherwise. + */ + public void commit(bool onSave) { + IDetailsPage page = getCurrentPage(); + if (page !is null) + page.commit(onSave); + } +/** + * Returns the current page visible in the part. + * @return the current page + */ + public IDetailsPage getCurrentPage() { + Control control = pageBook.getCurrentPage(); + if (control !is null) { + Object data = control.getData(); + if (null !is cast(IDetailsPage)data ) + return cast(IDetailsPage) data; + } + return null; + } + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IFormPart#dispose() + */ + public void dispose() { + foreach( k, v; pages ){ + PageBag pageBag = cast(PageBag) v; + pageBag.dispose(); + } + } + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IFormPart#initialize(dwtx.ui.forms.IManagedForm) + */ + public void initialize(IManagedForm form) { + this.managedForm = form; + } +/** + * Tests if the currently visible page is dirty. + * @return <code>true</code> if the page is dirty, <code>false</code> otherwise. + */ + public bool isDirty() { + IDetailsPage page = getCurrentPage(); + if (page !is null) + return page.isDirty(); + return false; + } +/** + * Tests if the currently visible page is stale and needs refreshing. + * @return <code>true</code> if the page is stale, <code>false</code> otherwise. + */ + public bool isStale() { + IDetailsPage page = getCurrentPage(); + if (page !is null) + return page.isStale(); + return false; + } + +/** + * Refreshes the current page. + */ + public void refresh() { + IDetailsPage page = getCurrentPage(); + if (page !is null) + page.refresh(); + } +/** + * Sets the focus to the currently visible page. + */ + public void setFocus() { + IDetailsPage page = getCurrentPage(); + if (page !is null) + page.setFocus(); + } + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IFormPart#setFormInput(java.lang.Object) + */ + public bool setFormInput(Object input) { + return false; + } + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IPartSelectionListener#selectionChanged(dwtx.ui.forms.IFormPart, + * dwtx.jface.viewers.ISelection) + */ + public void selectionChanged(IFormPart part, ISelection selection) { + this.masterPart = part; + if (currentSelection !is null) { + } + if (null !is cast(IStructuredSelection)selection ) + currentSelection = cast(IStructuredSelection) selection; + else + currentSelection = null; + update(); + } + private void update() { + Object key = null; + if (currentSelection !is null) { + foreach ( obj; currentSelection.iterator() ) { + if (key is null) + key = getKey(obj); + else if (getKey(obj).opEquals(key) is false) { + key = null; + break; + } + } + } + showPage(key); + } + private Object getKey(Object object) { + if (pageProvider !is null) { + Object key = pageProvider.getPageKey(object); + if (key !is null) + return key; + } + return object.classinfo; + } + private void showPage( Object key) { + checkLimit(); + final IDetailsPage oldPage = getCurrentPage(); + if (key !is null) { + PageBag pageBag = cast(PageBag)pages.get(key); + IDetailsPage page = pageBag !is null?pageBag.getPage():null; + if (page is null) { + // try to get the page dynamically from the provider + if (pageProvider !is null) { + page = pageProvider.getPage(key); + if (page !is null) { + registerPage(key, page, false); + } + } + } + if (page !is null) { + BusyIndicator.showWhile(pageBook.getDisplay(), dgRunnable( (IDetailsPage fpage,Object key,IDetailsPage oldPage) { + if (!pageBook.hasPage(key)) { + Composite parent = pageBook.createPage(key); + fpage.createContents(parent); + parent.setData(cast(Object)fpage); + } + //commit the current page + if (oldPage !is null && oldPage.isDirty()) + oldPage.commit(false); + //refresh the new page + if (fpage.isStale()) + fpage.refresh(); + fpage.selectionChanged(masterPart, currentSelection); + pageBook.showPage(key); + }, page, key, oldPage)); + return; + } + } + // If we are switching from an old page to nothing, + // don't loose data + if (oldPage !is null && oldPage.isDirty()) + oldPage.commit(false); + pageBook.showEmptyPage(); + } + private void checkLimit() { + if (pages.size() <= getPageLimit()) return; + // overflow + int currentTicket = PageBag.getCurrentTicket(); + int cutoffTicket = currentTicket - getPageLimit(); + foreach( key, v; cast(HashMap!(Object,Object))pages.dup ){ + PageBag pageBag = cast(PageBag)v; + if (pageBag.getTicket()<=cutoffTicket) { + // candidate - see if it is active and not fixed + if (!pageBag.isFixed() && !(cast(Object)pageBag.getPage()).opEquals(cast(Object)getCurrentPage())) { + // drop it + pageBag.dispose(); + pages.remove(key); + pageBook.removePage(key, false); + } + } + } + } + /** + * Returns the maximum number of pages that should be + * maintained in this part. When an attempt is made to + * add more pages, old pages are removed and disposed + * based on the order of creation (the oldest pages + * are removed). The exception is made for the + * page that should otherwise be disposed but is + * currently active. + * @return maximum number of pages for this part + */ + public int getPageLimit() { + return pageLimit; + } + /** + * Sets the page limit for this part. + * @see #getPageLimit() + * @param pageLimit the maximum number of pages that + * should be maintained in this part. + */ + public void setPageLimit(int pageLimit) { + this.pageLimit = pageLimit; + checkLimit(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/FormColors.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,737 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.FormColors; + +import dwtx.ui.forms.IFormColors; + +import dwt.DWT; +import dwt.graphics.Color; +import dwt.graphics.RGB; +import dwt.widgets.Display; + +import dwt.dwthelper.utils; +import tango.util.collection.HashMap; + +/** + * Manages colors that will be applied to forms and form widgets. The colors are + * chosen to make the widgets look correct in the editor area. If a different + * set of colors is needed, subclass this class and override 'initialize' and/or + * 'initializeColors'. + * + * @since 3.0 + */ +public class FormColors { + /** + * Key for the form title foreground color. + * + * @deprecated use <code>IFormColors.TITLE</code>. + */ + public static const String TITLE = IFormColors.TITLE; + + /** + * Key for the tree/table border color. + * + * @deprecated use <code>IFormColors.BORDER</code> + */ + public static const String BORDER = IFormColors.BORDER; + + /** + * Key for the section separator color. + * + * @deprecated use <code>IFormColors.SEPARATOR</code>. + */ + public static const String SEPARATOR = IFormColors.SEPARATOR; + + /** + * Key for the section title bar background. + * + * @deprecated use <code>IFormColors.TB_BG + */ + public static const String TB_BG = IFormColors.TB_BG; + + /** + * Key for the section title bar foreground. + * + * @deprecated use <code>IFormColors.TB_FG</code> + */ + public static const String TB_FG = IFormColors.TB_FG; + + /** + * Key for the section title bar gradient. + * + * @deprecated use <code>IFormColors.TB_GBG</code> + */ + public static const String TB_GBG = IFormColors.TB_GBG; + + /** + * Key for the section title bar border. + * + * @deprecated use <code>IFormColors.TB_BORDER</code>. + */ + public static const String TB_BORDER = IFormColors.TB_BORDER; + + /** + * Key for the section toggle color. Since 3.1, this color is used for all + * section styles. + * + * @deprecated use <code>IFormColors.TB_TOGGLE</code>. + */ + public static const String TB_TOGGLE = IFormColors.TB_TOGGLE; + + /** + * Key for the section toggle hover color. + * + * @since 3.1 + * @deprecated use <code>IFormColors.TB_TOGGLE_HOVER</code>. + */ + public static const String TB_TOGGLE_HOVER = IFormColors.TB_TOGGLE_HOVER; + + protected HashMap!(String,Object) colorRegistry; + + protected Color background; + + protected Color foreground; + + private bool shared; + + protected Display display; + + protected Color border; + + /** + * Creates form colors using the provided display. + * + * @param display + * the display to use + */ + public this(Display display) { + colorRegistry = new HashMap!(String,Object); + this.display = display; + initialize(); + } + + /** + * Returns the display used to create colors. + * + * @return the display + */ + public Display getDisplay() { + return display; + } + + /** + * Initializes the colors. Subclasses can override this method to change the + * way colors are created. Alternatively, only the color table can be + * modified by overriding <code>initializeColorTable()</code>. + * + * @see #initializeColorTable + */ + protected void initialize() { + background = display.getSystemColor(DWT.COLOR_LIST_BACKGROUND); + foreground = display.getSystemColor(DWT.COLOR_LIST_FOREGROUND); + initializeColorTable(); + updateBorderColor(); + } + + /** + * Allocates colors for the following keys: BORDER, SEPARATOR and + * TITLE. Subclasses can override to allocate these colors differently. + */ + protected void initializeColorTable() { + createTitleColor(); + createColor(IFormColors.SEPARATOR, getColor(IFormColors.TITLE).getRGB()); + RGB black = getSystemColor(DWT.COLOR_BLACK); + RGB borderRGB = getSystemColor(DWT.COLOR_TITLE_INACTIVE_BACKGROUND_GRADIENT); + createColor(IFormColors.BORDER, blend(borderRGB, black, 80)); + } + + /** + * Allocates colors for the section tool bar (all the keys that start with + * TB). Since these colors are only needed when TITLE_BAR style is used with + * the Section widget, they are not needed all the time and are allocated on + * demand. Consequently, this method will do nothing if the colors have been + * already initialized. Call this method prior to using colors with the TB + * keys to ensure they are available. + */ + public void initializeSectionToolBarColors() { + if (colorRegistry.containsKey(IFormColors.TB_BG)) + return; + createTitleBarGradientColors(); + createTitleBarOutlineColors(); + createTwistieColors(); + } + + /** + * Allocates additional colors for the form header, namely background + * gradients, bottom separator keylines and DND highlights. Since these + * colors are only needed for clients that want to use these particular + * style of header rendering, they are not needed all the time and are + * allocated on demand. Consequently, this method will do nothing if the + * colors have been already initialized. Call this method prior to using + * color keys with the H_ prefix to ensure they are available. + * + * @since 3.3 + */ + protected void initializeFormHeaderColors() { + if (colorRegistry.containsKey(IFormColors.H_BOTTOM_KEYLINE2)) + return; + createFormHeaderColors(); + } + + /** + * Returns the RGB value of the system color represented by the code + * argument, as defined in <code>DWT</code> class. + * + * @param code + * the system color constant as defined in <code>DWT</code> + * class. + * @return the RGB value of the system color + */ + public RGB getSystemColor(int code) { + return getDisplay().getSystemColor(code).getRGB(); + } + + /** + * Creates the color for the specified key using the provided RGB object. + * The color object will be returned and also put into the registry. When + * the class is disposed, the color will be disposed with it. + * + * @param key + * the unique color key + * @param rgb + * the RGB object + * @return the allocated color object + */ + public Color createColor(String key, RGB rgb) { + return createColor(key, rgb.red, rgb.green, rgb.blue); + } + + /** + * Creates a color that can be used for areas of the form that is inactive. + * These areas can contain images, links, controls and other content but are + * considered auxilliary to the main content area. + * + * <p> + * The color should not be disposed because it is managed by this class. + * + * @return the inactive form color + * @since 3.1 + */ + public Color getInactiveBackground() { + String key = "__ncbg__"; //$NON-NLS-1$ + Color color = getColor(key); + if (color is null) { + RGB sel = getSystemColor(DWT.COLOR_LIST_SELECTION); + // a blend of 95% white and 5% list selection system color + RGB ncbg = blend(sel, getSystemColor(DWT.COLOR_WHITE), 5); + color = createColor(key, ncbg); + } + return color; + } + + /** + * Creates the color for the specified key using the provided RGB values. + * The color object will be returned and also put into the registry. If + * there is already another color object under the same key in the registry, + * the existing object will be disposed. When the class is disposed, the + * color will be disposed with it. + * + * @param key + * the unique color key + * @param r + * red value + * @param g + * green value + * @param b + * blue value + * @return the allocated color object + */ + public Color createColor(String key, int r, int g, int b) { + Color c = new Color(display, r, g, b); + Color prevC = cast(Color) colorRegistry.get(key); + if (prevC !is null) + prevC.dispose(); + colorRegistry.add(key, c); + return c; + } + + /** + * Computes the border color relative to the background. Allocated border + * color is designed to work well with white. Otherwise, stanard widget + * background color will be used. + */ + protected void updateBorderColor() { + if (isWhiteBackground()) + border = getColor(IFormColors.BORDER); + else { + border = display.getSystemColor(DWT.COLOR_WIDGET_BACKGROUND); + Color bg = getImpliedBackground(); + if (border.getRed() is bg.getRed() + && border.getGreen() is bg.getGreen() + && border.getBlue() is bg.getBlue()) + border = display.getSystemColor(DWT.COLOR_WIDGET_DARK_SHADOW); + } + } + + /** + * Sets the background color. All the toolkits that use this class will + * share the same background. + * + * @param bg + * background color + */ + public void setBackground(Color bg) { + this.background = bg; + updateBorderColor(); + updateFormHeaderColors(); + } + + /** + * Sets the foreground color. All the toolkits that use this class will + * share the same foreground. + * + * @param fg + * foreground color + */ + public void setForeground(Color fg) { + this.foreground = fg; + } + + /** + * Returns the current background color. + * + * @return the background color + */ + public Color getBackground() { + return background; + } + + /** + * Returns the current foreground color. + * + * @return the foreground color + */ + public Color getForeground() { + return foreground; + } + + /** + * Returns the computed border color. Border color depends on the background + * and is recomputed whenever the background changes. + * + * @return the current border color + */ + public Color getBorderColor() { + return border; + } + + /** + * Tests if the background is white. White background has RGB value + * 255,255,255. + * + * @return <samp>true</samp> if background is white, <samp>false</samp> + * otherwise. + */ + public bool isWhiteBackground() { + Color bg = getImpliedBackground(); + return bg.getRed() is 255 && bg.getGreen() is 255 + && bg.getBlue() is 255; + } + + /** + * Returns the color object for the provided key or <samp>null </samp> if + * not in the registry. + * + * @param key + * the color key + * @return color object if found, or <samp>null </samp> if not. + */ + public Color getColor(String key) { + if (key.startsWith(IFormColors.TB_PREFIX)) + initializeSectionToolBarColors(); + else if (key.startsWith(IFormColors.H_PREFIX)) + initializeFormHeaderColors(); + return cast(Color) colorRegistry.get(key); + } + + /** + * Disposes all the colors in the registry. + */ + public void dispose() { + foreach( k, v; colorRegistry ) + (cast(Color) v).dispose(); + + colorRegistry = null; + } + + /** + * Marks the colors shared. This prevents toolkits that share this object + * from disposing it. + */ + public void markShared() { + this.shared = true; + } + + /** + * Tests if the colors are shared. + * + * @return <code>true</code> if shared, <code>false</code> otherwise. + */ + public bool isShared() { + return shared; + } + + /** + * Blends c1 and c2 based in the provided ratio. + * + * @param c1 + * first color + * @param c2 + * second color + * @param ratio + * percentage of the first color in the blend (0-100) + * @return the RGB value of the blended color + * @since 3.1 + */ + public static RGB blend(RGB c1, RGB c2, int ratio) { + int r = blend(c1.red, c2.red, ratio); + int g = blend(c1.green, c2.green, ratio); + int b = blend(c1.blue, c2.blue, ratio); + return new RGB(r, g, b); + } + + /** + * Tests the source RGB for range. + * + * @param rgb + * the tested RGB + * @param from + * range start (excluding the value itself) + * @param to + * range end (excluding the value itself) + * @return <code>true</code> if at least one of the primary colors in the + * source RGB are within the provided range, <code>false</code> + * otherwise. + * @since 3.1 + */ + public static bool testAnyPrimaryColor(RGB rgb, int from, int to) { + if (testPrimaryColor(rgb.red, from, to)) + return true; + if (testPrimaryColor(rgb.green, from, to)) + return true; + if (testPrimaryColor(rgb.blue, from, to)) + return true; + return false; + } + + /** + * Tests the source RGB for range. + * + * @param rgb + * the tested RGB + * @param from + * range start (excluding the value itself) + * @param to + * tange end (excluding the value itself) + * @return <code>true</code> if at least two of the primary colors in the + * source RGB are within the provided range, <code>false</code> + * otherwise. + * @since 3.1 + */ + public static bool testTwoPrimaryColors(RGB rgb, int from, int to) { + int total = 0; + if (testPrimaryColor(rgb.red, from, to)) + total++; + if (testPrimaryColor(rgb.green, from, to)) + total++; + if (testPrimaryColor(rgb.blue, from, to)) + total++; + return total >= 2; + } + + /** + * Blends two primary color components based on the provided ratio. + * + * @param v1 + * first component + * @param v2 + * second component + * @param ratio + * percentage of the first component in the blend + * @return + */ + private static int blend(int v1, int v2, int ratio) { + int b = (ratio * v1 + (100 - ratio) * v2) / 100; + return Math.min(255, b); + } + + private Color getImpliedBackground() { + if (getBackground() !is null) + return getBackground(); + return getDisplay().getSystemColor(DWT.COLOR_WIDGET_BACKGROUND); + } + + private static bool testPrimaryColor(int value, int from, int to) { + return value > from && value < to; + } + + private void createTitleColor() { + /* + * RGB rgb = getSystemColor(DWT.COLOR_LIST_SELECTION); // test too light + * if (testTwoPrimaryColors(rgb, 120, 151)) rgb = blend(rgb, BLACK, 80); + * else if (testTwoPrimaryColors(rgb, 150, 256)) rgb = blend(rgb, BLACK, + * 50); createColor(TITLE, rgb); + */ + RGB bg = getImpliedBackground().getRGB(); + RGB listSelection = getSystemColor(DWT.COLOR_LIST_SELECTION); + RGB listForeground = getSystemColor(DWT.COLOR_LIST_FOREGROUND); + RGB rgb = listSelection; + + // Group 1 + // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or + // between 0 and 120, then use 100% LIST_SELECTION as it is (no + // additions) + // Examples: XP Default, Win Classic Standard, Win High Con White, Win + // Classic Marine + if (testTwoPrimaryColors(listSelection, -1, 121)) + rgb = listSelection; + // Group 2 + // When LIST_BACKGROUND = white (255, 255, 255) or not black, text + // colour = LIST_SELECTION @ 100% Opacity + 50% LIST_FOREGROUND over + // LIST_BACKGROUND + // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or + // between 121 and 255, then add 50% LIST_FOREGROUND to LIST_SELECTION + // foreground colour + // Examples: Win Vista, XP Silver, XP Olive , Win Classic Plum, OSX + // Aqua, OSX Graphite, Linux GTK + else if (testTwoPrimaryColors(listSelection, 120, 256) + || (bg.red is 0 && bg.green is 0 && bg.blue is 0)) + rgb = blend(listSelection, listForeground, 50); + // Group 3 + // When LIST_BACKGROUND = black (0, 0, 0), text colour = LIST_SELECTION + // @ 100% Opacity + 50% LIST_FOREGROUND over LIST_BACKGROUND + // Rule: If LIST_BACKGROUND = 0, 0, 0, then add 50% LIST_FOREGROUND to + // LIST_SELECTION foreground colour + // Examples: Win High Con Black, Win High Con #1, Win High Con #2 + // (covered in the second part of the OR clause above) + createColor(IFormColors.TITLE, rgb); + } + + private void createTwistieColors() { + RGB rgb = getColor(IFormColors.TITLE).getRGB(); + RGB white = getSystemColor(DWT.COLOR_WHITE); + createColor(TB_TOGGLE, rgb); + rgb = blend(rgb, white, 60); + createColor(TB_TOGGLE_HOVER, rgb); + } + + private void createTitleBarGradientColors() { + RGB tbBg = getSystemColor(DWT.COLOR_TITLE_BACKGROUND); + RGB bg = getImpliedBackground().getRGB(); + + // Group 1 + // Rule: If at least 2 of the RGB values are equal to or between 180 and + // 255, then apply specified opacity for Group 1 + // Examples: Vista, XP Silver, Wn High Con #2 + // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + if (testTwoPrimaryColors(tbBg, 179, 256)) + tbBg = blend(tbBg, bg, 30); + + // Group 2 + // Rule: If at least 2 of the RGB values are equal to or between 121 and + // 179, then apply specified opacity for Group 2 + // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black + // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + else if (testTwoPrimaryColors(tbBg, 120, 180)) + tbBg = blend(tbBg, bg, 20); + + // Group 3 + // Rule: Everything else + // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX + // Aqua, Wn High Con White, Wn High Con #1 + // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + else { + tbBg = blend(tbBg, bg, 10); + } + + createColor(IFormColors.TB_BG, tbBg); + + // for backward compatibility + createColor(TB_GBG, tbBg); + } + + private void createTitleBarOutlineColors() { + // title bar outline - border color + RGB tbBorder = getSystemColor(DWT.COLOR_TITLE_BACKGROUND); + RGB bg = getImpliedBackground().getRGB(); + // Group 1 + // Rule: If at least 2 of the RGB values are equal to or between 180 and + // 255, then apply specified opacity for Group 1 + // Examples: Vista, XP Silver, Wn High Con #2 + // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND + if (testTwoPrimaryColors(tbBorder, 179, 256)) + tbBorder = blend(tbBorder, bg, 70); + + // Group 2 + // Rule: If at least 2 of the RGB values are equal to or between 121 and + // 179, then apply specified opacity for Group 2 + // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black + + // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND + else if (testTwoPrimaryColors(tbBorder, 120, 180)) + tbBorder = blend(tbBorder, bg, 50); + + // Group 3 + // Rule: Everything else + // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX + // Aqua, Wn High Con White, Wn High Con #1 + + // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND + else { + tbBorder = blend(tbBorder, bg, 30); + } + createColor(FormColors.TB_BORDER, tbBorder); + } + + private void updateFormHeaderColors() { + if (colorRegistry.containsKey(IFormColors.H_GRADIENT_END)) { + disposeIfFound(IFormColors.H_GRADIENT_END); + disposeIfFound(IFormColors.H_GRADIENT_START); + disposeIfFound(IFormColors.H_BOTTOM_KEYLINE1); + disposeIfFound(IFormColors.H_BOTTOM_KEYLINE2); + disposeIfFound(IFormColors.H_HOVER_LIGHT); + disposeIfFound(IFormColors.H_HOVER_FULL); + initializeFormHeaderColors(); + } + } + + private void disposeIfFound(String key) { + Color color = getColor(key); + if (color !is null) { + colorRegistry.removeKey(key); + color.dispose(); + } + } + + private void createFormHeaderColors() { + createFormHeaderGradientColors(); + createFormHeaderKeylineColors(); + createFormHeaderDNDColors(); + } + + private void createFormHeaderGradientColors() { + RGB titleBg = getSystemColor(DWT.COLOR_TITLE_BACKGROUND); + Color bgColor = getImpliedBackground(); + RGB bg = bgColor.getRGB(); + RGB bottom, top; + // Group 1 + // Rule: If at least 2 of the RGB values are equal to or between 180 and + // 255, then apply specified opacity for Group 1 + // Examples: Vista, XP Silver, Wn High Con #2 + // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + if (testTwoPrimaryColors(titleBg, 179, 256)) { + bottom = blend(titleBg, bg, 30); + top = bg; + } + + // Group 2 + // Rule: If at least 2 of the RGB values are equal to or between 121 and + // 179, then apply specified opacity for Group 2 + // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black + // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + else if (testTwoPrimaryColors(titleBg, 120, 180)) { + bottom = blend(titleBg, bg, 20); + top = bg; + } + + // Group 3 + // Rule: If at least 2 of the RGB values are equal to or between 0 and + // 120, then apply specified opacity for Group 3 + // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX + // Aqua, Wn High Con White, Wn High Con #1 + // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + else { + bottom = blend(titleBg, bg, 10); + top = bg; + } + createColor(IFormColors.H_GRADIENT_END, top); + createColor(IFormColors.H_GRADIENT_START, bottom); + } + + private void createFormHeaderKeylineColors() { + RGB titleBg = getSystemColor(DWT.COLOR_TITLE_BACKGROUND); + Color bgColor = getImpliedBackground(); + RGB bg = bgColor.getRGB(); + RGB keyline2; + // H_BOTTOM_KEYLINE1 + createColor(IFormColors.H_BOTTOM_KEYLINE1, new RGB(255, 255, 255)); + + // H_BOTTOM_KEYLINE2 + // Group 1 + // Rule: If at least 2 of the RGB values are equal to or between 180 and + // 255, then apply specified opacity for Group 1 + // Examples: Vista, XP Silver, Wn High Con #2 + // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND + if (testTwoPrimaryColors(titleBg, 179, 256)) + keyline2 = blend(titleBg, bg, 70); + + // Group 2 + // Rule: If at least 2 of the RGB values are equal to or between 121 and + // 179, then apply specified opacity for Group 2 + // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black + // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND + else if (testTwoPrimaryColors(titleBg, 120, 180)) + keyline2 = blend(titleBg, bg, 50); + + // Group 3 + // Rule: If at least 2 of the RGB values are equal to or between 0 and + // 120, then apply specified opacity for Group 3 + // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX + // Aqua, Wn High Con White, Wn High Con #1 + + // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND + else + keyline2 = blend(titleBg, bg, 30); + // H_BOTTOM_KEYLINE2 + createColor(IFormColors.H_BOTTOM_KEYLINE2, keyline2); + } + + private void createFormHeaderDNDColors() { + RGB titleBg = getSystemColor(DWT.COLOR_TITLE_BACKGROUND_GRADIENT); + Color bgColor = getImpliedBackground(); + RGB bg = bgColor.getRGB(); + RGB light, full; + // ALL Themes + // + // Light Highlight + // When *near* the 'hot' area + // Rule: If near the title in the 'hot' area, show background highlight + // TITLE_BACKGROUND_GRADIENT @ 40% + light = blend(titleBg, bg, 40); + // Full Highlight + // When *on* the title area (regions 1 and 2) + // Rule: If near the title in the 'hot' area, show background highlight + // TITLE_BACKGROUND_GRADIENT @ 60% + full = blend(titleBg, bg, 60); + // H_DND_LIGHT + // H_DND_FULL + createColor(IFormColors.H_HOVER_LIGHT, light); + createColor(IFormColors.H_HOVER_FULL, full); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/FormDialog.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.ui.forms.FormDialog; + +import dwtx.ui.forms.IManagedForm; +import dwtx.ui.forms.ManagedForm; + +import dwt.DWT; +import dwt.layout.GridData; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Label; +import dwt.widgets.Shell; +import dwtx.jface.dialogs.TrayDialog; +import dwtx.jface.window.IShellProvider; +import dwtx.ui.forms.widgets.FormToolkit; +import dwtx.ui.forms.widgets.ScrolledForm; +import dwtx.ui.internal.forms.Messages; + +import dwt.dwthelper.utils; + +/** + * A general-purpose dialog that hosts a form. Clients should extend the class + * and override <code>createFormContent(IManagedForm)</code> protected method. + * <p> + * Since forms with wrapped text typically don't have a preferred size, it is + * important to set the initial dialog size upon creation: + * <p> + * + * <pre> + * MyFormDialog dialog = new MyFormDialog(shell); + * dialog.create(); + * dialog.getShell().setSize(500, 500); + * dialog.open(); + * </pre> + * + * <p> + * Otherwise, the dialog may open very wide. + * <p> + * + * @since 3.3 + */ + +public class FormDialog : TrayDialog { + private FormToolkit toolkit; + + /** + * Creates a new form dialog for a provided parent shell. + * + * @param shell + * the parent shell + */ + public this(Shell shell) { + super(shell); + setShellStyle(getShellStyle() | DWT.RESIZE); + } + + /** + * Creates a new form dialog for a provided parent shell provider. + * + * @param parentShellProvider + * the parent shell provider + */ + public this(IShellProvider parentShellProvider) { + super(parentShellProvider); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.TrayDialog#close() + */ + public bool close() { + bool rcode = super.close(); + toolkit.dispose(); + return rcode; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.Dialog#createDialogArea(dwt.widgets.Composite) + */ + protected Control createDialogArea(Composite parent) { + toolkit = new FormToolkit(parent.getDisplay()); + ScrolledForm sform = toolkit.createScrolledForm(parent); + sform.setLayoutData(new GridData(GridData.FILL_BOTH)); + ManagedForm mform = new ManagedForm(toolkit, sform); + createFormContent(mform); + applyDialogFont(sform.getBody()); + return sform; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.TrayDialog#createButtonBar(dwt.widgets.Composite) + */ + protected Control createButtonBar(Composite parent) { + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + //Composite sep = new Composite(parent, DWT.NULL); + //sep.setBackground(parent.getDisplay().getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW)); + //gd.heightHint = 1; + Label sep = new Label(parent, DWT.HORIZONTAL|DWT.SEPARATOR); + sep.setLayoutData(gd); + Control bar = super.createButtonBar(parent); + return bar; + } + + /** + * Configures the dialog form and creates form content. Clients should + * override this method. + * + * @param mform + * the dialog form + */ + protected void createFormContent(IManagedForm mform) { + mform.getForm().setText(Messages.FormDialog_defaultTitle); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/HyperlinkGroup.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,248 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.HyperlinkGroup; + +import dwtx.ui.forms.HyperlinkSettings; + +import dwt.DWT; +import dwt.graphics.Color; +import dwt.widgets.Display; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwtx.ui.forms.events.HyperlinkEvent; +import dwtx.ui.forms.events.IHyperlinkListener; +import dwtx.ui.forms.widgets.Hyperlink; + +import dwt.dwthelper.utils; +import tango.util.collection.ArraySeq; + +/** + * Manages a group of hyperlinks. It tracks activation, updates normal and + * active colors and updates underline state depending on the underline + * preference. Hyperlink labels are added to the group after creation and are + * automatically removed from the group when they are disposed. + * + * @since 3.0 + */ + +public final class HyperlinkGroup : HyperlinkSettings { + private ArraySeq!(Object) links; + private Hyperlink lastActivated; + private Hyperlink lastEntered; + private GroupListener listener; + private bool isActiveBackgroundSet; + private bool isActiveForegroundSet; + private bool isBackgroundSet; + private bool isForegroundSet; + + private class GroupListener : Listener, IHyperlinkListener { + + private Color previousBackground; + private Color previousForeground; + + public void handleEvent(Event e) { + switch (e.type) { + case DWT.MouseEnter : + onMouseEnter(e); + break; + case DWT.MouseExit : + onMouseExit(e); + break; + case DWT.MouseDown : + onMouseDown(e); + break; + case DWT.Dispose : + unhook(cast(Hyperlink) e.widget); + break; + } + } + private void onMouseEnter(Event e) { + Hyperlink link = cast(Hyperlink) e.widget; + previousBackground = link.getBackground(); + previousForeground = link.getForeground(); + if (isActiveBackgroundSet) + link.setBackground(getActiveBackground()); + if (isActiveForegroundSet) + link.setForeground(getActiveForeground()); + if (getHyperlinkUnderlineMode() is UNDERLINE_HOVER) + link.setUnderlined(true); + link.setCursor(getHyperlinkCursor()); + } + private void onMouseExit(Event e) { + Hyperlink link = cast(Hyperlink) e.widget; + if (isActiveBackgroundSet) + link.setBackground(previousBackground); + if (isActiveForegroundSet) + link.setForeground(previousForeground); + if (getHyperlinkUnderlineMode() is UNDERLINE_HOVER) + link.setUnderlined(false); + } + public void linkActivated(HyperlinkEvent e) { + } + + public void linkEntered(HyperlinkEvent e) { + Hyperlink link = cast(Hyperlink) e.widget; + if (lastEntered !is null) { + linkExited(lastEntered); + } + lastEntered = link; + } + + public void linkExited(HyperlinkEvent e) { + linkExited(cast(Hyperlink) e.widget); + } + private void linkExited(Hyperlink link) { + link.setCursor(null); + if (lastEntered is link) + lastEntered = null; + } + } + + /** + * Creates a hyperlink group. + */ + + public this(Display display) { + links = new ArraySeq!(Object); + super(display); + listener = new GroupListener(); + } + + /** + * Returns the link that has been active the last, or <code>null</code> + * if no link has been active yet or the last active link has been + * disposed. + * + * @return the last active link or <code>null</code> + */ + public Hyperlink getLastActivated() { + return lastActivated; + } + /** + * Adds a hyperlink to the group to be jointly managed. Hyperlink will be + * managed until it is disposed. Settings like colors, cursors and modes + * will affect all managed hyperlinks. + * + * @param link + */ + + public void add(Hyperlink link) { + if (isBackgroundSet) + link.setBackground(getBackground()); + if (isForegroundSet) + link.setForeground(getForeground()); + if (getHyperlinkUnderlineMode() is UNDERLINE_ALWAYS) + link.setUnderlined(true); + hook(link); + } + + /** + * Sets the new active hyperlink background for all the links. + * + * @param newActiveBackground + * the new active background + */ + public void setActiveBackground(Color newActiveBackground) { + super.setActiveBackground(newActiveBackground); + isActiveBackgroundSet = true; + } + + /** + * Sets the new active hyperlink foreground for all the links. + * + * @param newActiveForeground + * the new active foreground + */ + public void setActiveForeground(Color newActiveForeground) { + super.setActiveForeground(newActiveForeground); + isActiveForegroundSet = true; + } + + /** + * Sets the group background and also sets the background of all the + * currently managed links. + * + * @param bg + * the new background + */ + public void setBackground(Color bg) { + super.setBackground(bg); + isBackgroundSet = true; + if (links !is null) { + for (int i = 0; i < links.size(); i++) { + Hyperlink label = cast(Hyperlink) links.get(i); + label.setBackground(bg); + } + } + } + /** + * Sets the group foreground and also sets the background of all the + * currently managed links. + * + * @param fg + * the new foreground + */ + public void setForeground(Color fg) { + super.setForeground(fg); + isForegroundSet = true; + if (links !is null) { + for (int i = 0; i < links.size(); i++) { + Hyperlink label = cast(Hyperlink) links.get(i); + label.setForeground(fg); + } + } + } + /** + * Sets the hyperlink underline mode. + * + * @param mode + * the new hyperlink underline mode + * @see HyperlinkSettings + */ + public void setHyperlinkUnderlineMode(int mode) { + super.setHyperlinkUnderlineMode(mode); + if (links !is null) { + for (int i = 0; i < links.size(); i++) { + Hyperlink label = cast(Hyperlink) links.get(i); + label.setUnderlined(mode is UNDERLINE_ALWAYS); + } + } + } + + private void hook(Hyperlink link) { + link.addListener(DWT.MouseDown, listener); + link.addHyperlinkListener(listener); + link.addListener(DWT.Dispose, listener); + link.addListener(DWT.MouseEnter, listener); + link.addListener(DWT.MouseExit, listener); + links.append(link); + } + + private void unhook(Hyperlink link) { + link.removeListener(DWT.MouseDown, listener); + link.removeHyperlinkListener(listener); + link.removeListener(DWT.MouseEnter, listener); + link.removeListener(DWT.MouseExit, listener); + if (lastActivated is link) + lastActivated = null; + if (lastEntered is link) + lastEntered = null; + links.remove(link); + } + + private void onMouseDown(Event e) { + if (e.button is 1) + return; + lastActivated = cast(Hyperlink) e.widget; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/HyperlinkSettings.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,189 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.HyperlinkSettings; + +import dwt.DWT; +import dwt.graphics.Color; +import dwt.graphics.Cursor; +import dwt.widgets.Display; +import dwtx.jface.resource.JFaceColors; +import dwtx.ui.internal.forms.widgets.FormsResources; + +import dwt.dwthelper.utils; + +/** + * Manages color and underline mode settings for a group of hyperlinks. The + * class is extended by HyperlinkGroup but is otwerwise not intended to be + * subclassed. + * + * @since 3.0 + */ +public class HyperlinkSettings { + /** + * Underline mode to be used when hyperlinks should not be underlined. + */ + public static const int UNDERLINE_NEVER = 1; + /** + * Underline mode to be used when hyperlinks should only be underlined on + * mouse hover. + */ + public static const int UNDERLINE_HOVER = 2; + /** + * Underline mode to be used when hyperlinks should always be underlined. + */ + public static const int UNDERLINE_ALWAYS = 3; + private int hyperlinkUnderlineMode = UNDERLINE_ALWAYS; + private Color background; + private Color foreground; + private Color activeBackground; + private Color activeForeground; + /** + * The constructor. + * + * @param display + * the display to use when creating colors. + */ + public this(Display display) { + initializeDefaultForegrounds(display); + } + /** + * Initializes the hyperlink foregrounds from the JFace defaults set for the + * entire workbench. + * + * @see JFaceColors + * @param display + * the display to use when creating colors + */ + public void initializeDefaultForegrounds(Display display) { + Color fg = JFaceColors.getHyperlinkText(display); + Color afg = JFaceColors.getActiveHyperlinkText(display); + if (fg is null) + fg = display.getSystemColor(DWT.COLOR_BLUE); + setForeground(fg); + setActiveForeground(afg); + } + /** + * Returns the background to use for the active hyperlink. + * + * @return active hyperlink background + */ + public Color getActiveBackground() { + return activeBackground; + } + /** + * Returns the foreground to use for the active hyperlink. + * + * @return active hyperlink foreground + */ + public Color getActiveForeground() { + return activeForeground; + } + /** + * Returns the background to use for the normal hyperlink. + * + * @return normal hyperlink background + */ + public Color getBackground() { + return background; + } + /** + * Returns the cursor to use when the hyperlink is active. This cursor will + * be shown before hyperlink listeners have been notified of hyperlink + * activation and hidden when the notification method returns. + * + * @return the busy cursor + */ + public Cursor getBusyCursor() { + return FormsResources.getBusyCursor(); + } + /** + * Returns the cursor to use when over text. + * + * @return the text cursor + */ + public Cursor getTextCursor() { + return FormsResources.getTextCursor(); + } + /** + * Returns the foreground to use for the normal hyperlink. + * + * @return the normal hyperlink foreground + */ + public Color getForeground() { + return foreground; + } + /** + * Returns the cursor to use when hovering over the hyperlink. + * + * @return the hyperlink cursor + */ + public Cursor getHyperlinkCursor() { + return FormsResources.getHandCursor(); + } + /** + * Returns the underline mode to be used for all the hyperlinks in this + * group. + * + * @return one of UNDERLINE_NEVER, UNDERLINE_ALWAYS, UNDERLINE_HOVER + */ + public int getHyperlinkUnderlineMode() { + return hyperlinkUnderlineMode; + } + /** + * Sets the new active hyperlink background for all the links. + * + * @param newActiveBackground + * the new active background + */ + public void setActiveBackground(Color newActiveBackground) { + activeBackground = newActiveBackground; + } + /** + * Sets the new active hyperlink foreground for all the links. + * + * @param newActiveForeground + * the new active foreground + */ + public void setActiveForeground(Color newActiveForeground) { + activeForeground = newActiveForeground; + } + /** + * Sets the new hyperlink background for all the links. + * + * @param newBackground + * the new hyperlink background + */ + public void setBackground(Color newBackground) { + background = newBackground; + } + /** + * Sets the new hyperlink foreground for all the links. + * + * @param newForeground + * the new hyperlink foreground + */ + public void setForeground(Color newForeground) { + foreground = newForeground; + } + /** + * Sets the new hyperlink underline mode for all the links in this group. + * + * @param mode + * one of <code>UNDERLINE_NEVER</code>, + * <code>UNDERLINE_HOVER</code> and + * <code>UNDERLINE_ALWAYS</code>. + */ + public void setHyperlinkUnderlineMode(int mode) { + hyperlinkUnderlineMode = mode; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/IDetailsPage.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.IDetailsPage; + +import dwtx.ui.forms.IFormPart; +import dwtx.ui.forms.IPartSelectionListener; + +import dwt.widgets.Composite; + +/** + * This interface should be implemented by clients providing + * pages to handle object types in DetailsPart. Most of the + * life cycle is the same as for the IFormPart. The page is + * a part selection listener since selections in the master + * part will be passed to the currently visible page. + * + * @see DetailsPart + * @see MasterDetailsBlock + * @since 3.0 + */ +public interface IDetailsPage : IFormPart, IPartSelectionListener { + /** + * Creates the contents of the page in the provided parent. + * @param parent the parent to create the page in + */ + void createContents(Composite parent); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/IDetailsPageProvider.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.IDetailsPageProvider; + +import dwtx.ui.forms.IDetailsPage; + +/** + * The class that implements this interface provides for dynamic + * computation of page key and the page itself based on the + * input object. It should be used in situations where + * using the object class as a static key is not enough + * i.e. different pages may need to be loaded for objects + * of the same type depending on their state. + * + * @see DetailsPart + * @see MasterDetailsBlock + * @since 3.0 + */ +public interface IDetailsPageProvider { +/** + * Returns the page key for the provided object. The assumption is + * that the provider knows about various object types and + * is in position to cast the object into a type and call methods + * on it to determine the matching page key. + * @param object the input object + * @return the page key for the provided object + */ + Object getPageKey(Object object); +/** + * Returns the page for the provided key. This method is the dynamic + * alternative to registering pages with the details part directly. + * @param key the page key + * @return the matching page for the provided key + */ + IDetailsPage getPage(Object key); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/IFormColors.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2007 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.ui.forms.IFormColors; + +import dwt.dwthelper.utils; + +/** + * A place to hold all the color constants used in the forms package. + * + * @since 3.3 + */ + +public interface IFormColors { + /** + * A prefix for all the keys. + */ + static const String PREFIX = "dwtx.ui.forms."; //$NON-NLS-1$ + /** + * Key for the form title foreground color. + */ + static const String TITLE = PREFIX ~ "TITLE"; //$NON-NLS-1$ + + /** + * A prefix for the header color constants. + */ + static const String H_PREFIX = PREFIX ~ "H_"; //$NON-NLS-1$ + /* + * A prefix for the section title bar color constants. + */ + static const String TB_PREFIX = PREFIX ~ "TB_"; //$NON-NLS-1$ + /** + * Key for the form header background gradient ending color. + */ + static const String H_GRADIENT_END = H_PREFIX ~ "GRADIENT_END"; //$NON-NLS-1$ + + /** + * Key for the form header background gradient starting color. + * + */ + static const String H_GRADIENT_START = H_PREFIX ~ "GRADIENT_START"; //$NON-NLS-1$ + /** + * Key for the form header bottom keyline 1 color. + * + */ + static const String H_BOTTOM_KEYLINE1 = H_PREFIX ~ "BOTTOM_KEYLINE1"; //$NON-NLS-1$ + /** + * Key for the form header bottom keyline 2 color. + * + */ + static const String H_BOTTOM_KEYLINE2 = H_PREFIX ~ "BOTTOM_KEYLINE2"; //$NON-NLS-1$ + /** + * Key for the form header light hover color. + * + */ + static const String H_HOVER_LIGHT = H_PREFIX ~ "H_HOVER_LIGHT"; //$NON-NLS-1$ + /** + * Key for the form header full hover color. + * + */ + static const String H_HOVER_FULL = H_PREFIX ~ "H_HOVER_FULL"; //$NON-NLS-1$ + + /** + * Key for the tree/table border color. + */ + static const String BORDER = PREFIX ~ "BORDER"; //$NON-NLS-1$ + + /** + * Key for the section separator color. + */ + static const String SEPARATOR = PREFIX ~ "SEPARATOR"; //$NON-NLS-1$ + + /** + * Key for the section title bar background. + */ + static const String TB_BG = TB_PREFIX ~ "BG"; //$NON-NLS-1$ + + /** + * Key for the section title bar foreground. + */ + static const String TB_FG = TB_PREFIX ~ "FG"; //$NON-NLS-1$ + + /** + * Key for the section title bar gradient. + * @deprecated Since 3.3, this color is not used any more. The + * tool bar gradient is created starting from {@link #TB_BG} to + * the section background color. + */ + static const String TB_GBG = TB_BG; + + /** + * Key for the section title bar border. + */ + static const String TB_BORDER = TB_PREFIX ~ "BORDER"; //$NON-NLS-1$ + + /** + * Key for the section toggle color. Since 3.1, this color is used for all + * section styles. + */ + static const String TB_TOGGLE = TB_PREFIX ~ "TOGGLE"; //$NON-NLS-1$ + + /** + * Key for the section toggle hover color. + * + */ + static const String TB_TOGGLE_HOVER = TB_PREFIX ~ "TOGGLE_HOVER"; //$NON-NLS-1$ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/IFormPart.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.IFormPart; + +import dwtx.ui.forms.IManagedForm; + +import dwt.dwthelper.utils; + +/** + * Classes that implement this interface can be added to the managed form and + * take part in the form life cycle. The part is initialized with the form and + * will be asked to accept focus. The part can receive form input and can elect + * to do something according to it (for example, select an object that matches + * the input). + * <p> + * The form part has two 'out of sync' states in respect to the model(s) that + * feed the form: <b>dirty</b> and <b>stale</b>. When a part is dirty, it + * means that the user interacted with it and now its widgets contain state that + * is newer than the model. In order to sync up with the model, 'commit' needs + * to be called. In contrast, the model can change 'under' the form (as a result + * of some actions outside the form), resulting in data in the model being + * 'newer' than the content presented in the form. A 'stale' form part is + * brought in sync with the model by calling 'refresh'. The part is responsible + * for notifying the form when one of these states change in the part. The form + * reserves the right to handle this notification in the most appropriate way + * for the situation (for example, if the form is in a page of the multi-page + * editor, it may do nothing for stale parts if the page is currently not + * showing). + * <p> + * When the form is disposed, each registered part is disposed as well. Parts + * are responsible for releasing any system resources they created and for + * removing themselves as listeners from all event providers. + * + * @see IManagedForm + * @since 3.0 + * + */ +public interface IFormPart { + /** + * Initializes the part. + * + * @param form + * the managed form that manages the part + */ + void initialize(IManagedForm form); + + /** + * Disposes the part allowing it to release allocated resources. + */ + void dispose(); + + /** + * Returns true if the part has been modified with respect to the data + * loaded from the model. + * + * @return true if the part has been modified with respect to the data + * loaded from the model + */ + bool isDirty(); + + /** + * If part is displaying information loaded from a model, this method + * instructs it to commit the new (modified) data back into the model. + * + * @param onSave + * indicates if commit is called during 'save' operation or for + * some other reason (for example, if form is contained in a + * wizard or a multi-page editor and the user is about to leave + * the page). + */ + void commit(bool onSave); + + /** + * Notifies the part that an object has been set as overall form's input. + * The part can elect to react by revealing or selecting the object, or do + * nothing if not applicable. + * + * @return <code>true</code> if the part has selected and revealed the + * input object, <code>false</code> otherwise. + */ + bool setFormInput(Object input); + + /** + * Instructs form part to transfer focus to the widget that should has focus + * in that part. The method can do nothing (if it has no widgets capable of + * accepting focus). + */ + void setFocus(); + + /** + * Tests whether the form part is stale and needs refreshing. Parts can + * receive notification from models that will make their content stale, but + * may need to delay refreshing to improve performance (for example, there + * is no need to immediately refresh a part on a form that is current on a + * hidden page). + * <p> + * It is important to differentiate 'stale' and 'dirty' states. Part is + * 'dirty' if user interacted with its editable widgets and changed the + * values. In contrast, part is 'stale' when the data it presents in the + * widgets has been changed in the model without direct user interaction. + * + * @return <code>true</code> if the part needs refreshing, + * <code>false</code> otherwise. + */ + bool isStale(); + + /** + * Refreshes the part completely from the information freshly obtained from + * the model. The method will not be called if the part is not stale. + * Otherwise, the part is responsible for clearing the 'stale' flag after + * refreshing itself. + */ + void refresh(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/IManagedForm.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,192 @@ +/******************************************************************************* + * Copyright (c) 2003, 2007 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.ui.forms.IManagedForm; + +import dwtx.ui.forms.IFormPart; +import dwtx.ui.forms.IMessageManager; + +import dwtx.jface.viewers.ISelection; +import dwtx.ui.forms.widgets.FormToolkit; +import dwtx.ui.forms.widgets.ScrolledForm; + +import dwt.dwthelper.utils; + +/** + * Managed form wraps a form widget and adds life cycle methods for form parts. + * A form part is a portion of the form that participates in form life cycle + * events. + * <p> + * There is no 1/1 mapping between widgets and form parts. A widget like Section + * can be a part by itself, but a number of widgets can gather around one form + * part. + * <p> + * This interface should not be extended or implemented. New form instances + * should be created using ManagedForm. + * + * @see ManagedForm + * @since 3.0 + */ +public interface IManagedForm { + /** + * Initializes the form by looping through the managed parts and + * initializing them. Has no effect if already called once. + * + * @since 3.1 + */ + public void initialize(); + + /** + * Returns the toolkit used by this form. + * + * @return the toolkit + */ + public FormToolkit getToolkit(); + + /** + * Returns the form widget managed by this form. + * + * @return the form widget + */ + public ScrolledForm getForm(); + + /** + * Reflows the form as a result of the layout change. + * + * @param changed + * if <code>true</code>, discard cached layout information + */ + public void reflow(bool changed); + + /** + * A part can use this method to notify other parts that implement + * IPartSelectionListener about selection changes. + * + * @param part + * the part that broadcasts the selection + * @param selection + * the selection in the part + */ + public void fireSelectionChanged(IFormPart part, ISelection selection); + + /** + * Returns all the parts currently managed by this form. + * + * @return the managed parts + */ + IFormPart[] getParts(); + + /** + * Adds the new part to the form. + * + * @param part + * the part to add + */ + void addPart(IFormPart part); + + /** + * Removes the part from the form. + * + * @param part + * the part to remove + */ + void removePart(IFormPart part); + + /** + * Sets the input of this page to the provided object. + * + * @param input + * the new page input + * @return <code>true</code> if the form contains this object, + * <code>false</code> otherwise. + */ + bool setInput(Object input); + + /** + * Returns the current page input. + * + * @return page input object or <code>null</code> if not applicable. + */ + Object getInput(); + + /** + * Tests if form is dirty. A managed form is dirty if at least one managed + * part is dirty. + * + * @return <code>true</code> if at least one managed part is dirty, + * <code>false</code> otherwise. + */ + bool isDirty(); + + /** + * Notifies the form that the dirty state of one of its parts has changed. + * The global dirty state of the form can be obtained by calling 'isDirty'. + * + * @see #isDirty + */ + void dirtyStateChanged(); + + /** + * Commits the dirty form. All pending changes in the widgets are flushed + * into the model. + * + * @param onSave + */ + void commit(bool onSave); + + /** + * Tests if form is stale. A managed form is stale if at least one managed + * part is stale. This can happen when the underlying model changes, + * resulting in the presentation of the part being out of sync with the + * model and needing refreshing. + * + * @return <code>true</code> if the form is stale, <code>false</code> + * otherwise. + */ + bool isStale(); + + /** + * Notifies the form that the stale state of one of its parts has changed. + * The global stale state of the form can be obtained by calling 'isStale'. + */ + void staleStateChanged(); + + /** + * Refreshes the form by refreshing every part that is stale. + */ + void refresh(); + + /** + * Sets the container that owns this form. Depending on the context, the + * container may be wizard, editor page, editor etc. + * + * @param container + * the container of this form + */ + void setContainer(Object container); + + /** + * Returns the container of this form. + * + * @return the form container + */ + Object getContainer(); + + /** + * Returns the message manager that will keep track of messages in this + * form. + * + * @return the message manager instance + * @since 3.3 + */ + IMessageManager getMessageManager(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/IMessage.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2007 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.ui.forms.IMessage; + +import dwt.widgets.Control; +import dwtx.jface.dialogs.IMessageProvider; + +import dwt.dwthelper.utils; + +/** + * This interface encapsulates a single message that can be shown in a form. + * Messages can be associated with controls, or be of a general nature. + * + * @see IMessageManager + * @since 3.3 + */ +public interface IMessage : IMessageProvider { + /** + * Returns the unique message key + * + * @return the unique message key + */ + Object getKey(); + + /** + * Returns data for application use + * + * @return data object + */ + Object getData(); + + /** + * Returns the control this message is associated with. + * + * @return the control or <code>null</code> if this is a general message. + */ + Control getControl(); + + /** + * Messages that are associated with controls can be shown with a prefix + * that indicates the origin of the message (e.g. the label preceeding the + * control). + * + * @return the message prefix or <code>null</code> if this is a general + * message + */ + String getPrefix(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/IMessageManager.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (c) 2007 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.ui.forms.IMessageManager; + +import dwtx.ui.forms.IMessagePrefixProvider; +import dwtx.ui.forms.IMessage; + +import dwt.widgets.Control; +import dwtx.jface.dialogs.IMessageProvider; +import dwtx.jface.fieldassist.ControlDecoration; +import dwtx.ui.forms.widgets.Form; + +import dwt.dwthelper.utils; + +/** + * This interface provides for managing typed messages in a form. Typed messages + * are messages associated with a type that indicates their severity (error, + * warning, information). The interface is responsible for: + * <ul> + * <li>Bridging the concept of typed messages and control decorations</li> + * <li>Adding one or more messages per control in a form</li> + * <li>Rolling the local messages up to the form header</li> + * <li>Adding one or more general messages to the form header</li> + * </ul> + * <p> + * To use it in a form, do the following: + * <ol> + * <li>For each interactive control, add a listener to it to monitor user input</li> + * <li>Every time the input changes, validate it. If there is a problem, add a + * message with a unique key to the manager. If there is already a message with + * the same key in the manager, its type and message text will be replaced (no + * duplicates). Note that you add can messages with different keys to the same + * control to track multiple problems with the user input.</li> + * <li>If the problem has been cleared, remove the message using the key + * (attempting to remove a message that is not in the manager is safe).</li> + * <li>If something happens in the form that is not related to any control, use + * the other <code>addMessage</code> method.</li> + * </ol> + * <p> + * This interface should only be referenced. It must not be implemented or + * extended. + * </p> + * + * @since 3.3 + * @see IMessageProvider + * @see IManagedForm + */ + +public interface IMessageManager { + /** + * Adds a general message that is not associated with any decorated field. + * Note that subsequent calls using the same key will not result in + * duplicate messages. Instead, the previous message with the same key will + * be replaced with the new message. + * + * @param key + * a unique message key that will be used to look the message up + * later + * + * @param messageText + * the message to add + * @param data + * an object for application use (can be <code>null</code>) + * @param type + * the message type as defined in <code>IMessageProvider</code>. + */ + void addMessage(Object key, String messageText, Object data, int type); + + /** + * Adds a message that should be associated with the provided control. Note + * that subsequent calls using the same key will not result in duplicate + * messages. Instead, the previous message with the same key will be + * replaced with the new message. + * + * @param key + * the unique message key + * @param messageText + * the message to add + * @param data + * an object for application use (can be <code>null</code>) + * @param type + * the message type + * @param control + * the control to associate the message with + */ + void addMessage(Object key, String messageText, Object data, int type, + Control control); + + /** + * Removes the general message with the provided key. Does nothing if + * message for the key does not exist. + * + * @param key + * the key of the message to remove + */ + void removeMessage(Object key); + + /** + * Removes all the general messages. If there are local messages associated + * with controls, the replacement message may show up drawing user's + * attention to these local messages. Otherwise, the container will clear + * the message area. + */ + void removeMessages(); + + /** + * Removes a keyed message associated with the provided control. Does + * nothing if the message for that key does not exist. + * + * @param key + * the id of the message to remove + * @param control + * the control the message is associated with + */ + void removeMessage(Object key, Control control); + + /** + * Removes all the messages associated with the provided control. Does + * nothing if there are no messages for this control. + * + * @param control + * the control the messages are associated with + */ + void removeMessages(Control control); + + /** + * Removes all the local field messages and all the general container + * messages. + */ + void removeAllMessages(); + + /** + * Updates the message container with the messages currently in the manager. + * There are two scenarios in which a client may want to use this method: + * <ol> + * <li>When controls previously managed by this manager have been disposed.</li> + * <li>When automatic update has been turned off.</li> + * </ol> + * In all other situations, the manager will keep the form in sync + * automatically. + * + * @see #setAutoUpdate(bool) + */ + void update(); + + /** + * Controls whether the form is automatically updated when messages are + * added or removed. By default, auto update is on. Clients can turn it off + * prior to adding or removing a number of messages as a batch. Turning it + * back on will trigger an update. + * + * @param enabled + * sets the state of the automatic update + */ + void setAutoUpdate(bool enabled); + + /** + * Tests whether the form will be automatically updated when messages are + * added or removed. + * + * @return <code>true</code> if auto update is active, <code>false</code> + * otherwise. + */ + bool isAutoUpdate(); + + /** + * Sets the alternative message prefix provider. The default prefix provider + * is set by the manager. + * + * @param provider + * the new prefix provider or <code>null</code> to turn the + * prefix generation off. + */ + void setMessagePrefixProvider(IMessagePrefixProvider provider); + + /** + * @return the current prefix provider or <code>null</code> if prefixes + * are not generated. + */ + IMessagePrefixProvider getMessagePrefixProvider(); + + /** + * Message manager uses DWT.LEFT|DWT.BOTTOM for the default decoration + * position. Use this method to change it. + * + * @param position + * the decoration position + * @see ControlDecoration + */ + void setDecorationPosition(int position); + + /** + * Returns the currently used decoration position for all control messages. + * + * @return the current decoration position + */ + + int getDecorationPosition(); + + /** + * When message manager is used in context of a form, and there are + * hyperlink listeners for messages in the header, the hyperlink event will + * carry an object of type <code>IMessage[]</code> as an href. You can use + * this method to create a summary text from this array consistent with the + * tool tip used by the form header. + * + * @param messages + * an array of messages + * @return a textual representation of the messages with one message per + * line. + * @see Form#addMessageHyperlinkListener(dwtx.ui.forms.events.IHyperlinkListener) + */ + String createSummary(IMessage[] messages); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/IMessagePrefixProvider.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2007 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.ui.forms.IMessagePrefixProvider; + +import dwt.widgets.Control; + +import dwt.dwthelper.utils; + +/** + * This interface computes the prefix of a message that is created in the + * context of a control. Since messages are rolled up to the header in the + * message manager, it is important to create a prefix to indicate the context + * of a message in the form. Typically the prefix is computed by looking at the + * label that preceeds the control, if present. Alternative providers may + * include other text to further specify the location of the message. + * + * @see IMessageManager + * @see IMessage + * @since 3.3 + */ +public interface IMessagePrefixProvider { + /** + * Returns the computed prefix for the provided control. + * + * @param control + * the control to provide the prefix for + * @return the computed prefix + */ + String getPrefix(Control control); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/IPartSelectionListener.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.IPartSelectionListener; + +import dwtx.ui.forms.IFormPart; + +import dwtx.jface.viewers.ISelection; + +/** + * Form parts can implement this interface if they want to be + * notified when another part on the same form changes selection + * state. + * + * @see IFormPart + * @since 3.0 + */ +public interface IPartSelectionListener { + /** + * Called when the provided part has changed selection state. + * + * @param part + * the selection source + * @param selection + * the new selection + */ + public void selectionChanged(IFormPart part, ISelection selection); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/ManagedForm.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,347 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.ManagedForm; + +import dwtx.ui.forms.IManagedForm; +import dwtx.ui.forms.IFormPart; +import dwtx.ui.forms.IMessageManager; +import dwtx.ui.forms.IPartSelectionListener; + +import dwt.widgets.Composite; +import dwtx.jface.viewers.ISelection; +import dwtx.ui.forms.widgets.FormToolkit; +import dwtx.ui.forms.widgets.ScrolledForm; +import dwtx.ui.internal.forms.MessageManager; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Runnable; +import tango.util.collection.ArraySeq; +import tango.core.Thread; + +/** + * Managed form wraps a form widget and adds life cycle methods for form parts. + * A form part is a portion of the form that participates in form life cycle + * events. + * <p> + * There is requirement for 1/1 mapping between widgets and form parts. A widget + * like Section can be a part by itself, but a number of widgets can join around + * one form part. + * <p> + * Note to developers: this class is left public to allow its use beyond the + * original intention (inside a multi-page editor's page). You should limit the + * use of this class to make new instances inside a form container (wizard page, + * dialog etc.). Clients that need access to the class should not do it + * directly. Instead, they should do it through IManagedForm interface as much + * as possible. + * + * @since 3.0 + */ +public class ManagedForm : IManagedForm { + private Object input; + + private ScrolledForm form; + + private FormToolkit toolkit; + + private Object container; + + private bool ownsToolkit; + + private bool initialized; + + private MessageManager messageManager; + + private ArraySeq!(Object) parts; + + /** + * Creates a managed form in the provided parent. Form toolkit and widget + * will be created and owned by this object. + * + * @param parent + * the parent widget + */ + public this(Composite parent) { + parts = new ArraySeq!(Object); + toolkit = new FormToolkit(parent.getDisplay()); + ownsToolkit = true; + form = toolkit.createScrolledForm(parent); + } + + /** + * Creates a managed form that will use the provided toolkit and + * + * @param toolkit + * @param form + */ + public this(FormToolkit toolkit, ScrolledForm form) { + parts = new ArraySeq!(Object); + this.form = form; + this.toolkit = toolkit; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#addPart(dwtx.ui.forms.IFormPart) + */ + public void addPart(IFormPart part) { + parts.append(cast(Object)part); + part.initialize(this); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#removePart(dwtx.ui.forms.IFormPart) + */ + public void removePart(IFormPart part) { + parts.remove(cast(Object)part); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#getParts() + */ + public IFormPart[] getParts() { + return arraycast!(IFormPart)(parts.toArray()); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#getToolkit() + */ + public FormToolkit getToolkit() { + return toolkit; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#getForm() + */ + public ScrolledForm getForm() { + return form; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#reflow(bool) + */ + public void reflow(bool changed) { + form.reflow(changed); + } + + /** + * A part can use this method to notify other parts that implement + * IPartSelectionListener about selection changes. + * + * @param part + * the part that broadcasts the selection + * @param selection + * the selection in the part + * @see IPartSelectionListener + */ + public void fireSelectionChanged(IFormPart part, ISelection selection) { + for (int i = 0; i < parts.size(); i++) { + IFormPart cpart = cast(IFormPart) parts.get(i); + if ((cast(Object)part).opEquals(cast(Object)cpart)) + continue; + if (null !is cast(IPartSelectionListener)cpart ) { + (cast(IPartSelectionListener) cpart).selectionChanged(part, + selection); + } + } + } + + /** + * Initializes the form by looping through the managed parts and + * initializing them. Has no effect if already called once. + */ + public void initialize() { + if (initialized) + return; + for (int i = 0; i < parts.size(); i++) { + IFormPart part = cast(IFormPart) parts.get(i); + part.initialize(this); + } + initialized = true; + } + + /** + * Disposes all the parts in this form. + */ + public void dispose() { + for (int i = 0; i < parts.size(); i++) { + IFormPart part = cast(IFormPart) parts.get(i); + part.dispose(); + } + if (ownsToolkit) { + toolkit.dispose(); + } + } + + /** + * Refreshes the form by refreshes all the stale parts. Since 3.1, this + * method is performed on a UI thread when called from another thread so it + * is not needed to wrap the call in <code>Display.syncExec</code> or + * <code>asyncExec</code>. + */ + public void refresh() { + Thread t = Thread.getThis(); + Thread dt = toolkit.getColors().getDisplay().getThread(); + if (t.opEquals(dt)) + doRefresh(); + else { + toolkit.getColors().getDisplay().asyncExec(dgRunnable( { + doRefresh(); + })); + } + } + + private void doRefresh() { + int nrefreshed = 0; + for (int i = 0; i < parts.size(); i++) { + IFormPart part = cast(IFormPart) parts.get(i); + if (part.isStale()) { + part.refresh(); + nrefreshed++; + } + } + if (nrefreshed > 0) + form.reflow(true); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#commit(bool) + */ + public void commit(bool onSave) { + for (int i = 0; i < parts.size(); i++) { + IFormPart part = cast(IFormPart) parts.get(i); + if (part.isDirty()) + part.commit(onSave); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#setInput(java.lang.Object) + */ + public bool setInput(Object input) { + bool pageResult = false; + + this.input = input; + for (int i = 0; i < parts.size(); i++) { + IFormPart part = cast(IFormPart) parts.get(i); + bool result = part.setFormInput(input); + if (result) + pageResult = true; + } + return pageResult; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#getInput() + */ + public Object getInput() { + return input; + } + + /** + * Transfers the focus to the first form part. + */ + public void setFocus() { + if (parts.size() > 0) { + IFormPart part = cast(IFormPart) parts.get(0); + part.setFocus(); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#isDirty() + */ + public bool isDirty() { + for (int i = 0; i < parts.size(); i++) { + IFormPart part = cast(IFormPart) parts.get(i); + if (part.isDirty()) + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#isStale() + */ + public bool isStale() { + for (int i = 0; i < parts.size(); i++) { + IFormPart part = cast(IFormPart) parts.get(i); + if (part.isStale()) + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#dirtyStateChanged() + */ + public void dirtyStateChanged() { + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#staleStateChanged() + */ + public void staleStateChanged() { + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#getContainer() + */ + public Object getContainer() { + return container; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IManagedForm#setContainer(java.lang.Object) + */ + public void setContainer(Object container) { + this.container = container; + } + + /* (non-Javadoc) + * @see dwtx.ui.forms.IManagedForm#getMessageManager() + */ + public IMessageManager getMessageManager() { + if (messageManager is null) + messageManager = new MessageManager(form); + return messageManager; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/MasterDetailsBlock.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,232 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.MasterDetailsBlock; + +import dwtx.ui.forms.DetailsPart; +import dwtx.ui.forms.IManagedForm; +import dwtx.ui.forms.FormColors; +import dwtx.ui.forms.IFormColors; + +import dwt.DWT; +import dwt.custom.SashForm; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.layout.GridData; +import dwt.layout.GridLayout; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwt.widgets.Sash; +import dwtx.ui.forms.widgets.FormToolkit; +import dwtx.ui.forms.widgets.ScrolledForm; + +import dwt.dwthelper.utils; +import tango.util.collection.ArraySeq; + +/** + * This class implements the 'master/details' UI pattern suitable for inclusion + * in a form. The block consists of two parts: 'master' and 'details' in a sash + * form that allows users to change the relative ratio on the page. The master + * part needs to be created by the users of this class. The details part is + * created by the block. + * <p> + * The master part is responsible for adding itself as a form part and firing + * selection events. The details part catches the selection events and tries to + * load a page registered to handle the selected object(s). The page shows the + * details of the selected object(s) and allows users to edit them. + * <p> + * Details pages can be registered statically using 'registerPage' or + * dynamically through the use of 'IDetailsPageProvider' in case where different + * pages need to be shown for objects of the same type depending on their state. + * <p> + * Subclasses are required to implement abstract methods of this class. Master + * part must be created and at least one details page should be registered in + * order to show details of the objects selected in the master part. Tool bar + * actions can be optionally added to the tool bar manager. + * + * @see DetailsPart + * @see IDetailsPage + * @see IDetailsPageProvider + * @since 3.0 + */ +public abstract class MasterDetailsBlock { + /** + * Details part created by the block. No attempt should be made to access + * this field inside <code>createMasterPart</code> because it has not been + * created yet and will be <code>null</code>. + */ + protected DetailsPart detailsPart; + + /** + * The form that is the parent of both master and details part. The form + * allows users to change the ratio between the two parts. + */ + protected SashForm sashForm; + + static const int DRAGGER_SIZE = 40; + + class MDSashForm : SashForm { + ArraySeq!(Sash) sashes; + Listener listener; + public this(Composite parent, int style) { + sashes = new ArraySeq!(Sash); + listener = dgListener ( (Event e){ + switch (e.type) { + case DWT.MouseEnter: + e.widget.setData("hover", Boolean.TRUE); //$NON-NLS-1$ + (cast(Control)e.widget).redraw(); + break; + case DWT.MouseExit: + e.widget.setData("hover", null); //$NON-NLS-1$ + (cast(Control)e.widget).redraw(); + break; + case DWT.Paint: + onSashPaint(e); + break; + case DWT.Resize: + hookSashListeners(); + break; + } + }); + super(parent, style); + } + + public void layout(bool changed) { + super.layout(changed); + hookSashListeners(); + } + + public void layout(Control [] children) { + super.layout(children); + hookSashListeners(); + } + + private void hookSashListeners() { + purgeSashes(); + Control [] children = getChildren(); + for (int i=0; i<children.length; i++) { + if ( auto sash = cast(Sash)children[i] ) { + if (sashes.contains(sash)) + continue; + sash.addListener(DWT.Paint, listener); + sash.addListener(DWT.MouseEnter, listener); + sash.addListener(DWT.MouseExit, listener); + sashes.append(sash); + } + } + } + private void purgeSashes() { + foreach ( sash; sashes.dup ) { + if (sash.isDisposed()) + sashes.remove(sash); + } + } + } + + /** + * Creates the content of the master/details block inside the managed form. + * This method should be called as late as possible inside the parent part. + * + * @param managedForm + * the managed form to create the block in + */ + public void createContent(IManagedForm managedForm) { + final ScrolledForm form = managedForm.getForm(); + FormToolkit toolkit = managedForm.getToolkit(); + GridLayout layout = new GridLayout(); + layout.marginWidth = 0; + layout.marginHeight = 0; + form.getBody().setLayout(layout); + sashForm = new MDSashForm(form.getBody(), DWT.NULL); + sashForm.setData("form", cast(Object)managedForm); //$NON-NLS-1$ + toolkit.adapt(sashForm, false, false); + sashForm.setMenu(form.getBody().getMenu()); + sashForm.setLayoutData(new GridData(GridData.FILL_BOTH)); + createMasterPart(managedForm, sashForm); + createDetailsPart(managedForm, sashForm); + hookResizeListener(); + createToolBarActions(managedForm); + form.updateToolBar(); + } + + private void hookResizeListener() { + Listener listener = (cast(MDSashForm)sashForm).listener; + Control [] children = sashForm.getChildren(); + for (int i=0; i<children.length; i++) { + if (null !is cast(Sash)children[i] ) continue; + children[i].addListener(DWT.Resize, listener); + } + } + + /** + * Implement this method to create a master part in the provided parent. + * Typical master parts are section parts that contain tree or table viewer. + * + * @param managedForm + * the parent form + * @param parent + * the parent composite + */ + protected abstract void createMasterPart(IManagedForm managedForm, + Composite parent); + + /** + * Implement this method to statically register pages for the expected + * object types. This mechanism can be used when there is 1->1 mapping + * between object classes and details pages. + * + * @param detailsPart + * the details part + */ + protected abstract void registerPages(DetailsPart detailsPart); + + /** + * Implement this method to create form tool bar actions and add them to the + * form tool bar if desired. + * + * @param managedForm + * the form that owns the tool bar + */ + protected abstract void createToolBarActions(IManagedForm managedForm); + + private void createDetailsPart(IManagedForm mform, Composite parent) { + detailsPart = new DetailsPart(mform, parent, DWT.NULL); + mform.addPart(detailsPart); + registerPages(detailsPart); + } + + private void onSashPaint(Event e) { + Sash sash = cast(Sash)e.widget; + IManagedForm form = cast(IManagedForm)sash.getParent().getData("form"); //$NON-NLS-1$ + FormColors colors = form.getToolkit().getColors(); + bool vertical = (sash.getStyle() & DWT.VERTICAL) !is 0; + GC gc = e.gc; + Boolean hover = cast(Boolean)sash.getData("hover"); //$NON-NLS-1$ + gc.setBackground(colors.getColor(IFormColors.TB_BG)); + gc.setForeground(colors.getColor(IFormColors.TB_BORDER)); + Point size = sash.getSize(); + if (vertical) { + if (hover !is null) + gc.fillRectangle(0, 0, size.x, size.y); + //else + //gc.drawLine(1, 0, 1, size.y-1); + } + else { + if (hover !is null) + gc.fillRectangle(0, 0, size.x, size.y); + //else + //gc.drawLine(0, 1, size.x-1, 1); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/SectionPart.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.ui.forms.SectionPart; + +import dwtx.ui.forms.AbstractFormPart; + +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwtx.ui.forms.events.ExpansionAdapter; +import dwtx.ui.forms.events.ExpansionEvent; +import dwtx.ui.forms.widgets.FormToolkit; +import dwtx.ui.forms.widgets.Section; + +import dwt.dwthelper.utils; + +/** + * Section part implements IFormPart interface based on the Section widget. It + * can either wrap the widget or create one itself. + * <p> + * Subclasses should extend <code>SectionPart</code> and implement life cycle + * methods like <code>refresh</code>, <code>commit</code>, + * <code>setFocus</code> etc. Note that most of these methods are not empty - + * calling <code>super</code> is required. + * + * @see Section + * @since 3.0 + */ +public class SectionPart : AbstractFormPart { + private Section section; + + /** + * Creates a new section part based on the provided section. + * + * @param section + * the section to use + */ + public this(Section section) { + this.section = section; + hookListeners(); + } + + /** + * Creates a new section part inside the provided parent and using the + * provided toolkit. The section part will create the section widget. + * + * @param parent + * the parent + * @param toolkit + * the toolkit to use + * @param style + * the section widget style + */ + public this(Composite parent, FormToolkit toolkit, int style) { + this(toolkit.createSection(parent, style)); + } + + /** + * Adds listeners to the underlying widget. + */ + protected void hookListeners() { + if ((section.getExpansionStyle() & Section.TWISTIE) !is 0 + || (section.getExpansionStyle() & Section.TREE_NODE) !is 0) { + section.addExpansionListener(new class ExpansionAdapter { + public void expansionStateChanging(ExpansionEvent e) { + this.outer.expansionStateChanging(e.getState()); + } + + public void expansionStateChanged(ExpansionEvent e) { + this.outer.expansionStateChanged(e.getState()); + } + }); + } + } + + /** + * Returns the section widget used in this part. + * + * @return the section widget + */ + public Section getSection() { + return section; + } + + /** + * The section is about to expand or collapse. + * + * @param expanding + * <code>true</code> for expansion, <code>false</code> for + * collapse. + */ + protected void expansionStateChanging(bool expanding) { + } + + /** + * The section has expanded or collapsed. + * + * @param expanded + * <code>true</code> for expansion, <code>false</code> for + * collapse. + */ + protected void expansionStateChanged(bool expanded) { + getManagedForm().getForm().reflow(false); + } + + /* (non-Javadoc) + * @see dwtx.ui.forms.AbstractFormPart#setFocus() + */ + public void setFocus() { + Control client = section.getClient(); + if (client !is null) + client.setFocus(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/events/ExpansionAdapter.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.events.ExpansionAdapter; +import dwtx.ui.forms.events.IExpansionListener; +import dwtx.ui.forms.events.ExpansionEvent; +import dwt.dwthelper.utils; +/** + * This adapter class provides default implementations for the methods + * described by the <code>ExpansionListener</code> interface. + * <p> + * Classes that wish to deal with <code>ExpansionEvent</code>s can extend + * this class and override only the methods which they are interested in. + * </p> + * + * @see IExpansionListener + * @see ExpansionEvent + * @since 3.0 + */ +public class ExpansionAdapter : IExpansionListener { + /** + * Sent when the link is entered. The default behaviour is to do nothing. + * + * @param e + * the event + */ + public void expansionStateChanging(ExpansionEvent e) { + } + /** + * Sent when the link is exited. The default behaviour is to do nothing. + * + * @param e + * the event + */ + public void expansionStateChanged(ExpansionEvent e) { + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/events/ExpansionEvent.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.events.ExpansionEvent; +import dwt.events.TypedEvent; +import dwt.dwthelper.utils; +/** + * Notifies listeners when expandable controls change expansion state. + * + * @since 3.0 + */ +public final class ExpansionEvent : TypedEvent { + private static const long serialVersionUID = 6009335074727417445L; + /** + * Creates a new expansion ecent. + * + * @param obj + * event source + * @param state + * the new expansion state + */ + public this(Object obj, bool state) { + super(obj); + data = state ? Boolean.TRUE : Boolean.FALSE; + } + /** + * Returns the new expansion state of the widget. + * + * @return <code>true</code> if the widget is now expaned, <code>false</code> + * otherwise. + */ + public bool getState() { + return data.opEquals(Boolean.TRUE) ? true : false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/events/HyperlinkAdapter.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.events.HyperlinkAdapter; +import dwtx.ui.forms.events.IHyperlinkListener; +import dwtx.ui.forms.events.HyperlinkEvent; +import dwt.dwthelper.utils; +/** + * This adapter class provides default implementations for the methods + * described by the <code>HyperlinkListener</code> interface. + * <p> + * Classes that wish to deal with <code>HyperlinkEvent</code> s can extend + * this class and override only the methods which they are interested in. + * </p> + * + * @see IHyperlinkListener + * @see HyperlinkEvent + * @since 3.0 + */ +public class HyperlinkAdapter : IHyperlinkListener { + /** + * Sent when the link is entered. The default behaviour is to do nothing. + * + * @param e + * the event + */ + public void linkEntered(HyperlinkEvent e) { + } + /** + * Sent when the link is exited. The default behaviour is to do nothing. + * + * @param e + * the event + */ + public void linkExited(HyperlinkEvent e) { + } + /** + * Sent when the link is activated. The default behaviour is to do nothing. + * + * @param e + * the event + */ + public void linkActivated(HyperlinkEvent e) { + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/events/HyperlinkEvent.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.events.HyperlinkEvent; +import dwt.events.TypedEvent; +import dwt.widgets.Widget; +import dwt.dwthelper.utils; +/** + * Notifies listeners about a hyperlink change. + * + * @since 3.0 + */ +public final class HyperlinkEvent : TypedEvent { + private static const long serialVersionUID = 6009335074727417445L; + private String label; + private int stateMask; + /** + * Creates a new hyperlink + * + * @param widget + * event source + * @param href + * the hyperlink reference that will be followed upon when the + * hyperlink is activated. + * @param label + * the name of the hyperlink (the text that is rendered as a + * link in the source widget). + * @param stateMask + * the given state mask + */ + public this(Widget widget, Object href, String label, int stateMask) { + super(widget); + this.widget = widget; + this.data = href; + this.label = label; + this.stateMask = stateMask; + } + /** + * The hyperlink reference that will be followed when the hyperlink is + * activated. + * + * @return the hyperlink reference object + */ + public Object getHref() { + return this.data; + } + /** + * The text of the hyperlink rendered in the source widget. + * + * @return the hyperlink label + */ + public String getLabel() { + return label; + } + /** + * Returns the value of the keyboard state mask present when + * the event occured, or DWT.NULL for no modifiers. + * @return the keyboard state mask or <code>DWT.NULL</code>. + */ + public int getStateMask() { + return stateMask; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/events/IExpansionListener.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.events.IExpansionListener; + +import dwtx.ui.forms.events.ExpansionEvent; + +/** + * Classes that implement this interface will be notified before and after the + * expandable control's expansion state changes. + * + * @since 3.0 + */ +public interface IExpansionListener { + /** + * Notifies the listener that the expandable control is about to change its + * expansion state. The state provided by the event is the new state. + * + * @param e + * the expansion event + */ + void expansionStateChanging(ExpansionEvent e); + /** + * Notifies the listener after the expandable control has changed its + * expansion state. The state provided by the event is the new state. + * + * @param e + * the expansion event + */ + void expansionStateChanged(ExpansionEvent e); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/events/IHyperlinkListener.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.events.IHyperlinkListener; + +import dwtx.ui.forms.events.HyperlinkEvent; + +/** + * Classes that implement this interface will be notified when hyperlinks are + * entered, exited and activated. + * + * @see dwtx.ui.forms.widgets.Hyperlink + * @see dwtx.ui.forms.widgets.ImageHyperlink + * @see dwtx.ui.forms.widgets.FormText + * @since 3.0 + */ +public interface IHyperlinkListener { + /** + * Sent when hyperlink is entered either by mouse entering the link client + * area, or keyboard focus switching to the hyperlink. + * + * @param e + * an event containing information about the hyperlink + */ + void linkEntered(HyperlinkEvent e); + /** + * Sent when hyperlink is exited either by mouse exiting the link client + * area, or keyboard focus switching from the hyperlink. + * + * @param e + * an event containing information about the hyperlink + */ + void linkExited(HyperlinkEvent e); + /** + * Sent when hyperlink is activated either by mouse click inside the link + * client area, or by pressing 'Enter' key while hyperlink has keyboard + * focus. + * + * @param e + * an event containing information about the hyperlink + */ + void linkActivated(HyperlinkEvent e); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/AbstractHyperlink.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,341 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.widgets.AbstractHyperlink; + + +import dwt.DWT; +import dwt.accessibility.ACC; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwtx.core.runtime.ListenerList; +import dwtx.ui.forms.events.HyperlinkEvent; +import dwtx.ui.forms.events.IHyperlinkListener; +import dwtx.ui.internal.forms.widgets.FormsResources; + +import dwt.dwthelper.utils; + +/** + * This is the base class for custom hyperlink widget. It is responsible for + * processing mouse and keyboard events, and converting them into unified + * hyperlink events. Subclasses are responsible for rendering the hyperlink in + * the client area. + * + * @since 3.0 + */ +public abstract class AbstractHyperlink : Canvas { + private bool hasFocus; + bool paintFocus=true; + + /* + * Armed link is one that will activate on a mouse up event, i.e. + * it has received a mouse down and mouse still on top of it. + */ + private bool armed; + + private ListenerList listeners; + + /** + * Amount of the margin width around the hyperlink (default is 1). + */ + protected int marginWidth = 1; + + /** + * Amount of the margin height around the hyperlink (default is 1). + */ + protected int marginHeight = 1; + + /** + * Creates a new hyperlink in the provided parent. + * + * @param parent + * the control parent + * @param style + * the widget style + */ + public this(Composite parent, int style) { + super(parent, style); + addListener(DWT.KeyDown, new class Listener { + public void handleEvent(Event e) { + if (e.character is '\r') { + handleActivate(e); + } + } + }); + addPaintListener(new class PaintListener { + public void paintControl(PaintEvent e) { + paint(e); + } + }); + addListener(DWT.Traverse, new class Listener { + public void handleEvent(Event e) { + switch (e.detail) { + case DWT.TRAVERSE_PAGE_NEXT: + case DWT.TRAVERSE_PAGE_PREVIOUS: + case DWT.TRAVERSE_ARROW_NEXT: + case DWT.TRAVERSE_ARROW_PREVIOUS: + case DWT.TRAVERSE_RETURN: + e.doit = false; + return; + } + e.doit = true; + } + }); + Listener listener = new class Listener { + public void handleEvent(Event e) { + switch (e.type) { + case DWT.FocusIn: + hasFocus = true; + handleEnter(e); + break; + case DWT.FocusOut: + hasFocus = false; + handleExit(e); + break; + case DWT.DefaultSelection: + handleActivate(e); + break; + case DWT.MouseEnter: + handleEnter(e); + break; + case DWT.MouseExit: + handleExit(e); + break; + case DWT.MouseDown: + handleMouseDown(e); + break; + case DWT.MouseUp: + handleMouseUp(e); + break; + case DWT.MouseMove: + handleMouseMove(e); + break; + } + } + }; + addListener(DWT.MouseEnter, listener); + addListener(DWT.MouseExit, listener); + addListener(DWT.MouseDown, listener); + addListener(DWT.MouseUp, listener); + addListener(DWT.MouseMove, listener); + addListener(DWT.FocusIn, listener); + addListener(DWT.FocusOut, listener); + setCursor(FormsResources.getHandCursor()); + } + + /** + * Adds the event listener to this hyperlink. + * + * @param listener + * the event listener to add + */ + public void addHyperlinkListener(IHyperlinkListener listener) { + if (listeners is null) + listeners = new ListenerList(); + listeners.add(cast(Object)listener); + } + + /** + * Removes the event listener from this hyperlink. + * + * @param listener + * the event listener to remove + */ + public void removeHyperlinkListener(IHyperlinkListener listener) { + if (listeners is null) + return; + listeners.remove(cast(Object)listener); + } + + /** + * Returns the selection state of the control. When focus is gained, the + * state will be <samp>true </samp>; it will switch to <samp>false </samp> + * when the control looses focus. + * + * @return <code>true</code> if the widget has focus, <code>false</code> + * otherwise. + */ + public bool getSelection() { + return hasFocus; + } + + /** + * Called when hyperlink is entered. Subclasses that override this method + * must call 'super'. + */ + protected void handleEnter(Event e) { + redraw(); + if (listeners is null) + return; + int size = listeners.size(); + HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(), + e.stateMask); + Object[] listenerList = listeners.getListeners(); + for (int i = 0; i < size; i++) { + IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i]; + listener.linkEntered(he); + } + } + + /** + * Called when hyperlink is exited. Subclasses that override this method + * must call 'super'. + */ + protected void handleExit(Event e) { + // disarm the link; won't activate on mouseup + armed = false; + redraw(); + if (listeners is null) + return; + int size = listeners.size(); + HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(), + e.stateMask); + Object[] listenerList = listeners.getListeners(); + for (int i = 0; i < size; i++) { + IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i]; + listener.linkExited(he); + } + } + + /** + * Called when hyperlink has been activated. Subclasses that override this + * method must call 'super'. + */ + protected void handleActivate(Event e) { + // disarm link, back to normal state + armed = false; + getAccessible().setFocus(ACC.CHILDID_SELF); + if (listeners is null) + return; + int size = listeners.size(); + setCursor(FormsResources.getBusyCursor()); + HyperlinkEvent he = new HyperlinkEvent(this, getHref(), getText(), + e.stateMask); + Object[] listenerList = listeners.getListeners(); + for (int i = 0; i < size; i++) { + IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i]; + listener.linkActivated(he); + } + if (!isDisposed()) + setCursor(FormsResources.getHandCursor()); + } + + /** + * Sets the object associated with this hyperlink. Concrete implementation + * of this class can use if to store text, URLs or model objects that need + * to be processed on hyperlink events. + * + * @param href + * the hyperlink object reference + */ + public void setHref(Object href) { + setData("href", href); //$NON-NLS-1$ + } + + /** + * Returns the object associated with this hyperlink. + * + * @see #setHref + * @return the hyperlink object reference + */ + public Object getHref() { + return getData("href"); //$NON-NLS-1$ + } + + /** + * Returns the textual representation of this hyperlink suitable for showing + * in tool tips or on the status line. + * + * @return the hyperlink text + */ + public String getText() { + return getToolTipText(); + } + + /** + * Paints the hyperlink as a reaction to the provided paint event. + * + * @param gc + * graphic context + */ + protected abstract void paintHyperlink(GC gc); + + /** + * Paints the control as a reaction to the provided paint event. + * + * @param e + * the paint event + */ + protected void paint(PaintEvent e) { + GC gc = e.gc; + Rectangle clientArea = getClientArea(); + if (clientArea.width is 0 || clientArea.height is 0) + return; + paintHyperlink(gc); + if (paintFocus && hasFocus) { + Rectangle carea = getClientArea(); + gc.setForeground(getForeground()); + gc.drawFocus(0, 0, carea.width, carea.height); + } + } + + private void handleMouseDown(Event e) { + if (e.button !is 1) + return; + // armed and ready to activate on mouseup + armed = true; + } + + private void handleMouseUp(Event e) { + if (!armed || e.button !is 1) + return; + Point size = getSize(); + // Filter out mouse up events outside + // the link. This can happen when mouse is + // clicked, dragged outside the link, then + // released. + if (e.x < 0) + return; + if (e.y < 0) + return; + if (e.x >= size.x) + return; + if (e.y >= size.y) + return; + handleActivate(e); + } + + private void handleMouseMove(Event e) { + // disarm link if we move out of bounds + if (armed) { + Point size = getSize(); + armed = (e.x >= 0 && e.y >= 0 && e.x < size.x && e.y < size.y); + } + } + + /* + * (non-Javadoc) + * @see dwt.widgets.Control#setEnabled(bool) + */ + + public void setEnabled (bool enabled) { + super.setEnabled(enabled); + redraw(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ColumnLayout.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,282 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * dinko.ivanov@sap.com - patch #70790 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.ui.forms.widgets.ColumnLayout; + +import dwtx.ui.forms.widgets.ILayoutExtension; +import dwtx.ui.forms.widgets.ColumnLayoutData; + +import dwt.DWT; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Layout; + +import dwt.dwthelper.utils; + +/** + * This layout manager arranges children of the composite parent in vertical + * columns. All the columns are identical size and children are stretched + * horizontally to fill the column width. The goal is to give layout some + * reasonable range of column numbers to allow it to handle various parent + * widths. That way, column number will drop to the lowest number in the range + * when width decreases, and grow up to the highest number in the range when + * allowed by the parent width. + * <p> + * In addition, the layout attempts to 'fill the space' equally i.e. to avoid + * large gaps at the and of the last column. + * <p> + * Child controls are layed out according to their 'natural' (preferred) size. + * For 'stretchy' controls that do not have natural preferred size, it is + * possible to set width and/or height hints using ColumnLayoutData objects. + * + * @see ColumnLayoutData + * @since 3.0 + */ +public final class ColumnLayout : Layout, ILayoutExtension { + /** + * Minimum number of columns (default is 1). + */ + public int minNumColumns = 1; + /** + * Maximum number of columns (default is 3). + */ + public int maxNumColumns = 3; + /** + * Horizontal spacing between columns (default is 5). + */ + public int horizontalSpacing = 5; + /** + * Vertical spacing between controls (default is 5). + */ + public int verticalSpacing = 5; + /** + * Top margin (default is 5). + */ + public int topMargin = 5; + /** + * Left margin (default is 5). + */ + public int leftMargin = 5; + /** + * Bottom margin (default is 5). + */ + public int bottomMargin = 5; + /** + * Right margin (default is 5). + */ + public int rightMargin = 5; + + /** + * Creates a new instance of the column layout. + */ + public this() { + } + + /+protected+/ Point computeSize(Composite composite, int wHint, int hHint, bool flushCache) { + if (wHint is 0) + return computeSize(composite, wHint, hHint, minNumColumns); + else if (wHint is DWT.DEFAULT) + return computeSize(composite, wHint, hHint, maxNumColumns); + else + return computeSize(composite, wHint, hHint, -1); + } + + private Point computeSize(Composite parent, int wHint, int hHint, int ncolumns) { + Control[] children = parent.getChildren(); + int cwidth = 0; + int cheight = 0; + Point[] sizes = new Point[children.length]; + + int cwHint = DWT.DEFAULT; + if (ncolumns !is -1) { + cwHint = wHint - leftMargin - rightMargin - (ncolumns - 1) * horizontalSpacing; + if (cwHint <= 0) + cwHint = 0; + else + cwHint /= ncolumns; + } + + for (int i = 0; i < children.length; i++) { + sizes[i] = computeControlSize(children[i], cwHint); + cwidth = Math.max(cwidth, sizes[i].x); + cheight += sizes[i].y; + } + if (ncolumns is -1) { + // must compute + ncolumns = (wHint - leftMargin - rightMargin - horizontalSpacing) / (cwidth + horizontalSpacing); + ncolumns = Math.min(ncolumns, children.length); + ncolumns = Math.max(ncolumns, minNumColumns); + ncolumns = Math.min(ncolumns, maxNumColumns); + } + int perColHeight = cheight / ncolumns; + if (cheight % ncolumns !is 0) + perColHeight++; + int colHeight = 0; + int[] heights = new int[ncolumns]; + int ncol = 0; + + bool fillIn = false; + + for (int i = 0; i < sizes.length; i++) { + int childHeight = sizes[i].y; + if (i>0 && colHeight + childHeight > perColHeight) { + heights[ncol] = colHeight; + ncol++; + if (ncol is ncolumns || fillIn) { + // overflow - start filling in + fillIn = true; + ncol = findShortestColumn(heights); + } + colHeight = heights[ncol]; + } + if (colHeight > 0) + colHeight += verticalSpacing; + colHeight += childHeight; + } + heights[ncol] = Math.max(heights[ncol],colHeight); + + Point size = new Point(0, 0); + for (int i = 0; i < ncolumns; i++) { + size.y = Math.max(size.y, heights[i]); + } + size.x = cwidth * ncolumns + (ncolumns - 1) * horizontalSpacing; + size.x += leftMargin + rightMargin; + //System.out.println("ColumnLayout: whint="+wHint+", size.x="+size.x); + size.y += topMargin + bottomMargin; + return size; + } + + private Point computeControlSize(Control c, int wHint) { + ColumnLayoutData cd = cast(ColumnLayoutData) c.getLayoutData(); + int widthHint = cd !is null ? cd.widthHint : wHint; + int heightHint = cd !is null ? cd.heightHint : DWT.DEFAULT; + return c.computeSize(widthHint, heightHint); + } + + private int findShortestColumn(int[] heights) { + int result = 0; + int height = Integer.MAX_VALUE; + for (int i = 0; i < heights.length; i++) { + if (height > heights[i]) { + height = heights[i]; + result = i; + } + } + return result; + } + + /* + * (non-Javadoc) + * + * @see dwt.widgets.Layout#layout(dwt.widgets.Composite, + * bool) + */ + protected void layout(Composite parent, bool flushCache) { + Control[] children = parent.getChildren(); + Rectangle carea = parent.getClientArea(); + int cwidth = 0; + int cheight = 0; + Point[] sizes = new Point[children.length]; + for (int i = 0; i < children.length; i++) { + sizes[i] = computeControlSize(children[i], DWT.DEFAULT); + cwidth = Math.max(cwidth, sizes[i].x); + cheight += sizes[i].y; + } + int ncolumns = (carea.width - leftMargin - rightMargin - horizontalSpacing) / (cwidth + horizontalSpacing); + ncolumns = Math.min(ncolumns, children.length); + ncolumns = Math.max(ncolumns, minNumColumns); + ncolumns = Math.min(ncolumns, maxNumColumns); + int realWidth = (carea.width - leftMargin - rightMargin + horizontalSpacing) / ncolumns - horizontalSpacing; +// int childrenPerColumn = children.length / ncolumns; +// if (children.length % ncolumns !is 0) +// childrenPerColumn++; +// int colWidth = 0; + + int fillWidth = Math.max(cwidth, realWidth); + + int perColHeight = cheight / ncolumns; + if (cheight % ncolumns !is 0) + perColHeight++; + + int colHeight = 0; + int[] heights = new int[ncolumns]; + int ncol = 0; + int x = leftMargin; + bool fillIn = false; + + for (int i = 0; i < sizes.length; i++) { + Control child = children[i]; + Point csize = sizes[i]; + ColumnLayoutData cd = cast(ColumnLayoutData) child.getLayoutData(); + int align_ = cd !is null ? cd.horizontalAlignment : ColumnLayoutData.FILL; + int childWidth = align_ is ColumnLayoutData.FILL ? fillWidth : csize.x; + + if (i>0 && colHeight + csize.y > perColHeight) { + heights[ncol] = colHeight; + if (fillIn || ncol is ncolumns-1) { + // overflow - start filling in + fillIn = true; + ncol = findShortestColumn(heights); + + x = leftMargin + ncol * (fillWidth + horizontalSpacing); + + } + else { + ncol++; + x += fillWidth + horizontalSpacing; + } + colHeight = heights[ncol]; + } + if (colHeight > 0) + colHeight += verticalSpacing; + + + switch (align_) { + case ColumnLayoutData.LEFT : + case ColumnLayoutData.FILL : + child.setBounds(x, topMargin+colHeight, childWidth, csize.y); + break; + case ColumnLayoutData.RIGHT : + child.setBounds(x + fillWidth - childWidth, topMargin+colHeight, childWidth, csize.y); + break; + case ColumnLayoutData.CENTER : + child.setBounds(x + fillWidth / 2 - childWidth / 2, topMargin+colHeight, childWidth, csize.y); + break; + } + + colHeight += csize.y; + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.widgets.ILayoutExtension#computeMaximumWidth(dwt.widgets.Composite, + * bool) + */ + public int computeMaximumWidth(Composite parent, bool changed) { + return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.widgets.ILayoutExtension#computeMinimumWidth(dwt.widgets.Composite, + * bool) + */ + public int computeMinimumWidth(Composite parent, bool changed) { + return computeSize(parent, 0, DWT.DEFAULT, changed).x; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ColumnLayoutData.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.widgets.ColumnLayoutData; +import dwt.DWT; +/** + * This class is used to store layout data for the <code>ColumnLayout</code> + * class. You can control width and height hints, as well as horizontal + * alignment using instances of this class. As with other layouts, they are not + * required to get the default behaviour. + * + * @see ColumnLayout + * @since 3.0 + */ +public final class ColumnLayoutData { + /** + * Width hint that will be used instead of the computed control width when + * used in conjunction with <code>ColumnLayout</code> class (default is + * DWT.DEFAULT). + */ + public int widthHint = DWT.DEFAULT; + /** + * Height hint that will be used instead of the computed control height + * when used in conjunction with <code>ColumnLayout</code> class (default + * is DWT.DEFAULT). + */ + public int heightHint = DWT.DEFAULT; + /** + * Horizontal alignment constant - control will be aligned to the left. + */ + public static const int LEFT = 1; + /** + * Horizontal alignment constant - control will be aligned to the right. + */ + public static const int CENTER = 2; + /** + * Horizontal alignment constant - control will be centered. + */ + public static const int RIGHT = 3; + /** + * Horizontal alignment constant - control will fill the column. + */ + public static const int FILL = 4; + /** + * Horizontal alignment variable (default is FILL). + */ + public int horizontalAlignment = FILL; + /** + * Convinience constructor for the class. + * + * @param wHint + * width hint for the control + * @param hHint + * height hint for the control + */ + public this(int wHint, int hHint) { + this.widthHint = wHint; + this.heightHint = hHint; + } + /** + * Convinience constructor for the class. + * + * @param wHint + * width hint for the control + */ + public this(int wHint) { + this.widthHint = wHint; + } + /** + * The default constructor. + * + */ + public this() { + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ExpandableComposite.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,1136 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * Kai Nacke - Fix for Bug 202382 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.ui.forms.widgets.ExpandableComposite; + +import dwtx.ui.forms.widgets.ToggleHyperlink; +import dwtx.ui.forms.widgets.ILayoutExtension; +import dwtx.ui.forms.widgets.SizeCache; +import dwtx.ui.forms.widgets.Twistie; +import dwtx.ui.forms.widgets.TreeNode; +import dwtx.ui.forms.widgets.Hyperlink; + +import dwt.DWT; +import dwt.events.FocusEvent; +import dwt.events.FocusListener; +import dwt.events.KeyAdapter; +import dwt.events.KeyEvent; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.events.TraverseEvent; +import dwt.events.TraverseListener; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Label; +import dwt.widgets.Layout; +import dwt.widgets.Listener; +import dwt.widgets.Menu; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.ListenerList; +import dwtx.ui.forms.events.ExpansionEvent; +import dwtx.ui.forms.events.HyperlinkAdapter; +import dwtx.ui.forms.events.HyperlinkEvent; +import dwtx.ui.forms.events.IExpansionListener; +import dwtx.ui.internal.forms.widgets.FormUtil; +import dwtx.ui.internal.forms.widgets.FormsResources; + +import dwt.dwthelper.utils; + +/** + * This composite is capable of expanding or collapsing a single client that is + * its direct child. The composite renders an expansion toggle affordance + * (according to the chosen style), and a title that also acts as a hyperlink + * (can be selected and is traversable). The client is layed out below the title + * when expanded, or hidden when collapsed. + * <p> + * The widget can be instantiated as-is, or subclassed to modify some aspects of + * it. * + * <p> + * Since 3.1, left/right arrow keys can be used to control the expansion state. + * If several expandable composites are created in the same parent, up/down + * arrow keys can be used to traverse between them. Expandable text accepts + * mnemonics and mnemonic activation will toggle the expansion state. + * + * <p> + * While expandable composite recognize that different styles can be used to + * render the title bar, and even defines the constants for these styles (<code>TITLE_BAR</code> + * and <code>SHORT_TITLE_BAR</code> the actual painting is done in the + * subclasses. + * + * @see Section + * @since 3.0 + */ +public class ExpandableComposite : Canvas { + /** + * If this style is used, a twistie will be used to render the expansion + * toggle. + */ + public static const int TWISTIE = 1 << 1; + + /** + * If this style is used, a tree node with either + or - signs will be used + * to render the expansion toggle. + */ + public static const int TREE_NODE = 1 << 2; + + /** + * If this style is used, the title text will be rendered as a hyperlink + * that can individually accept focus. Otherwise, it will still act like a + * hyperlink, but only the toggle control will accept focus. + */ + public static const int FOCUS_TITLE = 1 << 3; + + /** + * If this style is used, the client origin will be vertically aligned with + * the title text. Otherwise, it will start at x = 0. + */ + public static const int CLIENT_INDENT = 1 << 4; + + /** + * If this style is used, computed size of the composite will take the + * client width into consideration only in the expanded state. Otherwise, + * client width will always be taken into acount. + */ + public static const int COMPACT = 1 << 5; + + /** + * If this style is used, the control will be created in the expanded state. + * This state can later be changed programmatically or by the user if + * TWISTIE or TREE_NODE style is used. + */ + public static const int EXPANDED = 1 << 6; + + /** + * If this style is used, title bar decoration will be painted behind the + * text. + */ + public static const int TITLE_BAR = 1 << 8; + + /** + * If this style is used, a short version of the title bar decoration will + * be painted behind the text. This style is useful when a more descrete + * option is needed for the title bar. + * + * @since 3.1 + */ + public static const int SHORT_TITLE_BAR = 1 << 9; + + /** + * If this style is used, title will not be rendered. + */ + public static const int NO_TITLE = 1 << 12; + + /** + * By default, text client is right-aligned. If this style is used, it will + * be positioned after the text control and vertically centered with it. + */ + public static const int LEFT_TEXT_CLIENT_ALIGNMENT = 1 << 13; + + /** + * Width of the margin that will be added around the control (default is 0). + */ + public int marginWidth = 0; + + /** + * Height of the margin that will be added around the control (default is + * 0). + */ + public int marginHeight = 0; + + /** + * Vertical spacing between the title area and the composite client control + * (default is 3). + */ + public int clientVerticalSpacing = 3; + + /** + * Vertical spacing between the title area and the description control + * (default is 0). The description control is normally placed at the new + * line as defined in the font used to render it. This value will be added + * to it. + * + * @since 3.3 + */ + public int descriptionVerticalSpacing = 0; + + /** + * Horizontal margin around the inside of the title bar area when TITLE_BAR + * or SHORT_TITLE_BAR style is used. This variable is not used otherwise. + * + * @since 3.3 + */ + public int titleBarTextMarginWidth = 6; + + /** + * The toggle widget used to expand the composite. + */ + protected ToggleHyperlink toggle; + package ToggleHyperlink toggle_package(){ + return toggle; + } + package ToggleHyperlink toggle_package(ToggleHyperlink t){ + return (toggle = t); + } + + /** + * The text label for the title. + */ + protected Control textLabel; + package Control textLabel_package(){ + return textLabel; + } + package Control textLabel_package(Control t){ + return (textLabel = t); + } + + /** + * @deprecated this variable was left as protected by mistake. It will be + * turned into static and hidden in the future versions. Do not + * use them and do not change its value. + */ + protected int VGAP = 3; + /** + * @deprecated this variable was left as protected by mistake. It will be + * turned into static and hidden in the future versions. Do not + * use it and do not change its value. + */ + protected int GAP = 4; + + static const int IGAP = 4; + static const int IVGAP = 3; + + private static Point NULL_SIZE_; + private static Point NULL_SIZE(){ + if( NULL_SIZE_ is null ){ + synchronized(ExpandableComposite.classinfo ){ + if( NULL_SIZE_ is null ){ + NULL_SIZE_ = new Point(0, 0); + } + } + } + return NULL_SIZE_; + } + + private static const int VSPACE = 3; + + private static const int SEPARATOR_HEIGHT = 2; + + private int expansionStyle = TWISTIE | FOCUS_TITLE | EXPANDED; + + private bool expanded; + + private Control textClient; + + private Control client; + + private ListenerList listeners; + + private Color titleBarForeground; + + private class ExpandableLayout : Layout, ILayoutExtension { + + private SizeCache toggleCache; + + private SizeCache textClientCache; + + private SizeCache textLabelCache; + + private SizeCache descriptionCache; + + private SizeCache clientCache; + + this(){ + toggleCache = new SizeCache(); + textClientCache = new SizeCache(); + textLabelCache = new SizeCache(); + descriptionCache = new SizeCache(); + clientCache = new SizeCache(); + } + private void initCache(bool shouldFlush) { + toggleCache.setControl(toggle); + textClientCache.setControl(textClient); + textLabelCache.setControl(textLabel); + descriptionCache.setControl(getDescriptionControl()); + clientCache.setControl(client); + + if (shouldFlush) { + toggleCache.flush(); + textClientCache.flush(); + textLabelCache.flush(); + descriptionCache.flush(); + clientCache.flush(); + } + } + + protected void layout(Composite parent, bool changed) { + initCache(changed); + + Rectangle clientArea = parent.getClientArea(); + int thmargin = 0; + int tvmargin = 0; + + if (hasTitleBar()) { + thmargin = titleBarTextMarginWidth; + tvmargin = IVGAP; + } + int x = marginWidth + thmargin; + int y = marginHeight + tvmargin; + Point tsize = NULL_SIZE; + Point tcsize = NULL_SIZE; + if (toggle !is null) + tsize = toggleCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + int twidth = clientArea.width - marginWidth - marginWidth + - thmargin - thmargin; + if (tsize.x > 0) + twidth -= tsize.x + IGAP; + if (textClient !is null) { + tcsize = textClientCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + } + Point size = NULL_SIZE; + if (textLabel !is null) { + if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) { + size = textLabelCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + if (twidth < size.x + IGAP + tcsize.x) { + twidth -= IGAP; + if (null !is cast(Label)textLabel ) + size = FormUtil.computeWrapSize(new GC(textLabel), (cast(Label)textLabel).getText(), cast(int) Math.round(twidth*(size.x/cast(float)(size.x+tcsize.x)))); + else + size = textLabelCache.computeSize(cast(int) Math.round(twidth*(size.x/cast(float)(size.x+tcsize.x))), DWT.DEFAULT); + tcsize = textClientCache.computeSize(twidth-size.x, DWT.DEFAULT); + } + } + else { + if (tcsize.x > 0) + twidth -= tcsize.x + IGAP; + size = textLabelCache.computeSize(twidth, DWT.DEFAULT); + } + } + if (null !is cast(Label)textLabel ) { + Point defSize = textLabelCache.computeSize(DWT.DEFAULT, + DWT.DEFAULT); + if (defSize.y is size.y) { + // One line - pick the smaller of the two widths + size.x = Math.min(defSize.x, size.x); + } + } + if (toggle !is null) { + GC gc = new GC(this.outer); + gc.setFont(getFont()); + FontMetrics fm = gc.getFontMetrics(); + int textHeight = fm.getHeight(); + gc.dispose(); + if (textClient !is null + && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 0) { + textHeight = Math.max(textHeight, tcsize.y); + } + int ty = textHeight / 2 - tsize.y / 2 + 1; + ty = Math.max(ty, 0); + ty += marginHeight + tvmargin; + toggle.setLocation(x, ty); + toggle.setSize(tsize); + x += tsize.x + IGAP; + } + if (textLabel !is null) { + int ty = y; + if (textClient !is null + && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 0) { + if (size.y < tcsize.y) + ty = tcsize.y / 2 - size.y / 2 + marginHeight + + tvmargin; + } + textLabelCache.setBounds(x, ty, size.x, size.y); + } + if (textClient !is null) { + int tcx; + if ((expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 0) { + tcx = x + size.x + GAP; + } else { + tcx = clientArea.width - tcsize.x - marginWidth - thmargin; + } + textClientCache.setBounds(tcx, y, tcsize.x, tcsize.y); + } + int tbarHeight = 0; + if (size.y > 0) + tbarHeight = size.y; + if (tcsize.y > 0) + tbarHeight = Math.max(tbarHeight, tcsize.y); + y += tbarHeight; + if (hasTitleBar()) + y += tvmargin; + if (getSeparatorControl() !is null) { + y += VSPACE; + getSeparatorControl().setBounds(marginWidth, y, + clientArea.width - marginWidth - marginWidth, + SEPARATOR_HEIGHT); + y += SEPARATOR_HEIGHT; + if (expanded) + y += VSPACE; + } + if (expanded) { + int areaWidth = clientArea.width - marginWidth - marginWidth + - thmargin - thmargin; + int cx = marginWidth + thmargin; + if ((expansionStyle & CLIENT_INDENT) !is 0) { + cx = x; + areaWidth -= x; + } + if (client !is null) { + Point dsize = null; + Control desc = getDescriptionControl(); + if (desc !is null) { + dsize = descriptionCache.computeSize(areaWidth, + DWT.DEFAULT); + y += descriptionVerticalSpacing; + descriptionCache.setBounds(cx, y, areaWidth, dsize.y); + y += dsize.y + clientVerticalSpacing; + } else { + y += clientVerticalSpacing; + if (getSeparatorControl() !is null) + y -= VSPACE; + } + int cwidth = areaWidth; + int cheight = clientArea.height - marginHeight + - marginHeight - y; + clientCache.setBounds(cx, y, cwidth, cheight); + } + } + } + + protected Point computeSize(Composite parent, int wHint, int hHint, + bool changed) { + initCache(changed); + + int width = 0, height = 0; + Point tsize = NULL_SIZE; + int twidth = 0; + if (toggle !is null) { + tsize = toggleCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + twidth = tsize.x + IGAP; + } + int thmargin = 0; + int tvmargin = 0; + + if (hasTitleBar()) { + thmargin = titleBarTextMarginWidth; + tvmargin = IVGAP; + } + int innerwHint = wHint; + if (innerwHint !is DWT.DEFAULT) + innerwHint -= twidth + marginWidth + marginWidth + thmargin + + thmargin; + + int innertHint = innerwHint; + + Point tcsize = NULL_SIZE; + if (textClient !is null) { + tcsize = textClientCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + } + Point size = NULL_SIZE; + + if (textLabel !is null) { + if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) { + size = textLabelCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + if (innertHint !is DWT.DEFAULT && innertHint < size.x + IGAP + tcsize.x) { + innertHint -= IGAP; + if (null !is cast(Label)textLabel ) + size = FormUtil.computeWrapSize(new GC(textLabel), (cast(Label)textLabel).getText(), cast(int) Math.round(innertHint*(size.x/cast(float)(size.x+tcsize.x)))); + else + size = textLabelCache.computeSize(cast(int) Math.round(innertHint*(size.x/cast(float)(size.x+tcsize.x))), DWT.DEFAULT); + tcsize = textClientCache.computeSize(innertHint-size.x, DWT.DEFAULT); + } + } else { + if (innertHint !is DWT.DEFAULT && tcsize.x > 0) + innertHint -= IGAP + tcsize.x; + size = textLabelCache.computeSize(innertHint, DWT.DEFAULT); + } + } + if (null !is cast(Label)textLabel ) { + Point defSize = textLabelCache.computeSize(DWT.DEFAULT, + DWT.DEFAULT); + if (defSize.y is size.y) { + // One line - pick the smaller of the two widths + size.x = Math.min(defSize.x, size.x); + } + } + if (size.x > 0) + width = size.x; + if (tcsize.x > 0) + width += IGAP + tcsize.x; + if (toggle !is null) + width += twidth; + height = tcsize.y > 0 ? Math.max(tcsize.y, size.y) : size.y; + if (getSeparatorControl() !is null) { + height += VSPACE + SEPARATOR_HEIGHT; + if (expanded && client !is null) + height += VSPACE; + } + // if (hasTitleBar()) + // height += VSPACE; + if ((expanded || (expansionStyle & COMPACT) is 0) && client !is null) { + int cwHint = wHint; + int clientIndent = 0; + if ((expansionStyle & CLIENT_INDENT) !is 0) + clientIndent = twidth; + + if (cwHint !is DWT.DEFAULT) { + cwHint -= marginWidth + marginWidth + thmargin + thmargin; + if ((expansionStyle & CLIENT_INDENT) !is 0) + if (tcsize.x > 0) + cwHint -= twidth; + } + Point dsize = null; + Point csize = clientCache.computeSize(FormUtil.getWidthHint( + cwHint, client), DWT.DEFAULT); + if (getDescriptionControl() !is null) { + int dwHint = cwHint; + if (dwHint is DWT.DEFAULT) { + dwHint = csize.x; + if ((expansionStyle & CLIENT_INDENT) !is 0) + dwHint -= twidth; + } + dsize = descriptionCache.computeSize(dwHint, DWT.DEFAULT); + } + if (dsize !is null) { + width = Math.max(width, dsize.x + clientIndent); + if (expanded) + height += descriptionVerticalSpacing + dsize.y + + clientVerticalSpacing; + } else { + height += clientVerticalSpacing; + if (getSeparatorControl() !is null) + height -= VSPACE; + } + width = Math.max(width, csize.x + clientIndent); + if (expanded) + height += csize.y; + } + if (toggle !is null) + height = height - size.y + Math.max(size.y, tsize.y); + + Point result = new Point(width + marginWidth + marginWidth + + thmargin + thmargin, height + marginHeight + marginHeight + + tvmargin + tvmargin); + return result; + } + + public int computeMinimumWidth(Composite parent, bool changed) { + return computeSize(parent, 0, DWT.DEFAULT, changed).x; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.parts.ILayoutExtension#computeMinimumWidth(dwt.widgets.Composite, + * bool) + */ + public int computeMaximumWidth(Composite parent, bool changed) { + return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x; + } + } + + /** + * Creates an expandable composite using a TWISTIE toggle. + * + * @param parent + * the parent composite + * @param style + * DWT style bits + */ + public this(Composite parent, int style) { + this(parent, style, TWISTIE); + } + + /** + * Creates the expandable composite in the provided parent. + * + * @param parent + * the parent + * @param style + * the control style (as expected by DWT subclass) + * @param expansionStyle + * the style of the expansion widget (TREE_NODE, TWISTIE, + * CLIENT_INDENT, COMPACT, FOCUS_TITLE, + * LEFT_TEXT_CLIENT_ALIGNMENT, NO_TITLE) + */ + public this(Composite parent, int style, int expansionStyle) { + listeners = new ListenerList(); + super(parent, style); + this.expansionStyle = expansionStyle; + if ((expansionStyle & TITLE_BAR) !is 0) + setBackgroundMode(DWT.INHERIT_DEFAULT); + super.setLayout(new ExpandableLayout()); + if (hasTitleBar()) { + this.addPaintListener(new class PaintListener { + public void paintControl(PaintEvent e) { + onPaint(e); + } + }); + } + if ((expansionStyle & TWISTIE) !is 0) + toggle = new Twistie(this, DWT.NULL); + else if ((expansionStyle & TREE_NODE) !is 0) + toggle = new TreeNode(this, DWT.NULL); + else + expanded = true; + if ((expansionStyle & EXPANDED) !is 0) + expanded = true; + if (toggle !is null) { + toggle.setExpanded(expanded); + toggle.addHyperlinkListener(new class HyperlinkAdapter { + public void linkActivated(HyperlinkEvent e) { + toggleState(); + } + }); + toggle.addPaintListener(new class PaintListener { + public void paintControl(PaintEvent e) { + if (null !is cast(Label)textLabel && !isFixedStyle()) + textLabel.setForeground(toggle.hover_package ? toggle + .getHoverDecorationColor() + : getTitleBarForeground()); + } + }); + toggle.addKeyListener(new class KeyAdapter { + public void keyPressed(KeyEvent e) { + if (e.keyCode is DWT.ARROW_UP) { + verticalMove(false); + e.doit = false; + } else if (e.keyCode is DWT.ARROW_DOWN) { + verticalMove(true); + e.doit = false; + } + } + }); + if ((getExpansionStyle()&FOCUS_TITLE) is 0) { + toggle.paintFocus=false; + toggle.addFocusListener(new class FocusListener { + public void focusGained(FocusEvent e) { + textLabel.redraw(); + } + + public void focusLost(FocusEvent e) { + textLabel.redraw(); + } + }); + } + } + if ((expansionStyle & FOCUS_TITLE) !is 0) { + Hyperlink link = new Hyperlink(this, DWT.WRAP); + link.addHyperlinkListener(new class HyperlinkAdapter { + public void linkActivated(HyperlinkEvent e) { + programmaticToggleState(); + } + }); + textLabel = link; + } else if ((expansionStyle & NO_TITLE) is 0) { + final Label label = new Label(this, DWT.WRAP); + if (!isFixedStyle()) { + label.setCursor(FormsResources.getHandCursor()); + Listener listener = dgListener( (Event e, Label label) { + switch (e.type) { + case DWT.MouseDown: + if (toggle !is null) + toggle.setFocus(); + break; + case DWT.MouseUp: + label.setCursor(FormsResources.getBusyCursor()); + programmaticToggleState(); + label.setCursor(FormsResources.getHandCursor()); + break; + case DWT.MouseEnter: + if (toggle !is null) { + label.setForeground(toggle + .getHoverDecorationColor()); + toggle.hover_package = true; + toggle.redraw(); + } + break; + case DWT.MouseExit: + if (toggle !is null) { + label.setForeground(getTitleBarForeground()); + toggle.hover_package = false; + toggle.redraw(); + } + break; + case DWT.Paint: + if (toggle !is null) { + paintTitleFocus(e.gc); + } + break; + } + }, label ); + label.addListener(DWT.MouseDown, listener); + label.addListener(DWT.MouseUp, listener); + label.addListener(DWT.MouseEnter, listener); + label.addListener(DWT.MouseExit, listener); + label.addListener(DWT.Paint, listener); + } + textLabel = label; + } + if (textLabel !is null) { + textLabel.setMenu(getMenu()); + textLabel.addTraverseListener(new class TraverseListener { + public void keyTraversed(TraverseEvent e) { + if (e.detail is DWT.TRAVERSE_MNEMONIC) { + // steal the mnemonic + if (!isVisible() || !isEnabled()) + return; + if (FormUtil.mnemonicMatch(getText(), e.character)) { + e.doit = false; + if (!isFixedStyle()) { + programmaticToggleState(); + } + setFocus(); + } + } + } + }); + } + } + + /* (non-Javadoc) + * @see dwt.widgets.Control#forceFocus() + */ + public bool forceFocus() { + return false; + } + + /** + * Overrides 'super' to pass the menu to the text label. + * + * @param menu + * the menu from the parent to attach to this control. + */ + + public void setMenu(Menu menu) { + if (textLabel !is null) + textLabel.setMenu(menu); + super.setMenu(menu); + } + + /** + * Prevents assignment of the layout manager - expandable composite uses its + * own layout. + */ + public final void setLayout(Layout layout) { + } + + /** + * Sets the background of all the custom controls in the expandable. + */ + public void setBackground(Color bg) { + super.setBackground(bg); + if ((getExpansionStyle() & TITLE_BAR) is 0) { + if (textLabel !is null) + textLabel.setBackground(bg); + if (toggle !is null) + toggle.setBackground(bg); + } + } + + /** + * Sets the foreground of all the custom controls in the expandable. + */ + public void setForeground(Color fg) { + super.setForeground(fg); + if (textLabel !is null) + textLabel.setForeground(fg); + if (toggle !is null) + toggle.setForeground(fg); + } + + /** + * Sets the color of the toggle control. + * + * @param c + * the color object + */ + public void setToggleColor(Color c) { + if (toggle !is null) + toggle.setDecorationColor(c); + } + + /** + * Sets the active color of the toggle control (when the mouse enters the + * toggle area). + * + * @param c + * the active color object + */ + public void setActiveToggleColor(Color c) { + if (toggle !is null) + toggle.setHoverDecorationColor(c); + } + + /** + * Sets the fonts of all the custom controls in the expandable. + */ + public void setFont(Font font) { + super.setFont(font); + if (textLabel !is null) + textLabel.setFont(font); + if (toggle !is null) + toggle.setFont(font); + } + + /* + * (non-Javadoc) + * + * @see dwt.widgets.Control#setEnabled(bool) + */ + + public void setEnabled(bool enabled) { + if (textLabel !is null) + textLabel.setEnabled(enabled); + if (toggle !is null) + toggle.setEnabled(enabled); + super.setEnabled(enabled); + } + + /** + * Sets the client of this expandable composite. The client must not be + * <samp>null </samp> and must be a direct child of this container. + * + * @param client + * the client that will be expanded or collapsed + */ + public void setClient(Control client) { + Assert.isTrue(client !is null && client.getParent().opEquals(this)); + this.client = client; + } + + /** + * Returns the current expandable client. + * + * @return the client control + */ + public Control getClient() { + return client; + } + + /** + * Sets the title of the expandable composite. The title will act as a + * hyperlink and activating it will toggle the client between expanded and + * collapsed state. + * + * @param title + * the new title string + * @see #getText() + */ + public void setText(String title) { + if (null !is cast(Label)textLabel ) + (cast(Label) textLabel).setText(title); + else if (null !is cast(Hyperlink)textLabel ) + (cast(Hyperlink) textLabel).setText(title); + } + + /** + * Returns the title string. + * + * @return the title string + * @see #setText(String) + */ + public String getText() { + if (null !is cast(Label)textLabel ) + return (cast(Label) textLabel).getText(); + else if (null !is cast(Hyperlink)textLabel ) + return (cast(Hyperlink) textLabel).getText(); + else + return ""; //$NON-NLS-1$ + } + + /** + * Tests the expanded state of the composite. + * + * @return <samp>true </samp> if expanded, <samp>false </samp> if collapsed. + */ + public bool isExpanded() { + return expanded; + } + + /** + * Returns the bitwise-ORed style bits for the expansion control. + * + * @return the bitwise-ORed style bits for the expansion control + */ + public int getExpansionStyle() { + return expansionStyle; + } + + /** + * Programmatically changes expanded state. + * + * @param expanded + * the new expanded state + */ + public void setExpanded(bool expanded) { + internalSetExpanded(expanded); + if (toggle !is null) + toggle.setExpanded(expanded); + } + + /** + * Performs the expansion state change for the expandable control. + * + * @param expanded + * the expansion state + */ + protected void internalSetExpanded(bool expanded) { + if (this.expanded !is expanded) { + this.expanded = expanded; + if (getDescriptionControl() !is null) + getDescriptionControl().setVisible(expanded); + if (client !is null) + client.setVisible(expanded); + layout(); + } + } + + /** + * Adds the listener that will be notified when the expansion state changes. + * + * @param listener + * the listener to add + */ + public void addExpansionListener(IExpansionListener listener) { + listeners.add(cast(Object)listener); + } + + /** + * Removes the expansion listener. + * + * @param listener + * the listner to remove + */ + public void removeExpansionListener(IExpansionListener listener) { + listeners.remove(cast(Object)listener); + } + + /** + * If TITLE_BAR or SHORT_TITLE_BAR style is used, title bar decoration will + * be painted behind the text in this method. The default implementation + * does nothing - subclasses are responsible for rendering the title area. + * + * @param e + * the paint event + */ + protected void onPaint(PaintEvent e) { + } + + /** + * Returns description control that will be placed under the title if + * present. + * + * @return the description control or <samp>null </samp> if not used. + */ + protected Control getDescriptionControl() { + return null; + } + + /** + * Returns the separator control that will be placed between the title and + * the description if present. + * + * @return the separator control or <samp>null </samp> if not used. + */ + protected Control getSeparatorControl() { + return null; + } + + /** + * Computes the size of the expandable composite. + * + * @see dwt.widgets.Composite#computeSize + */ + public Point computeSize(int wHint, int hHint, bool changed) { + checkWidget(); + Point size; + ExpandableLayout layout = cast(ExpandableLayout) getLayout(); + if (wHint is DWT.DEFAULT || hHint is DWT.DEFAULT) { + size = layout.computeSize(this, wHint, hHint, changed); + } else { + size = new Point(wHint, hHint); + } + Rectangle trim = computeTrim(0, 0, size.x, size.y); + return new Point(trim.width, trim.height); + } + + /** + * Returns <samp>true </samp> if the composite is fixed i.e. cannot be + * expanded or collapsed. Fixed control will still contain the title, + * separator and description (if present) as well as the client, but will be + * in the permanent expanded state and the toggle affordance will not be + * shown. + * + * @return <samp>true </samp> if the control is fixed in the expanded state, + * <samp>false </samp> if it can be collapsed. + */ + protected bool isFixedStyle() { + return (expansionStyle & TWISTIE) is 0 + && (expansionStyle & TREE_NODE) is 0; + } + + /** + * Returns the text client control. + * + * @return Returns the text client control if specified, or + * <code>null</code> if not. + */ + public Control getTextClient() { + return textClient; + } + + /** + * Sets the text client control. Text client is a control that is a child of + * the expandable composite and is placed to the right of the text. It can + * be used to place small image hyperlinks. If more than one control is + * needed, use Composite to hold them. Care should be taken that the height + * of the control is comparable to the height of the text. + * + * @param textClient + * the textClient to set or <code>null</code> if not needed any + * more. + */ + public void setTextClient(Control textClient) { + if (this.textClient !is null) + this.textClient.dispose(); + this.textClient = textClient; + } + + /** + * Returns the difference in height between the text and the text client (if + * set). This difference can cause vertical alignment problems when two + * expandable composites are placed side by side, one with and one without + * the text client. Use this method obtain the value to add to either + * <code>descriptionVerticalSpacing</code> (if you have description) or + * <code>clientVerticalSpacing</code> to correct the alignment of the + * expandable without the text client. + * + * @return the difference in height between the text and the text client or + * 0 if no corrective action is needed. + * @since 3.3 + */ + public int getTextClientHeightDifference() { + if (textClient is null || textLabel is null) + return 0; + int theight = textLabel.computeSize(DWT.DEFAULT, DWT.DEFAULT).y; + int tcheight = textClient.computeSize(DWT.DEFAULT, DWT.DEFAULT).y; + return Math.max(tcheight - theight, 0); + } + + /** + * Tests if this expandable composite renders a title bar around the text. + * + * @return <code>true</code> for <code>TITLE_BAR</code> or + * <code>SHORT_TITLE_BAR</code> styles, <code>false</code> + * otherwise. + */ + protected bool hasTitleBar() { + return (getExpansionStyle() & TITLE_BAR) !is 0 + || (getExpansionStyle() & SHORT_TITLE_BAR) !is 0; + } + + /** + * Sets the color of the title bar foreground when TITLE_BAR style is used. + * + * @param color + * the title bar foreground + */ + public void setTitleBarForeground(Color color) { + titleBarForeground = color; + textLabel.setForeground(color); + } + + /** + * Returns the title bar foreground when TITLE_BAR style is used. + * + * @return the title bar foreground + */ + public Color getTitleBarForeground() { + return titleBarForeground; + } + + // end of APIs + + private void toggleState() { + bool newState = !isExpanded(); + fireExpanding(newState, true); + internalSetExpanded(newState); + fireExpanding(newState, false); + if (newState) + FormUtil.ensureVisible(this); + } + + private void fireExpanding(bool state, bool before) { + int size = listeners.size(); + if (size is 0) + return; + ExpansionEvent e = new ExpansionEvent(this, state); + Object [] listenerList = listeners.getListeners(); + for (int i = 0; i < size; i++) { + IExpansionListener listener = cast(IExpansionListener) listenerList[i]; + if (before) + listener.expansionStateChanging(e); + else + listener.expansionStateChanged(e); + } + } + + private void verticalMove(bool down) { + Composite parent = getParent(); + Control[] children = parent.getChildren(); + for (int i = 0; i < children.length; i++) { + Control child = children[i]; + if (child is this) { + ExpandableComposite sibling = getSibling(children, i, down); + if (sibling !is null && sibling.toggle !is null) { + sibling.setFocus(); + } + break; + } + } + } + + private ExpandableComposite getSibling(Control[] children, int index, + bool down) { + int loc = down ? index + 1 : index - 1; + while (loc >= 0 && loc < children.length) { + Control c = children[loc]; + if (null !is cast(ExpandableComposite)c && c.isVisible()) + return cast(ExpandableComposite) c; + loc = down ? loc + 1 : loc - 1; + } + return null; + } + + private void programmaticToggleState() { + if (toggle !is null) + toggle.setExpanded(!toggle.isExpanded()); + toggleState(); + } + + private void paintTitleFocus(GC gc) { + Point size = textLabel.getSize(); + gc.setBackground(textLabel.getBackground()); + gc.setForeground(textLabel.getForeground()); + if (toggle.isFocusControl()) + gc.drawFocus(0, 0, size.x, size.y); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/Form.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,801 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.widgets.Form; + +import dwtx.ui.forms.widgets.SizeCache; +import dwtx.ui.forms.widgets.FormText; +import dwtx.ui.forms.widgets.ILayoutExtension; +import dwtx.ui.forms.widgets.LayoutComposite; + +import dwt.DWT; +import dwt.dnd.DragSourceListener; +import dwt.dnd.DropTargetListener; +import dwt.dnd.Transfer; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Layout; +import dwt.widgets.Menu; +import dwtx.jface.action.IMenuManager; +import dwtx.jface.action.IToolBarManager; +import dwtx.ui.forms.IFormColors; +import dwtx.ui.forms.IMessage; +import dwtx.ui.forms.events.IHyperlinkListener; +import dwtx.ui.internal.forms.widgets.FormHeading; +import dwtx.ui.internal.forms.widgets.FormUtil; + +import dwt.dwthelper.utils; + +/** + * Form is a custom control that renders a title and an optional background + * image above the body composite. It can be used alone when part of parents + * that are scrolled. If scrolling is required, use <code>ScrolledForm</code> + * instead because it has an instance of <code>Form</code> and adds scrolling + * capability. + * <p> + * Form can have a title if set. If not set, title area will not be left empty - + * form body will be resized to fill the entire form. In addition, an optional + * title image can be set and is rendered to the left of the title (since 3.2). + * <p> + * Form can have a title drop down menu if the menu bar manager is not empty + * (since 3.3). + * <p> + * Form title can support drag and drop if drag and drop support methods are + * invoked. When used, additional decoration is rendered behind the title to + * reinforce the drag and drop ability (since 3.3). + * <p> + * The form supports status messages. These messages can have various severity + * (error, warning, info or none). If status hyperlink handler is specified, the + * messages with the specified severity other than none will be rendered as + * hyperlinks. + * <p> + * Form can have a background image behind the title text. The image is tiled as + * many times as needed to fill the title area. Alternatively, gradient + * background can be painted vertically or horizontally. + * <p> + * Form can be put in a 'busy' state. While in this state, title image is + * replaced with an animation that lasts as long as the 'busy' state is active. + * <p> + * It is possible to create an optional head client control. When created, this + * control is placed in the form heading as a second row. + * <p> + * Form has a custom layout manager that is wrap-enabled. If a form is placed in + * a composite whose layout manager implements ILayoutExtension, the body of the + * form will participate in wrapping as long as its layout manager implements + * ILayoutExtension as well. + * <p> + * Children of the form should typically be created using FormToolkit to match + * the appearance and behaviour. When creating children, use the form body as a + * parent by calling 'getBody()' on the form instance. Example: + * + * <pre> + * FormToolkit toolkit = new FormToolkit(parent.getDisplay()); + * Form form = toolkit.createForm(parent); + * form.setText("Sample form"); + * form.getBody().setLayout(new GridLayout()); + * toolkit.createButton(form.getBody(), "Checkbox", DWT.CHECK); + * </pre> + * + * <p> + * No layout manager has been set on the body. Clients are required to set the + * desired layout manager explicitly. + * <p> + * Although the class is not final, it should not be subclassed. + * + * @since 3.0 + */ +public class Form : Composite { + private FormHeading head; + + private Composite body_; + + private SizeCache body_Cache; + + private SizeCache headCache; + + private FormText selectionText; + + private class FormLayout : Layout, ILayoutExtension { + public int computeMinimumWidth(Composite composite, bool flushCache) { + return computeSize(composite, 5, DWT.DEFAULT, flushCache).x; + } + + public int computeMaximumWidth(Composite composite, bool flushCache) { + return computeSize(composite, DWT.DEFAULT, DWT.DEFAULT, flushCache).x; + } + + public Point computeSize(Composite composite, int wHint, int hHint, + bool flushCache) { + if (flushCache) { + body_Cache.flush(); + headCache.flush(); + } + body_Cache.setControl(body_); + headCache.setControl(head); + + int width = 0; + int height = 0; + + Point hsize = headCache.computeSize(FormUtil.getWidthHint(wHint, + head), DWT.DEFAULT); + width = Math.max(hsize.x, width); + height = hsize.y; + + bool ignoreBody=getData(FormUtil.IGNORE_BODY) !is null; + + Point bsize; + if (ignoreBody) + bsize = new Point(0,0); + else + bsize = body_Cache.computeSize(FormUtil.getWidthHint(wHint, + body_), DWT.DEFAULT); + width = Math.max(bsize.x, width); + height += bsize.y; + return new Point(width, height); + } + + protected void layout(Composite composite, bool flushCache) { + if (flushCache) { + body_Cache.flush(); + headCache.flush(); + } + body_Cache.setControl(body_); + headCache.setControl(head); + Rectangle carea = composite.getClientArea(); + + Point hsize = headCache.computeSize(carea.width, DWT.DEFAULT); + headCache.setBounds(0, 0, carea.width, hsize.y); + body_Cache + .setBounds(0, hsize.y, carea.width, carea.height - hsize.y); + } + } + + /** + * Creates the form content control as a child of the provided parent. + * + * @param parent + * the parent widget + */ + public this(Composite parent, int style) { + + body_Cache = new SizeCache(); + headCache = new SizeCache(); + + super(parent, DWT.NO_BACKGROUND | style); + super.setLayout(new FormLayout()); + head = new FormHeading(this, DWT.NULL); + head.setMenu(parent.getMenu()); + body_ = new LayoutComposite(this, DWT.NULL); + body_.setMenu(parent.getMenu()); + } + + /** + * Passes the menu to the form body. + * + * @param menu + * the parent menu + */ + public void setMenu(Menu menu) { + super.setMenu(menu); + head.setMenu(menu); + body_.setMenu(menu); + } + + /** + * Fully delegates the size computation to the internal layout manager. + */ + public final Point computeSize(int wHint, int hHint, bool changed) { + return (cast(FormLayout) getLayout()).computeSize(this, wHint, hHint, + changed); + } + + /** + * Prevents from changing the custom control layout. + */ + public final void setLayout(Layout layout) { + } + + /** + * Returns the title text that will be rendered at the top of the form. + * + * @return the title text + */ + public String getText() { + return head.getText(); + } + + /** + * Returns the title image that will be rendered to the left of the title. + * + * @return the title image or <code>null</code> if not set. + * @since 3.2 + */ + public Image getImage() { + return head.getImage(); + } + + /** + * Sets the foreground color of the form. This color will also be used for + * the body. + * + * @param fg + * the foreground color + */ + public void setForeground(Color fg) { + super.setForeground(fg); + head.setForeground(fg); + body_.setForeground(fg); + } + + /** + * Sets the background color of the form. This color will also be used for + * the body. + * + * @param bg + * the background color + */ + public void setBackground(Color bg) { + super.setBackground(bg); + head.setBackground(bg); + body_.setBackground(bg); + } + + /** + * Sets the font of the header text. + * + * @param font + * the new font + */ + public void setFont(Font font) { + super.setFont(font); + head.setFont(font); + } + + /** + * Sets the text to be rendered at the top of the form above the body as a + * title. + * <p> + * <strong>Note:</strong> Mnemonics are indicated by an '&' that causes + * the next character to be the mnemonic. Mnemonics are not applicable in + * the case of the form title but need to be taken into acount due to the + * usage of the underlying widget that renders mnemonics in the title area. + * The mnemonic indicator character '&' can be escaped by doubling it in + * the string, causing a single '&' to be displayed. + * </p> + * + * @param text + * the title text + */ + public void setText(String text) { + head.setText(text); + layout(); + redraw(); + } + + /** + * Sets the image to be rendered to the left of the title. This image will + * be temporarily hidden in two cases: + * + * <ol> + * <li>When the form is busy - replaced with a busy animation</li> + * <li>When the form has message set - replaced with the image indicating + * message severity</li> + * </ol> + * + * @param image + * the title image or <code>null</code> to show no image. + * @since 3.2 + */ + public void setImage(Image image) { + head.setImage(image); + layout(); + redraw(); + } + + /** + * Sets the background colors to be painted behind the title text in a + * gradient. Note that this method will reset color previously set by + * {@link #setBackground(Color)}. This is necessary for the simulated + * transparency of the heading in all of its children control. + * + * @param gradientColors + * the array of colors that form the gradient + * @param percents + * the partition of the overall space between the gradient colors + * @param vertical + * of <code>true</code>, the gradient will be rendered + * vertically, if <code>false</code> the orientation will be + * horizontal. + */ + + public void setTextBackground(Color[] gradientColors, int[] percents, + bool vertical) { + head.setTextBackground(gradientColors, percents, vertical); + } + + /** + * Returns the optional background image of the form head. + * + * @return the background image or <code>null</code> if not specified. + */ + public Image getBackgroundImage() { + return head.getHeadingBackgroundImage(); + } + + /** + * Sets the optional background image to be rendered behind the title + * starting at the position 0,0. If the image is smaller than the container + * in any dimension, it will be tiled. + * + * @since 3.2 + * + * @param backgroundImage + * the head background image. + * + */ + public void setBackgroundImage(Image backgroundImage) { + head.setHeadingBackgroundImage(backgroundImage); + } + + /** + * Returns the tool bar manager that is used to manage tool items in the + * form's title area. + * + * @return form tool bar manager + */ + public IToolBarManager getToolBarManager() { + return head.getToolBarManager(); + } + + /** + * Sets the tool bar vertical alignment relative to the header. Can be + * useful when there is more free space at the second row (with the head + * client). + * + * @param alignment + * DWT.TOP or DWT.BOTTOM + * @since 3.3 + */ + + public void setToolBarVerticalAlignment(int alignment) { + head.setToolBarAlignment(alignment); + } + + /** + * Returns the current tool bar alignment (if used). + * + * @return DWT.TOP or DWT.BOTTOM + * @since 3.3 + */ + + public int getToolBarVerticalAlignment() { + return head.getToolBarAlignment(); + } + + /** + * Returns the menu manager that is used to manage title area drop-down menu + * items. + * + * @return title area drop-down menu manager + * @since 3.3 + */ + public IMenuManager getMenuManager() { + return head.getMenuManager(); + } + + /** + * Updates the local tool bar manager if used. Does nothing if local tool + * bar manager has not been created yet. + */ + public void updateToolBar() { + head.updateToolBar(); + } + + /** + * Returns the container that occupies the head of the form (the form area + * above the body). Use this container as a parent for the head client. + * + * @return the head of the form. + * @since 3.2 + */ + public Composite getHead() { + return head; + } + + /** + * Returns the optional head client if set. + * + * @return the head client or <code>null</code> if not set. + * @see #setHeadClient(Control) + * @since 3.2 + */ + public Control getHeadClient() { + return head.getHeadClient(); + } + + /** + * Sets the optional head client. Head client is placed after the form + * title. This option causes the tool bar to be placed in the second raw of + * the header (below the head client). + * <p> + * The head client must be a child of the composite returned by + * <code>getHead()</code> method. + * + * @param headClient + * the optional child of the head + * @since 3.2 + */ + public void setHeadClient(Control headClient) { + head.setHeadClient(headClient); + layout(); + } + + /** + * Returns the container that occupies the body of the form (the form area + * below the title). Use this container as a parent for the controls that + * should be in the form. No layout manager has been set on the form body. + * + * @return Returns the body of the form. + */ + public Composite getBody() { + return body_; + } + + /** + * Tests if the background image is tiled to cover the entire area of the + * form heading. + * + * @return <code>true</code> if heading background image is tiled, + * <code>false</code> otherwise. + */ + public bool isBackgroundImageTiled() { + return head.isBackgroundImageTiled(); + } + + /** + * Sets whether the header background image is repeated to cover the entire + * heading area or not. + * + * @param backgroundImageTiled + * set <code>true</code> to tile the image, or + * <code>false</code> to paint the background image only once + * at 0,0 + */ + public void setBackgroundImageTiled(bool backgroundImageTiled) { + head.setBackgroundImageTiled(backgroundImageTiled); + } + + /** + * Returns the background image alignment. + * + * @deprecated due to the underlying widget limitations, background image is + * either painted at 0,0 and/or tiled. + * @return DWT.LEFT + */ + public int getBackgroundImageAlignment() { + return DWT.LEFT; + } + + /** + * Sets the background image alignment. + * + * @deprecated due to the underlying widget limitations, background image is + * always tiled and alignment cannot be controlled. + * @param backgroundImageAlignment + * The backgroundImageAlignment to set. + * @since 3.1 + */ + public void setBackgroundImageAlignment(int backgroundImageAlignment) { + } + + /** + * Tests if background image is clipped. + * + * @deprecated due to the underlying widget limitations, background image is + * always clipped. + * @return true + * @since 3.1 + */ + public bool isBackgroundImageClipped() { + return true; + } + + /** + * Sets whether the background image is clipped. + * + * @deprecated due to the underlying widget limitations, background image is + * always clipped. + * @param backgroundImageClipped + * the value to set + * @since 3.1 + */ + public void setBackgroundImageClipped(bool backgroundImageClipped) { + } + + /** + * Tests if the form head separator is visible. + * + * @return <code>true</code> if the head/body separator is visible, + * <code>false</code> otherwise + * @since 3.2 + */ + public bool isSeparatorVisible() { + return head.isSeparatorVisible(); + } + + /** + * If set, adds a separator between the head and body. Since 3.3, the colors + * that are used to render it are {@link IFormColors#H_BOTTOM_KEYLINE1} and + * {@link IFormColors#H_BOTTOM_KEYLINE2}. + * + * @param addSeparator + * <code>true</code> to make the separator visible, + * <code>false</code> otherwise. + * @since 3.2 + */ + public void setSeparatorVisible(bool addSeparator) { + head.setSeparatorVisible(addSeparator); + } + + /** + * Returns the color used to render the optional head separator. If gradient + * text background is used additional colors from the gradient will be used + * to render the separator. + * + * @return separator color or <code>null</code> if not set. + * @since 3.2 + * @deprecated use <code>getHeadColor(IFormColors.H_BOTTOM_KEYLINE2)</code> + */ + + public Color getSeparatorColor() { + return head.getColor(IFormColors.H_BOTTOM_KEYLINE2); + } + + /** + * Sets the color to be used to render the optional head separator. + * + * @param separatorColor + * the color to render the head separator or <code>null</code> + * to use the default color. + * @since 3.2 + * @deprecated use + * <code>setHeadColor(IFormColors.H_BOTTOM_KEYLINE2, separatorColor)</code> + */ + public void setSeparatorColor(Color separatorColor) { + head.putColor(IFormColors.H_BOTTOM_KEYLINE2, separatorColor); + } + + /** + * Sets the color used to paint an aspect of the form heading. + * + * @param key + * a valid form heading color key as defined in + * {@link IFormColors}. Relevant keys all start with an H_ + * prefix. + * @param color + * the color to use for the provided key + * @since 3.3 + */ + + public void setHeadColor(String key, Color color) { + head.putColor(key, color); + } + + /** + * Returns the color that is currently use to paint an aspect of the form + * heading, or <code>null</code> if not defined. + * + * @param key + * the color key + * @return the color object or <code>null</code> if not set. + * @since 3.3 + */ + + public Color getHeadColor(String key) { + return head.getColor(key); + } + + /** + * Sets the message for this form. Message text is rendered in the form head + * when shown. + * + * @param message + * the message, or <code>null</code> to clear the message + * @see #setMessage(String, int) + * @since 3.2 + */ + public void setMessage(String message) { + this.setMessage(message, 0, null); + } + + /** + * Sets the message for this form with an indication of what type of message + * it is. + * <p> + * The valid message types are one of <code>NONE</code>, + * <code>INFORMATION</code>,<code>WARNING</code>, or + * <code>ERROR</code> defined in IMessageProvider interface. + * </p> + * + * @param newMessage + * the message, or <code>null</code> to clear the message + * @param newType + * the message type + * @see dwtx.jface.dialogs.IMessageProvider + * @since 3.2 + */ + + public void setMessage(String newMessage, int newType) { + this.setMessage(newMessage, newType, null); + } + + /** + * Sets the message for this form with an indication of what type of message + * it is. + * <p> + * The valid message types are one of <code>NONE</code>, + * <code>INFORMATION</code>,<code>WARNING</code>, or + * <code>ERROR</code> defined in IMessageProvider interface. + * </p> + * <p> + * In addition to the summary message, this method also sets an array of + * individual messages. + * + * + * @param newMessage + * the message, or <code>null</code> to clear the message + * @param newType + * the message type + * @param children + * the individual messages that contributed to the overall + * message + * @see dwtx.jface.dialogs.IMessageProvider + * @since 3.3 + */ + + public void setMessage(String newMessage, int newType, IMessage[] children) { + head.showMessage(newMessage, newType, children); + layout(); + } + + /** + * Adds a message hyperlink listener. If at least one listener is present, + * messages will be rendered as hyperlinks. + * + * @param listener + * @see #removeMessageHyperlinkListener(IHyperlinkListener) + * @since 3.3 + */ + public void addMessageHyperlinkListener(IHyperlinkListener listener) { + head.addMessageHyperlinkListener(listener); + } + + /** + * Remove the message hyperlink listener. + * + * @param listener + * @see #addMessageHyperlinkListener(IHyperlinkListener) + * @since 3.3 + */ + public void removeMessageHyperlinkListener(IHyperlinkListener listener) { + head.removeMessageHyperlinkListener(listener); + } + + /** + * Tests if the form is in the 'busy' state. Busy form displays 'busy' + * animation in the area of the title image. + * + * @return <code>true</code> if busy, <code>false</code> otherwise. + * @since 3.2 + */ + + public bool isBusy() { + return head.isBusy(); + } + + /** + * Sets the form's busy state. Busy form will display 'busy' animation in + * the area of the title image. + * + * @param busy + * the form's busy state + * @since 3.2 + */ + + public void setBusy(bool busy) { + head.setBusy(busy); + } + + /** + * Adds support for dragging items out of the form title area via a user + * drag-and-drop operation. + * + * @param operations + * a bitwise OR of the supported drag and drop operation types ( + * <code>DROP_COPY</code>,<code>DROP_LINK</code>, and + * <code>DROP_MOVE</code>) + * @param transferTypes + * the transfer types that are supported by the drag operation + * @param listener + * the callback that will be invoked to set the drag data and to + * cleanup after the drag and drop operation finishes + * @see dwt.dnd.DND + * @since 3.3 + */ + public void addTitleDragSupport(int operations, Transfer[] transferTypes, + DragSourceListener listener) { + head.addDragSupport(operations, transferTypes, listener); + } + + /** + * Adds support for dropping items into the form title area via a user + * drag-and-drop operation. + * + * @param operations + * a bitwise OR of the supported drag and drop operation types ( + * <code>DROP_COPY</code>,<code>DROP_LINK</code>, and + * <code>DROP_MOVE</code>) + * @param transferTypes + * the transfer types that are supported by the drop operation + * @param listener + * the callback that will be invoked after the drag and drop + * operation finishes + * @see dwt.dnd.DND + * @since 3.3 + */ + public void addTitleDropSupport(int operations, Transfer[] transferTypes, + DropTargetListener listener) { + head.addDropSupport(operations, transferTypes, listener); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.IMessageProvider#getMessage() + */ + public String getMessage() { + return head.getMessage(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.IMessageProvider#getMessageType() + */ + public int getMessageType() { + return head.getMessageType(); + } + + /** + * Returns the children messages that the cause of the summary message + * currently set on the form. + * + * @return an array of children messages or <code>null</code> if not set. + * @see #setMessage(String, int, IMessage[]) + * @since 3.3 + */ + public IMessage[] getChildrenMessages() { + return head.getChildrenMessages(); + } + + void setSelectionText(FormText text) { + if (selectionText !is null && selectionText !is text) { + selectionText.clearSelection(); + } + this.selectionText = text; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/FormText.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,1710 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.widgets.FormText; + +import dwtx.ui.forms.widgets.ILayoutExtension; +import dwtx.ui.forms.widgets.Form; + +import dwt.DWT; +import dwt.DWTException; +import dwt.accessibility.ACC; +import dwt.accessibility.Accessible; +import dwt.accessibility.AccessibleAdapter; +import dwt.accessibility.AccessibleControlAdapter; +import dwt.accessibility.AccessibleControlEvent; +import dwt.accessibility.AccessibleEvent; +import dwt.custom.ScrolledComposite; +import dwt.dnd.Clipboard; +import dwt.dnd.TextTransfer; +import dwt.dnd.Transfer; +import dwt.events.DisposeEvent; +import dwt.events.DisposeListener; +import dwt.events.FocusEvent; +import dwt.events.FocusListener; +import dwt.events.MenuEvent; +import dwt.events.MenuListener; +import dwt.events.MouseEvent; +import dwt.events.MouseListener; +import dwt.events.MouseMoveListener; +import dwt.events.MouseTrackListener; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.events.SelectionAdapter; +import dwt.events.SelectionEvent; +import dwt.events.SelectionListener; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Layout; +import dwt.widgets.Listener; +import dwt.widgets.Menu; +import dwt.widgets.MenuItem; +import dwt.widgets.TypedListener; +import dwtx.core.runtime.ListenerList; +import dwtx.ui.forms.HyperlinkSettings; +import dwtx.ui.forms.events.HyperlinkEvent; +import dwtx.ui.forms.events.IHyperlinkListener; +import dwtx.ui.internal.forms.Messages; +import dwtx.ui.internal.forms.widgets.ControlSegment; +import dwtx.ui.internal.forms.widgets.FormFonts; +import dwtx.ui.internal.forms.widgets.FormTextModel; +import dwtx.ui.internal.forms.widgets.FormUtil; +import dwtx.ui.internal.forms.widgets.IFocusSelectable; +import dwtx.ui.internal.forms.widgets.IHyperlinkSegment; +import dwtx.ui.internal.forms.widgets.ImageSegment; +import dwtx.ui.internal.forms.widgets.Locator; +import dwtx.ui.internal.forms.widgets.Paragraph; +import dwtx.ui.internal.forms.widgets.ParagraphSegment; +import dwtx.ui.internal.forms.widgets.SelectionData; +import dwtx.ui.internal.forms.widgets.TextSegment; + +import dwt.dwthelper.utils; +import dwt.dwthelper.InputStream; +import tango.io.Stdout; +import tango.util.collection.HashMap; +import tango.util.collection.ArraySeq; + +/** + * This class is a read-only text control that is capable of rendering wrapped + * text. Text can be rendered as-is or by parsing the formatting XML tags. + * Independently, words that start with http:// can be converted into hyperlinks + * on the fly. + * <p> + * When configured to use formatting XML, the control requires the root element + * <code>form</code> to be used. The following tags can be children of the + * <code>form</code> element: + * </p> + * <ul> + * <li><b>p </b>- for defining paragraphs. The following attributes are + * allowed: + * <ul> + * <li><b>vspace </b>- if set to 'false', no vertical space will be added + * (default is 'true')</li> + * </ul> + * </li> + * <li><b>li </b>- for defining list items. The following attributes are + * allowed: + * <ul> + * <li><b>vspace </b>- the same as with the <b>p </b> tag</li> + * <li><b>style </b>- could be 'bullet' (default), 'text' and 'image'</li> + * <li><b>value </b>- not used for 'bullet'. For text, it is the value of the + * text that is rendered as a bullet. For image, it is the href of the image to + * be rendered as a bullet.</li> + * <li><b>indent </b>- the number of pixels to indent the text in the list item + * </li> + * <li><b>bindent </b>- the number of pixels to indent the bullet itself</li> + * </ul> + * </li> + * </ul> + * <p> + * Text in paragraphs and list items will be wrapped according to the width of + * the control. The following tags can appear as children of either <b>p </b> or + * <b>li </b> elements: + * <ul> + * <li><b>img </b>- to render an image. Element accepts attribute 'href' that + * is a key to the <code>Image</code> set using 'setImage' method. Vertical + * position of image relative to surrounding text is optionally controlled by + * the attribute <b>align</b> that can have values <b>top</b>, <b>middle</b> + * and <b>bottom</b></li> + * <li><b>a </b>- to render a hyperlink. Element accepts attribute 'href' that + * will be provided to the hyperlink listeners via HyperlinkEvent object. The + * element also accepts 'nowrap' attribute (default is false). When set to + * 'true', the hyperlink will not be wrapped. Hyperlinks automatically created + * when 'http://' is encountered in text are not wrapped.</li> + * <li><b>b </b>- the enclosed text will use bold font.</li> + * <li><b>br </b>- forced line break (no attributes).</li> + * <li><b>span </b>- the enclosed text will have the color and font specified + * in the element attributes. Color is provided using 'color' attribute and is a + * key to the Color object set by 'setColor' method. Font is provided using + * 'font' attribute and is a key to the Font object set by 'setFont' method. As with + * hyperlinks, it is possible to block wrapping by setting 'nowrap' to true + * (false by default). + * </li> + * <li><b>control (new in 3.1)</b> - to place a control that is a child of the + * text control. Element accepts attribute 'href' that is a key to the Control + * object set using 'setControl' method. Optionally, attribute 'fill' can be set + * to <code>true</code> to make the control fill the entire width of the text. + * Form text is not responsible for creating or disposing controls, it only + * places them relative to the surrounding text. Similar to <b>img</b>, + * vertical position of the control can be set using the <b>align</b> + * attribute. In addition, <b>width</b> and <b>height</b> attributes can + * be used to force the dimensions of the control. If not used, + * the preferred control size will be used. + * </ul> + * <p> + * None of the elements can nest. For example, you cannot have <b>b </b> inside + * a <b>span </b>. This was done to keep everything simple and transparent. + * Since 3.1, an exception to this rule has been added to support nesting images + * and text inside the hyperlink tag (<b>a</b>). Image enclosed in the + * hyperlink tag acts as a hyperlink, can be clicked on and can accept and + * render selection focus. When both text and image is enclosed, selection and + * rendering will affect both as a single hyperlink. + * </p> + * <p> + * Since 3.1, it is possible to select text. Text selection can be + * programmatically accessed and also copied to clipboard. Non-textual objects + * (images, controls etc.) in the selection range are ignored. + * <p> + * Care should be taken when using this control. Form text is not an HTML + * browser and should not be treated as such. If you need complex formatting + * capabilities, use Browser widget. If you need editing capabilities and + * font/color styles of text segments is all you need, use StyleText widget. + * Finally, if all you need is to wrap text, use DWT Label widget and create it + * with DWT.WRAP style. + * + * @see FormToolkit + * @see TableWrapLayout + * @since 3.0 + */ +public class FormText : Canvas { + /** + * The object ID to be used when registering action to handle URL hyperlinks + * (those that should result in opening the web browser). Value is + * "urlHandler". + */ + public static const String URL_HANDLER_ID = "urlHandler"; //$NON-NLS-1$ + + /** + * Value of the horizontal margin (default is 0). + */ + public int marginWidth = 0; + + /** + * Value of tue vertical margin (default is 1). + */ + public int marginHeight = 1; + + // private fields + private static const bool DEBUG_TEXT = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXT)); + private static const bool DEBUG_TEXTSIZE = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXTSIZE)); + + private static const bool DEBUG_FOCUS = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_FOCUS)); + + private bool hasFocus; + + private bool paragraphsSeparated = true; + + private FormTextModel model; + + private ListenerList listeners; + + private HashMap!(String,Object) resourceTable; + + private IHyperlinkSegment entered; + + private IHyperlinkSegment armed; + + private bool mouseFocus = false; + + private bool controlFocusTransfer = false; + + private bool inSelection = false; + + private SelectionData selData; + + private static const String INTERNAL_MENU = "__internal_menu__"; //$NON-NLS-1$ + + private static const String CONTROL_KEY = "__segment__"; //$NON-NLS-1$ + + private class FormTextLayout : Layout, ILayoutExtension { + public this() { + } + + public int computeMaximumWidth(Composite parent, bool changed) { + return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x; + } + + public int computeMinimumWidth(Composite parent, bool changed) { + return computeSize(parent, 5, DWT.DEFAULT, true).x; + } + + /* + * @see Layout#computeSize(Composite, int, int, bool) + */ + public Point computeSize(Composite composite, int wHint, int hHint, + bool changed) { + long start = 0; + + if (DEBUG_TEXT) + start = System.currentTimeMillis(); + int innerWidth = wHint; + if (innerWidth !is DWT.DEFAULT) + innerWidth -= marginWidth * 2; + Point textSize = computeTextSize(innerWidth); + int textWidth = textSize.x + 2 * marginWidth; + int textHeight = textSize.y + 2 * marginHeight; + Point result = new Point(textWidth, textHeight); + if (DEBUG_TEXT) { + long stop = System.currentTimeMillis(); + Stdout.formatln("FormText computeSize: {}ms", (stop - start)); //$NON-NLS-1$ + } + if (DEBUG_TEXTSIZE) { + Stdout.formatln("FormText ({}), computeSize: wHint={}, result={}", model.getAccessibleText(), wHint, result); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return result; + } + + private Point computeTextSize(int wHint) { + Paragraph[] paragraphs = model.getParagraphs(); + GC gc = new GC(this.outer); + gc.setFont(getFont()); + Locator loc = new Locator(); + int width = wHint !is DWT.DEFAULT ? wHint : 0; + FontMetrics fm = gc.getFontMetrics(); + int lineHeight = fm.getHeight(); + bool selectableInTheLastRow = false; + for (int i = 0; i < paragraphs.length; i++) { + Paragraph p = paragraphs[i]; + if (i > 0 && getParagraphsSeparated() + && p.getAddVerticalSpace()) + loc.y += getParagraphSpacing(lineHeight); + loc.rowHeight = 0; + loc.indent = p.getIndent(); + loc.x = p.getIndent(); + ParagraphSegment[] segments = p.getSegments(); + if (segments.length > 0) { + selectableInTheLastRow = false; + int pwidth = 0; + for (int j = 0; j < segments.length; j++) { + ParagraphSegment segment = segments[j]; + segment.advanceLocator(gc, wHint, loc, resourceTable, + false); + if (wHint !is DWT.DEFAULT) { + width = Math.max(width, loc.width); + } else { + pwidth += loc.width; + } + if (null !is cast(IFocusSelectable)segment ) + selectableInTheLastRow = true; + } + if (wHint is DWT.DEFAULT) + width = Math.max(width, pwidth); + loc.y += loc.rowHeight; + } else { + // empty new line + loc.y += lineHeight; + } + } + gc.dispose(); + if (selectableInTheLastRow) + loc.y += 1; + return new Point(width, loc.y); + } + + protected void layout(Composite composite, bool flushCache) { + long start = 0; + + if (DEBUG_TEXT) { + start = System.currentTimeMillis(); + } + selData = null; + Rectangle carea = composite.getClientArea(); + if (DEBUG_TEXTSIZE) { + Stdout.formatln("FormText layout ({}), carea={}",model.getAccessibleText(),carea); //$NON-NLS-1$ //$NON-NLS-2$ + } + GC gc = new GC(composite); + gc.setFont(getFont()); + ensureBoldFontPresent(getFont()); + gc.setForeground(getForeground()); + gc.setBackground(getBackground()); + + Locator loc = new Locator(); + loc.marginWidth = marginWidth; + loc.marginHeight = marginHeight; + loc.x = marginWidth; + loc.y = marginHeight; + FontMetrics fm = gc.getFontMetrics(); + int lineHeight = fm.getHeight(); + + Paragraph[] paragraphs = model.getParagraphs(); + IHyperlinkSegment selectedLink = getSelectedLink(); + for (int i = 0; i < paragraphs.length; i++) { + Paragraph p = paragraphs[i]; + if (i > 0 && paragraphsSeparated && p.getAddVerticalSpace()) + loc.y += getParagraphSpacing(lineHeight); + loc.indent = p.getIndent(); + loc.resetCaret(); + loc.rowHeight = 0; + p.layout(gc, carea.width, loc, lineHeight, resourceTable, + selectedLink); + } + gc.dispose(); + if (DEBUG_TEXT) { + long stop = System.currentTimeMillis(); + Stdout.formatln("FormText.layout: {}ms", (stop - start)); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + + /** + * Contructs a new form text widget in the provided parent and using the + * styles. + * + * @param parent + * form text parent control + * @param style + * the widget style + */ + public this(Composite parent, int style) { + resourceTable = new HashMap!(String,Object); + super(parent, DWT.NO_BACKGROUND | DWT.WRAP | style); + setLayout(new FormTextLayout()); + model = new FormTextModel(); + addDisposeListener(new class DisposeListener { + public void widgetDisposed(DisposeEvent e) { + model.dispose(); + disposeResourceTable(true); + } + }); + addPaintListener(new class PaintListener { + public void paintControl(PaintEvent e) { + paint(e); + } + }); + addListener(DWT.KeyDown, new class Listener { + public void handleEvent(Event e) { + if (e.character is '\r') { + activateSelectedLink(); + return; + } + } + }); + addListener(DWT.Traverse, new class Listener { + public void handleEvent(Event e) { + if (DEBUG_FOCUS) + Stdout.formatln("Traversal: {}", e); //$NON-NLS-1$ + switch (e.detail) { + case DWT.TRAVERSE_PAGE_NEXT: + case DWT.TRAVERSE_PAGE_PREVIOUS: + case DWT.TRAVERSE_ARROW_NEXT: + case DWT.TRAVERSE_ARROW_PREVIOUS: + e.doit = false; + return; + } + if (!model.hasFocusSegments()) { + e.doit = true; + return; + } + if (e.detail is DWT.TRAVERSE_TAB_NEXT) + e.doit = advance(true); + else if (e.detail is DWT.TRAVERSE_TAB_PREVIOUS) + e.doit = advance(false); + else if (e.detail !is DWT.TRAVERSE_RETURN) + e.doit = true; + } + }); + addFocusListener(new class FocusListener { + public void focusGained(FocusEvent e) { + if (!hasFocus) { + hasFocus = true; + if (DEBUG_FOCUS) { + Stdout.formatln("FormText: focus gained"); //$NON-NLS-1$ + } + if (!mouseFocus && !controlFocusTransfer) { + handleFocusChange(); + } + } + } + + public void focusLost(FocusEvent e) { + if (DEBUG_FOCUS) { + Stdout.formatln("FormText: focus lost"); //$NON-NLS-1$ + } + if (hasFocus) { + hasFocus = false; + if (!controlFocusTransfer) + handleFocusChange(); + } + } + }); + addMouseListener(new class MouseListener { + public void mouseDoubleClick(MouseEvent e) { + } + + public void mouseDown(MouseEvent e) { + // select a link + handleMouseClick(e, true); + } + + public void mouseUp(MouseEvent e) { + // activate a link + handleMouseClick(e, false); + } + }); + addMouseTrackListener(new class MouseTrackListener { + public void mouseEnter(MouseEvent e) { + handleMouseMove(e); + } + + public void mouseExit(MouseEvent e) { + if (entered !is null) { + exitLink(entered, e.stateMask); + paintLinkHover(entered, false); + entered = null; + setCursor(null); + } + } + + public void mouseHover(MouseEvent e) { + handleMouseHover(e); + } + }); + addMouseMoveListener(new class MouseMoveListener { + public void mouseMove(MouseEvent e) { + handleMouseMove(e); + } + }); + initAccessible(); + ensureBoldFontPresent(getFont()); + createMenu(); + // we will handle traversal of controls, if any + setTabList(cast(Control[])null); + } + + /** + * Test for focus. + * + * @return <samp>true </samp> if the widget has focus. + */ + public bool getFocus() { + return hasFocus; + } + + /** + * Test if the widget is currently processing the text it is about to + * render. + * + * @return <samp>true </samp> if the widget is still loading the text, + * <samp>false </samp> otherwise. + * @deprecated not used any more - returns <code>false</code> + */ + public bool isLoading() { + return false; + } + + /** + * Returns the text that will be shown in the control while the real content + * is loading. + * + * @return loading text message + * @deprecated loading text is not used since 3.1 + */ + public String getLoadingText() { + return null; + } + + /** + * Sets the text that will be shown in the control while the real content is + * loading. This is significant when content to render is loaded from the + * input stream that was created from a remote URL, and the time to load the + * entire content is nontrivial. + * + * @param loadingText + * loading text message + * @deprecated use setText(loadingText, false, false); + */ + public void setLoadingText(String loadingText) { + setText(loadingText, false, false); + } + + /** + * If paragraphs are separated, spacing will be added between them. + * Otherwise, new paragraphs will simply start on a new line with no + * spacing. + * + * @param value + * <samp>true </samp> if paragraphs are separated, </samp> false + * </samp> otherwise. + */ + public void setParagraphsSeparated(bool value) { + paragraphsSeparated = value; + } + + /** + * Tests if there is some inter-paragraph spacing. + * + * @return <samp>true </samp> if paragraphs are separated, <samp>false + * </samp> otherwise. + */ + public bool getParagraphsSeparated() { + return paragraphsSeparated; + } + + /** + * Registers the image referenced by the provided key. + * <p> + * For <samp>img </samp> tags, an object of a type <samp>Image </samp> must + * be registered using the key equivalent to the value of the <samp>href + * </samp> attribute used in the tag. + * + * @param key + * unique key that matches the value of the <samp>href </samp> + * attribute. + * @param image + * an object of a type <samp>Image </samp>. + */ + public void setImage(String key, Image image) { + resourceTable.add("i." ~ key, image); //$NON-NLS-1$ + } + + /** + * Registers the color referenced by the provided key. + * <p> + * For <samp>span </samp> tags, an object of a type <samp>Color </samp> must + * be registered using the key equivalent to the value of the <samp>color + * </samp> attribute. + * + * @param key + * unique key that matches the value of the <samp>color </samp> + * attribute. + * @param color + * an object of the type <samp>Color </samp> or <samp>null</samp> + * if the key needs to be cleared. + */ + public void setColor(String key, Color color) { + String fullKey = "c." ~ key; //$NON-NLS-1$ + if (color is null) + resourceTable.removeKey(fullKey); + else + resourceTable.add(fullKey, color); + } + + /** + * Registers the font referenced by the provided key. + * <p> + * For <samp>span </samp> tags, an object of a type <samp>Font </samp> must + * be registered using the key equivalent to the value of the <samp>font + * </samp> attribute. + * + * @param key + * unique key that matches the value of the <samp>font </samp> + * attribute. + * @param font + * an object of the type <samp>Font </samp> or <samp>null</samp> + * if the key needs to be cleared. + */ + public void setFont(String key, Font font) { + String fullKey = "f." ~ key; //$NON-NLS-1$ + if (font is null) + resourceTable.removeKey(fullKey); + else + resourceTable.add(fullKey, font); + model.clearCache(fullKey); + } + + /** + * Registers the control referenced by the provided key. + * <p> + * For <samp>control</samp> tags, an object of a type <samp>Control</samp> + * must be registered using the key equivalent to the value of the + * <samp>control</samp> attribute. + * + * @param key + * unique key that matches the value of the <samp>control</samp> + * attribute. + * @param control + * an object of the type <samp>Control</samp> or <samp>null</samp> + * if the existing control at the specified key needs to be + * removed. + * @since 3.1 + */ + public void setControl(String key, Control control) { + String fullKey = "o." ~ key; //$NON-NLS-1$ + if (control is null) + resourceTable.removeKey(fullKey); + else + resourceTable.add(fullKey, control); + } + + /** + * Sets the font to use to render the default text (text that does not have + * special font property assigned). Bold font will be constructed from this + * font. + * + * @param font + * the default font to use + */ + public void setFont(Font font) { + super.setFont(font); + model.clearCache(null); + Font boldFont = cast(Font) resourceTable.get(FormTextModel.BOLD_FONT_ID); + if (boldFont !is null) { + FormFonts.getInstance().markFinished(boldFont); + resourceTable.removeKey(FormTextModel.BOLD_FONT_ID); + } + ensureBoldFontPresent(getFont()); + } + + /** + * Sets the provided text. Text can be rendered as-is, or by parsing the + * formatting tags. Optionally, sections of text starting with http:// will + * be converted to hyperlinks. + * + * @param text + * the text to render + * @param parseTags + * if <samp>true </samp>, formatting tags will be parsed. + * Otherwise, text will be rendered as-is. + * @param expandURLs + * if <samp>true </samp>, URLs found in the untagged text will be + * converted into hyperlinks. + */ + public void setText(String text, bool parseTags, bool expandURLs) { + disposeResourceTable(false); + entered = null; + if (parseTags) + model.parseTaggedText(text, expandURLs); + else + model.parseRegularText(text, expandURLs); + hookControlSegmentFocus(); + layout(); + redraw(); + } + + /** + * Sets the contents of the stream. Optionally, URLs in untagged text can be + * converted into hyperlinks. The caller is responsible for closing the + * stream. + * + * @param is + * stream to render + * @param expandURLs + * if <samp>true </samp>, URLs found in untagged text will be + * converted into hyperlinks. + */ + public void setContents(InputStream is_, bool expandURLs) { + entered = null; + disposeResourceTable(false); + model.parseInputStream(is_, expandURLs); + hookControlSegmentFocus(); + layout(); + redraw(); + } + + private void hookControlSegmentFocus() { + Paragraph[] paragraphs = model.getParagraphs(); + if (paragraphs is null) + return; + Listener listener = new class Listener { + public void handleEvent(Event e) { + switch (e.type) { + case DWT.FocusIn: + if (!controlFocusTransfer) + syncControlSegmentFocus(cast(Control) e.widget); + break; + case DWT.Traverse: + if (DEBUG_FOCUS) + Stdout.formatln("Control traversal: {}", e); //$NON-NLS-1$ + switch (e.detail) { + case DWT.TRAVERSE_PAGE_NEXT: + case DWT.TRAVERSE_PAGE_PREVIOUS: + case DWT.TRAVERSE_ARROW_NEXT: + case DWT.TRAVERSE_ARROW_PREVIOUS: + e.doit = false; + return; + } + Control c = cast(Control) e.widget; + ControlSegment segment = cast(ControlSegment) c + .getData(CONTROL_KEY); + if (e.detail is DWT.TRAVERSE_TAB_NEXT) + e.doit = advanceControl(c, segment, true); + else if (e.detail is DWT.TRAVERSE_TAB_PREVIOUS) + e.doit = advanceControl(c, segment, false); + if (!e.doit) + e.detail = DWT.TRAVERSE_NONE; + break; + } + } + }; + for (int i = 0; i < paragraphs.length; i++) { + Paragraph p = paragraphs[i]; + ParagraphSegment[] segments = p.getSegments(); + for (int j = 0; j < segments.length; j++) { + if (auto cs = cast(ControlSegment)segments[j] ) { + Control c = cs.getControl(resourceTable); + if (c !is null) { + if (c.getData(CONTROL_KEY) is null) { + // first time - hook + c.setData(CONTROL_KEY, cs); + attachTraverseListener(c, listener); + } + } + } + } + } + } + + private void attachTraverseListener(Control c, Listener listener) { + if ( auto parent = cast(Composite) c ) { + Control[] children = parent.getChildren(); + for (int i = 0; i < children.length; i++) { + attachTraverseListener(children[i], listener); + } + if (auto canv = cast(Canvas)c ) { + // If Canvas, the control iteself can accept + // traverse events and should be monitored + c.addListener(DWT.Traverse, listener); + c.addListener(DWT.FocusIn, listener); + } + } else { + c.addListener(DWT.Traverse, listener); + c.addListener(DWT.FocusIn, listener); + } + } + + /** + * If we click on the control randomly, our internal book-keeping will be + * off. We need to update the model and mark the control segment and + * currently selected. Hyperlink that may have had focus must also be + * exited. + * + * @param control + * the control that got focus + */ + private void syncControlSegmentFocus(Control control) { + ControlSegment cs = null; + + while (control !is null) { + cs = cast(ControlSegment) control.getData(CONTROL_KEY); + if (cs !is null) + break; + control = control.getParent(); + } + if (cs is null) + return; + IFocusSelectable current = model.getSelectedSegment(); + // If the model and the control match, all is well + if (current is cs) + return; + IHyperlinkSegment oldLink = null; + if (current !is null && null !is cast(IHyperlinkSegment)current ) { + oldLink = cast(IHyperlinkSegment) current; + exitLink(oldLink, DWT.NULL); + } + if (DEBUG_FOCUS) + Stdout.formatln("Sync control: {}, oldLink={}", cs, oldLink); //$NON-NLS-1$ //$NON-NLS-2$ + model.select(cs); + if (oldLink !is null) + paintFocusTransfer(oldLink, null); + // getAccessible().setFocus(model.getSelectedSegmentIndex()); + } + + private bool advanceControl(Control c, ControlSegment segment, + bool next) { + Composite parent = c.getParent(); + if (parent is this) { + // segment-level control + IFocusSelectable nextSegment = model.getNextFocusSegment(next); + if (nextSegment !is null) { + controlFocusTransfer = true; + super.forceFocus(); + controlFocusTransfer = false; + model.select(segment); + return advance(next); + } + // nowhere to go + return setFocusToNextSibling(this, next); + } + if (setFocusToNextSibling(c, next)) + return true; + // still here - must go one level up + segment = cast(ControlSegment) parent.getData(CONTROL_KEY); + return advanceControl(parent, segment, next); + } + + private bool setFocusToNextSibling(Control c, bool next) { + Composite parent = c.getParent(); + Control[] children = parent.getTabList(); + for (int i = 0; i < children.length; i++) { + Control child = children[i]; + if (child is c) { + // here + if (next) { + for (int j = i + 1; j < children.length; j++) { + Control nc = children[j]; + if (nc.setFocus()) + return false; + } + } else { + for (int j = i - 1; j >= 0; j--) { + Control pc = children[j]; + if (pc.setFocus()) + return false; + } + } + } + } + return false; + } + + /** + * Controls whether whitespace inside paragraph and list items is + * normalized. Note that the new value will not affect the current text in + * the control, only subsequent calls to <code>setText</code> or + * <code>setContents</code>. + * <p> + * If normalized: + * <ul> + * <li>all white space characters will be condensed into at most one when + * between words.</li> + * <li>new line characters will be ignored and replaced with one white + * space character</li> + * <li>white space characters after the opening tags and before the closing + * tags will be trimmed</li> + * + * @param value + * <code>true</code> if whitespace is normalized, + * <code>false</code> otherwise. + */ + public void setWhitespaceNormalized(bool value) { + model.setWhitespaceNormalized(value); + } + + /** + * Tests whether whitespace inside paragraph and list item is normalized. + * + * @see #setWhitespaceNormalized(bool) + * @return <code>true</code> if whitespace is normalized, + * <code>false</code> otherwise. + */ + public bool isWhitespaceNormalized() { + return model.isWhitespaceNormalized(); + } + + /** + * Disposes the internal menu if created and sets the menu provided as a + * parameter. + * + * @param menu + * the menu to associate with this text control + */ + public void setMenu(Menu menu) { + Menu currentMenu = super.getMenu(); + if (currentMenu !is null && INTERNAL_MENU.equals(stringcast(currentMenu.getData()))) { + // internal menu set + if (menu !is null) { + currentMenu.dispose(); + super.setMenu(menu); + } + } else + super.setMenu(menu); + } + + private void createMenu() { + Menu menu = new Menu(this); + final MenuItem copyItem = new MenuItem(menu, DWT.PUSH); + copyItem.setText(Messages.FormText_copy); + + SelectionListener listener = new class SelectionAdapter { + public void widgetSelected(SelectionEvent e) { + if (e.widget is copyItem) { + copy(); + } + } + }; + copyItem.addSelectionListener(listener); + menu.addMenuListener(new class MenuListener { + public void menuShown(MenuEvent e) { + copyItem.setEnabled(canCopy()); + } + + public void menuHidden(MenuEvent e) { + } + }); + menu.setData(stringcast(INTERNAL_MENU)); + super.setMenu(menu); + } + + /** + * Returns the hyperlink settings that are in effect for this control. + * + * @return current hyperlinks settings + */ + public HyperlinkSettings getHyperlinkSettings() { + return model.getHyperlinkSettings(); + } + + /** + * Sets the hyperlink settings to be used for this control. Settings will + * affect things like hyperlink color, rendering style, cursor etc. + * + * @param settings + * hyperlink settings for this control + */ + public void setHyperlinkSettings(HyperlinkSettings settings) { + model.setHyperlinkSettings(settings); + } + + /** + * Adds a listener that will handle hyperlink events. + * + * @param listener + * the listener to add + */ + public void addHyperlinkListener(IHyperlinkListener listener) { + if (listeners is null) + listeners = new ListenerList(); + listeners.add(cast(Object)listener); + } + + /** + * Removes the hyperlink listener. + * + * @param listener + * the listener to remove + */ + public void removeHyperlinkListener(IHyperlinkListener listener) { + if (listeners is null) + return; + listeners.remove(cast(Object)listener); + } + + /** + * Adds a selection listener. A Selection event is sent by the widget when + * the selection has changed. + * <p> + * <code>widgetDefaultSelected</code> is not called for FormText. + * </p> + * + * @param listener + * the listener + * @exception DWTException + * <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been + * disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the + * thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException + * <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * @since 3.1 + */ + public void addSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener is null) { + DWT.error(DWT.ERROR_NULL_ARGUMENT); + } + TypedListener typedListener = new TypedListener(listener); + addListener(DWT.Selection, typedListener); + } + + /** + * Removes the specified selection listener. + * <p> + * + * @param listener + * the listener + * @exception DWTException + * <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been + * disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the + * thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException + * <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * @since 3.1 + */ + public void removeSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener is null) { + DWT.error(DWT.ERROR_NULL_ARGUMENT); + } + removeListener(DWT.Selection, listener); + } + + /** + * Returns the selected text. + * <p> + * + * @return selected text, or an empty String if there is no selection. + * @exception DWTException + * <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been + * disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the + * thread that created the receiver</li> + * </ul> + * @since 3.1 + */ + + public String getSelectionText() { + checkWidget(); + if (selData !is null) + return selData.getSelectionText(); + return ""; //$NON-NLS-1$ + } + + /** + * Tests if the text is selected and can be copied into the clipboard. + * + * @return <code>true</code> if the selected text can be copied into the + * clipboard, <code>false</code> otherwise. + * @since 3.1 + */ + public bool canCopy() { + return selData !is null && selData.canCopy(); + } + + /** + * Copies the selected text into the clipboard. Does nothing if no text is + * selected or the text cannot be copied for any other reason. + * + * @since 3.1 + */ + + public void copy() { + if (!canCopy()) + return; + Clipboard clipboard = new Clipboard(getDisplay()); + Object[] o = [ stringcast(getSelectionText()) ]; + Transfer[] t = [ TextTransfer.getInstance() ]; + clipboard.setContents(o, t); + clipboard.dispose(); + } + + /** + * Returns the reference of the hyperlink that currently has keyboard focus, + * or <code>null</code> if there are no hyperlinks in the receiver or no + * hyperlink has focus at the moment. + * + * @return href of the selected hyperlink or <code>null</code> if none + * selected. + * @since 3.1 + */ + public Object getSelectedLinkHref() { + IHyperlinkSegment link = getSelectedLink(); + return link !is null ? stringcast(link.getHref()) : null; + } + + /** + * Returns the text of the hyperlink that currently has keyboard focus, or + * <code>null</code> if there are no hyperlinks in the receiver or no + * hyperlink has focus at the moment. + * + * @return text of the selected hyperlink or <code>null</code> if none + * selected. + * @since 3.1 + */ + public String getSelectedLinkText() { + IHyperlinkSegment link = getSelectedLink(); + return link !is null ? link.getText() : null; + } + + private IHyperlinkSegment getSelectedLink() { + IFocusSelectable segment = model.getSelectedSegment(); + if (segment !is null && null !is cast(IHyperlinkSegment)segment ) + return cast(IHyperlinkSegment) segment; + return null; + } + + private void initAccessible() { + Accessible accessible = getAccessible(); + accessible.addAccessibleListener(new class AccessibleAdapter { + public void getName(AccessibleEvent e) { + if (e.childID is ACC.CHILDID_SELF) + e.result = model.getAccessibleText(); + else { + int linkCount = model.getHyperlinkCount(); + if (e.childID >= 0 && e.childID < linkCount) { + IHyperlinkSegment link = model.getHyperlink(e.childID); + e.result = link.getText(); + } + } + } + + public void getHelp(AccessibleEvent e) { + e.result = getToolTipText(); + int linkCount = model.getHyperlinkCount(); + if (e.result is null && e.childID >= 0 && e.childID < linkCount) { + IHyperlinkSegment link = model.getHyperlink(e.childID); + e.result = link.getText(); + } + } + }); + accessible.addAccessibleControlListener(new class AccessibleControlAdapter { + public void getChildAtPoint(AccessibleControlEvent e) { + Point pt = toControl(new Point(e.x, e.y)); + IHyperlinkSegment link = model.findHyperlinkAt(pt.x, pt.y); + if (link !is null) + e.childID = model.indexOf(link); + else + e.childID = ACC.CHILDID_SELF; + } + + public void getLocation(AccessibleControlEvent e) { + Rectangle location = null; + if (e.childID !is ACC.CHILDID_SELF + && e.childID !is ACC.CHILDID_NONE) { + int index = e.childID; + IHyperlinkSegment link = model.getHyperlink(index); + if (link !is null) { + location = link.getBounds(); + } + } + if (location is null) { + location = getBounds(); + } + Point pt = toDisplay(new Point(location.x, location.y)); + e.x = pt.x; + e.y = pt.y; + e.width = location.width; + e.height = location.height; + } + + public void getFocus(AccessibleControlEvent e) { + int childID = ACC.CHILDID_NONE; + + if (model.hasFocusSegments()) { + int selectedIndex = model.getSelectedSegmentIndex(); + if (selectedIndex !is -1) { + childID = selectedIndex; + } + } + e.childID = childID; + } + + public void getDefaultAction (AccessibleControlEvent e) { + if (model.getHyperlinkCount() > 0) { + e.result = DWT.getMessage ("SWT_Press"); //$NON-NLS-1$ + } + } + + public void getChildCount(AccessibleControlEvent e) { + e.detail = model.getHyperlinkCount(); + } + + public void getRole(AccessibleControlEvent e) { + int role = 0; + int childID = e.childID; + int linkCount = model.getHyperlinkCount(); + if (childID is ACC.CHILDID_SELF) { + if (linkCount > 0) { + role = ACC.ROLE_LINK; + } else { + role = ACC.ROLE_TEXT; + } + } else if (childID >= 0 && childID < linkCount) { + role = ACC.ROLE_LINK; + } + e.detail = role; + } + + public void getSelection(AccessibleControlEvent e) { + int selectedIndex = model.getSelectedSegmentIndex(); + e.childID = (selectedIndex is -1) ? ACC.CHILDID_NONE + : selectedIndex; + } + + public void getState(AccessibleControlEvent e) { + int linkCount = model.getHyperlinkCount(); + int selectedIndex = model.getSelectedSegmentIndex(); + int state = 0; + int childID = e.childID; + if (childID is ACC.CHILDID_SELF) { + state = ACC.STATE_NORMAL; + } else if (childID >= 0 && childID < linkCount) { + state = ACC.STATE_SELECTABLE; + if (isFocusControl()) { + state |= ACC.STATE_FOCUSABLE; + } + if (selectedIndex is childID) { + state |= ACC.STATE_SELECTED; + if (isFocusControl()) { + state |= ACC.STATE_FOCUSED; + } + } + } + state |= ACC.STATE_READONLY; + e.detail = state; + } + + public void getChildren(AccessibleControlEvent e) { + int linkCount = model.getHyperlinkCount(); + Object[] children = new Object[linkCount]; + for (int i = 0; i < linkCount; i++) { + children[i] = new Integer(i); + } + e.children = children; + } + + public void getValue(AccessibleControlEvent e) { + // e.result = model.getAccessibleText(); + } + }); + } + + private void startSelection(MouseEvent e) { + inSelection = true; + selData = new SelectionData(e); + redraw(); + Form form = FormUtil.getForm(this); + if (form !is null) + form.setSelectionText(this); + } + + private void endSelection(MouseEvent e) { + inSelection = false; + if (selData !is null) { + if (!selData.isEnclosed()) + selData = null; + else + computeSelection(); + } + notifySelectionChanged(); + } + + private void computeSelection() { + GC gc = new GC(this); + Paragraph[] paragraphs = model.getParagraphs(); + IHyperlinkSegment selectedLink = getSelectedLink(); + if (getDisplay().getFocusControl() !is this) + selectedLink = null; + for (int i = 0; i < paragraphs.length; i++) { + Paragraph p = paragraphs[i]; + if (i > 0) + selData.markNewLine(); + p.computeSelection(gc, resourceTable, selectedLink, selData); + } + gc.dispose(); + } + + void clearSelection() { + selData = null; + if (!isDisposed()) { + redraw(); + notifySelectionChanged(); + } + } + + private void notifySelectionChanged() { + Event event = new Event(); + event.widget = this; + event.display = this.getDisplay(); + event.type = DWT.Selection; + notifyListeners(DWT.Selection, event); + getAccessible().selectionChanged(); + } + + private void handleDrag(MouseEvent e) { + if (selData !is null) { + ScrolledComposite scomp = FormUtil.getScrolledComposite(this); + if (scomp !is null) { + FormUtil.ensureVisible(scomp, this, e); + } + selData.update(e); + redraw(); + } + } + + private void handleMouseClick(MouseEvent e, bool down) { + if (DEBUG_FOCUS) + Stdout.formatln("FormText: mouse click({})", down ); //$NON-NLS-1$ //$NON-NLS-2$ + if (down) { + // select a hyperlink + mouseFocus = true; + IHyperlinkSegment segmentUnder = model.findHyperlinkAt(e.x, e.y); + if (segmentUnder !is null) { + IHyperlinkSegment oldLink = getSelectedLink(); + if (getDisplay().getFocusControl() !is this) { + setFocus(); + } + model.selectLink(segmentUnder); + enterLink(segmentUnder, e.stateMask); + paintFocusTransfer(oldLink, segmentUnder); + } + if (e.button is 1) { + startSelection(e); + armed = segmentUnder; + } + else { + } + } else { + if (e.button is 1) { + endSelection(e); + IHyperlinkSegment segmentUnder = model + .findHyperlinkAt(e.x, e.y); + if (segmentUnder !is null && armed is segmentUnder && selData is null) { + activateLink(segmentUnder, e.stateMask); + armed = null; + } + } + mouseFocus = false; + } + } + + private void handleMouseHover(MouseEvent e) { + } + + private void updateTooltipText(ParagraphSegment segment) { + String tooltipText = null; + if (segment !is null) { + tooltipText = segment.getTooltipText(); + } + String currentTooltipText = getToolTipText(); + + if ((currentTooltipText !is null && tooltipText is null) + || (currentTooltipText is null && tooltipText !is null)) + setToolTipText(tooltipText); + } + + private void handleMouseMove(MouseEvent e) { + if (inSelection) { + handleDrag(e); + return; + } + ParagraphSegment segmentUnder = model.findSegmentAt(e.x, e.y); + updateTooltipText(segmentUnder); + if (segmentUnder is null) { + if (entered !is null) { + exitLink(entered, e.stateMask); + paintLinkHover(entered, false); + entered = null; + } + setCursor(null); + } else { + if (auto linkUnder = cast(IHyperlinkSegment) segmentUnder ) { + if (entered !is null && linkUnder !is entered) { + // Special case: links are so close that there are 0 pixels between. + // Must exit the link before entering the next one. + exitLink(entered, e.stateMask); + paintLinkHover(entered, false); + entered = null; + } + if (entered is null) { + entered = linkUnder; + enterLink(linkUnder, e.stateMask); + paintLinkHover(entered, true); + setCursor(model.getHyperlinkSettings().getHyperlinkCursor()); + } + } else { + if (entered !is null) { + exitLink(entered, e.stateMask); + paintLinkHover(entered, false); + entered = null; + } + if (null !is cast(TextSegment)segmentUnder ) + setCursor(model.getHyperlinkSettings().getTextCursor()); + else + setCursor(null); + } + } + } + + private bool advance(bool next) { + if (DEBUG_FOCUS) + Stdout.formatln("Advance: next={}", next); //$NON-NLS-1$ + IFocusSelectable current = model.getSelectedSegment(); + if (current !is null && null !is cast(IHyperlinkSegment)current ) + exitLink(cast(IHyperlinkSegment) current, DWT.NULL); + IFocusSelectable newSegment = null; + bool valid = false; + // get the next segment that can accept focus. Links + // can always accept focus but controls may not + while (!valid) { + if (!model.traverseFocusSelectableObjects(next)) + break; + newSegment = model.getSelectedSegment(); + if (newSegment is null) + break; + valid = setControlFocus(next, newSegment); + } + IHyperlinkSegment newLink = null !is cast(IHyperlinkSegment)newSegment ? cast(IHyperlinkSegment) newSegment + : null; + if (valid) + enterLink(newLink, DWT.NULL); + IHyperlinkSegment oldLink = null !is cast(IHyperlinkSegment)current ? cast(IHyperlinkSegment) current + : null; + if (oldLink !is null || newLink !is null) + paintFocusTransfer(oldLink, newLink); + if (newLink !is null) + ensureVisible(newLink); + if (newLink !is null) + getAccessible().setFocus(model.getSelectedSegmentIndex()); + return !valid; + } + + private bool setControlFocus(bool next, IFocusSelectable selectable) { + controlFocusTransfer = true; + bool result = selectable.setFocus(resourceTable, next); + controlFocusTransfer = false; + return result; + } + + private void handleFocusChange() { + if (DEBUG_FOCUS) { + Stdout.formatln("Handle focus change: hasFocus={}, mouseFocus={}", hasFocus, //$NON-NLS-1$ + mouseFocus); //$NON-NLS-1$ + } + if (hasFocus) { + bool advance = true; + if (!mouseFocus) { + // if (model.restoreSavedLink() is false) + bool valid = false; + IFocusSelectable selectable = null; + while (!valid) { + if (!model.traverseFocusSelectableObjects(advance)) + break; + selectable = model.getSelectedSegment(); + if (selectable is null) + break; + valid = setControlFocus(advance, selectable); + } + if (selectable is null) + setFocusToNextSibling(this, true); + else + ensureVisible(selectable); + if ( auto hls = cast(IHyperlinkSegment)selectable ) { + enterLink(hls, DWT.NULL); + paintFocusTransfer(null, hls); + } + } + } else { + paintFocusTransfer(getSelectedLink(), null); + model.selectLink(null); + } + } + + private void enterLink(IHyperlinkSegment link, int stateMask) { + if (link is null || listeners is null) + return; + int size = listeners.size(); + HyperlinkEvent he = new HyperlinkEvent(this, stringcast(link.getHref()), link + .getText(), stateMask); + Object [] listenerList = listeners.getListeners(); + for (int i = 0; i < size; i++) { + IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i]; + listener.linkEntered(he); + } + } + + private void exitLink(IHyperlinkSegment link, int stateMask) { + if (link is null || listeners is null) + return; + int size = listeners.size(); + HyperlinkEvent he = new HyperlinkEvent(this, stringcast(link.getHref()), link + .getText(), stateMask); + Object [] listenerList = listeners.getListeners(); + for (int i = 0; i < size; i++) { + IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i]; + listener.linkExited(he); + } + } + + private void paintLinkHover(IHyperlinkSegment link, bool hover) { + GC gc = new GC(this); + HyperlinkSettings settings = getHyperlinkSettings(); + Color newFg = hover ? settings.getActiveForeground() : settings + .getForeground(); + if (newFg !is null) + gc.setForeground(newFg); + gc.setBackground(getBackground()); + gc.setFont(getFont()); + bool selected = (link is getSelectedLink()); + (cast(ParagraphSegment) link).paint(gc, hover, resourceTable, selected, + selData, null); + gc.dispose(); + } + + private void activateSelectedLink() { + IHyperlinkSegment link = getSelectedLink(); + if (link !is null) + activateLink(link, DWT.NULL); + } + + private void activateLink(IHyperlinkSegment link, int stateMask) { + setCursor(model.getHyperlinkSettings().getBusyCursor()); + if (listeners !is null) { + int size = listeners.size(); + HyperlinkEvent e = new HyperlinkEvent(this, stringcast(link.getHref()), link + .getText(), stateMask); + Object [] listenerList = listeners.getListeners(); + for (int i = 0; i < size; i++) { + IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i]; + listener.linkActivated(e); + } + } + if (!isDisposed() && model.linkExists(link)) { + setCursor(model.getHyperlinkSettings().getHyperlinkCursor()); + } + } + + private void ensureBoldFontPresent(Font regularFont) { + Font boldFont = cast(Font) resourceTable.get(FormTextModel.BOLD_FONT_ID); + if (boldFont !is null) + return; + boldFont = FormFonts.getInstance().getBoldFont(getDisplay(), regularFont); + resourceTable.add(FormTextModel.BOLD_FONT_ID, boldFont); + } + + private void paint(PaintEvent e) { + GC gc = e.gc; + gc.setFont(getFont()); + ensureBoldFontPresent(getFont()); + gc.setForeground(getForeground()); + gc.setBackground(getBackground()); + repaint(gc, e.x, e.y, e.width, e.height); + } + + private void repaint(GC gc, int x, int y, int width, int height) { + Image textBuffer = new Image(getDisplay(), width, height); + Color bg = getBackground(); + Color fg = getForeground(); + if (!getEnabled()) { + bg = getDisplay().getSystemColor(DWT.COLOR_WIDGET_BACKGROUND); + fg = getDisplay().getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW); + } + GC textGC = new GC(textBuffer, gc.getStyle()); + textGC.setForeground(fg); + textGC.setBackground(bg); + textGC.setFont(getFont()); + textGC.fillRectangle(0, 0, width, height); + Rectangle repaintRegion = new Rectangle(x, y, width, height); + + Paragraph[] paragraphs = model.getParagraphs(); + IHyperlinkSegment selectedLink = getSelectedLink(); + if (getDisplay().getFocusControl() !is this) + selectedLink = null; + for (int i = 0; i < paragraphs.length; i++) { + Paragraph p = paragraphs[i]; + p + .paint(textGC, repaintRegion, resourceTable, selectedLink, + selData); + } + textGC.dispose(); + gc.drawImage(textBuffer, x, y); + textBuffer.dispose(); + } + + private int getParagraphSpacing(int lineHeight) { + return lineHeight / 2; + } + + private void paintFocusTransfer(IHyperlinkSegment oldLink, + IHyperlinkSegment newLink) { + GC gc = new GC(this); + Color bg = getBackground(); + Color fg = getForeground(); + gc.setFont(getFont()); + if (oldLink !is null) { + gc.setBackground(bg); + gc.setForeground(fg); + oldLink.paintFocus(gc, bg, fg, false, null); + } + if (newLink !is null) { + // ensureVisible(newLink); + gc.setBackground(bg); + gc.setForeground(fg); + newLink.paintFocus(gc, bg, fg, true, null); + } + gc.dispose(); + } + + private void ensureVisible(IFocusSelectable segment) { + if (mouseFocus) { + mouseFocus = false; + return; + } + if (segment is null) + return; + Rectangle bounds = segment.getBounds(); + ScrolledComposite scomp = FormUtil.getScrolledComposite(this); + if (scomp is null) + return; + Point origin = FormUtil.getControlLocation(scomp, this); + origin.x += bounds.x; + origin.y += bounds.y; + FormUtil.ensureVisible(scomp, origin, new Point(bounds.width, + bounds.height)); + } + + /** + * Overrides the method by fully trusting the layout manager (computed width + * or height may be larger than the provider width or height hints). Callers + * should be prepared that the computed width is larger than the provided + * wHint. + * + * @see dwt.widgets.Composite#computeSize(int, int, bool) + */ + public Point computeSize(int wHint, int hHint, bool changed) { + checkWidget(); + Point size; + FormTextLayout layout = cast(FormTextLayout) getLayout(); + if (wHint is DWT.DEFAULT || hHint is DWT.DEFAULT) { + size = layout.computeSize(this, wHint, hHint, changed); + } else { + size = new Point(wHint, hHint); + } + Rectangle trim = computeTrim(0, 0, size.x, size.y); + if (DEBUG_TEXTSIZE) + Stdout.formatln("FormText Computed size: {}",trim); //$NON-NLS-1$ + return new Point(trim.width, trim.height); + } + + private void disposeResourceTable(bool disposeBoldFont) { + if (disposeBoldFont) { + Font boldFont = cast(Font) resourceTable + .get(FormTextModel.BOLD_FONT_ID); + if (boldFont !is null) { + FormFonts.getInstance().markFinished(boldFont); + resourceTable.removeKey(FormTextModel.BOLD_FONT_ID); + } + } + ArraySeq!(String) imagesToRemove = new ArraySeq!(String); + foreach( key, obj; resourceTable ){ + if (key.startsWith(ImageSegment.SEL_IMAGE_PREFIX)) { + if (auto image = cast(Image)obj ) { + if (!image.isDisposed()) { + image.dispose(); + imagesToRemove.append(key); + } + } + } + } + for (int i = 0; i < imagesToRemove.size(); i++) { + resourceTable.removeKey(imagesToRemove.get(i)); + } + } + + /* + * (non-Javadoc) + * + * @see dwt.widgets.Control#setEnabled(bool) + */ + public void setEnabled(bool enabled) { + super.setEnabled(enabled); + redraw(); + } + + /* (non-Javadoc) + * @see dwt.widgets.Control#setFocus() + */ + public bool setFocus() { + FormUtil.setFocusScrollingEnabled(this, false); + bool result = super.setFocus(); + FormUtil.setFocusScrollingEnabled(this, true); + return result; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/FormToolkit.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,916 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * Michael Williamson (eclipse-bugs@magnaworks.com) - patch (see Bugzilla #92545) + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.ui.forms.widgets.FormToolkit; + +import dwtx.ui.forms.widgets.Hyperlink; +import dwtx.ui.forms.widgets.ImageHyperlink; +import dwtx.ui.forms.widgets.ExpandableComposite; +import dwtx.ui.forms.widgets.Form; +import dwtx.ui.forms.widgets.ScrolledPageBook; +import dwtx.ui.forms.widgets.ScrolledForm; +import dwtx.ui.forms.widgets.FormText; +import dwtx.ui.forms.widgets.Section; +import dwtx.ui.forms.widgets.LayoutComposite; + +import dwt.DWT; +import dwt.custom.CCombo; +import dwt.custom.ScrolledComposite; +import dwt.events.FocusAdapter; +import dwt.events.FocusEvent; +import dwt.events.KeyAdapter; +import dwt.events.KeyEvent; +import dwt.events.MouseAdapter; +import dwt.events.MouseEvent; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.RGB; +import dwt.graphics.Rectangle; +import dwt.widgets.Button; +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.Table; +import dwt.widgets.Text; +import dwt.widgets.Tree; +import dwt.widgets.Widget; +import dwtx.jface.resource.JFaceResources; +import dwtx.jface.window.Window; +import dwtx.ui.forms.FormColors; +import dwtx.ui.forms.HyperlinkGroup; +import dwtx.ui.forms.IFormColors; +import dwtx.ui.internal.forms.widgets.FormFonts; +import dwtx.ui.internal.forms.widgets.FormUtil; + +import dwt.dwthelper.utils; + +/** + * The toolkit is responsible for creating DWT controls adapted to work in + * Eclipse forms. In addition to changing their presentation properties (fonts, + * colors etc.), various listeners are attached to make them behave correctly in + * the form context. + * <p> + * In addition to being the control factory, the toolkit is also responsible for + * painting flat borders for select controls, managing hyperlink groups and + * control colors. + * <p> + * The toolkit creates some of the most common controls used to populate Eclipse + * forms. Controls that must be created using their constructors, + * <code>adapt()</code> method is available to change its properties in the + * same way as with the supported toolkit controls. + * <p> + * Typically, one toolkit object is created per workbench part (for example, an + * editor or a form wizard). The toolkit is disposed when the part is disposed. + * To conserve resources, it is possible to create one color object for the + * entire plug-in and share it between several toolkits. The plug-in is + * responsible for disposing the colors (disposing the toolkit that uses shared + * color object will not dispose the colors). + * <p> + * FormToolkit is normally instantiated, but can also be subclassed if some of + * the methods needs to be modified. In those cases, <code>super</code> must + * be called to preserve normal behaviour. + * + * @since 3.0 + */ +public class FormToolkit { + public static const String KEY_DRAW_BORDER = "FormWidgetFactory.drawBorder"; //$NON-NLS-1$ + + public static const String TREE_BORDER = "treeBorder"; //$NON-NLS-1$ + + public static const String TEXT_BORDER = "textBorder"; //$NON-NLS-1$ + + private int borderStyle = DWT.NULL; + + private FormColors colors; + + private int orientation; + + // private KeyListener deleteListener; + private BorderPainter borderPainter; + + private BoldFontHolder boldFontHolder; + + private HyperlinkGroup hyperlinkGroup; + + /* default */ + VisibilityHandler visibilityHandler; + + /* default */ + KeyboardHandler keyboardHandler; + + private class BorderPainter : PaintListener { + public void paintControl(PaintEvent event) { + Composite composite = cast(Composite) event.widget; + Control[] children = composite.getChildren(); + for (int i = 0; i < children.length; i++) { + Control c = children[i]; + bool inactiveBorder = false; + bool textBorder = false; + if (!c.isVisible()) + continue; + /* + * if (c.getEnabled() is false && !(c instanceof CCombo)) + * continue; + */ + if (null !is cast(Hyperlink)c ) + continue; + Object flag = c.getData(KEY_DRAW_BORDER); + if (flag !is null) { + if (flag.opEquals(Boolean.FALSE)) + continue; + if (flag.opEquals(stringcast(TREE_BORDER))) + inactiveBorder = true; + else if (flag.opEquals(stringcast(TEXT_BORDER))) + textBorder = true; + } + if (getBorderStyle() is DWT.BORDER) { + if (!inactiveBorder && !textBorder) { + continue; + } + if (null !is cast(Text)c || null !is cast(Table)c + || null !is cast(Tree)c ) + continue; + } + if (!inactiveBorder + && (null !is cast(Text)c || null !is cast(CCombo)c || textBorder)) { + Rectangle b = c.getBounds(); + GC gc = event.gc; + gc.setForeground(c.getBackground()); + gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1, + b.height + 1); + // gc.setForeground(getBorderStyle() is DWT.BORDER ? colors + // .getBorderColor() : colors.getForeground()); + gc.setForeground(colors.getBorderColor()); + if (null !is cast(CCombo)c ) + gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1, + b.height + 1); + else + gc.drawRectangle(b.x - 1, b.y - 2, b.width + 1, + b.height + 3); + } else if (inactiveBorder || null !is cast(Table)c + || null !is cast(Tree)c ) { + Rectangle b = c.getBounds(); + GC gc = event.gc; + gc.setForeground(colors.getBorderColor()); + gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1, + b.height + 1); + } + } + } + } + + private static class VisibilityHandler : FocusAdapter { + public void focusGained(FocusEvent e) { + Widget w = e.widget; + if (null !is cast(Control)w ) { + FormUtil.ensureVisible(cast(Control) w); + } + } + } + + private static class KeyboardHandler : KeyAdapter { + public void keyPressed(KeyEvent e) { + Widget w = e.widget; + if (null !is cast(Control)w ) { + if (e.doit) + FormUtil.processKey(e.keyCode, cast(Control) w); + } + } + } + + private class BoldFontHolder { + private Font normalFont; + + private Font boldFont; + + public this() { + } + + public Font getBoldFont(Font font) { + createBoldFont(font); + return boldFont; + } + + private void createBoldFont(Font font) { + if (normalFont is null || !normalFont.opEquals(font)) { + normalFont = font; + dispose(); + } + if (boldFont is null) { + boldFont = FormFonts.getInstance().getBoldFont(colors.getDisplay(), + normalFont); + } + } + + public void dispose() { + if (boldFont !is null) { + FormFonts.getInstance().markFinished(boldFont); + boldFont = null; + } + } + } + + /** + * Creates a toolkit that is self-sufficient (will manage its own colors). + * + */ + public this(Display display) { + this(new FormColors(display)); + } + + /** + * Creates a toolkit that will use the provided (shared) colors. The toolkit + * will dispose the colors if and only if they are <b>not</b> marked as + * shared via the <code>markShared()</code> method. + * + * @param colors + * the shared colors + */ + public this(FormColors colors) { + orientation = Window.getDefaultOrientation(); + this.colors = colors; + initialize(); + } + + /** + * Creates a button as a part of the form. + * + * @param parent + * the button parent + * @param text + * an optional text for the button (can be <code>null</code>) + * @param style + * the button style (for example, <code>DWT.PUSH</code>) + * @return the button widget + */ + public Button createButton(Composite parent, String text, int style) { + Button button = new Button(parent, style | DWT.FLAT | orientation); + if (text !is null) + button.setText(text); + adapt(button, true, true); + return button; + } + + /** + * Creates the composite as a part of the form. + * + * @param parent + * the composite parent + * @return the composite widget + */ + public Composite createComposite(Composite parent) { + return createComposite(parent, DWT.NULL); + } + + /** + * Creates the composite as part of the form using the provided style. + * + * @param parent + * the composite parent + * @param style + * the composite style + * @return the composite widget + */ + public Composite createComposite(Composite parent, int style) { + Composite composite = new LayoutComposite(parent, style | orientation); + adapt(composite); + return composite; + } + + /** + * Creats the composite that can server as a separator between various parts + * of a form. Separator height should be controlled by setting the height + * hint on the layout data for the composite. + * + * @param parent + * the separator parent + * @return the separator widget + */ + public Composite createCompositeSeparator(Composite parent) { + final Composite composite = new Composite(parent, orientation); + composite.addListener(DWT.Paint, dgListener( (Event e, Composite composite) { + if (composite.isDisposed()) + return; + Rectangle bounds = composite.getBounds(); + GC gc = e.gc; + gc.setForeground(colors.getColor(IFormColors.SEPARATOR)); + if (colors.getBackground() !is null) + gc.setBackground(colors.getBackground()); + gc.fillGradientRectangle(0, 0, bounds.width, bounds.height, + false); + }, composite)); + if (null !is cast(Section)parent ) + (cast(Section) parent).setSeparatorControl(composite); + return composite; + } + + /** + * Creates a label as a part of the form. + * + * @param parent + * the label parent + * @param text + * the label text + * @return the label widget + */ + public Label createLabel(Composite parent, String text) { + return createLabel(parent, text, DWT.NONE); + } + + /** + * Creates a label as a part of the form. + * + * @param parent + * the label parent + * @param text + * the label text + * @param style + * the label style + * @return the label widget + */ + public Label createLabel(Composite parent, String text, int style) { + Label label = new Label(parent, style | orientation); + if (text !is null) + label.setText(text); + adapt(label, false, false); + return label; + } + + /** + * Creates a hyperlink as a part of the form. The hyperlink will be added to + * the hyperlink group that belongs to this toolkit. + * + * @param parent + * the hyperlink parent + * @param text + * the text of the hyperlink + * @param style + * the hyperlink style + * @return the hyperlink widget + */ + public Hyperlink createHyperlink(Composite parent, String text, int style) { + Hyperlink hyperlink = new Hyperlink(parent, style | orientation); + if (text !is null) + hyperlink.setText(text); + hyperlink.addFocusListener(visibilityHandler); + hyperlink.addKeyListener(keyboardHandler); + hyperlinkGroup.add(hyperlink); + return hyperlink; + } + + /** + * Creates an image hyperlink as a part of the form. The hyperlink will be + * added to the hyperlink group that belongs to this toolkit. + * + * @param parent + * the hyperlink parent + * @param style + * the hyperlink style + * @return the image hyperlink widget + */ + public ImageHyperlink createImageHyperlink(Composite parent, int style) { + ImageHyperlink hyperlink = new ImageHyperlink(parent, style + | orientation); + hyperlink.addFocusListener(visibilityHandler); + hyperlink.addKeyListener(keyboardHandler); + hyperlinkGroup.add(hyperlink); + return hyperlink; + } + + /** + * Creates a rich text as a part of the form. + * + * @param parent + * the rich text parent + * @param trackFocus + * if <code>true</code>, the toolkit will monitor focus + * transfers to ensure that the hyperlink in focus is visible in + * the form. + * @return the rich text widget + */ + public FormText createFormText(Composite parent, bool trackFocus) { + FormText engine = new FormText(parent, DWT.WRAP | orientation); + engine.marginWidth = 1; + engine.marginHeight = 0; + engine.setHyperlinkSettings(getHyperlinkGroup()); + adapt(engine, trackFocus, true); + engine.setMenu(parent.getMenu()); + return engine; + } + + /** + * Adapts a control to be used in a form that is associated with this + * toolkit. This involves adjusting colors and optionally adding handlers to + * ensure focus tracking and keyboard management. + * + * @param control + * a control to adapt + * @param trackFocus + * if <code>true</code>, form will be scrolled horizontally + * and/or vertically if needed to ensure that the control is + * visible when it gains focus. Set it to <code>false</code> if + * the control is not capable of gaining focus. + * @param trackKeyboard + * if <code>true</code>, the control that is capable of + * gaining focus will be tracked for certain keys that are + * important to the underlying form (for example, PageUp, + * PageDown, ScrollUp, ScrollDown etc.). Set it to + * <code>false</code> if the control is not capable of gaining + * focus or these particular key event are already used by the + * control. + */ + public void adapt(Control control, bool trackFocus, bool trackKeyboard) { + control.setBackground(colors.getBackground()); + control.setForeground(colors.getForeground()); + if (null !is cast(ExpandableComposite)control ) { + ExpandableComposite ec = cast(ExpandableComposite) control; + if (ec.toggle_package !is null) { + if (trackFocus) + ec.toggle_package.addFocusListener(visibilityHandler); + if (trackKeyboard) + ec.toggle_package.addKeyListener(keyboardHandler); + } + if (ec.textLabel_package !is null) { + if (trackFocus) + ec.textLabel_package.addFocusListener(visibilityHandler); + if (trackKeyboard) + ec.textLabel_package.addKeyListener(keyboardHandler); + } + return; + } + if (trackFocus) + control.addFocusListener(visibilityHandler); + if (trackKeyboard) + control.addKeyListener(keyboardHandler); + } + + /** + * Adapts a composite to be used in a form associated with this toolkit. + * + * @param composite + * the composite to adapt + */ + public void adapt(Composite composite) { + composite.setBackground(colors.getBackground()); + composite.addMouseListener(new class MouseAdapter { + public void mouseDown(MouseEvent e) { + (cast(Control) e.widget).setFocus(); + } + }); + composite.setMenu(composite.getParent().getMenu()); + } + + /** + * A helper method that ensures the provided control is visible when + * ScrolledComposite is somewhere in the parent chain. If scroll bars are + * visible and the control is clipped, the client of the scrolled composite + * will be scrolled to reveal the control. + * + * @param c + * the control to reveal + */ + public static void ensureVisible(Control c) { + FormUtil.ensureVisible(c); + } + + /** + * Creates a section as a part of the form. + * + * @param parent + * the section parent + * @param sectionStyle + * the section style + * @return the section widget + */ + public Section createSection(Composite parent, int sectionStyle) { + Section section = new Section(parent, orientation, sectionStyle); + section.setMenu(parent.getMenu()); + adapt(section, true, true); + if (section.toggle_package !is null) { + section.toggle_package.setHoverDecorationColor(colors + .getColor(IFormColors.TB_TOGGLE_HOVER)); + section.toggle_package.setDecorationColor(colors + .getColor(IFormColors.TB_TOGGLE)); + } + section.setFont(boldFontHolder.getBoldFont(parent.getFont())); + if ((sectionStyle & Section.TITLE_BAR) !is 0 + || (sectionStyle & Section.SHORT_TITLE_BAR) !is 0) { + colors.initializeSectionToolBarColors(); + section.setTitleBarBackground(colors.getColor(IFormColors.TB_BG)); + section.setTitleBarBorderColor(colors + .getColor(IFormColors.TB_BORDER)); + section.setTitleBarForeground(colors + .getColor(IFormColors.TB_TOGGLE)); + } + return section; + } + + /** + * Creates an expandable composite as a part of the form. + * + * @param parent + * the expandable composite parent + * @param expansionStyle + * the expandable composite style + * @return the expandable composite widget + */ + public ExpandableComposite createExpandableComposite(Composite parent, + int expansionStyle) { + ExpandableComposite ec = new ExpandableComposite(parent, orientation, + expansionStyle); + ec.setMenu(parent.getMenu()); + adapt(ec, true, true); + ec.setFont(boldFontHolder.getBoldFont(ec.getFont())); + return ec; + } + + /** + * Creates a separator label as a part of the form. + * + * @param parent + * the separator parent + * @param style + * the separator style + * @return the separator label + */ + public Label createSeparator(Composite parent, int style) { + Label label = new Label(parent, DWT.SEPARATOR | style | orientation); + label.setBackground(colors.getBackground()); + label.setForeground(colors.getBorderColor()); + return label; + } + + /** + * Creates a table as a part of the form. + * + * @param parent + * the table parent + * @param style + * the table style + * @return the table widget + */ + public Table createTable(Composite parent, int style) { + Table table = new Table(parent, style | borderStyle | orientation); + adapt(table, false, false); + // hookDeleteListener(table); + return table; + } + + /** + * Creates a text as a part of the form. + * + * @param parent + * the text parent + * @param value + * the text initial value + * @return the text widget + */ + public Text createText(Composite parent, String value) { + return createText(parent, value, DWT.SINGLE); + } + + /** + * Creates a text as a part of the form. + * + * @param parent + * the text parent + * @param value + * the text initial value + * @param style + * the text style + * @return the text widget + */ + public Text createText(Composite parent, String value, int style) { + Text text = new Text(parent, borderStyle | style | orientation); + if (value !is null) + text.setText(value); + text.setForeground(colors.getForeground()); + text.setBackground(colors.getBackground()); + text.addFocusListener(visibilityHandler); + return text; + } + + /** + * Creates a tree widget as a part of the form. + * + * @param parent + * the tree parent + * @param style + * the tree style + * @return the tree widget + */ + public Tree createTree(Composite parent, int style) { + Tree tree = new Tree(parent, borderStyle | style | orientation); + adapt(tree, false, false); + // hookDeleteListener(tree); + return tree; + } + + /** + * Creates a scrolled form widget in the provided parent. If you do not + * require scrolling because there is already a scrolled composite up the + * parent chain, use 'createForm' instead. + * + * @param parent + * the scrolled form parent + * @return the form that can scroll itself + * @see #createForm + */ + public ScrolledForm createScrolledForm(Composite parent) { + ScrolledForm form = new ScrolledForm(parent, DWT.V_SCROLL + | DWT.H_SCROLL | orientation); + form.setExpandHorizontal(true); + form.setExpandVertical(true); + form.setBackground(colors.getBackground()); + form.setForeground(colors.getColor(IFormColors.TITLE)); + form.setFont(JFaceResources.getHeaderFont()); + return form; + } + + /** + * Creates a form widget in the provided parent. Note that this widget does + * not scroll its content, so make sure there is a scrolled composite up the + * parent chain. If you require scrolling, use 'createScrolledForm' instead. + * + * @param parent + * the form parent + * @return the form that does not scroll + * @see #createScrolledForm + */ + public Form createForm(Composite parent) { + Form formContent = new Form(parent, orientation); + formContent.setBackground(colors.getBackground()); + formContent.setForeground(colors.getColor(IFormColors.TITLE)); + formContent.setFont(JFaceResources.getHeaderFont()); + return formContent; + } + + /** + * Takes advantage of the gradients and other capabilities to decorate the + * form heading using colors computed based on the current skin and + * operating system. + * + * @since 3.3 + * @param form + * the form to decorate + */ + + public void decorateFormHeading(Form form) { + Color top = colors.getColor(IFormColors.H_GRADIENT_END); + Color bot = colors.getColor(IFormColors.H_GRADIENT_START); + form.setTextBackground([ top, bot ], [ 100 ], + true); + form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE1, colors + .getColor(IFormColors.H_BOTTOM_KEYLINE1)); + form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE2, colors + .getColor(IFormColors.H_BOTTOM_KEYLINE2)); + form.setHeadColor(IFormColors.H_HOVER_LIGHT, colors + .getColor(IFormColors.H_HOVER_LIGHT)); + form.setHeadColor(IFormColors.H_HOVER_FULL, colors + .getColor(IFormColors.H_HOVER_FULL)); + form.setHeadColor(IFormColors.TB_TOGGLE, colors + .getColor(IFormColors.TB_TOGGLE)); + form.setHeadColor(IFormColors.TB_TOGGLE_HOVER, colors + .getColor(IFormColors.TB_TOGGLE_HOVER)); + form.setSeparatorVisible(true); + } + + /** + * Creates a scrolled page book widget as a part of the form. + * + * @param parent + * the page book parent + * @param style + * the text style + * @return the scrolled page book widget + */ + public ScrolledPageBook createPageBook(Composite parent, int style) { + ScrolledPageBook book = new ScrolledPageBook(parent, style + | orientation); + adapt(book, true, true); + book.setMenu(parent.getMenu()); + return book; + } + + /** + * Disposes the toolkit. + */ + public void dispose() { + if (colors.isShared() is false) { + colors.dispose(); + colors = null; + } + boldFontHolder.dispose(); + } + + /** + * Returns the hyperlink group that manages hyperlinks for this toolkit. + * + * @return the hyperlink group + */ + public HyperlinkGroup getHyperlinkGroup() { + return hyperlinkGroup; + } + + /** + * Sets the background color for the entire toolkit. The method delegates + * the call to the FormColors object and also updates the hyperlink group so + * that hyperlinks and other objects are in sync. + * + * @param bg + * the new background color + */ + public void setBackground(Color bg) { + hyperlinkGroup.setBackground(bg); + colors.setBackground(bg); + } + + /** + * Refreshes the hyperlink colors by loading from JFace settings. + */ + public void refreshHyperlinkColors() { + hyperlinkGroup.initializeDefaultForegrounds(colors.getDisplay()); + } + + /** + * Paints flat borders for widgets created by this toolkit within the + * provided parent. Borders will not be painted if the global border style + * is DWT.BORDER (i.e. if native borders are used). Call this method during + * creation of a form composite to get the borders of its children painted. + * Care should be taken when selection layout margins. At least one pixel + * pargin width and height must be chosen to allow the toolkit to paint the + * border on the parent around the widgets. + * <p> + * Borders are painted for some controls that are selected by the toolkit by + * default. If a control needs a border but is not on its list, it is + * possible to force border in the following way: + * + * <pre> + * + * + * + * widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER); + * + * or + * + * widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER); + * + * + * + * </pre> + * + * @param parent + * the parent that owns the children for which the border needs + * to be painted. + */ + public void paintBordersFor(Composite parent) { + // if (borderStyle is DWT.BORDER) + // return; + if (borderPainter is null) + borderPainter = new BorderPainter(); + parent.addPaintListener(borderPainter); + } + + /** + * Returns the colors used by this toolkit. + * + * @return the color object + */ + public FormColors getColors() { + return colors; + } + + /** + * Returns the border style used for various widgets created by this + * toolkit. The intent of the toolkit is to create controls with styles that + * yield a 'flat' appearance. On systems where the native borders are + * already flat, we set the style to DWT.BORDER and don't paint the borders + * ourselves. Otherwise, the style is set to DWT.NULL, and borders are + * painted by the toolkit. + * + * @return the global border style + */ + public int getBorderStyle() { + return borderStyle; + } + + /** + * Returns the margin required around the children whose border is being + * painted by the toolkit using {@link #paintBordersFor(Composite)}. Since + * the border is painted around the controls on the parent, a number of + * pixels needs to be reserved for this border. For windowing systems where + * the native border is used, this margin is 0. + * + * @return the margin in the parent when children have their border painted + * @since 3.3 + */ + public int getBorderMargin() { + return getBorderStyle() is DWT.BORDER ? 0 : 2; + } + + /** + * Sets the border style to be used when creating widgets. The toolkit + * chooses the correct style based on the platform but this value can be + * changed using this method. + * + * @param style + * <code>DWT.BORDER</code> or <code>DWT.NULL</code> + * @see #getBorderStyle + */ + public void setBorderStyle(int style) { + this.borderStyle = style; + } + + /** + * A utility method that ensures that the control is visible in the scrolled + * composite. The prerequisite for this method is that the control has a + * class that extends ScrolledComposite somewhere in the parent chain. If + * the control is partially or fully clipped, the composite is scrolled to + * set by setting the origin to the control origin. + * + * @param c + * the control to make visible + * @param verticalOnly + * if <code>true</code>, the scrolled composite will be + * scrolled only vertically if needed. Otherwise, the scrolled + * composite origin will be set to the control origin. + * @since 3.1 + */ + public static void setControlVisible(Control c, bool verticalOnly) { + ScrolledComposite scomp = FormUtil.getScrolledComposite(c); + if (scomp is null) + return; + Point location = FormUtil.getControlLocation(scomp, c); + scomp.setOrigin(location); + } + + private void initialize() { + initializeBorderStyle(); + hyperlinkGroup = new HyperlinkGroup(colors.getDisplay()); + hyperlinkGroup.setBackground(colors.getBackground()); + visibilityHandler = new VisibilityHandler(); + keyboardHandler = new KeyboardHandler(); + boldFontHolder = new BoldFontHolder(); + } + + private void initializeBorderStyle() { + String osname = System.getProperty("os.name"); //$NON-NLS-1$ + String osversion = System.getProperty("os.version"); //$NON-NLS-1$ + if (osname.startsWith("Windows") && "5.1".compareTo(osversion) <= 0) { //$NON-NLS-1$ //$NON-NLS-2$ + // Skinned widgets used on newer Windows (e.g. XP (5.1), Vista + // (6.0)) + // Check for Windows Classic. If not used, set the style to BORDER + RGB rgb = colors.getSystemColor(DWT.COLOR_WIDGET_BACKGROUND); + if (rgb.red !is 212 || rgb.green !is 208 || rgb.blue !is 200) + borderStyle = DWT.BORDER; + } else if (osname.startsWith("Mac")) //$NON-NLS-1$ + borderStyle = DWT.BORDER; + } + + /** + * Returns the orientation that all the widgets created by this toolkit will + * inherit, if set. Can be <code>DWT.NULL</code>, + * <code>DWT.LEFT_TO_RIGHT</code> and <code>DWT.RIGHT_TO_LEFT</code>. + * + * @return orientation style for this toolkit, or <code>DWT.NULL</code> if + * not set. The default orientation is inherited from the Window + * default orientation. + * @see dwtx.jface.window.Window#getDefaultOrientation() + * @since 3.1 + */ + + public int getOrientation() { + return orientation; + } + + /** + * Sets the orientation that all the widgets created by this toolkit will + * inherit. Can be <code>DWT.NULL</code>, <code>DWT.LEFT_TO_RIGHT</code> + * and <code>DWT.RIGHT_TO_LEFT</code>. + * + * @param orientation + * style for this toolkit. + * @since 3.1 + */ + + public void setOrientation(int orientation) { + this.orientation = orientation; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/Hyperlink.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,291 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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.ui.forms.widgets.Hyperlink; + +import dwtx.ui.forms.widgets.AbstractHyperlink; + +import dwt.DWT; +import dwt.accessibility.ACC; +import dwt.accessibility.Accessible; +import dwt.accessibility.AccessibleAdapter; +import dwt.accessibility.AccessibleControlAdapter; +import dwt.accessibility.AccessibleControlEvent; +import dwt.accessibility.AccessibleEvent; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; + +import dwtx.ui.internal.forms.widgets.FormUtil; + +import dwt.dwthelper.utils; + +/** + * Hyperlink is a concrete implementation of the abstract base class that draws + * text in the client area. Text can be wrapped and underlined. Hyperlink is + * typically added to the hyperlink group so that certain properties are managed + * for all the hyperlinks that belong to it. + * <p> + * Hyperlink can be extended. + * + * @see dwtx.ui.forms.HyperlinkGroup + * @since 3.0 + */ +public class Hyperlink : AbstractHyperlink { + private String text; + private static const String ELLIPSIS = "..."; //$NON-NLS-1$ + private bool underlined; + // The tooltip is used for two purposes - the application can set + // a tooltip or the tooltip can be used to display the full text when the + // the text has been truncated due to the label being too short. + // The appToolTip stores the tooltip set by the application. Control.tooltiptext + // contains whatever tooltip is currently being displayed. + private String appToolTipText; + + /** + * Creates a new hyperlink control in the provided parent. + * + * @param parent + * the control parent + * @param style + * the widget style + */ + public this(Composite parent, int style) { + super(parent, style); + initAccessible(); + } + + protected void initAccessible() { + Accessible accessible = getAccessible(); + accessible.addAccessibleListener(new class AccessibleAdapter { + public void getName(AccessibleEvent e) { + e.result = getText(); + if (e.result is null) + getHelp(e); + } + + public void getHelp(AccessibleEvent e) { + e.result = getToolTipText(); + } + }); + accessible.addAccessibleControlListener(new class AccessibleControlAdapter { + public void getChildAtPoint(AccessibleControlEvent e) { + Point pt = toControl(new Point(e.x, e.y)); + e.childID = (getBounds().contains(pt)) ? ACC.CHILDID_SELF + : ACC.CHILDID_NONE; + } + + public void getLocation(AccessibleControlEvent e) { + Rectangle location = getBounds(); + Point pt = toDisplay(new Point(location.x, location.y)); + e.x = pt.x; + e.y = pt.y; + e.width = location.width; + e.height = location.height; + } + + public void getChildCount(AccessibleControlEvent e) { + e.detail = 0; + } + + public void getRole(AccessibleControlEvent e) { + e.detail = ACC.ROLE_LINK; + } + + public void getDefaultAction (AccessibleControlEvent e) { + e.result = DWT.getMessage ("SWT_Press"); //$NON-NLS-1$ + } + + public void getState(AccessibleControlEvent e) { + int state = ACC.STATE_NORMAL; + if (this.outer.getSelection()) + state = ACC.STATE_SELECTED | ACC.STATE_FOCUSED; + e.detail = state; + } + }); + } + + /** + * Sets the underlined state. It is not necessary to call this method when + * in a hyperlink group. + * + * @param underlined + * if <samp>true </samp>, a line will be drawn below the text for + * each wrapped line. + */ + public void setUnderlined(bool underlined) { + this.underlined = underlined; + redraw(); + } + + /** + * Returns the underline state of the hyperlink. + * + * @return <samp>true </samp> if text is underlined, <samp>false </samp> + * otherwise. + */ + public bool isUnderlined() { + return underlined; + } + + /** + * Overrides the parent by incorporating the margin. + */ + public Point computeSize(int wHint, int hHint, bool changed) { + checkWidget(); + int innerWidth = wHint; + if (innerWidth !is DWT.DEFAULT) + innerWidth -= marginWidth * 2; + Point textSize = computeTextSize(innerWidth, hHint); + int textWidth = textSize.x + 2 * marginWidth; + int textHeight = textSize.y + 2 * marginHeight; + return new Point(textWidth, textHeight); + } + + /** + * Returns the current hyperlink text. + * + * @return hyperlink text + */ + public String getText() { + return text; + } + + /* (non-Javadoc) + * @see dwt.widgets.Control#getToolTipText() + */ + public String getToolTipText () { + checkWidget(); + return appToolTipText; + } + + /* (non-Javadoc) + * @see dwt.widgets.Control#setToolTipText(java.lang.String) + */ + public void setToolTipText (String string) { + super.setToolTipText (string); + appToolTipText = super.getToolTipText(); + } + + /** + * Sets the text of this hyperlink. + * + * @param text + * the hyperlink text + */ + public void setText(String text) { + if (text !is null) + this.text = text; + else + this.text = ""; //$NON-NLS-1$ + redraw(); + } + + /** + * Paints the hyperlink text. + * + * @param gc + * graphic context + */ + protected void paintHyperlink(GC gc) { + Rectangle carea = getClientArea(); + Rectangle bounds = new Rectangle(marginWidth, marginHeight, carea.width + - marginWidth - marginWidth, carea.height - marginHeight + - marginHeight); + paintText(gc, bounds); + } + + /** + * Paints the hyperlink text in provided bounding rectangle. + * + * @param gc + * graphic context + * @param bounds + * the bounding rectangle in which to paint the text + */ + protected void paintText(GC gc, Rectangle bounds) { + gc.setFont(getFont()); + gc.setForeground(getForeground()); + if ((getStyle() & DWT.WRAP) !is 0) { + FormUtil.paintWrapText(gc, text, bounds, underlined); + } else { + Point totalSize = computeTextSize(DWT.DEFAULT, DWT.DEFAULT); + bool shortenText_ =false; + if (bounds.width<totalSize.x) { + // shorten + shortenText_=true; + } + int textWidth = Math.min(bounds.width, totalSize.x); + int textHeight = totalSize.y; + String textToDraw = getText(); + if (shortenText_) { + textToDraw = shortenText(gc, getText(), bounds.width); + if (appToolTipText is null) { + super.setToolTipText(getText()); + } + } + else { + super.setToolTipText(appToolTipText); + } + gc.drawText(textToDraw, bounds.x, bounds.y, true); + if (underlined) { + int descent = gc.getFontMetrics().getDescent(); + int lineY = bounds.y + textHeight - descent + 1; + gc.drawLine(bounds.x, lineY, bounds.x + textWidth, lineY); + } + } + } + + protected String shortenText(GC gc, String t, int width) { + if (t is null) return null; + int w = gc.textExtent(ELLIPSIS).x; + if (width<=w) return t; + int l = t.length; + int max = l/2; + int min = 0; + int mid = (max+min)/2 - 1; + if (mid <= 0) return t; + while (min < mid && mid < max) { + String s1 = t.substring(0, mid); + String s2 = t.substring(l-mid, l); + int l1 = gc.textExtent(s1).x; + int l2 = gc.textExtent(s2).x; + if (l1+w+l2 > width) { + max = mid; + mid = (max+min)/2; + } else if (l1+w+l2 < width) { + min = mid; + mid = (max+min)/2; + } else { + min = max; + } + } + if (mid is 0) return t; + return t.substring(0, mid)~ELLIPSIS~t.substring(l-mid, l); + } + + protected Point computeTextSize(int wHint, int hHint) { + Point extent; + GC gc = new GC(this); + gc.setFont(getFont()); + if ((getStyle() & DWT.WRAP) !is 0 && wHint !is DWT.DEFAULT) { + extent = FormUtil.computeWrapSize(gc, getText(), wHint); + } else { + extent = gc.textExtent(getText()); + if ((getStyle() & DWT.WRAP) is 0 && wHint !is DWT.DEFAULT) + extent.x = wHint; + } + gc.dispose(); + return extent; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ILayoutExtension.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.widgets.ILayoutExtension; +import dwt.widgets.Composite; +/** + * Classes that extend abstract class Layout and implement this interface can + * take part in layout computation of the TableWrapLayout manager. This layout + * uses alternative algorithm that computes columns before rows. It allows it + * to 'flow' wrapped text proportionally (similar to the way web browser + * renders tables). Custom layout managers that implement this interface will + * allow TableWrapLayout to properly compute width hint to pass. + * + * @see TableWrapLayout + * @see ColumnLayout + * @since 3.0 + */ +public interface ILayoutExtension { + /** + * Computes the minimum width of the parent. All widgets capable of word + * wrapping should return the width of the longest word that cannot be + * broken any further. + * + * @param parent the parent composite + * @param changed <code>true</code> if the cached information should be + * flushed, <code>false</code> otherwise. + * @return the minimum width of the parent composite + */ + public int computeMinimumWidth(Composite parent, bool changed); + /** + * Computes the maximum width of the parent. All widgets capable of word + * wrapping should return the length of the entire text with wrapping + * turned off. + * + * @param parent the parent composite + * @param changed <code>true</code> if the cached information + * should be flushed, <code>false</code> otherwise. + * @return the maximum width of the parent composite + */ + public int computeMaximumWidth(Composite parent, bool changed); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ImageHyperlink.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,299 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * Chriss Gross (schtoo@schtoo.com) - fix for 61670 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.ui.forms.widgets.ImageHyperlink; + +import dwtx.ui.forms.widgets.Hyperlink; + +import dwt.DWT; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; +import dwt.widgets.Event; + +import dwt.dwthelper.utils; + +/** + * This class extends hyperlink widget by adding the capability to render an + * image relative to the text. If no text has been set, only image will be + * shown. Images for hover and active states can be set in addition to the + * normal state image. + * <p> + * When image is taller than the text, additional style can be provided to + * control vertical alignment (supported values are DWT.TOP, DWT.BOTTOM and + * DWT.CENTER). + * <p> + * The class does not need to be sublassed but it is allowed to do so if some + * aspect of the image hyperlink needs to be modified. + * + * @since 3.0 + */ +public class ImageHyperlink : Hyperlink { + /** + * Amount of pixels between the image and the text (default is 5). + */ + public int textSpacing = 5; + + private Image image; + + private Image hoverImage; + + private Image activeImage; + + private int state; + + private static const int HOVER = 1 << 1; + + private static const int ACTIVE = 1 << 2; + + private int verticalAlignment = DWT.CENTER; + + private int horizontalAlignment = DWT.LEFT; + + /** + * Creates the image hyperlink instance. + * + * @param parent + * the control parent + * @param style + * the control style (DWT.WRAP, BOTTOM, TOP, MIDDLE, LEFT, RIGHT) + */ + public this(Composite parent, int style) { + super(parent, removeAlignment(style)); + extractAlignment(style); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.widgets.AbstractHyperlink#paintHyperlink(dwt.events.PaintEvent) + */ + protected void paintHyperlink(GC gc) { + paintHyperlink(gc, getClientArea()); + } + + protected void paintHyperlink(GC gc, Rectangle bounds) { + Image image = null; + if ((state & ACTIVE) !is 0) + image = activeImage; + else if ((state & HOVER) !is 0) + image = hoverImage; + if (image is null) + image = this.image; + Rectangle ibounds = image !is null ? image.getBounds() : new Rectangle(0, 0, 0, 0); + Point maxsize = computeMaxImageSize(); + int spacing = image !is null?textSpacing:0; + int textWidth = bounds.width - maxsize.x - spacing + - marginWidth - marginWidth; + int y = bounds.y+marginHeight + maxsize.y / 2 - ibounds.height / 2; + + if (horizontalAlignment is DWT.LEFT) { + int x = bounds.x+marginWidth + maxsize.x / 2 - ibounds.width / 2; + int textX = bounds.x + marginWidth + maxsize.x + spacing; + if (image !is null) + gc.drawImage(image, x, y); + if (getText() !is null) + drawText(gc, bounds, textX, textWidth); + } else if (horizontalAlignment is DWT.RIGHT) { + int x = bounds.x+marginWidth; + if (getText() !is null) { + x += drawText(gc, bounds, x, textWidth); + } + x += maxsize.x / 2 - ibounds.width / 2 + spacing; + if (image !is null) + gc.drawImage(image, x, y); + } + } + + private int drawText(GC gc, Rectangle clientArea, int textX, int textWidth) { + Point textSize = computeTextSize(textWidth, DWT.DEFAULT); + int slotHeight = clientArea.height - marginHeight - marginHeight; + int textY; + textWidth = textSize.x; + int textHeight = textSize.y; + if (verticalAlignment is DWT.BOTTOM) { + textY = marginHeight + slotHeight - textHeight; + } else if (verticalAlignment is DWT.CENTER) { + textY = marginHeight + slotHeight / 2 - textHeight / 2; + } else { + textY = marginHeight; + } + paintText(gc, new Rectangle(textX, textY, textWidth, textHeight)); + return textWidth; + } + + /** + * Computes the control size by reserving space for images in addition to + * text. + * + * @param wHint + * width hint + * @param hHint + * height hint + * @param changed + * if <code>true</code>, any cached layout data should be + * computed anew + */ + public Point computeSize(int wHint, int hHint, bool changed) { + checkWidget(); + Point isize = computeMaxImageSize(); + int spacing = isize.x>0?textSpacing:0; + Point textSize = null; + if (getText() !is null) { + int innerWHint = wHint; + if (wHint !is DWT.DEFAULT) { + innerWHint = wHint - 2 * marginWidth - isize.x - spacing; + } + textSize = super.computeSize(innerWHint, hHint, changed); + } + int width = isize.x; + int height = isize.y; + if (textSize !is null) { + width += spacing; + width += textSize.x; + height = Math.max(height, textSize.y); + } + width += 2 * marginWidth; + height += 2 * marginHeight; + return new Point(width, height); + } + + protected void handleEnter(Event e) { + state = HOVER; + super.handleEnter(e); + } + + protected void handleExit(Event e) { + state = 0; + super.handleExit(e); + } + + protected void handleActivate(Event e) { + state &= ACTIVE; + redraw(); + super.handleActivate(e); + state &= ~ACTIVE; + if (!isDisposed()) + redraw(); + } + + /** + * Returns active image. + * + * @return active image or <code>null</code> if not set. + */ + public Image getActiveImage() { + return activeImage; + } + + /** + * Sets the image to show when link is activated. + * + * @param activeImage + * + */ + public void setActiveImage(Image activeImage) { + this.activeImage = activeImage; + } + + /** + * Returns the hover image. + * + * @return hover image or <code>null</code> if not set. + */ + public Image getHoverImage() { + return hoverImage; + } + + /** + * Sets the image to show when link is hover state (on mouse over). + * + * @param hoverImage + */ + public void setHoverImage(Image hoverImage) { + this.hoverImage = hoverImage; + } + + /** + * Returns the image to show in the normal state. + * + * @return normal image or <code>null</code> if not set. + */ + public Image getImage() { + return image; + } + + /** + * Sets the image to show when link is in the normal state. + * + * @param image + */ + public void setImage(Image image) { + this.image = image; + } + + private Point computeMaxImageSize() { + int x = 0; + int y = 0; + if (image !is null) { + x = Math.max(image.getBounds().width, x); + y = Math.max(image.getBounds().height, y); + } + if (hoverImage !is null) { + x = Math.max(hoverImage.getBounds().width, x); + y = Math.max(hoverImage.getBounds().height, y); + } + if (activeImage !is null) { + x = Math.max(activeImage.getBounds().width, x); + y = Math.max(activeImage.getBounds().height, y); + } + return new Point(x, y); + } + + private static int removeAlignment(int style) { + int resultStyle = style; + if ((style & DWT.CENTER) !is 0) { + resultStyle &= (~DWT.CENTER); + } + if ((style & DWT.TOP) !is 0) { + resultStyle &= (~DWT.TOP); + } + if ((style & DWT.BOTTOM) !is 0) { + resultStyle &= (~DWT.BOTTOM); + } + if ((style & DWT.LEFT) !is 0) { + resultStyle &= (~DWT.LEFT); + } + if ((style & DWT.RIGHT) !is 0) { + resultStyle &= (~DWT.RIGHT); + } + return resultStyle; + } + + private void extractAlignment(int style) { + if ((style & DWT.CENTER) !is 0) { + verticalAlignment = DWT.CENTER; + } else if ((style & DWT.TOP) !is 0) { + verticalAlignment = DWT.TOP; + } else if ((style & DWT.BOTTOM) !is 0) { + verticalAlignment = DWT.BOTTOM; + } + if ((style & DWT.LEFT) !is 0) { + horizontalAlignment = DWT.LEFT; + } else if ((style & DWT.RIGHT) !is 0) { + horizontalAlignment = DWT.RIGHT; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/LayoutCache.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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.ui.forms.widgets.LayoutCache; + +import dwtx.ui.forms.widgets.SizeCache; + +import dwt.graphics.Point; +import dwt.widgets.Control; + +import dwt.dwthelper.utils; + +/** + * Caches the preferred sizes of an array of controls + * + * @since 3.0 + */ +public class LayoutCache { + private SizeCache[] caches; + + /** + * Creates an empty layout cache + */ + public this() { + } + + /** + * Creates a cache for the given array of controls + * + * @param controls + */ + public this(Control[] controls) { + rebuildCache(controls); + } + + /** + * Returns the size cache for the given control + * + * @param idx + * @return the size cache for the given control + */ + public SizeCache getCache(int idx) { + return caches[idx]; + } + + /** + * Sets the controls that are being cached here. If these are the same + * controls that were used last time, this method does nothing. Otherwise, + * the cache is flushed and a new cache is created for the new controls. + * + * @param controls + */ + public void setControls(Control[] controls) { + // If the number of controls has changed, discard the entire cache + if (controls.length !is caches.length) { + rebuildCache(controls); + return; + } + + for (int idx = 0; idx < controls.length; idx++) { + caches[idx].setControl(controls[idx]); + } + } + + /** + * Creates a new size cache for the given set of controls, discarding any + * existing cache. + * + * @param controls the controls whose size is being cached + */ + private void rebuildCache(Control[] controls) { + SizeCache[] newCache = new SizeCache[controls.length]; + + for (int idx = 0; idx < controls.length; idx++) { + // Try to reuse existing caches if possible + if (idx < caches.length) { + newCache[idx] = caches[idx]; + newCache[idx].setControl(controls[idx]); + } else { + newCache[idx] = new SizeCache(controls[idx]); + } + } + + caches = newCache; + } + + /** + * Computes the preferred size of the nth control + * + * @param controlIndex index of the control whose size will be computed + * @param widthHint width of the control (or DWT.DEFAULT if unknown) + * @param heightHint height of the control (or DWT.DEFAULT if unknown) + * @return the preferred size of the control + */ + public Point computeSize(int controlIndex, int widthHint, int heightHint) { + return caches[controlIndex].computeSize(widthHint, heightHint); + } + + /** + * Flushes the cache for the given control. This should be called if exactly + * one of the controls has changed but the remaining controls remain unmodified + * + * @param controlIndex + */ + public void flush(int controlIndex) { + caches[controlIndex].flush(); + } + + /** + * Flushes the cache. + */ + public void flush() { + for (int idx = 0; idx < caches.length; idx++) { + caches[idx].flush(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/LayoutComposite.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.widgets.LayoutComposite; + +import dwtx.ui.forms.widgets.TableWrapLayout; +import dwtx.ui.forms.widgets.ColumnLayout; + +import dwt.graphics.Point; +import dwt.widgets.Composite; +import dwt.widgets.Layout; + +import dwt.dwthelper.utils; + +/** + * The class overrides default method for computing size in Composite by + * accepting size returned from layout managers as-is. The default code accepts + * width or height hint assuming it is correct. However, it is possible that + * the computation using the provided width hint results in a real size that is + * larger. This can result in wrapped text widgets being clipped, asking to + * render in bounds narrower than the longest word. + */ +/* package */class LayoutComposite : Composite { + public this(Composite parent, int style) { + super(parent, style); + setMenu(parent.getMenu()); + } + public Point computeSize(int wHint, int hHint, bool changed) { + Layout layout = getLayout(); + if (null !is cast(TableWrapLayout)layout ) + return (cast(TableWrapLayout) layout).computeSize(cast(Composite)this, wHint, hHint, + changed); + if (null !is cast(ColumnLayout)layout ) + return (cast(ColumnLayout) layout).computeSize(cast(Composite)this, wHint, hHint, + changed); + return super.computeSize(wHint, hHint, changed); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ScrolledForm.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,293 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.widgets.ScrolledForm; + +import dwtx.ui.forms.widgets.SharedScrolledComposite; +import dwtx.ui.forms.widgets.Form; + +import dwt.DWT; +import dwt.graphics.Color; +import dwt.graphics.Image; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Menu; +import dwtx.jface.action.IToolBarManager; +import dwtx.ui.forms.IMessage; + +import dwt.dwthelper.utils; + +/** + * ScrolledForm is a control that is capable of scrolling an instance of the + * Form class. It should be created in a parent that will allow it to use all + * the available area (for example, a shell, a view or an editor). + * <p> + * Children of the form should typically be created using FormToolkit to match + * the appearance and behaviour. When creating children, use a form body as a + * parent by calling 'getBody()' on the form instance. Example: + * + * <pre> + * FormToolkit toolkit = new FormToolkit(parent.getDisplay()); + * ScrolledForm form = toolkit.createScrolledForm(parent); + * form.setText("Sample form"); + * form.getBody().setLayout(new GridLayout()); + * toolkit.createButton(form.getBody(), "Checkbox", DWT.CHECK); + * </pre> + * + * <p> + * No layout manager has been set on the body. Clients are required to set the + * desired layout manager explicitly. + * <p> + * Although the class is not final, it is not expected to be be extended. + * + * @since 3.0 + */ +public class ScrolledForm : SharedScrolledComposite { + private Form content; + + public this(Composite parent) { + this(parent, DWT.V_SCROLL | DWT.H_SCROLL); + } + + /** + * Creates the form control as a child of the provided parent. + * + * @param parent + * the parent widget + */ + public this(Composite parent, int style) { + super(parent, style); + super.setMenu(parent.getMenu()); + content = new Form(this, DWT.NULL); + super.setContent(content); + content.setMenu(getMenu()); + } + + /** + * Passes the menu to the body. + * + * @param menu + */ + public void setMenu(Menu menu) { + super.setMenu(menu); + if (content !is null) + content.setMenu(menu); + } + + /** + * Returns the title text that will be rendered at the top of the form. + * + * @return the title text + */ + public String getText() { + return content.getText(); + } + + /** + * Returns the title image that will be rendered to the left of the title. + * + * @return the title image + */ + public Image getImage() { + return content.getImage(); + } + + /** + * Sets the foreground color of the form. This color will also be used for + * the body. + */ + public void setForeground(Color fg) { + super.setForeground(fg); + content.setForeground(fg); + } + + /** + * Sets the background color of the form. This color will also be used for + * the body. + */ + public void setBackground(Color bg) { + super.setBackground(bg); + content.setBackground(bg); + } + + /** + * The form sets the content widget. This method should not be called by + * classes that instantiate this widget. + */ + public final void setContent(Control c) { + } + + /** + * Sets the text to be rendered at the top of the form above the body as a + * title. + * <p> + * <strong>Note:</strong> Mnemonics are indicated by an '&' that causes + * the next character to be the mnemonic. Mnemonics are not applicable in + * the case of the form title but need to be taken into acount due to the + * usage of the underlying widget that renders mnemonics in the title area. + * The mnemonic indicator character '&' can be escaped by doubling it in + * the string, causing a single '&' to be displayed. + * </p> + * + * @param text + * the title text + */ + public void setText(String text) { + content.setText(text); + reflow(true); + } + + /** + * Sets the image to be rendered to the left of the title. + * + * @param image + * the title image or <code>null</code> for no image. + */ + public void setImage(Image image) { + content.setImage(image); + reflow(true); + } + + /** + * Returns the optional background image of this form. The image is rendered + * starting at the position 0,0 and is painted behind the title. + * + * @return Returns the background image. + */ + public Image getBackgroundImage() { + return content.getBackgroundImage(); + } + + /** + * Sets the optional background image to be rendered behind the title + * starting at the position 0,0. + * + * @param backgroundImage + * The backgroundImage to set. + */ + public void setBackgroundImage(Image backgroundImage) { + content.setBackgroundImage(backgroundImage); + } + + /** + * Returns the tool bar manager that is used to manage tool items in the + * form's title area. + * + * @return form tool bar manager + */ + public IToolBarManager getToolBarManager() { + return content.getToolBarManager(); + } + + /** + * Updates the local tool bar manager if used. Does nothing if local tool + * bar manager has not been created yet. + */ + public void updateToolBar() { + content.updateToolBar(); + } + + /** + * Returns the container that occupies the body of the form (the form area + * below the title). Use this container as a parent for the controls that + * should be in the form. No layout manager has been set on the form body. + * + * @return Returns the body of the form. + */ + public Composite getBody() { + return content.getBody(); + } + + /** + * Returns the instance of the form owned by the scrolled form. + * + * @return the form instance + */ + public Form getForm() { + return content; + } + + /** + * Sets the form's busy state. Busy form will display 'busy' animation in + * the area of the title image. + * + * @param busy + * the form's busy state + * @see Form#setBusy(bool) + * @since 3.3 + */ + + public void setBusy(bool busy) { + content.setBusy(busy); + reflow(true); + } + + /** + * Sets the optional head client. + * + * @param headClient + * the optional child of the head + * @see Form#setHeadClient(Control) + * @since 3.3 + */ + public void setHeadClient(Control headClient) { + content.setHeadClient(headClient); + reflow(true); + } + + /** + * Sets the form message. + * + * @param newMessage + * the message text or <code>null</code> to reset. + * @param newType + * as defined in + * {@link dwtx.jface.dialogs.IMessageProvider}. + * @param messages + * an optional array of children that itemize individual + * messages or <code>null</code> for a simple message. + * @since 3.3 + * @see Form#setMessage(String, int) + */ + public void setMessage(String newMessage, int newType, IMessage[] messages) { + content.setMessage(newMessage, newType, messages); + reflow(true); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageContainer#setMessage(java.lang.String, + * int) + */ + public void setMessage(String newMessage, int newType) { + this.setMessage(newMessage, newType, null); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.IMessageProvider#getMessage() + */ + public String getMessage() { + return content.getMessage(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.IMessageProvider#getMessageType() + */ + public int getMessageType() { + return content.getMessageType(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ScrolledFormText.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,174 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.widgets.ScrolledFormText; + +import dwtx.ui.forms.widgets.SharedScrolledComposite; +import dwtx.ui.forms.widgets.FormText; + +import dwt.DWT; +import dwt.graphics.Color; +import dwt.widgets.Composite; +import dwt.widgets.Control; + +import dwt.dwthelper.utils; +import dwt.dwthelper.InputStream; + +/** + * ScrolledFormText is a control that is capable of scrolling an instance of + * the FormText class. It should be created in a parent that will allow it to + * use all the available area (for example, a shell, a view or an editor). The + * form text can be created by the class itself, or set from outside. In the + * later case, the form text instance must be a direct child of the + * ScrolledFormText instance. + * <p> + * The class assumes that text to be rendered contains formatting tags. In case + * of a string, it will enclose the text in 'form' root element if missing from + * the text as a convinience. For example: + * + * <pre> + * ftext.setText("<p>Some text here</>"); + * </pre> + * + * will not cause an error. The same behavior does not exist for content from + * the input stream, however - it must be well formed in that case. + * </p> + + * @since 3.0 + * @see FormText + */ +public class ScrolledFormText : SharedScrolledComposite { + private FormText content; + private String text; + /** + * Creates the new scrolled text instance in the provided parent + * + * @param parent + * the parent composite + * @param createFormText + * if <code>true</code>, enclosing form text instance will be + * created in this constructor. + */ + public this(Composite parent, bool createFormText) { + this(parent, DWT.V_SCROLL | DWT.H_SCROLL, createFormText); + } + /** + * Creates the new scrolled text instance in the provided parent + * + * @param parent + * the parent composite + * @param style + * the style to pass to the scrolled composite + * @param createFormText + * if <code>true</code>, enclosing form text instance will be + * created in this constructor. + */ + public this(Composite parent, int style, bool createFormText) { + super(parent, style); + if (createFormText) + setFormText(new FormText(this, DWT.NULL)); + } + /** + * Sets the form text to be managed by this scrolled form text. The + * instance must be a direct child of this class. If this method is used, + * <code>false</code> must be passed in either of the constructors to + * avoid creating form text instance. + * + * @param formText + * the form text instance to use. + */ + public void setFormText(FormText formText) { + this.content = formText; + super.setContent(content); + content.setMenu(getMenu()); + if (text !is null) + loadText(text); + } + /** + * Sets the foreground color of the scrolled form text. + * + * @param fg + * the foreground color + */ + public void setForeground(Color fg) { + super.setForeground(fg); + if (content !is null) + content.setForeground(fg); + } + /** + * Sets the background color of the scrolled form text. + * + * @param bg + * the background color + */ + public void setBackground(Color bg) { + super.setBackground(bg); + if (content !is null) + content.setBackground(bg); + } + /** + * The class sets the content widget. This method should not be called by + * classes that instantiate this widget. + * + * @param c + * content control + */ + public final void setContent(Control c) { + } + /** + * Sets the text to be rendered in the scrolled form text. The text must + * contain formatting tags. + * + * @param text + * the text to be rendered + */ + public void setText(String text) { + this.text = text; + loadText(text); + reflow(true); + } + /** + * Sets the contents to rendered in the scrolled form text. The stream must + * contain formatting tags. The caller is responsible for closing the input + * stream. The call may be long running. For best results, call this method + * from another thread and call 'reflow' when done (but make both calls + * using 'Display.asyncExec' because these calls must be made in the event + * dispatching thread). + * + * @param is + * content input stream + */ + public void setContents(InputStream is_) { + loadContents(is_); + } + /** + * Returns the instance of the form text. + * + * @return the form text instance + */ + public FormText getFormText() { + return content; + } + private void loadText(String text) { + if (content !is null) { + String markup = text; + if (!markup.startsWith("<form>")) //$NON-NLS-1$ + markup = "<form>" ~ text ~ "</form>"; //$NON-NLS-1$//$NON-NLS-2$ + content.setText(markup, true, false); + } + } + private void loadContents(InputStream is_) { + if (content !is null) { + content.setContents(is_, false); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ScrolledPageBook.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.widgets.ScrolledPageBook; + +import dwtx.ui.forms.widgets.SharedScrolledComposite; +import dwtx.ui.forms.widgets.LayoutComposite; + +import dwt.DWT; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.layout.GridLayout; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwtx.ui.internal.forms.widgets.WrappedPageBook; + +import dwt.dwthelper.utils; +import tango.util.collection.HashMap; +/** + * ScrolledPageBook is a class that is capable of stacking several composites + * (pages), while showing one at a time. The content is scrolled if there is + * not enough space to fit it in the client area. + * + * @since 3.0 + */ +public class ScrolledPageBook : SharedScrolledComposite { + private WrappedPageBook pageBook; + private HashMap!(Object,Object) pages; + private Composite emptyPage; + private Control currentPage; + /** + * Creates a new instance in the provided parent + * + * @param parent + */ + public this(Composite parent) { + this(parent, DWT.H_SCROLL | DWT.V_SCROLL); + } + /** + * Creates a new instance in the provided parent and with the provided + * style. + * + * @param parent + * the control parent + * @param style + * the style to use + */ + public this(Composite parent, int style) { + super(parent, style); + pageBook = new WrappedPageBook(this, DWT.NULL); + setContent(pageBook); + pages = new HashMap!(Object,Object); + setExpandHorizontal(true); + setExpandVertical(true); + this.addListener(DWT.Traverse, dgListener( (Event e) { + switch (e.detail) { + case DWT.TRAVERSE_ESCAPE : + case DWT.TRAVERSE_RETURN : + case DWT.TRAVERSE_TAB_NEXT : + case DWT.TRAVERSE_TAB_PREVIOUS : + e.doit = true; + break; + } + })); + } + /** + * Removes the default size of the composite, allowing the control to + * shrink to the trim. + * + * @param wHint + * the width hint + * @param hHint + * the height hint + * @param changed + * if <code>true</code>, do not use cached values + */ + public Point computeSize(int wHint, int hHint, bool changed) { + Rectangle trim = computeTrim(0, 0, 10, 10); + return new Point(trim.width, trim.height); + } + /** + * Tests if the page under the provided key is currently in the book. + * + * @param key + * the page key + * @return <code>true</code> if page exists, <code>false</code> + * otherwise. + */ + public bool hasPage(Object key) { + return pages.containsKey(key); + } + /** + * Creates a new page for the provided key. Use the returned composite to + * create children in it. + * + * @param key + * the page key + * @return the newly created page composite + */ + public Composite createPage(Object key) { + Composite page = createPage(); + pages.add(key, page); + return page; + } + /** + * Returns the page book container. + * + * @return the page book container + */ + public Composite getContainer() { + return pageBook; + } + /** + * Registers a page under the privided key to be managed by the page book. + * The page must be a direct child of the page book container. + * + * @param key + * the page key + * @param page + * the page composite to register + * @see #createPage(Object) + * @see #getContainer + */ + public void registerPage(Object key, Control page) { + pages.add(key, page); + } + /** + * Removes the page under the provided key from the page book. Does nothing + * if page with that key does not exist. + * + * @param key + * the page key. + */ + public void removePage(Object key) { + removePage(key, true); + } + /** + * Removes the page under the provided key from the page book. Does nothing + * if page with that key does not exist. + * + * @param key + * the page key. + * @param showEmptyPage + * if <code>true</code>, shows the empty page + * after page removal. + */ + public void removePage(Object key, bool showEmptyPage_) { + Control page = cast(Control) pages.get(key); + if (page !is null) { + pages.remove(key); + page.dispose(); + if (showEmptyPage_) + showEmptyPage(); + } + } + /** + * Shows the page with the provided key and hides the page previously + * showing. Does nothing if the page with that key does not exist. + * + * @param key + * the page key + */ + public void showPage(Object key) { + Control page = cast(Control) pages.get(key); + if (page !is null) { + pageBook.showPage(page); + if (currentPage !is null && currentPage !is page) { + // switching pages - force layout + if (null !is cast(Composite)page ) + (cast(Composite) page).layout(false); + } + currentPage = page; + } else { + showEmptyPage(); + } + reflow(true); + } + /** + * Shows a page with no children to be used if the desire is to not show + * any registered page. + */ + public void showEmptyPage() { + if (emptyPage is null) { + emptyPage = createPage(); + emptyPage.setLayout(new GridLayout()); + } + pageBook.showPage(emptyPage); + currentPage = emptyPage; + reflow(true); + } + /** + * Sets focus on the current page if shown. + */ + public bool setFocus() { + if (currentPage !is null) + return currentPage.setFocus(); + return super.setFocus(); + } + /** + * Returns the page currently showing. + * + * @return the current page + */ + public Control getCurrentPage() { + return currentPage; + } + private Composite createPage() { + Composite page = new LayoutComposite(pageBook, DWT.NULL); + page.setBackground(getBackground()); + page.setForeground(getForeground()); + page.setMenu(pageBook.getMenu()); + return page; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/Section.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,484 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * Michael Williamson (eclipse-bugs@magnaworks.com) - patch (see Bugzilla #92545) + * + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.ui.forms.widgets.Section; + +import dwtx.ui.forms.widgets.ExpandableComposite; +import dwtx.ui.forms.widgets.SharedScrolledComposite; + +import dwt.DWT; +import dwt.events.PaintEvent; +import dwt.graphics.Color; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwt.widgets.Text; +import dwtx.core.runtime.Assert; +import dwtx.ui.internal.forms.widgets.FormImages; +import dwtx.ui.internal.forms.widgets.FormUtil; + +import dwt.dwthelper.utils; + +import tango.util.collection.HashMap; + +/** + * A variation of the expandable composite that adds optional description below + * the title. Section is often used as a basic building block in forms because + * it provides for logical grouping of information. + * <p> + * In case of the TITLE_BAR style, Section renders the title bar in a way + * compatible with the rest of the workbench. Since it is a widget, all the + * colors must be supplied directly. When created by the form toolkit, these + * colors are supplied by the toolkit. The toolkit initializes these colors + * based on the system colors. For this reason, it is recommended to create the + * section by the toolkit instead of through its own constructor. + * <p> + * Since 3.1, it is possible to set a control to be used for section + * description. If used, <code>DESCRIPTION</code> style should not be set. A + * typical way to take advantage of the new method is to set an instance of + * <code>FormText</code> to provide for hyperlinks and images in the + * description area. + * + * @since 3.0 + */ +public class Section : ExpandableComposite { + /** + * Description style. If used, description will be rendered below the title. + */ + public static const int DESCRIPTION = 1 << 7; + + private Control descriptionControl; + + private Control separator; + + private HashMap!(String,Object) titleColors; + + private static const String COLOR_BG = "bg"; //$NON-NLS-1$ + + private static const String COLOR_GBG = "gbg"; //$NON-NLS-1$ + + private static const String COLOR_BORDER = "border"; //$NON-NLS-1$ + + /** + * Creates a new section instance in the provided parent. + * + * @param parent + * the parent composite + * @param style + * the style to use + */ + public this(Composite parent, int style) { + this(parent, DWT.NULL, style); + } + + this(Composite parent, int cstyle, int style) { + super(parent, cstyle | getBackgroundStyle(style), style); + int rtl = cstyle & DWT.RIGHT_TO_LEFT; + if ((style & DESCRIPTION) !is 0) { + descriptionControl = new Text(this, DWT.READ_ONLY | DWT.WRAP | rtl); + } + if ((style & TITLE_BAR) !is 0) { + Listener listener = new class Listener{ + public void handleEvent(Event e) { + Image image = this.outer.callSuperGetBackgroundImage(); + if (image !is null) { + FormImages.getInstance().markFinished(image); + } + this.outer.callSuperSetBackgroundImage(null); + } + }; + addListener(DWT.Dispose, listener); + addListener(DWT.Resize, listener); + } + } + + private static int getBackgroundStyle(int estyle) { + return ((estyle & TITLE_BAR) !is 0) ? DWT.NO_BACKGROUND : DWT.NULL; + } + + private Image callSuperGetBackgroundImage(){ + return super.getBackgroundImage(); + } + private void callSuperSetBackgroundImage(Image image){ + return super.setBackgroundImage(image); + } + protected void internalSetExpanded(bool expanded) { + super.internalSetExpanded(expanded); + if ((getExpansionStyle() & TITLE_BAR) !is 0) { + if (!expanded) + super.setBackgroundImage(null); + } + reflow(); + } + + /** + * Reflows this section and all the parents up the hierarchy until a + * SharedScrolledComposite is reached. + */ + protected void reflow() { + Composite c = this; + while (c !is null) { + c.setRedraw(false); + c = c.getParent(); + if (null !is cast(SharedScrolledComposite)c ) { + break; + } + } + c = this; + while (c !is null) { + c.layout(true); + c = c.getParent(); + if (null !is cast(SharedScrolledComposite)c ) { + (cast(SharedScrolledComposite) c).reflow(true); + break; + } + } + c = this; + while (c !is null) { + c.setRedraw(true); + c = c.getParent(); + if (null !is cast(SharedScrolledComposite)c ) { + break; + } + } + } + + /** + * Sets the description text. Has no effect if DESCRIPTION style was not + * used to create the control. + * + * @param description + */ + public void setDescription(String description) { + if (null !is cast(Text)descriptionControl ) + (cast(Text) descriptionControl).setText(description); + } + + /** + * Returns the current description text. + * + * @return description text or <code>null</code> if DESCRIPTION style was + * not used to create the control. + */ + public String getDescription() { + if (null !is cast(Text)descriptionControl ) + return (cast(Text) descriptionControl).getText(); + return null; + } + + /** + * Sets the separator control of this section. The separator must not be + * <samp>null </samp> and must be a direct child of this container. If + * defined, separator will be placed below the title text and will remain + * visible regardless of the expansion state. + * + * @param separator + * the separator that will be placed below the title text. + */ + public void setSeparatorControl(Control separator) { + Assert.isTrue(separator !is null && separator.getParent().opEquals(this)); + this.separator = separator; + } + + /** + * Returns the control that is used as a separator betweeen the title and + * the client, or <samp>null </samp> if not set. + * + * @return separator control or <samp>null </samp> if not set. + */ + public Control getSeparatorControl() { + return separator; + } + + /** + * Sets the background of the section. + * + * @param bg + * the new background + */ + public void setBackground(Color bg) { + super.setBackground(bg); + if (descriptionControl !is null + && (getExpansionStyle() & DESCRIPTION) !is 0) + descriptionControl.setBackground(bg); + } + + /** + * Sets the foreground of the section. + * + * @param fg + * the new foreground. + */ + public void setForeground(Color fg) { + super.setForeground(fg); + if (descriptionControl !is null + && (getExpansionStyle() & DESCRIPTION) !is 0) + descriptionControl.setForeground(fg); + } + + /** + * Returns the control used to render the description. In 3.1, this method + * was promoted to public. + * + * @return description control or <code>null</code> if DESCRIPTION style + * was not used to create the control and description control was + * not set by the client. + * @see #setDescriptionControl(dwt.widgets.Control) + */ + public Control getDescriptionControl() { + return descriptionControl; + } + + /** + * Sets the description control of this section. The control must not be + * <samp>null</samp> and must be a direct child of this container. If + * defined, contol will be placed below the title text and the separator and + * will be hidden int he collapsed state. + * <p> + * This method and <code>DESCRIPTION</code> style are mutually exclusive. + * Use the method only if you want to create the description control + * yourself. + * + * @since 3.1 + * @param descriptionControl + * the control that will be placed below the title text. + */ + public void setDescriptionControl(Control descriptionControl) { + Assert.isTrue((getExpansionStyle() & DESCRIPTION) is 0); + Assert.isTrue(descriptionControl !is null + && descriptionControl.getParent().opEquals(this)); + this.descriptionControl = descriptionControl; + } + + /** + * Sets the color of the title bar border when TITLE_BAR style is used. + * + * @param color + * the title bar border color + */ + public void setTitleBarBorderColor(Color color) { + putTitleBarColor(COLOR_BORDER, color); + } + + /** + * Sets the color of the title bar background when TITLE_BAR style is used. + * This color is used as a starting color for the vertical gradient. + * + * @param color + * the title bar border background + */ + public void setTitleBarBackground(Color color) { + putTitleBarColor(COLOR_BG, color); + } + + /** + * Sets the color of the title bar gradient background when TITLE_BAR style + * is used. This color is used at the height where title controls end + * (toggle, tool bar). + * + * @param color + * the title bar gradient background + */ + public void setTitleBarGradientBackground(Color color) { + putTitleBarColor(COLOR_GBG, color); + } + + /** + * Returns the title bar border color when TITLE_BAR style is used. + * + * @return the title bar border color + */ + public Color getTitleBarBorderColor() { + if (titleColors is null) + return null; + return cast(Color) titleColors.get(COLOR_BORDER); + } + + /** + * Returns the title bar gradient background color when TITLE_BAR style is + * used. + * + * @return the title bar gradient background + */ + public Color getTitleBarGradientBackground() { + if (titleColors is null) + return null; + if ((getExpansionStyle() & SHORT_TITLE_BAR) !is 0) + return getBackground(); + return cast(Color) titleColors.get(COLOR_GBG); + } + + /** + * Returns the title bar background when TITLE_BAR style is used. + * + * @return the title bar background + */ + public Color getTitleBarBackground() { + if (titleColors is null) + return null; + return cast(Color) titleColors.get(COLOR_BG); + } + + private void putTitleBarColor(String key, Color color) { + if (color is null) + return; + if (titleColors is null) + titleColors = new HashMap!(String,Object); + titleColors.add(key, color); + } + + protected void onPaint(PaintEvent e) { + Color bg = null; + Color fg = null; + Color border = null; + + GC gc = e.gc; + Image buffer = null; + Rectangle bounds = getClientArea(); + + if ((getExpansionStyle() & TITLE_BAR) !is 0) { + buffer = new Image(getDisplay(), bounds.width, bounds.height); + buffer.setBackground(getBackground()); + gc = new GC(buffer); + } + if (titleColors !is null) { + bg = cast(Color) titleColors.get(COLOR_BG); + fg = getTitleBarForeground(); + border = cast(Color) titleColors.get(COLOR_BORDER); + } + if (bg is null) + bg = getBackground(); + if (fg is null) + fg = getForeground(); + if (border is null) + border = fg; + int theight = 0; + int gradientheight = 0; + int tvmargin = IGAP; + if ((getExpansionStyle() & TITLE_BAR) !is 0) { + Point tsize = null; + Point tcsize = null; + if (toggle !is null) + tsize = toggle.getSize(); + int twidth = bounds.width - marginWidth - marginWidth; + if (tsize !is null) + twidth -= tsize.x + IGAP; + if (getTextClient() !is null) + tcsize = getTextClient().getSize(); + if (tcsize !is null) + twidth -= tcsize.x + IGAP; + Point size = textLabel.getSize(); + if (tsize !is null) + theight += Math.max(theight, tsize.y); + gradientheight = theight; + if (tcsize !is null) { + theight = Math.max(theight, tcsize.y); + } + theight = Math.max(theight, size.y); + gradientheight = Math.max(gradientheight, size.y); + theight += tvmargin + tvmargin; + gradientheight += tvmargin + tvmargin; + } else { + theight = 5; + } + if ((getExpansionStyle() & TITLE_BAR) !is 0) { + if (getBackgroundImage() is null) + updateHeaderImage(bg, bounds, gradientheight, theight); + gc.setBackground(getBackground()); + gc.fillRectangle(bounds.x, bounds.y, bounds.width, bounds.height); + drawBackground(gc, bounds.x, bounds.y, bounds.width, theight); + if (marginWidth > 0) { + // fix up margins + gc.setBackground(getBackground()); + gc.fillRectangle(0, 0, marginWidth, theight); + gc.fillRectangle(bounds.x + bounds.width - marginWidth, 0, + marginWidth, theight); + } + } else if (isExpanded()) { + gc.setForeground(bg); + gc.setBackground(getBackground()); + gc.fillGradientRectangle(marginWidth, marginHeight, bounds.width + - marginWidth - marginWidth, theight, true); + } + gc.setBackground(getBackground()); + FormUtil.setAntialias(gc, DWT.ON); + // repair the upper left corner + gc.fillPolygon([ marginWidth, marginHeight, marginWidth, + marginHeight + 2, marginWidth + 2, marginHeight ]); + // repair the upper right corner + gc.fillPolygon([ bounds.width - marginWidth - 3, + marginHeight, bounds.width - marginWidth, marginHeight, + bounds.width - marginWidth, marginHeight + 3 ]); + gc.setForeground(border); + if (isExpanded() || (getExpansionStyle() & TITLE_BAR) !is 0) { + // top left curve + gc.drawLine(marginWidth, marginHeight + 2, marginWidth + 2, + marginHeight); + // top edge + gc.drawLine(marginWidth + 2, marginHeight, bounds.width + - marginWidth - 3, marginHeight); + // top right curve + gc.drawLine(bounds.width - marginWidth - 3, marginHeight, + bounds.width - marginWidth - 1, marginHeight + 2); + } else { + // collapsed short title bar + // top edge + gc.drawLine(marginWidth, marginHeight, bounds.width - 1, + marginHeight); + } + if ((getExpansionStyle() & TITLE_BAR) !is 0 || isExpanded()) { + // left vertical edge gradient + gc.fillGradientRectangle(marginWidth, marginHeight + 2, 1, + gradientheight - 2, true); + // right vertical edge gradient + gc.fillGradientRectangle(bounds.width - marginWidth - 1, + marginHeight + 2, 1, gradientheight - 2, true); + } + if ((getExpansionStyle() & TITLE_BAR) !is 0) { + // New in 3.3 - edge treatmant + gc.setForeground(getDisplay().getSystemColor(DWT.COLOR_WHITE)); + gc.drawPolyline([ marginWidth + 1, + marginHeight + gradientheight - 1, marginWidth + 1, + marginHeight + 2, marginWidth + 2, marginHeight + 2, + marginWidth + 2, marginHeight + 1, + bounds.width - marginWidth - 3, marginHeight + 1, + bounds.width - marginWidth - 3, marginHeight + 2, + bounds.width - marginWidth - 2, marginHeight + 2, + bounds.width - marginWidth - 2, + marginHeight + gradientheight - 1 ]); + } + if (buffer !is null) { + gc.dispose(); + e.gc.drawImage(buffer, 0, 0); + buffer.dispose(); + } + } + + private void updateHeaderImage(Color bg, Rectangle bounds, int theight, + int realtheight) { + Image image = FormImages.getInstance().getGradient(getDisplay(), getBackground(), bg, realtheight, theight, marginHeight); + super.setBackgroundImage(image); + } + + /** + * Background image is used for the title gradient - does nothing. + */ + public final void setBackgroundImage(Image image) { + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/SharedScrolledComposite.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,282 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.widgets.SharedScrolledComposite; + +import dwtx.ui.forms.widgets.SizeCache; + +import dwt.DWT; +import dwt.custom.ScrolledComposite; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwt.widgets.ScrollBar; +import dwtx.ui.internal.forms.widgets.FormUtil; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Runnable; + +/** + * This class is used to provide common scrolling services to a number of + * controls in the toolkit. Classes that extend it are not required to implement + * any method. + * + * @since 3.0 + */ +public abstract class SharedScrolledComposite : ScrolledComposite { + private static const int H_SCROLL_INCREMENT = 5; + + private static const int V_SCROLL_INCREMENT = 64; + + private bool ignoreLayouts = true; + + private bool ignoreResizes = false; + + private bool expandHorizontal = false; + + private bool expandVertical = false; + + private SizeCache contentCache; + + private bool reflowPending = false; + + private bool delayedReflow = true; + + /** + * Creates the new instance. + * + * @param parent + * the parent composite + * @param style + * the style to use + */ + public this(Composite parent, int style) { + contentCache = new SizeCache(); + super(parent, style); + addListener(DWT.Resize, new class Listener { + public void handleEvent(Event e) { + if (!ignoreResizes) { + scheduleReflow(false); + } + } + }); + initializeScrollBars(); + } + + /** + * Sets the foreground of the control and its content. + * + * @param fg + * the new foreground color + */ + public void setForeground(Color fg) { + super.setForeground(fg); + if (getContent() !is null) + getContent().setForeground(fg); + } + + /** + * Sets the background of the control and its content. + * + * @param bg + * the new background color + */ + public void setBackground(Color bg) { + super.setBackground(bg); + if (getContent() !is null) + getContent().setBackground(bg); + } + + /** + * Sets the font of the form. This font will be used to render the title + * text. It will not affect the body. + */ + public void setFont(Font font) { + super.setFont(font); + if (getContent() !is null) + getContent().setFont(font); + } + + /** + * Overrides 'super' to pass the proper colors and font + */ + public void setContent(Control content) { + super.setContent(content); + if (content !is null) { + content.setForeground(getForeground()); + content.setBackground(getBackground()); + content.setFont(getFont()); + } + } + + /** + * If content is set, transfers focus to the content. + */ + public bool setFocus() { + bool result; + FormUtil.setFocusScrollingEnabled(this, false); + if (getContent() !is null) + result = getContent().setFocus(); + else + result = super.setFocus(); + FormUtil.setFocusScrollingEnabled(this, true); + return result; + } + + /* + * (non-Javadoc) + * + * @see dwt.widgets.Composite#layout(bool) + */ + public void layout(bool changed) { + if (ignoreLayouts) { + return; + } + + ignoreLayouts = true; + ignoreResizes = true; + super.layout(changed); + ignoreResizes = false; + } + + /* + * (non-Javadoc) + * + * @see dwt.custom.ScrolledComposite#setExpandHorizontal(bool) + */ + public void setExpandHorizontal(bool expand) { + expandHorizontal = expand; + super.setExpandHorizontal(expand); + } + + /* + * (non-Javadoc) + * + * @see dwt.custom.ScrolledComposite#setExpandVertical(bool) + */ + public void setExpandVertical(bool expand) { + expandVertical = expand; + super.setExpandVertical(expand); + } + + /** + * Recomputes the body layout and the scroll bars. The method should be used + * when changes somewhere in the form body invalidate the current layout + * and/or scroll bars. + * + * @param flushCache + * if <code>true</code>, drop the cached data + */ + public void reflow(bool flushCache) { + Composite c = cast(Composite) getContent(); + Rectangle clientArea = getClientArea(); + if (c is null) + return; + + contentCache.setControl(c); + if (flushCache) { + contentCache.flush(); + } + try { + setRedraw(false); + Point newSize = contentCache.computeSize(FormUtil.getWidthHint( + clientArea.width, c), FormUtil.getHeightHint(clientArea.height, + c)); + + // Point currentSize = c.getSize(); + if (!(expandHorizontal && expandVertical)) { + c.setSize(newSize); + } + + setMinSize(newSize); + FormUtil.updatePageIncrement(this); + + // reduce vertical scroll increment if necessary + ScrollBar vbar = getVerticalBar(); + if (vbar !is null) { + if (getClientArea().height - 5 < V_SCROLL_INCREMENT) + getVerticalBar().setIncrement(getClientArea().height - 5); + else + getVerticalBar().setIncrement(V_SCROLL_INCREMENT); + } + + ignoreLayouts = false; + layout(flushCache); + ignoreLayouts = true; + + contentCache.layoutIfNecessary(); + } finally { + setRedraw(true); + } + } + + private void updateSizeWhilePending() { + Control c = getContent(); + Rectangle area = getClientArea(); + setMinSize(area.width, c.getSize().y); + } + + private void scheduleReflow(bool flushCache) { + if (delayedReflow) { + if (reflowPending) { + updateSizeWhilePending(); + return; + } + getDisplay().asyncExec( dgRunnable( (bool flushCache) { + if (!isDisposed()) + reflow(flushCache); + reflowPending = false; + }, flushCache)); + reflowPending = true; + } else + reflow(flushCache); + } + + private void initializeScrollBars() { + ScrollBar hbar = getHorizontalBar(); + if (hbar !is null) { + hbar.setIncrement(H_SCROLL_INCREMENT); + } + ScrollBar vbar = getVerticalBar(); + if (vbar !is null) { + vbar.setIncrement(V_SCROLL_INCREMENT); + } + FormUtil.updatePageIncrement(this); + } + + /** + * Tests if the control uses delayed reflow. + * @return <code>true</code> if reflow requests will + * be delayed, <code>false</code> otherwise. + */ + public bool isDelayedReflow() { + return delayedReflow; + } + + /** + * Sets the delayed reflow feature. When used, + * it will schedule a reflow on resize requests + * and reject subsequent reflows until the + * scheduled one is performed. This improves + * performance by + * @param delayedReflow + * The delayedReflow to set. + */ + public void setDelayedReflow(bool delayedReflow) { + this.delayedReflow = delayedReflow; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/SizeCache.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,537 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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.ui.forms.widgets.SizeCache; + +import dwtx.ui.forms.widgets.ILayoutExtension; + +import dwt.DWT; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Button; +import dwt.widgets.Combo; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Label; +import dwt.widgets.List; +import dwt.widgets.Layout; +import dwt.widgets.ProgressBar; +import dwt.widgets.Sash; +import dwt.widgets.Scale; +import dwt.widgets.Scrollable; +import dwt.widgets.Slider; +import dwt.widgets.Text; +import dwt.widgets.ToolBar; +import dwt.widgets.Tree; +import dwtx.jface.util.Geometry; +import dwtx.ui.internal.forms.widgets.FormUtil; + +import dwt.dwthelper.utils; +import tango.util.collection.ArraySeq; +/** + * Caches the preferred size of an DWT control + * + * @since 3.0 + */ +public class SizeCache { + private Control control; + + private Point preferredSize; + + private int cachedWidthQuery; + private int cachedWidthResult; + + private int cachedHeightQuery; + private int cachedHeightResult; + + private int minimumWidth; + private int heightAtMinimumWidth = -1; + private int maximumWidth; + + /** + * True iff we should recursively flush all children on the next layout + */ + private bool flushChildren; + + /** + * True iff changing the height hint does not affect the preferred width and changing + * the width hint does not change the preferred height + */ + private bool independentDimensions = false; + + /** + * True iff the preferred height for any hint larger than the preferred width will not + * change the preferred height. + */ + private bool preferredWidthOrLargerIsMinimumHeight = false; + + // HACK: these values estimate how much to subtract from the width and height + // hints that get passed into computeSize, in order to produce a result + // that is exactly the desired size. To be removed once bug 46112 is fixed (note: + // bug 46112 is currently flagged as a duplicate, but there is still no workaround). + private int widthAdjustment = 0; + + private int heightAdjustment = 0; + + private int minimumHeight; + + private int widthAtMinimumHeight = -1; + + // If the layout is dirty, this is the size of the control at the time its + // layout was dirtied. null if the layout is not dirty. + private Point dirtySize = null; + + + // END OF HACK + + public this() { + this(null); + } + + /** + * Creates a cache for size computations on the given control + * + * @param control the control for which sizes will be calculated, + * or null to always return (0,0) + */ + public this(Control control) { + setControl(control); + } + + /** + * Sets the control whose size is being cached. Does nothing (will not + * even flush the cache) if this is the same control as last time. + * + * @param newControl the control whose size is being cached, or null to always return (0,0) + */ + public void setControl(Control newControl) { + if (newControl !is control) { + control = newControl; + if (control is null) { + independentDimensions = true; + preferredWidthOrLargerIsMinimumHeight = false; + widthAdjustment = 0; + heightAdjustment = 0; + } else { + independentDimensions = independentLengthAndWidth(control); + preferredWidthOrLargerIsMinimumHeight = isPreferredWidthMaximum(control); + computeHintOffset(control); + flush(); + } + } + } + + /** + * Returns the control whose size is being cached + * + * @return the control whose size is being cached, or null if this cache always returns (0,0) + */ + public Control getControl() { + return control; + } + + /** + * Flush the cache (should be called if the control's contents may have changed since the + * last query) + */ + public void flush() { + flush(true); + } + + public void flush(bool recursive) { + preferredSize = null; + cachedWidthQuery = -1; + cachedWidthResult = -1; + cachedHeightQuery = -1; + cachedHeightResult = -1; + minimumWidth = -1; + maximumWidth = -1; + minimumHeight = -1; + heightAtMinimumWidth = -1; + widthAtMinimumHeight = -1; + + if (recursive || dirtySize !is null) { + if (control is null || control.isDisposed()) { + dirtySize = new Point(0,0); + control = null; + } else { + dirtySize = control.getSize(); + } + } + + this.flushChildren = this.flushChildren || recursive; + } + + private Point getPreferredSize() { + if (preferredSize is null) { + preferredSize = controlComputeSize(DWT.DEFAULT, DWT.DEFAULT); + } + + return preferredSize; + } + + /** + * Computes the preferred size of the control. + * + * @param widthHint the known width of the control (pixels) or DWT.DEFAULT if unknown + * @param heightHint the known height of the control (pixels) or DWT.DEFAULT if unknown + * @return the preferred size of the control + */ + public Point computeSize(int widthHint, int heightHint) { + if (control is null || control.isDisposed()) { + return new Point(0, 0); + } + + // If we're asking for a result smaller than the minimum width + int minWidth = computeMinimumWidth(); + + if (widthHint !is DWT.DEFAULT && widthHint + widthAdjustment < minWidth) { + if (heightHint is DWT.DEFAULT) { + return new Point(minWidth, computeHeightAtMinimumWidth()); + } + + widthHint = minWidth - widthAdjustment; + } + + // If we're asking for a result smaller than the minimum height + int minHeight = computeMinimumHeight(); + + if (heightHint !is DWT.DEFAULT && heightHint + heightAdjustment < minHeight) { + if (widthHint is DWT.DEFAULT) { + return new Point(computeWidthAtMinimumHeight(), minHeight); + } + + heightHint = minHeight - heightAdjustment; + } + + // If both dimensions were supplied in the input, compute the trivial result + if (widthHint !is DWT.DEFAULT && heightHint !is DWT.DEFAULT) { + return new Point(widthHint + widthAdjustment, heightHint + heightAdjustment); + } + + // No hints given -- find the preferred size + if (widthHint is DWT.DEFAULT && heightHint is DWT.DEFAULT) { + return Geometry.copy(getPreferredSize()); + } + + // If the length and width are independent, compute the preferred size + // and adjust whatever dimension was supplied in the input + if (independentDimensions) { + Point result = Geometry.copy(getPreferredSize()); + + if (widthHint !is DWT.DEFAULT) { + result.x = widthHint + widthAdjustment; + } + + if (heightHint !is DWT.DEFAULT) { + result.y = heightHint + heightAdjustment; + } + + return result; + } + + // Computing a height + if (heightHint is DWT.DEFAULT) { + // If we know the control's preferred size + if (preferredSize !is null) { + // If the given width is the preferred width, then return the preferred size + if (widthHint + widthAdjustment is preferredSize.x) { + return Geometry.copy(preferredSize); + } + } + + // If we have a cached height measurement + if (cachedHeightQuery !is -1) { + // If this was measured with the same width hint + if (cachedHeightQuery is widthHint) { + return new Point(widthHint + widthAdjustment, cachedHeightResult); + } + } + + // If this is a control where any hint larger than the + // preferred width results in the minimum height, determine if + // we can compute the result based on the preferred height + if (preferredWidthOrLargerIsMinimumHeight) { + // Computed the preferred size (if we don't already know it) + getPreferredSize(); + + // If the width hint is larger than the preferred width, then + // we can compute the result from the preferred width + if (widthHint + widthAdjustment >= preferredSize.x) { + return new Point(widthHint + widthAdjustment, preferredSize.y); + } + } + + // Else we can't find an existing size in the cache, so recompute + // it from scratch. + Point newHeight = controlComputeSize(widthHint - widthAdjustment, DWT.DEFAULT); + + cachedHeightQuery = heightHint; + cachedHeightResult = newHeight.y; + + return newHeight; + } + + // Computing a width + if (widthHint is DWT.DEFAULT) { + // If we know the control's preferred size + if (preferredSize !is null) { + // If the given height is the preferred height, then return the preferred size + if (heightHint + heightAdjustment is preferredSize.y) { + return Geometry.copy(preferredSize); + } + } + + // If we have a cached width measurement with the same height hint + if (cachedWidthQuery is heightHint) { + return new Point(cachedWidthResult, heightHint + heightAdjustment); + } + + Point widthResult = controlComputeSize(DWT.DEFAULT, heightHint - heightAdjustment); + + cachedWidthQuery = heightHint; + cachedWidthResult = widthResult.x; + + return widthResult; + } + + return controlComputeSize(widthHint, heightHint); + } + + /** + * Compute the control's size, and ensure that non-default hints are returned verbatim + * (this tries to compensate for DWT's hints, which aren't really the outer width of the + * control). + * + * @param widthHint the horizontal hint + * @param heightHint the vertical hint + * @return the control's size + */ + public Point computeAdjustedSize(int widthHint, int heightHint) { + int adjustedWidthHint = widthHint is DWT.DEFAULT ? DWT.DEFAULT : Math + .max(0, widthHint - widthAdjustment); + int adjustedHeightHint = heightHint is DWT.DEFAULT ? DWT.DEFAULT : Math + .max(0, heightHint - heightAdjustment); + + Point result = computeSize(adjustedWidthHint, adjustedHeightHint); + + // If the amounts we subtracted off the widthHint and heightHint didn't do the trick, then + // manually adjust the result to ensure that a non-default hint will return that result verbatim. + + return result; + } + + /** + * Returns true if the preferred length of the given control is + * independent of the width and visa-versa. If this returns true, + * then changing the widthHint argument to control.computeSize will + * never change the resulting height and changing the heightHint + * will never change the resulting width. Returns false if unknown. + * <p> + * This information can be used to improve caching. Incorrectly returning + * a value of false may decrease performance, but incorrectly returning + * a value of true will generate incorrect layouts... so always return + * false if unsure. + * </p> + * + * @param control + * @return + */ + static bool independentLengthAndWidth(Control control) { + if (control is null || control.isDisposed()) { + return true; + } + + if (null !is cast(Button)control || null !is cast(ProgressBar)control + || null !is cast(Sash)control || null !is cast(Scale)control + || null !is cast(Slider)control || null !is cast(List)control + || null !is cast(Combo)control || null !is cast(Tree)control ) { + return true; + } + + if (null !is cast(Label)control || null !is cast(Text)control ) { + return (control.getStyle() & DWT.WRAP) is 0; + } + + // Unless we're certain that the control has this property, we should + // return false. + + return false; + } + + /** + * Try to figure out how much we need to subtract from the hints that we + * pass into the given control's computeSize(...) method. This tries to + * compensate for bug 46112. To be removed once DWT provides an "official" + * way to compute one dimension of a control's size given the other known + * dimension. + * + * @param control + */ + private void computeHintOffset(Control control) { + if (null !is cast(Scrollable)control ) { + // For scrollables, subtract off the trim size + Scrollable scrollable = cast(Scrollable) control; + Rectangle trim = scrollable.computeTrim(0, 0, 0, 0); + + widthAdjustment = trim.width; + heightAdjustment = trim.height; + } else { + // For non-composites, subtract off 2 * the border size + widthAdjustment = control.getBorderWidth() * 2; + heightAdjustment = widthAdjustment; + } + } + + private Point controlComputeSize(int widthHint, int heightHint) { + Point result = control.computeSize(widthHint, heightHint, flushChildren); + flushChildren = false; + + return result; + } + + /** + * Returns true only if the control will return a constant height for any + * width hint larger than the preferred width. Returns false if there is + * any situation in which the control does not have this property. + * + * <p> + * Note: this method is only important for wrapping controls, and it can + * safely return false for anything else. AFAIK, all DWT controls have this + * property, but to be safe they will only be added to the list once the + * property has been confirmed. + * </p> + * + * @param control + * @return + */ + private static bool isPreferredWidthMaximum(Control control) { + return (null !is cast(ToolBar)control + //|| control instanceof CoolBar + || null !is cast(Label)control ); + } + + public int computeMinimumWidth() { + if (minimumWidth is -1) { + if (null !is cast(Composite)control ) { + Layout layout = (cast(Composite)control).getLayout(); + if (null !is cast(ILayoutExtension)layout ) { + minimumWidth = (cast(ILayoutExtension)layout).computeMinimumWidth(cast(Composite)control, flushChildren); + flushChildren = false; + } + } + } + + if (minimumWidth is -1) { + Point minWidth = controlComputeSize(FormUtil.getWidthHint(5, control), DWT.DEFAULT); + minimumWidth = minWidth.x; + heightAtMinimumWidth = minWidth.y; + } + + return minimumWidth; + } + + public int computeMaximumWidth() { + if (maximumWidth is -1) { + if (null !is cast(Composite)control ) { + Layout layout = (cast(Composite)control).getLayout(); + if (null !is cast(ILayoutExtension)layout ) { + maximumWidth = (cast(ILayoutExtension)layout).computeMaximumWidth(cast(Composite)control, flushChildren); + flushChildren = false; + } + } + } + + if (maximumWidth is -1) { + maximumWidth = getPreferredSize().x; + } + + return maximumWidth; + } + + private int computeHeightAtMinimumWidth() { + int minimumWidth = computeMinimumWidth(); + + if (heightAtMinimumWidth is -1) { + heightAtMinimumWidth = controlComputeSize(minimumWidth - widthAdjustment, DWT.DEFAULT).y; + } + + return heightAtMinimumWidth; + } + + private int computeWidthAtMinimumHeight() { + int minimumHeight = computeMinimumHeight(); + + if (widthAtMinimumHeight is -1) { + widthAtMinimumHeight = controlComputeSize(DWT.DEFAULT, minimumHeight - heightAdjustment).x; + } + + return widthAtMinimumHeight; + } + + private int computeMinimumHeight() { + if (minimumHeight is -1) { + Point sizeAtMinHeight = controlComputeSize(DWT.DEFAULT, 0); + + minimumHeight = sizeAtMinHeight.y; + widthAtMinimumHeight = sizeAtMinHeight.x; + } + + return minimumHeight; + } + + public Point computeMinimumSize() { + return new Point(computeMinimumWidth(), computeMinimumHeight()); + } + + public void setSize(Point newSize) { + if (control !is null) { + control.setSize(newSize); + } + + layoutIfNecessary(); + } + + public void setSize(int width, int height) { + if (control !is null) { + control.setSize(width, height); + } + + layoutIfNecessary(); + } + + public void setBounds(int x, int y, int width, int height) { + if (control !is null) { + control.setBounds(x, y, width, height); + } + + layoutIfNecessary(); + } + + public void setBounds(Rectangle bounds) { + if (control !is null) { + control.setBounds(bounds); + } + + layoutIfNecessary(); + } + + public void layoutIfNecessary() { + if (dirtySize !is null && control !is null && null !is cast(Composite)control ) { + if (control.getSize().opEquals(dirtySize)) { + (cast(Composite)control).layout(flushChildren); + flushChildren = false; + } + } + dirtySize = null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/TableWrapData.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,201 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.widgets.TableWrapData; + +import dwt.DWT; +import dwt.graphics.Point; + +import dwt.dwthelper.utils; + +/** + * Layout data used in conjunction with <code>HTMLTableLayout</code>. + * Children in a composite that uses this layout should call <samp>setLayoutData + * </samp> and pass an instance of this class to control physical placement in + * the parent. + * + * @see TableWrapLayout + * @since 3.0 + */ +public final class TableWrapData { + /** + * The control will be left-justified. + */ + public static const int LEFT = 1 << 1; + + /** + * The control will be centered horizontally. + */ + public static const int CENTER = 1 << 2; + + /** + * The control will be right-justified. + */ + public static const int RIGHT = 1 << 3; + + /** + * The control will be aligned with the top of the cell. + */ + public static const int TOP = 1 << 4; + + /** + * The control will be centered vertically. + */ + public static const int MIDDLE = 1 << 5; + + /** + * The control will be aligned with the bottom of the cell. + */ + public static const int BOTTOM = 1 << 6; + + /** + * The control will have the same width as the column it occupies. + */ + public static const int FILL = 1 << 7; + + /** + * In addition to filling width or height, the control will take part in + * allocation of any excess space. Note that this constant can only be + * passed to the constructor (cannot be directly assigned to + * <code>align</code> variable). + */ + public static const int FILL_GRAB = 1 << 8; + + /** + * Number of columns to span (default is 1). + */ + public int colspan = 1; + + /** + * Number of rows to span (default is 1). + */ + public int rowspan = 1; + + /** + * Horizontal alignment (LEFT, CENTER, RIGHT or FILL; default is LEFT). + */ + public int align_ = LEFT; + + /** + * Vertical alignment (TOP, MIDDLE, BOTTOM or FILL; default is TOP). + */ + public int valign = TOP; + + /** + * Horizontal indent (default is 0). + */ + public int indent = 0; + + /** + * Maximum width of the control (default is DWT.DEFAULT). + */ + public int maxWidth = DWT.DEFAULT; + + /** + * Maximum height of the control (default is DWT.DEFAULT). + */ + public int maxHeight = DWT.DEFAULT; + + /** + * Height hint of the control (default is DWT.DEFAULT). + */ + public int heightHint = DWT.DEFAULT; + + /** + * If <code>true</code>, take part in excess horizontal space + * distribution. (default is <code>false</code>). + */ + public bool grabHorizontal; + + /** + * If <code>true</code>, will grab any excess vertical space (default is + * <code>false</code>). Note that since TableWrapLayout works top-down + * and does not grows to fill the parent, this only applies to local excess + * space created by fixed-height children that span multiple rows. + */ + public bool grabVertical; + + int childIndex; + + bool isItemData = true; + + int compWidth; + + Point compSize; + + /** + * The default constructor. + */ + public this() { + } + + /** + * The convenience constructor - allows passing the horizontal alignment + * style. + * + * @param align + * horizontal alignment (LEFT, CENTER, RIGHT, FILL or FILL_GRAB). + */ + public this(int align_) { + this(align_, TOP, 1, 1); + } + + /** + * The convenience constructor - allows passing the alignment styles. + * + * @param align + * horizontal alignment (LEFT, CENTER, RIGHT, FILL or FILL_GRAB). + * @param valign + * vertical alignment (TOP, MIDDLE, BOTTOM, FILL or FILL_GRAB). + */ + public this(int align_, int valign) { + this(align_, valign, 1, 1); + } + + /** + * The convenience constructor - allows passing the alignment styles, column + * and row spans. + * + * @param align + * horizontal alignment (LEFT, CENTER, RIGHT, FILL or FILL_GRAB). + * @param valign + * vertical alignment (TOP, MIDDLE, BOTTOM, FILL or FILL_GRAB) + * @param rowspan + * row span (1 or more) + * @param colspan + * column span (1 or more) + */ + public this(int align_, int valign, int rowspan, int colspan) { + if (align_ !is LEFT && align_ !is CENTER && align_ !is RIGHT && align_ !is FILL + && align_ !is FILL_GRAB) + DWT.error(DWT.ERROR_INVALID_ARGUMENT, null, "align"); //$NON-NLS-1$ + if (valign !is TOP && valign !is MIDDLE && valign !is BOTTOM + && valign !is FILL && valign !is FILL_GRAB) + DWT.error(DWT.ERROR_INVALID_ARGUMENT, null, "valign"); //$NON-NLS-1$ + if (rowspan < 1) + DWT.error(DWT.ERROR_INVALID_ARGUMENT, null, "rowspan"); //$NON-NLS-1$ + if (colspan < 1) + DWT.error(DWT.ERROR_INVALID_ARGUMENT, null, "colspan"); //$NON-NLS-1$ + if (align_ is FILL_GRAB) { + this.align_ = FILL; + grabHorizontal = true; + } else + this.align_ = align_; + if (valign is FILL_GRAB) { + this.valign = FILL; + grabVertical = true; + } else + this.valign = valign; + this.rowspan = rowspan; + this.colspan = colspan; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/TableWrapLayout.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,888 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.widgets.TableWrapLayout; + +import dwtx.ui.forms.widgets.TableWrapData; +import dwtx.ui.forms.widgets.ILayoutExtension; +import dwtx.ui.forms.widgets.LayoutCache; +import dwtx.ui.forms.widgets.SizeCache; + +import dwt.DWT; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Layout; + +import dwt.dwthelper.utils; +import tango.util.collection.ArraySeq; +import tango.util.collection.HashMap; + +/** + * This implementation of the layout algorithm attempts to position controls in + * the composite using a two-pass autolayout HTML table altorithm recommeded by + * HTML 4.01 W3C specification (see + * http://www.w3.org/TR/html4/appendix/notes.html#h-B.5.2.2). The main + * differences with GridLayout is that it has two passes and that width and + * height are not calculated in the same pass. + * <p> + * The advantage of the algorithm over GridLayout is that it is capable of + * flowing text controls capable of line wrap. These controls do not have + * natural 'preferred size'. Instead, they are capable of providing the required + * height if the width is set. Consequently, this algorithm first calculates the + * widths that will be assigned to columns, and then passes those widths to the + * controls to calculate the height. When a composite with this layout is a + * child of the scrolling composite, they should interact in such a way that + * reduction in the scrolling composite width results in the reflow and increase + * of the overall height. + * <p> + * If none of the columns contain expandable and wrappable controls, the + * end-result will be similar to the one provided by GridLayout. The difference + * will show up for layouts that contain controls whose minimum and maximum + * widths are not the same. + * + * @see TableWrapData + * @since 3.0 + */ +public final class TableWrapLayout : Layout, ILayoutExtension { + + public alias Layout.computeSize computeSize; + /** + * Number of columns to use when positioning children (default is 1). + */ + public int numColumns = 1; + + /** + * Left margin variable (default is 5). + */ + public int leftMargin = 5; + + /** + * Right margin variable (default is 5). + */ + public int rightMargin = 5; + + /** + * Top margin variable (default is 5). + */ + public int topMargin = 5; + + /** + * Botom margin variable (default is 5). + */ + public int bottomMargin = 5; + + /** + * Horizontal spacing (default is 5). + */ + public int horizontalSpacing = 5; + + /** + * Vertical spacing (default is 5). + */ + public int verticalSpacing = 5; + + /** + * If set to <code>true</code>, all the columns will have the same width. + * Otherwise, column widths will be computed based on controls in them and + * their layout data (default is <code>false</code>). + */ + public bool makeColumnsEqualWidth = false; + + private bool initialLayout = true; + + private ArraySeq!(Object) grid = null; + + private HashMap!(Object,Object) rowspans; + + private int[] minColumnWidths, maxColumnWidths; + + private int widestColumnWidth; + + private int[] growingColumns; + + private int[] growingRows; + + private LayoutCache cache; + + private class RowSpan { + Control child; + + int row; + + int column; + + int height; + + int totalHeight; + + public this(Control child, int column, int row) { + this.child = child; + this.column = column; + this.row = row; + } + + /* + * Updates this row span's height with the given one if it is within + * this span. + */ + public void update(int currentRow, int rowHeight) { + TableWrapData td = cast(TableWrapData) child.getLayoutData(); + // is currentRow within this span? + if (currentRow >= row && currentRow < row + td.rowspan) { + totalHeight += rowHeight; + if (currentRow > row) + totalHeight += verticalSpacing; + } + } + + public int getRequiredHeightIncrease() { + if (totalHeight < height) + return height - totalHeight; + return 0; + } + } + + this(){ + cache = new LayoutCache(); + } + + /** + * Implements ILayoutExtension. Should not be called directly. + * + * @see ILayoutExtension + */ + public int computeMinimumWidth(Composite parent, bool changed) { + + Control[] children = parent.getChildren(); + if (changed) { + cache.flush(); + } + + cache.setControls(children); + + changed = true; + initializeIfNeeded(parent, changed); + if (initialLayout) { + changed = true; + initialLayout = false; + } + if (grid is null || changed) { + changed = true; + grid = new ArraySeq!(Object); + createGrid(parent); + } + if (minColumnWidths is null) + minColumnWidths = new int[numColumns]; + for (int i = 0; i < numColumns; i++) { + minColumnWidths[i] = 0; + } + return internalGetMinimumWidth(parent, changed); + } + + /** + * Implements ILayoutExtension. Should not be called directly. + * + * @see ILayoutExtension + */ + public int computeMaximumWidth(Composite parent, bool changed) { + Control[] children = parent.getChildren(); + if (changed) { + cache.flush(); + } + + cache.setControls(children); + + changed = true; + initializeIfNeeded(parent, changed); + if (initialLayout) { + changed = true; + initialLayout = false; + } + if (grid is null || changed) { + changed = true; + grid = new ArraySeq!(Object); + createGrid(parent); + } + if (maxColumnWidths is null) + maxColumnWidths = new int[numColumns]; + for (int i = 0; i < numColumns; i++) { + maxColumnWidths[i] = 0; + } + return internalGetMaximumWidth(parent, changed); + } + + /** + * @see Layout#layout(Composite, bool) + */ + protected void layout(Composite parent, bool changed) { + + Rectangle clientArea = parent.getClientArea(); + Control[] children = parent.getChildren(); + if (changed) { + cache.flush(); + } + + if (children.length is 0) + return; + + cache.setControls(children); + + int parentWidth = clientArea.width; + changed = true; + initializeIfNeeded(parent, changed); + if (initialLayout) { + changed = true; + initialLayout = false; + } + if (grid is null || changed) { + changed = true; + grid = new ArraySeq!(Object); + createGrid(parent); + } + resetColumnWidths(); + int minWidth = internalGetMinimumWidth(parent, changed); + int maxWidth = internalGetMaximumWidth(parent, changed); + int tableWidth = parentWidth; + int[] columnWidths; + if (parentWidth <= minWidth) { + tableWidth = minWidth; + if (makeColumnsEqualWidth) { + columnWidths = new int[numColumns]; + for (int i = 0; i < numColumns; i++) { + columnWidths[i] = widestColumnWidth; + } + } else + columnWidths = minColumnWidths; + } else if (parentWidth > maxWidth) { + if (growingColumns.length is 0) { + tableWidth = maxWidth; + columnWidths = maxColumnWidths; + } else { + columnWidths = new int[numColumns]; + int colSpace = tableWidth - leftMargin - rightMargin; + colSpace -= (numColumns - 1) * horizontalSpacing; + int extra = parentWidth - maxWidth; + int colExtra = extra / growingColumns.length; + for (int i = 0; i < numColumns; i++) { + columnWidths[i] = maxColumnWidths[i]; + if (isGrowingColumn(i)) { + columnWidths[i] += colExtra; + } + } + } + } else { + columnWidths = new int[numColumns]; + if (makeColumnsEqualWidth) { + int colSpace = tableWidth - leftMargin - rightMargin; + colSpace -= (numColumns - 1) * horizontalSpacing; + int col = colSpace / numColumns; + for (int i = 0; i < numColumns; i++) { + columnWidths[i] = col; + } + } else { + columnWidths = assignExtraSpace(tableWidth, maxWidth, minWidth); + } + } + int y = topMargin+clientArea.y; + int[] rowHeights = computeRowHeights(children, columnWidths, changed); + for (int i = 0; i < grid.size(); i++) { + int rowHeight = rowHeights[i]; + int x = leftMargin+clientArea.x; + TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array; + for (int j = 0; j < numColumns; j++) { + TableWrapData td = row[j]; + if (td.isItemData) { + Control child = children[td.childIndex]; + placeControl(child, td, x, y, rowHeights, i); + } + x += columnWidths[j]; + if (j < numColumns - 1) + x += horizontalSpacing; + } + y += rowHeight + verticalSpacing; + } + } + + int[] computeRowHeights(Control[] children, int[] columnWidths, + bool changed) { + int[] rowHeights = new int[grid.size()]; + for (int i = 0; i < grid.size(); i++) { + TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array; + rowHeights[i] = 0; + for (int j = 0; j < numColumns; j++) { + TableWrapData td = row[j]; + if (td.isItemData is false) { + continue; + } + Control child = children[td.childIndex]; + int span = td.colspan; + int cwidth = 0; + for (int k = j; k < j + span; k++) { + cwidth += columnWidths[k]; + if (k < j + span - 1) + cwidth += horizontalSpacing; + } + Point size = computeSize(td.childIndex, cwidth, td.indent, td.maxWidth, td.maxHeight); + td.compWidth = cwidth; + if (td.heightHint !is DWT.DEFAULT) { + size = new Point(size.x, td.heightHint); + } + td.compSize = size; + RowSpan rowspan = cast(RowSpan) rowspans.get(child); + if (rowspan is null) { + rowHeights[i] = Math.max(rowHeights[i], size.y); + } else + rowspan.height = size.y; + } + updateRowSpans(i, rowHeights[i]); + } + foreach( k, v; rowspans ){ + RowSpan rowspan = cast(RowSpan) v; + int increase = rowspan.getRequiredHeightIncrease(); + if (increase is 0) + continue; + TableWrapData td = cast(TableWrapData) rowspan.child.getLayoutData(); + int ngrowing = 0; + int[] affectedRows = new int[grid.size()]; + for (int i = 0; i < growingRows.length; i++) { + int growingRow = growingRows[i]; + if (growingRow >= rowspan.row + && growingRow < rowspan.row + td.rowspan) { + affectedRows[ngrowing++] = growingRow; + } + } + if (ngrowing is 0) { + ngrowing = 1; + affectedRows[0] = rowspan.row + td.rowspan - 1; + } + increase += increase % ngrowing; + int perRowIncrease = increase / ngrowing; + for (int i = 0; i < ngrowing; i++) { + int growingRow = affectedRows[i]; + rowHeights[growingRow] += perRowIncrease; + } + } + return rowHeights; + } + + bool isGrowingColumn(int col) { + if (growingColumns is null) + return false; + for (int i = 0; i < growingColumns.length; i++) { + if (col is growingColumns[i]) + return true; + } + return false; + } + + int[] assignExtraSpace(int tableWidth, int maxWidth, int minWidth) { + int fixedPart = leftMargin + rightMargin + (numColumns - 1) + * horizontalSpacing; + int D = maxWidth - minWidth; + int W = tableWidth - fixedPart - minWidth; + int widths[] = new int[numColumns]; + int rem = 0; + for (int i = 0; i < numColumns; i++) { + int cmin = minColumnWidths[i]; + int cmax = maxColumnWidths[i]; + int d = cmax - cmin; + int extra = D !is 0 ? (d * W) / D : 0; + if (i < numColumns - 1) { + widths[i] = cmin + extra; + rem += widths[i]; + } else { + widths[i] = tableWidth - fixedPart - rem; + } + } + return widths; + } + + Point computeSize(int childIndex, int width, int indent, int maxWidth, int maxHeight) { + int widthArg = width - indent; + SizeCache controlCache = cache.getCache(childIndex); + if (!isWrap(controlCache.getControl())) + widthArg = DWT.DEFAULT; + Point size = controlCache.computeSize(widthArg, DWT.DEFAULT); + if (maxWidth !is DWT.DEFAULT) + size.x = Math.min(size.x, maxWidth); + if (maxHeight !is DWT.DEFAULT) + size.y = Math.min(size.y, maxHeight); + size.x += indent; + return size; + } + + void placeControl(Control control, TableWrapData td, int x, int y, + int[] rowHeights, int row) { + int xloc = x + td.indent; + int yloc = y; + int height = td.compSize.y; + int colWidth = td.compWidth - td.indent; + int width = td.compSize.x-td.indent; + width = Math.min(width, colWidth); + int slotHeight = rowHeights[row]; + RowSpan rowspan = cast(RowSpan) rowspans.get(control); + if (rowspan !is null) { + slotHeight = 0; + for (int i = row; i < row + td.rowspan; i++) { + if (i > row) + slotHeight += verticalSpacing; + slotHeight += rowHeights[i]; + } + } + // align horizontally + if (td.align_ is TableWrapData.CENTER) { + xloc = x + colWidth / 2 - width / 2; + } else if (td.align_ is TableWrapData.RIGHT) { + xloc = x + colWidth - width; + } else if (td.align_ is TableWrapData.FILL) { + width = colWidth; + } + // align vertically + if (td.valign is TableWrapData.MIDDLE) { + yloc = y + slotHeight / 2 - height / 2; + } else if (td.valign is TableWrapData.BOTTOM) { + yloc = y + slotHeight - height; + } else if (td.valign is TableWrapData.FILL) { + height = slotHeight; + } + control.setBounds(xloc, yloc, width, height); + } + + void createGrid(Composite composite) { + int row, column, rowFill, columnFill; + Control[] children; + TableWrapData spacerSpec; + ArraySeq!(Object) growingCols = new ArraySeq!(Object); + ArraySeq!(Object) growingRows = new ArraySeq!(Object); + rowspans = new HashMap!(Object,Object); + // + children = composite.getChildren(); + if (children.length is 0) + return; + // + grid.append( new ArrayWrapperT!(TableWrapData)(createEmptyRow())); + row = 0; + column = 0; + // Loop through the children and place their associated layout specs in + // the + // grid. Placement occurs left to right, top to bottom (i.e., by row). + for (int i = 0; i < children.length; i++) { + // Find the first available spot in the grid. + Control child = children[i]; + TableWrapData spec = cast(TableWrapData) child.getLayoutData(); + while ((cast(ArrayWrapperT!(TableWrapData)) grid.get(row)).array[column] !is null) { + column = column + 1; + if (column >= numColumns) { + row = row + 1; + column = 0; + if (row >= grid.size()) { + grid.append(new ArrayWrapperT!(TableWrapData)(createEmptyRow())); + } + } + } + // See if the place will support the widget's horizontal span. If + // not, go to the + // next row. + if (column + spec.colspan - 1 >= numColumns) { + grid.append(new ArrayWrapperT!(TableWrapData)(createEmptyRow())); + row = row + 1; + column = 0; + } + // The vertical span for the item will be at least 1. If it is > 1, + // add other rows to the grid. + if (spec.rowspan > 1) { + rowspans.add(child, new RowSpan(child, column, row)); + } + for (int j = 2; j <= spec.rowspan; j++) { + if (row + j > grid.size()) { + grid.append(new ArrayWrapperT!(TableWrapData)(createEmptyRow())); + } + } + // Store the layout spec. Also cache the childIndex. NOTE: That we + // assume the children of a + // composite are maintained in the order in which they are created + // and added to the composite. + (cast(ArrayWrapperT!(TableWrapData)) grid.get(row)).array[column] = spec; + spec.childIndex = i; + if (spec.grabHorizontal) { + updateGrowingColumns(growingCols, spec, column); + } + if (spec.grabVertical) { + updateGrowingRows(growingRows, spec, row); + } + // Put spacers in the grid to account for the item's vertical and + // horizontal + // span. + rowFill = spec.rowspan - 1; + columnFill = spec.colspan - 1; + for (int r = 1; r <= rowFill; r++) { + for (int c = 0; c < spec.colspan; c++) { + spacerSpec = new TableWrapData(); + spacerSpec.isItemData = false; + (cast(ArrayWrapperT!(TableWrapData)) grid.get(row + r)).array[column + c] = spacerSpec; + } + } + for (int c = 1; c <= columnFill; c++) { + for (int r = 0; r < spec.rowspan; r++) { + spacerSpec = new TableWrapData(); + spacerSpec.isItemData = false; + (cast(ArrayWrapperT!(TableWrapData)) grid.get(row + r)).array[column + c] = spacerSpec; + } + } + column = column + spec.colspan - 1; + } + // Fill out empty grid cells with spacers. + for (int k = column + 1; k < numColumns; k++) { + spacerSpec = new TableWrapData(); + spacerSpec.isItemData = false; + (cast(ArrayWrapperT!(TableWrapData)) grid.get(row)).array[k] = spacerSpec; + } + for (int k = row + 1; k < grid.size(); k++) { + spacerSpec = new TableWrapData(); + spacerSpec.isItemData = false; + (cast(ArrayWrapperT!(TableWrapData)) grid.get(k)).array[column] = spacerSpec; + } + growingColumns = new int[growingCols.size()]; + for (int i = 0; i < growingCols.size(); i++) { + growingColumns[i] = (cast(Integer) growingCols.get(i)).intValue(); + } + this.growingRows = new int[growingRows.size()]; + for (int i = 0; i < growingRows.size(); i++) { + this.growingRows[i] = (cast(Integer) growingRows.get(i)).intValue(); + } + } + + private void updateGrowingColumns(ArraySeq!(Object) growingColumns, + TableWrapData spec, int column) { + int affectedColumn = column + spec.colspan - 1; + for (int i = 0; i < growingColumns.size(); i++) { + Integer col = cast(Integer) growingColumns.get(i); + if (col.intValue() is affectedColumn) + return; + } + growingColumns.append(new Integer(affectedColumn)); + } + + private void updateGrowingRows(ArraySeq!(Object) growingRows, TableWrapData spec, + int row) { + int affectedRow = row + spec.rowspan - 1; + for (int i = 0; i < growingRows.size(); i++) { + Integer irow = cast(Integer) growingRows.get(i); + if (irow.intValue() is affectedRow) + return; + } + growingRows.append(new Integer(affectedRow)); + } + + private TableWrapData[] createEmptyRow() { + TableWrapData[] row = new TableWrapData[numColumns]; + for (int i = 0; i < numColumns; i++) + row[i] = null; + return row; + } + + /** + * @see Layout#computeSize(Composite, int, int, bool) + */ + /+protected+/ override Point computeSize(Composite parent, int wHint, int hHint, + bool changed) { + Control[] children = parent.getChildren(); + if (changed) { + cache.flush(); + } + if (children.length is 0) { + return new Point(0, 0); + } + cache.setControls(children); + + int parentWidth = wHint; + changed = true; + initializeIfNeeded(parent, changed); + if (initialLayout) { + changed = true; + initialLayout = false; + } + if (grid is null || changed) { + changed = true; + grid = new ArraySeq!(Object); + createGrid(parent); + } + resetColumnWidths(); + int minWidth = internalGetMinimumWidth(parent, changed); + int maxWidth = internalGetMaximumWidth(parent, changed); + + if (wHint is DWT.DEFAULT) + parentWidth = maxWidth; + + int tableWidth = parentWidth; + int[] columnWidths; + if (parentWidth <= minWidth) { + tableWidth = minWidth; + if (makeColumnsEqualWidth) { + columnWidths = new int[numColumns]; + for (int i = 0; i < numColumns; i++) { + columnWidths[i] = widestColumnWidth; + } + } else + columnWidths = minColumnWidths; + } else if (parentWidth >= maxWidth) { + if (makeColumnsEqualWidth) { + columnWidths = new int[numColumns]; + int colSpace = parentWidth - leftMargin - rightMargin; + colSpace -= (numColumns - 1) * horizontalSpacing; + int col = colSpace / numColumns; + for (int i = 0; i < numColumns; i++) { + columnWidths[i] = col; + } + } else { + tableWidth = maxWidth; + columnWidths = maxColumnWidths; + } + } else { + columnWidths = new int[numColumns]; + if (makeColumnsEqualWidth) { + int colSpace = tableWidth - leftMargin - rightMargin; + colSpace -= (numColumns - 1) * horizontalSpacing; + int col = colSpace / numColumns; + for (int i = 0; i < numColumns; i++) { + columnWidths[i] = col; + } + } else { + columnWidths = assignExtraSpace(tableWidth, maxWidth, minWidth); + } + } + int totalHeight = 0; + int innerHeight = 0; + // compute widths + for (int i = 0; i < grid.size(); i++) { + TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array; + // assign widths, calculate heights + int rowHeight = 0; + for (int j = 0; j < numColumns; j++) { + TableWrapData td = row[j]; + if (td.isItemData is false) { + continue; + } + Control child = children[td.childIndex]; + int span = td.colspan; + int cwidth = 0; + for (int k = j; k < j + span; k++) { + if (k > j) + cwidth += horizontalSpacing; + cwidth += columnWidths[k]; + } + int cy = td.heightHint; + if (cy is DWT.DEFAULT) { + Point size = computeSize(td.childIndex, cwidth, td.indent, td.maxWidth, td.maxHeight); + cy = size.y; + } + RowSpan rowspan = cast(RowSpan) rowspans.get(child); + if (rowspan !is null) { + // don't take the height of this child into acount + // because it spans multiple rows + rowspan.height = cy; + } else { + rowHeight = Math.max(rowHeight, cy); + } + } + updateRowSpans(i, rowHeight); + if (i > 0) + innerHeight += verticalSpacing; + innerHeight += rowHeight; + } + if (!rowspans.drained()) + innerHeight = compensateForRowSpans(innerHeight); + totalHeight = topMargin + innerHeight + bottomMargin; + return new Point(tableWidth, totalHeight); + } + + private void updateRowSpans(int row, int rowHeight) { + if (rowspans is null || rowspans.size() is 0) + return; + foreach( k, v; rowspans ){ + RowSpan rowspan = cast(RowSpan) v; + rowspan.update(row, rowHeight); + } + } + + private int compensateForRowSpans(int totalHeight) { + foreach( k, v; rowspans ){ + RowSpan rowspan = cast(RowSpan) v; + totalHeight += rowspan.getRequiredHeightIncrease(); + } + return totalHeight; + } + + int internalGetMinimumWidth(Composite parent, bool changed) { + if (changed) + //calculateMinimumColumnWidths(parent, true); + calculateColumnWidths(parent, minColumnWidths, false, true); + int minimumWidth = 0; + widestColumnWidth = 0; + if (makeColumnsEqualWidth) { + for (int i = 0; i < numColumns; i++) { + widestColumnWidth = Math.max(widestColumnWidth, + minColumnWidths[i]); + } + } + for (int i = 0; i < numColumns; i++) { + if (i > 0) + minimumWidth += horizontalSpacing; + if (makeColumnsEqualWidth) + minimumWidth += widestColumnWidth; + else + minimumWidth += minColumnWidths[i]; + } + // add margins + minimumWidth += leftMargin + rightMargin; + return minimumWidth; + } + + int internalGetMaximumWidth(Composite parent, bool changed) { + if (changed) + //calculateMaximumColumnWidths(parent, true); + calculateColumnWidths(parent, maxColumnWidths, true, true); + int maximumWidth = 0; + for (int i = 0; i < numColumns; i++) { + if (i > 0) + maximumWidth += horizontalSpacing; + maximumWidth += maxColumnWidths[i]; + } + // add margins + maximumWidth += leftMargin + rightMargin; + return maximumWidth; + } + + void resetColumnWidths() { + if (minColumnWidths is null) + minColumnWidths = new int[numColumns]; + if (maxColumnWidths is null) + maxColumnWidths = new int[numColumns]; + for (int i = 0; i < numColumns; i++) { + minColumnWidths[i] = 0; + } + for (int i = 0; i < numColumns; i++) { + maxColumnWidths[i] = 0; + } + } + + void calculateColumnWidths(Composite parent, int [] columnWidths, bool max, bool changed) { + bool secondPassNeeded=false; + for (int i = 0; i < grid.size(); i++) { + TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array; + for (int j = 0; j < numColumns; j++) { + TableWrapData td = row[j]; + if (td.isItemData is false) + continue; + + if (td.colspan>1) { + // we will not do controls with multiple column span + // here - increment and continue + secondPassNeeded=true; + j+=td.colspan-1; + continue; + } + + SizeCache childCache = cache.getCache(td.childIndex); + // !! + int width = max?childCache.computeMaximumWidth():childCache.computeMinimumWidth(); + if (td.maxWidth !is DWT.DEFAULT) + width = Math.min(width, td.maxWidth); + + width += td.indent; + columnWidths[j] = Math.max(columnWidths[j], width); + } + } + if (!secondPassNeeded) return; + + // Second pass for controls with multi-column horizontal span + for (int i = 0; i < grid.size(); i++) { + TableWrapData[] row = (cast(ArrayWrapperT!(TableWrapData)) grid.get(i)).array; + for (int j = 0; j < numColumns; j++) { + TableWrapData td = row[j]; + if (td.isItemData is false || td.colspan is 1) + continue; + + SizeCache childCache = cache.getCache(td.childIndex); + int width = max?childCache.computeMaximumWidth():childCache.computeMinimumWidth(); + if (td.maxWidth !is DWT.DEFAULT) + width = Math.min(width, td.maxWidth); + + width += td.indent; + // check if the current width is enough to + // support the control; if not, add the delta to + // the last column or to all the growing columns, if present + int current = 0; + for (int k = j; k < j + td.colspan; k++) { + if (k > j) + current += horizontalSpacing; + current += columnWidths[k]; + } + if (width <= current) { + // we are ok - nothing to do here + } else { + int ndiv = 0; + if (growingColumns !is null) { + for (int k = j; k < j + td.colspan; k++) { + if (isGrowingColumn(k)) { + ndiv++; + } + } + } + if (ndiv is 0) { + // add the delta to the last column + columnWidths[j + td.colspan - 1] += width + - current; + } else { + // distribute the delta to the growing + // columns + int percolumn = (width - current) / ndiv; + if ((width - current) % ndiv > 0) + percolumn++; + for (int k = j; k < j + td.colspan; k++) { + if (isGrowingColumn(k)) + columnWidths[k] += percolumn; + } + } + } + } + } + } + + bool isWrap(Control control) { + if (null !is cast(Composite)control + && null !is cast(ILayoutExtension)((cast(Composite) control).getLayout()) ) + return true; + return (control.getStyle() & DWT.WRAP) !is 0; + } + + private void initializeIfNeeded(Composite parent, bool changed) { + if (changed) + initialLayout = true; + if (initialLayout) { + initializeLayoutData(parent); + initialLayout = false; + } + } + + void initializeLayoutData(Composite composite) { + Control[] children = composite.getChildren(); + for (int i = 0; i < children.length; i++) { + Control child = children[i]; + if (child.getLayoutData() is null) { + child.setLayoutData(new TableWrapData()); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/ToggleHyperlink.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.widgets.ToggleHyperlink; + +import dwtx.ui.forms.widgets.AbstractHyperlink; + +import dwt.DWT; +import dwt.accessibility.ACC; +import dwt.accessibility.AccessibleAdapter; +import dwt.accessibility.AccessibleControlAdapter; +import dwt.accessibility.AccessibleControlEvent; +import dwt.accessibility.AccessibleEvent; +import dwt.graphics.Color; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwtx.ui.forms.events.HyperlinkAdapter; +import dwtx.ui.forms.events.HyperlinkEvent; +import dwtx.ui.internal.forms.Messages; + +import dwt.dwthelper.utils; + +/** + * A custom selectable control that can be used to control areas that can be + * expanded or collapsed. + * <p> + * This is an abstract class. Subclasses are responsible for rendering the + * control using decoration and hover decoration color. Control should be + * rendered based on the current expansion state. + * + * @since 3.0 + */ +public abstract class ToggleHyperlink : AbstractHyperlink { + protected int innerWidth; + protected int innerHeight; + protected bool hover; + package bool hover_package(){ + return hover; + } + package bool hover_package( bool v){ + return (hover = v); + } + private bool expanded; + private Color decorationColor; + private Color hoverColor; + /** + * Creates a control in a provided composite. + * + * @param parent + * the parent + * @param style + * the style + */ + public this(Composite parent, int style) { + super(parent, style); + Listener listener = new class Listener { + public void handleEvent(Event e) { + switch (e.type) { + case DWT.MouseEnter: + hover=true; + redraw(); + break; + case DWT.MouseExit: + hover = false; + redraw(); + break; + case DWT.KeyDown: + onKeyDown(e); + break; + } + } + }; + addListener(DWT.MouseEnter, listener); + addListener(DWT.MouseExit, listener); + addListener(DWT.KeyDown, listener); + addHyperlinkListener(new class HyperlinkAdapter { + public void linkActivated(HyperlinkEvent e) { + setExpanded(!isExpanded()); + } + }); + initAccessible(); + } + /** + * Sets the color of the decoration. + * + * @param decorationColor + */ + public void setDecorationColor(Color decorationColor) { + this.decorationColor = decorationColor; + } + /** + * Returns the color of the decoration. + * + * @return decoration color + */ + public Color getDecorationColor() { + return decorationColor; + } + /** + * Sets the hover color of decoration. Hover color will be used when mouse + * enters the decoration area. + * + * @param hoverColor + * the hover color to use + */ + public void setHoverDecorationColor(Color hoverColor) { + this.hoverColor = hoverColor; + } + /** + * Returns the hover color of the decoration. + * + * @return the hover color of the decoration. + * @since 3.1 + */ + public Color getHoverDecorationColor() { + return hoverColor; + } + + /** + * Returns the hover color of the decoration. + * + * @return the hover color of the decoration. + * @deprecated use <code>getHoverDecorationColor</code> + * @see #getHoverDecorationColor() + */ + public Color geHoverDecorationColor() { + return hoverColor; + } + /** + * Computes the size of the control. + * + * @param wHint + * width hint + * @param hHint + * height hint + * @param changed + * if true, flush any saved layout state + */ + public Point computeSize(int wHint, int hHint, bool changed) { + int width, height; + /* + if (wHint !is DWT.DEFAULT) + width = wHint; + else */ + width = innerWidth + 2 * marginWidth; + /* + if (hHint !is DWT.DEFAULT) + height = hHint; + else */ + height = innerHeight + 2 * marginHeight; + return new Point(width, height); + } + /** + * Returns the expansion state of the toggle control. When toggle is in the + * normal (downward) state, the value is <samp>true </samp>. Collapsed + * control will return <samp>false </samp>. + * + * @return <samp>false </samp> if collapsed, <samp>true </samp> otherwise. + */ + public bool isExpanded() { + return expanded; + } + /** + * Sets the expansion state of the twistie control + * + * @param expanded the expansion state + */ + public void setExpanded(bool expanded) { + this.expanded = expanded; + getAccessible().selectionChanged(); + redraw(); + } + private void initAccessible() { + getAccessible().addAccessibleListener(new class AccessibleAdapter { + public void getHelp(AccessibleEvent e) { + e.result = getToolTipText(); + } + public void getName(AccessibleEvent e) { + String name=Messages.ToggleHyperlink_accessibleName; + if (null !is cast(ExpandableComposite)getParent() ) { + name ~= Messages.ToggleHyperlink_accessibleColumn ~ (cast(ExpandableComposite)getParent()).getText(); + int index = name.indexOf('&'); + if (index !is -1) { + name = name.substring(0, index) + name.substring(index + 1); + } + } + e.result = name; + } + public void getDescription(AccessibleEvent e) { + getName(e); + } + }); + getAccessible().addAccessibleControlListener( + new class AccessibleControlAdapter { + public void getChildAtPoint(AccessibleControlEvent e) { + Point testPoint = toControl(new Point(e.x, e.y)); + if (getBounds().contains(testPoint)) { + e.childID = ACC.CHILDID_SELF; + } + } + public void getLocation(AccessibleControlEvent e) { + Rectangle location = getBounds(); + Point pt = toDisplay(new Point(location.x, location.y)); + e.x = pt.x; + e.y = pt.y; + e.width = location.width; + e.height = location.height; + } + public void getSelection (AccessibleControlEvent e) { + if (this.outer.getSelection()) + e.childID = ACC.CHILDID_SELF; + } + + public void getFocus (AccessibleControlEvent e) { + if (this.outer.getSelection()) + e.childID = ACC.CHILDID_SELF; + } + public void getChildCount(AccessibleControlEvent e) { + e.detail = 0; + } + public void getRole(AccessibleControlEvent e) { + e.detail = ACC.ROLE_TREE; + } + public void getState(AccessibleControlEvent e) { + int state = ACC.STATE_FOCUSABLE; + if (this.outer.getSelection()) + state |= ACC.STATE_FOCUSED; + state |= this.outer.isExpanded() + ? ACC.STATE_EXPANDED + : ACC.STATE_COLLAPSED; + e.detail = state; + } + }); + } + private void onKeyDown(Event e) { + if (e.keyCodeisDWT.ARROW_RIGHT) { + // expand if collapsed + if (!isExpanded()) { + handleActivate(e); + } + e.doit=false; + } + else if (e.keyCodeisDWT.ARROW_LEFT) { + // collapse if expanded + if (isExpanded()) { + handleActivate(e); + } + e.doit=false; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/TreeNode.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.forms.widgets.TreeNode; + +import dwtx.ui.forms.widgets.ToggleHyperlink; + +import dwt.DWT; +import dwt.events.PaintEvent; +import dwt.graphics.GC; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; + +import dwt.dwthelper.utils; + +/** + * A custom selectable control that can be used to control areas that can be + * expanded or collapsed. The control control can be toggled between selected + * and deselected state with a mouse or by pressing 'Enter' while the control + * has focus. + * <p> + * The control is rendered as box with a '+' or '-' sign, depending on the + * expansion state. Focus indication is rendered around the box when the + * control has keyboard focus. + * + * @see Twistie + * @since 3.0 + */ +public class TreeNode : ToggleHyperlink { + /** + * Creates a control in a provided composite. + * + * @param parent + * the parent + * @param style + * the style + */ + public this(Composite parent, int style) { + super(parent, style); + innerWidth = 10; + innerHeight = 10; + } + protected void paint(PaintEvent e) { + paintHyperlink(e.gc); + } + protected void paintHyperlink(GC gc) { + Rectangle box = getBoxBounds(gc); + gc.setForeground(getDisplay().getSystemColor( + DWT.COLOR_WIDGET_NORMAL_SHADOW)); + gc.drawRectangle(box); + gc.setForeground(getForeground()); + gc.drawLine(box.x + 2, box.y + 4, box.x + 6, box.y + 4); + if (!isExpanded()) { + gc.drawLine(box.x + 4, box.y + 2, box.x + 4, box.y + 6); + } + if (paintFocus && getSelection()) { + gc.setForeground(getForeground()); + gc.drawFocus(box.x - 1, box.y - 1, box.width + 3, box.height + 3); + } + } + private Rectangle getBoxBounds(GC gc) { + int x = 1; + int y = 0; + gc.setFont(getFont()); + //int height = gc.getFontMetrics().getHeight(); + //y = height / 2 - 4; + //y = Math.max(y, 0); + y = 2; + return new Rectangle(x, y, 8, 8); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/forms/widgets/Twistie.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.forms.widgets.Twistie; + +import dwtx.ui.forms.widgets.ToggleHyperlink; + +import dwt.DWT; +import dwt.graphics.Color; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.widgets.Composite; + +import dwt.dwthelper.utils; + +/** + * A custom selectable control that can be used to control areas that can be + * expanded or collapsed. The control control can be toggled between selected + * and deselected state with a mouse or by pressing 'Enter' while the control + * has focus. + * <p> + * The control is rendered as a triangle that points to the right in the + * collapsed and down in the expanded state. Triangle color can be changed. + * + * @see TreeNode + * @since 3.0 + */ +public class Twistie : ToggleHyperlink { + private static const int[] onPoints = [ 0, 2, 8, 2, 4, 6 ]; + + private static const int[] offPoints = [ 2, -1, 2, 8, 6, 4 ]; + + /** + * Creates a control in a provided composite. + * + * @param parent + * the parent + * @param style + * the style + */ + public this(Composite parent, int style) { + super(parent, style); + innerWidth = 9; + innerHeight = 9; + } + + /* + * @see SelectableControl#paint(GC) + */ + protected void paintHyperlink(GC gc) { + Color bg; + if (!isEnabled()) + bg = getDisplay().getSystemColor(DWT.COLOR_WIDGET_NORMAL_SHADOW); + else if (hover && getHoverDecorationColor() !is null) + bg = getHoverDecorationColor(); + else if (getDecorationColor() !is null) + bg = getDecorationColor(); + else + bg = getForeground(); + gc.setBackground(bg); + int[] data; + Point size = getSize(); + int x = (size.x - 9) / 2; + int y = (size.y - 9) / 2; + if (isExpanded()) + data = translate(onPoints, x, y); + else + data = translate(offPoints, x, y); + gc.fillPolygon(data); + gc.setBackground(getBackground()); + } + + private int[] translate(int[] data, int x, int y) { + int[] target = new int[data.length]; + for (int i = 0; i < data.length; i += 2) { + target[i] = data[i] + x; + } + for (int i = 1; i < data.length; i += 2) { + target[i] = data[i] + y; + } + return target; + } + + /* + * (non-Javadoc) + * + * @see dwt.widgets.Control#setEnabled(bool) + */ + public void setEnabled(bool enabled) { + super.setEnabled(enabled); + redraw(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/IMessageToolTipManager.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2007 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.ui.internal.forms.IMessageToolTipManager; + + +import dwt.widgets.Control; +import dwtx.ui.forms.IMessageManager; +import dwtx.ui.forms.widgets.Form; + +import dwt.dwthelper.utils; + +/** + * The classes that implement this interface are responsible for managing custom + * tool tips for message-related controls in the form header. By default, a + * simple manager is installed by the form that uses the built-in widget tool + * tips for this purpose. Clients can replace this behaviour with richer tool + * tips that show images, links and other content. + * <p> + * The message-related controls in the header are: + * <ul> + * <li>Image label - used to replace the form title image with the message type + * image</li> + * <li>Message label - renders the message as a static text.</li> + * <li>Message hyperlink - renders the message as a hyperlink.</li> + * </ul> + * The message manager will be asked to create the tool tip for any and all of + * the controls listed above in its factory method. After that, it will be asked + * to update whenever the message information changes in the form. For this + * reason, the manager is expected to retain references to the tool tips and + * update them with new content when asked. + * + * @see IMessageManager + * @see Form + * <p> + * <strong>EXPERIMENTAL</strong>. This class or interface has been added + * as part of a work in progress. There is no guarantee that this API will + * work or that it will remain the same. Please do not use this API without + * consulting with the Platform UA team. + * </p> + * @since 3.3 + */ +public interface IMessageToolTipManager { + /** + * Creates the custom tool tip for the provided control. + * + * @param control + * the control for which to create a custom tool tip + * @param imageControl + * <code>true</code> if the control is used to render the title + * image, <code>false</code> otherwise. + */ + void createToolTip(Control control, bool imageControl); + + /** + * Updates all the managed tool tips. The manager should get the current + * message, message type and optional children messages from the form to + * update the custom tool tips. + */ + void update(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/MessageManager.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,650 @@ +/******************************************************************************* + * Copyright (c) 2007 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.ui.internal.forms.MessageManager; + +import dwtx.ui.internal.forms.Messages; + +import dwt.DWT; +import dwt.custom.CLabel; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Label; +import dwtx.jface.dialogs.IMessageProvider; +import dwtx.jface.fieldassist.ControlDecoration; +import dwtx.jface.fieldassist.FieldDecoration; +import dwtx.jface.fieldassist.FieldDecorationRegistry; +import dwtx.ui.forms.IMessage; +import dwtx.ui.forms.IMessageManager; +import dwtx.ui.forms.IMessagePrefixProvider; +import dwtx.ui.forms.widgets.Hyperlink; +import dwtx.ui.forms.widgets.ScrolledForm; + +import dwt.dwthelper.utils; + +import tango.util.collection.ArraySeq; +import tango.util.collection.HashMap; +import tango.util.Convert; + +/** + * @see IMessageManager + */ + +public class MessageManager : IMessageManager { + + private static final DefaultPrefixProvider DEFAULT_PREFIX_PROVIDER_; + private static final DefaultPrefixProvider DEFAULT_PREFIX_PROVIDER(){ + if( DEFAULT_PREFIX_PROVIDER_ is null ){ + synchronized(MessageManager.classinfo){ + if( DEFAULT_PREFIX_PROVIDER_ is null ){ + DEFAULT_PREFIX_PROVIDER_ = new DefaultPrefixProvider(); + } + } + } + return DEFAULT_PREFIX_PROVIDER_; + } + private ArraySeq!(Object) messages; + private HashMap!(Object,Object) decorators; + private bool autoUpdate = true; + private ScrolledForm scrolledForm; + private IMessagePrefixProvider prefixProvider; + private int decorationPosition = DWT.LEFT | DWT.BOTTOM; + + private static FieldDecoration standardError_; + private static FieldDecoration standardError(){ + if( standardError_ is null ){ + synchronized(MessageManager.classinfo){ + if( standardError_ is null ){ + standardError_ = FieldDecorationRegistry + .getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_ERROR); + } + } + } + return standardError_; + } + + private static FieldDecoration standardWarning_; + private static FieldDecoration standardWarning(){ + if( standardWarning_ is null ){ + synchronized(MessageManager.classinfo){ + if( standardWarning_ is null ){ + standardWarning_ = FieldDecorationRegistry + .getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_WARNING); + } + } + } + return standardWarning_; + } + + private static FieldDecoration standardInformation_; + private static FieldDecoration standardInformation(){ + if( standardInformation_ is null ){ + synchronized(MessageManager.classinfo){ + if( standardInformation_ is null ){ + standardInformation_ = FieldDecorationRegistry + .getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION); + } + } + } + return standardInformation_; + } + + private static const String[] SINGLE_MESSAGE_SUMMARY_KEYS = [ + Messages.MessageManager_sMessageSummary, + Messages.MessageManager_sMessageSummary, + Messages.MessageManager_sWarningSummary, + Messages.MessageManager_sErrorSummary ]; + + private static const String[] MULTIPLE_MESSAGE_SUMMARY_KEYS = [ + Messages.MessageManager_pMessageSummary, + Messages.MessageManager_pMessageSummary, + Messages.MessageManager_pWarningSummary, + Messages.MessageManager_pErrorSummary ]; + + static class Message : IMessage { + Control control; + Object data; + Object key; + String message; + int type; + String prefix; + + this(Object key, String message, int type, Object data) { + this.key = key; + this.message = message; + this.type = type; + this.data = data; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.IMessage#getKey() + */ + public Object getKey() { + return key; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.IMessageProvider#getMessage() + */ + public String getMessage() { + return message; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.dialogs.IMessageProvider#getMessageType() + */ + public int getMessageType() { + return type; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.messages.IMessage#getControl() + */ + public Control getControl() { + return control; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.messages.IMessage#getData() + */ + public Object getData() { + return data; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.messages.IMessage#getPrefix() + */ + public String getPrefix() { + return prefix; + } + } + + static class DefaultPrefixProvider : IMessagePrefixProvider { + + public String getPrefix(Control c) { + Composite parent = c.getParent(); + Control[] siblings = parent.getChildren(); + for (int i = 0; i < siblings.length; i++) { + if (siblings[i] is c) { + // this is us - go backward until you hit + // a label-like widget + for (int j = i - 1; j >= 0; j--) { + Control label = siblings[j]; + String ltext = null; + if ( auto l = cast(Label)label ) { + ltext = l.getText(); + } else if ( auto hl = cast(Hyperlink)label ) { + ltext = hl.getText(); + } else if ( auto cl = cast(CLabel)label ) { + ltext = cl.getText(); + } + if (ltext !is null) { + if (!ltext.endsWith(":")) //$NON-NLS-1$ + return ltext ~ ": "; //$NON-NLS-1$ + return ltext ~ " "; //$NON-NLS-1$ + } + } + break; + } + } + return null; + } + } + + class ControlDecorator { + private ControlDecoration decoration; + private ArraySeq!(Message) controlMessages = new ArraySeq!(Message); + private String prefix; + + this(Control control) { + this.decoration = new ControlDecoration(control, decorationPosition, scrolledForm.getBody()); + } + + public bool isDisposed() { + return decoration.getControl() is null; + } + + void updatePrefix() { + prefix = null; + } + + void updatePosition() { + Control control = decoration.getControl(); + decoration.dispose(); + this.decoration = new ControlDecoration(control, decorationPosition, scrolledForm.getBody()); + update(); + } + + String getPrefix() { + if (prefix is null) + createPrefix(); + return prefix; + } + + private void createPrefix() { + if (prefixProvider is null) { + prefix = ""; //$NON-NLS-1$ + return; + } + prefix = prefixProvider.getPrefix(decoration.getControl()); + if (prefix is null) + // make a prefix anyway + prefix = ""; //$NON-NLS-1$ + } + + void addAll(ArraySeq!(Object) target) { + target.addAll(controlMessages); + } + + void addMessage(Object key, String text, Object data, int type) { + Message message = this.outer.addMessage(getPrefix(), key, + text, data, type, controlMessages); + message.control = decoration.getControl(); + if (isAutoUpdate()) + update(); + } + + bool removeMessage(Object key) { + Message message = findMessage(key, controlMessages); + if (message !is null) { + controlMessages.remove(message); + if (isAutoUpdate()) + update(); + } + return message !is null; + } + + bool removeMessages() { + if (controlMessages.isEmpty()) + return false; + controlMessages.clear(); + if (isAutoUpdate()) + update(); + return true; + } + + public void update() { + if (controlMessages.isEmpty()) { + decoration.setDescriptionText(null); + decoration.hide(); + } else { + auto peers = createPeers(controlMessages); + int type = (cast(IMessage) peers.get(0)).getMessageType(); + String description = createDetails(createPeers(peers), true); + if (type is IMessageProvider.ERROR) + decoration.setImage(standardError.getImage()); + else if (type is IMessageProvider.WARNING) + decoration.setImage(standardWarning.getImage()); + else if (type is IMessageProvider.INFORMATION) + decoration.setImage(standardInformation.getImage()); + decoration.setDescriptionText(description); + decoration.show(); + } + } + } + + /** + * Creates a new instance of the message manager that will work with the + * provided form. + * + * @param scrolledForm + * the form to control + */ + public this(ScrolledForm scrolledForm) { + prefixProvider = DEFAULT_PREFIX_PROVIDER; + messages = new ArraySeq!(Object); + decorators = new HashMap!(Object,Object); + this.scrolledForm = scrolledForm; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#addMessage(java.lang.Object, + * java.lang.String, int) + */ + public void addMessage(Object key, String messageText, Object data, int type) { + addMessage(null, key, messageText, data, type, messages); + if (isAutoUpdate()) + updateForm(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#addMessage(java.lang.Object, + * java.lang.String, int, dwt.widgets.Control) + */ + public void addMessage(Object key, String messageText, Object data, + int type, Control control) { + ControlDecorator dec = cast(ControlDecorator) decorators.get(control); + + if (dec is null) { + dec = new ControlDecorator(control); + decorators.put(control, dec); + } + dec.addMessage(key, messageText, data, type); + if (isAutoUpdate()) + updateForm(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#removeMessage(java.lang.Object) + */ + public void removeMessage(Object key) { + Message message = findMessage(key, messages); + if (message !is null) { + messages.remove(message); + if (isAutoUpdate()) + updateForm(); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#removeMessages() + */ + public void removeMessages() { + if (!messages.isEmpty()) { + messages.clear(); + if (isAutoUpdate()) + updateForm(); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#removeMessage(java.lang.Object, + * dwt.widgets.Control) + */ + public void removeMessage(Object key, Control control) { + ControlDecorator dec = cast(ControlDecorator) decorators.get(control); + if (dec is null) + return; + if (dec.removeMessage(key)) + if (isAutoUpdate()) + updateForm(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#removeMessages(dwt.widgets.Control) + */ + public void removeMessages(Control control) { + ControlDecorator dec = cast(ControlDecorator) decorators.get(control); + if (dec !is null) { + if (dec.removeMessages()) { + if (isAutoUpdate()) + updateForm(); + } + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#removeAllMessages() + */ + public void removeAllMessages() { + bool needsUpdate = false; + for (Enumeration enm = decorators.elements(); enm.hasMoreElements();) { + ControlDecorator control = cast(ControlDecorator) enm.nextElement(); + if (control.removeMessages()) + needsUpdate = true; + } + if (!messages.isEmpty()) { + messages.clear(); + needsUpdate = true; + } + if (needsUpdate && isAutoUpdate()) + updateForm(); + } + + /* + * Adds the message if it does not already exist in the provided list. + */ + + private Message addMessage(String prefix, Object key, String messageText, + Object data, int type, ArraySeq!(Object) list) { + Message message = findMessage(key, list); + if (message is null) { + message = new Message(key, messageText, type, data); + message.prefix = prefix; + list.add(message); + } else { + message.message = messageText; + message.type = type; + message.data = data; + } + return message; + } + + /* + * Finds the message with the provided key in the provided list. + */ + + private Message findMessage(Object key, ArraySeq!(Object) list) { + for (int i = 0; i < list.size(); i++) { + Message message = cast(Message) list.get(i); + if (message.getKey().equals(key)) + return message; + } + return null; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#update() + */ + public void update() { + // Update decorations + for (Iterator iter = decorators.values().iterator(); iter.hasNext();) { + ControlDecorator dec = cast(ControlDecorator) iter.next(); + dec.update(); + } + // Update the form + updateForm(); + } + + /* + * Updates the container by rolling the messages up from the controls. + */ + + private void updateForm() { + ArrayList mergedList = new ArrayList(); + mergedList.addAll(messages); + for (Enumeration enm = decorators.elements(); enm.hasMoreElements();) { + ControlDecorator dec = cast(ControlDecorator) enm.nextElement(); + dec.addAll(mergedList); + } + update(mergedList); + } + + private void update(ArraySeq!(Object) mergedList) { + pruneControlDecorators(); + if (scrolledForm.getForm().getHead().getBounds().height is 0 || mergedList.isEmpty() || mergedList is null) { + scrolledForm.setMessage(null, IMessageProvider.NONE); + return; + } + auto peers = createPeers(mergedList); + int maxType = (cast(IMessage) peers.get(0)).getMessageType(); + String messageText; + IMessage[] array = arraycast!(IMessage)( peers + .toArray()); + if (peers.size() is 1 && (cast(Message) peers.get(0)).prefix is null) { + // a single message + IMessage message = cast(IMessage) peers.get(0); + messageText = message.getMessage(); + scrolledForm.setMessage(messageText, maxType, array); + } else { + // show a summary message for the message + // and list of errors for the details + if (peers.size() > 1) + messageText = Messages.bind( + MULTIPLE_MESSAGE_SUMMARY_KEYS[maxType], + [ to!(String)(peers.size()) ]); //$NON-NLS-1$ + else + messageText = SINGLE_MESSAGE_SUMMARY_KEYS[maxType]; + scrolledForm.setMessage(messageText, maxType, array); + } + } + + private static String getFullMessage(IMessage message) { + if (message.getPrefix() is null) + return message.getMessage(); + return message.getPrefix() + message.getMessage(); + } + + private ArraySeq!(Message) createPeers(ArraySeq!(Message) messages) { + auto peers = new ArraySeq!(Message); + int maxType = 0; + foreach( message; messages ){ + if (message.type > maxType) { + peers.clear(); + maxType = message.type; + } + if (message.type is maxType) + peers.append(message); + } + return peers; + } + + private String createDetails(ArraySeq!(Object) messages, bool excludePrefix) { + StringWriter sw = new StringWriter(); + PrintWriter out_ = new PrintWriter(sw); + + for (int i = 0; i < messages.size(); i++) { + if (i > 0) + out_.println(); + IMessage m = cast(IMessage) messages.get(i); + out_.print(excludePrefix ? m.getMessage() : getFullMessage(m)); + } + out_.flush(); + return sw.toString(); + } + + public static String createDetails(IMessage[] messages) { + if (messages is null || messages.length is 0) + return null; + StringWriter sw = new StringWriter(); + PrintWriter out_ = new PrintWriter(sw); + + for (int i = 0; i < messages.length; i++) { + if (i > 0) + out_.println(); + out_.print(getFullMessage(messages[i])); + } + out_.flush(); + return sw.toString(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#createSummary(dwtx.ui.forms.IMessage[]) + */ + public String createSummary(IMessage[] messages) { + return createDetails(messages); + } + + private void pruneControlDecorators() { + for (Iterator iter = decorators.values().iterator(); iter.hasNext();) { + ControlDecorator dec = cast(ControlDecorator) iter.next(); + if (dec.isDisposed()) + iter.remove(); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#getMessagePrefixProvider() + */ + public IMessagePrefixProvider getMessagePrefixProvider() { + return prefixProvider; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#setMessagePrefixProvider(dwtx.ui.forms.IMessagePrefixProvider) + */ + public void setMessagePrefixProvider(IMessagePrefixProvider provider) { + this.prefixProvider = provider; + for (Iterator iter = decorators.values().iterator(); iter.hasNext();) { + ControlDecorator dec = cast(ControlDecorator) iter.next(); + dec.updatePrefix(); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#getDecorationPosition() + */ + public int getDecorationPosition() { + return decorationPosition; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#setDecorationPosition(int) + */ + public void setDecorationPosition(int position) { + this.decorationPosition = position; + for (Iterator iter = decorators.values().iterator(); iter.hasNext();) { + ControlDecorator dec = cast(ControlDecorator) iter.next(); + dec.updatePosition(); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#isAutoUpdate() + */ + public bool isAutoUpdate() { + return autoUpdate; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.IMessageManager#setAutoUpdate(bool) + */ + public void setAutoUpdate(bool autoUpdate) { + bool needsUpdate = !this.autoUpdate && autoUpdate; + this.autoUpdate = autoUpdate; + if (needsUpdate) + update(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/Messages.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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.ui.internal.forms.Messages; + +import dwt.dwthelper.utils; + +public class Messages /*extends NLS*/ { + private static const String BUNDLE_NAME = "dwtx.ui.internal.forms.Messages"; //$NON-NLS-1$ + +// private const() { +// } + +// static { + // initialize resource bundle + //NLS.initializeMessages(BUNDLE_NAME, Messages.class); +// } + + public static String FormDialog_defaultTitle; + public static String FormText_copy; + public static String Form_tooltip_minimize; + public static String Form_tooltip_restore; + /* + * Message manager + */ + public static String MessageManager_sMessageSummary; + public static String MessageManager_sWarningSummary; + public static String MessageManager_sErrorSummary; + public static String MessageManager_pMessageSummary; + public static String MessageManager_pWarningSummary; + public static String MessageManager_pErrorSummary; + public static String ToggleHyperlink_accessibleColumn; + public static String ToggleHyperlink_accessibleName; + + public static String bind(String string, String[] strings) { + // TODO Auto-generated method stub + return null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/AggregateHyperlinkSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.internal.forms.widgets.AggregateHyperlinkSegment; + +import java.util.Hashtable; +import java.util.Vector; + +import dwt.graphics.Color; +import dwt.graphics.GC; +import dwt.graphics.Rectangle; + +/** + * This segment contains a collection of images and links that all belong to one + * logical hyperlink. + */ +public class AggregateHyperlinkSegment extends ParagraphSegment implements + IHyperlinkSegment { + private String href; + + private Vector segments = new Vector(); + + public AggregateHyperlinkSegment() { + } + + public void add(TextHyperlinkSegment segment) { + segments.add(segment); + } + + public void add(ImageHyperlinkSegment segment) { + segments.add(segment); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#advanceLocator(dwt.graphics.GC, + * int, dwtx.ui.internal.forms.widgets.Locator, + * java.util.Hashtable, bool) + */ + public bool advanceLocator(GC gc, int wHint, Locator loc, + Hashtable objectTable, bool computeHeightOnly) { + bool newLine = false; + for (int i = 0; i < segments.size(); i++) { + ParagraphSegment segment = (ParagraphSegment) segments.get(i); + if (segment.advanceLocator(gc, wHint, loc, objectTable, + computeHeightOnly)) + newLine = true; + } + return newLine; + } + + /** + * @return Returns the href. + */ + public String getHref() { + return href; + } + + /** + * @param href + * The href to set. + */ + public void setHref(String href) { + this.href = href; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#repaint(dwt.graphics.GC, + * bool) + */ + public void paint(GC gc, bool hover, Hashtable resourceTable, + bool selected, SelectionData selData, Rectangle repaintRegion) { + for (int i = 0; i < segments.size(); i++) { + ParagraphSegment segment = (ParagraphSegment) segments.get(i); + segment.paint(gc, hover, resourceTable, selected, selData, + repaintRegion); + } + } + + public String getText() { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < segments.size(); i++) { + IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i); + buf.append(segment.getText()); + } + return buf.toString(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#paintFocus(dwt.graphics.GC, + * dwt.graphics.Color, dwt.graphics.Color, + * bool) + */ + public void paintFocus(GC gc, Color bg, Color fg, bool selected, + Rectangle repaintRegion) { + for (int i = 0; i < segments.size(); i++) { + IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i); + segment.paintFocus(gc, bg, fg, selected, repaintRegion); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#getBounds() + */ + public Rectangle getBounds() { + Rectangle bounds = new Rectangle(Integer.MAX_VALUE, Integer.MAX_VALUE, + 0, 0); + // TODO this is wrong + for (int i = 0; i < segments.size(); i++) { + IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i); + Rectangle sbounds = segment.getBounds(); + bounds.x = Math.min(bounds.x, sbounds.x); + bounds.y = Math.min(bounds.y, sbounds.y); + bounds.width = Math.max(bounds.width, sbounds.width); + bounds.height = Math.max(bounds.height, sbounds.height); + } + return bounds; + } + + public bool contains(int x, int y) { + for (int i = 0; i < segments.size(); i++) { + IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i); + if (segment.contains(x, y)) + return true; + } + return false; + } + + public bool intersects(Rectangle rect) { + for (int i = 0; i < segments.size(); i++) { + IHyperlinkSegment segment = (IHyperlinkSegment) segments.get(i); + if (segment.intersects(rect)) + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#layout(dwt.graphics.GC, + * int, dwtx.ui.internal.forms.widgets.Locator, + * java.util.Hashtable, bool, + * dwtx.ui.internal.forms.widgets.SelectionData) + */ + public void layout(GC gc, int width, Locator locator, + Hashtable resourceTable, bool selected) { + for (int i = 0; i < segments.size(); i++) { + ParagraphSegment segment = (ParagraphSegment) segments.get(i); + segment.layout(gc, width, locator, resourceTable, selected); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#computeSelection(dwt.graphics.GC, + * java.util.Hashtable, bool, + * dwtx.ui.internal.forms.widgets.SelectionData) + */ + public void computeSelection(GC gc, Hashtable resourceTable, + SelectionData selData) { + for (int i = 0; i < segments.size(); i++) { + ParagraphSegment segment = (ParagraphSegment) segments.get(i); + segment.computeSelection(gc, resourceTable, selData); + } + } + + public void clearCache(String fontId) { + for (int i = 0; i < segments.size(); i++) { + ParagraphSegment segment = (ParagraphSegment) segments.get(i); + segment.clearCache(fontId); + } + } + + public String getTooltipText() { + if (segments.size() > 0) + return ((ParagraphSegment) segments.get(0)).getTooltipText(); + return super.getTooltipText(); + } + + public bool isFocusSelectable(Hashtable resourceTable) { + return true; + } + + public bool setFocus(Hashtable resourceTable, bool direction) { + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/BreakSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.internal.forms.widgets.BreakSegment; + +import java.util.Hashtable; + +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.graphics.Rectangle; + +/** + * This segment serves as break within a paragraph. It has no data - + * just starts a new line and resets the locator. + */ + +public class BreakSegment extends ParagraphSegment { + /* (non-Javadoc) + * @see dwtx.ui.forms.internal.widgets.ParagraphSegment#advanceLocator(dwt.graphics.GC, int, dwtx.ui.forms.internal.widgets.Locator, java.util.Hashtable) + */ + public bool advanceLocator(GC gc, int wHint, Locator locator, + Hashtable objectTable, bool computeHeightOnly) { + if (locator.rowHeightis0) { + FontMetrics fm = gc.getFontMetrics(); + locator.rowHeight = fm.getHeight(); + } + if (computeHeightOnly) locator.collectHeights(); + locator.x = locator.indent; + locator.y += locator.rowHeight; + locator.rowHeight = 0; + locator.leading = 0; + return true; + } + + public void paint(GC gc, bool hover, Hashtable resourceTable, bool selected, SelectionData selData, Rectangle repaintRegion) { + //nothing to paint + } + public bool contains(int x, int y) { + return false; + } + public bool intersects(Rectangle rect) { + return false; + } + /* (non-Javadoc) + * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#layout(dwt.graphics.GC, int, dwtx.ui.internal.forms.widgets.Locator, java.util.Hashtable, bool, dwtx.ui.internal.forms.widgets.SelectionData) + */ + public void layout(GC gc, int width, Locator locator, Hashtable ResourceTable, + bool selected) { + locator.resetCaret(); + if (locator.rowHeightis0) { + FontMetrics fm = gc.getFontMetrics(); + locator.rowHeight = fm.getHeight(); + } + locator.y += locator.rowHeight; + locator.rowHeight = 0; + locator.rowCounter++; + } + + /* (non-Javadoc) + * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#computeSelection(dwt.graphics.GC, java.util.Hashtable, bool, dwtx.ui.internal.forms.widgets.SelectionData) + */ + public void computeSelection(GC gc, Hashtable resourceTable, SelectionData selData) { + selData.markNewLine(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/BulletParagraph.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.internal.forms.widgets.BulletParagraph; + +import java.util.Hashtable; + +import dwt.graphics.Color; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; + +public class BulletParagraph extends Paragraph { + public static final int CIRCLE = 1; + + public static final int TEXT = 2; + + public static final int IMAGE = 3; + + private int style = CIRCLE; + + private String text; + + private int CIRCLE_DIAM = 5; + + private int SPACING = 10; + + private int indent = -1; + + private int bindent = -1; + + private Rectangle bbounds; + + /** + * Constructor for BulletParagraph. + * + * @param addVerticalSpace + */ + public BulletParagraph(bool addVerticalSpace) { + super(addVerticalSpace); + } + + public int getIndent() { + int ivalue = indent; + if (ivalue !is -1) + return ivalue; + switch (style) { + case CIRCLE: + ivalue = CIRCLE_DIAM + SPACING; + break; + default: + ivalue = 20; + break; + } + return getBulletIndent() + ivalue; + } + + public int getBulletIndent() { + if (bindent !is -1) + return bindent; + return 0; + } + + /* + * @see IBulletParagraph#getBulletStyle() + */ + public int getBulletStyle() { + return style; + } + + public void setBulletStyle(int style) { + this.style = style; + } + + public void setBulletText(String text) { + this.text = text; + } + + public void setIndent(int indent) { + this.indent = indent; + } + + public void setBulletIndent(int bindent) { + this.bindent = bindent; + } + + /* + * @see IBulletParagraph#getBulletText() + */ + public String getBulletText() { + return text; + } + + public void layout(GC gc, int width, Locator loc, int lineHeight, + Hashtable resourceTable, IHyperlinkSegment selectedLink) { + computeRowHeights(gc, width, loc, lineHeight, resourceTable); + layoutBullet(gc, loc, lineHeight, resourceTable); + super.layout(gc, width, loc, lineHeight, resourceTable, selectedLink); + } + + public void paint(GC gc, Rectangle repaintRegion, + Hashtable resourceTable, IHyperlinkSegment selectedLink, + SelectionData selData) { + paintBullet(gc, repaintRegion, resourceTable); + super.paint(gc, repaintRegion, resourceTable, selectedLink, selData); + } + + private void layoutBullet(GC gc, Locator loc, int lineHeight, + Hashtable resourceTable) { + int x = loc.x - getIndent() + getBulletIndent(); + int rowHeight = ((int[]) loc.heights.get(0))[0]; + if (style is CIRCLE) { + int y = loc.y + rowHeight / 2 - CIRCLE_DIAM / 2; + bbounds = new Rectangle(x, y, CIRCLE_DIAM, CIRCLE_DIAM); + } else if (style is TEXT && text !is null) { + //int height = gc.getFontMetrics().getHeight(); + Point textSize = gc.textExtent(text); + bbounds = new Rectangle(x, loc.y, textSize.x, textSize.y); + } else if (style is IMAGE && text !is null) { + Image image = (Image) resourceTable.get(text); + if (image !is null) { + Rectangle ibounds = image.getBounds(); + int y = loc.y + rowHeight / 2 - ibounds.height / 2; + bbounds = new Rectangle(x, y, ibounds.width, ibounds.height); + } + } + } + + public void paintBullet(GC gc, Rectangle repaintRegion, + Hashtable resourceTable) { + if (bbounds is null) + return; + int x = bbounds.x; + int y = bbounds.y; + if (repaintRegion !is null) { + x -= repaintRegion.x; + y -= repaintRegion.y; + } + if (style is CIRCLE) { + Color bg = gc.getBackground(); + Color fg = gc.getForeground(); + gc.setBackground(fg); + gc.fillRectangle(x, y + 1, 5, 3); + gc.fillRectangle(x + 1, y, 3, 5); + gc.setBackground(bg); + } else if (style is TEXT && text !is null) { + gc.drawText(text, x, y); + } else if (style is IMAGE && text !is null) { + Image image = (Image) resourceTable.get(text); + if (image !is null) + gc.drawImage(image, x, y); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/BusyIndicator.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,335 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 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 + * Stefan Mucke - fix for Bug 156456 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.ui.internal.forms.widgets.BusyIndicator; + +import dwtx.ui.internal.forms.widgets.FormUtil; + +import dwt.DWT; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.ImageData; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Display; +import dwtx.jface.resource.ImageDescriptor; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Runnable; + +import tango.util.Convert; +import tango.core.Thread; + +public final class BusyIndicator : Canvas { + + class BusyThread : Thread { + Rectangle bounds; + Display display; + GC offScreenImageGC; + Image offScreenImage; + Image timage; + bool stop; + + private this(Rectangle bounds, Display display, GC offScreenImageGC, Image offScreenImage) { + this.bounds = bounds; + this.display = display; + this.offScreenImageGC = offScreenImageGC; + this.offScreenImage = offScreenImage; + } + + public void run() { + try { + /* + * Create an off-screen image to draw on, and fill it with + * the shell background. + */ + FormUtil.setAntialias(offScreenImageGC, DWT.ON); + display.syncExec(dgRunnable( { + if (!isDisposed()) + drawBackground(offScreenImageGC, 0, 0, + bounds.width, + bounds.height); + })); + if (isDisposed()) + return; + + /* + * Create the first image and draw it on the off-screen + * image. + */ + int imageDataIndex = 0; + ImageData imageData; + synchronized (this.outer) { + timage = getImage(imageDataIndex); + imageData = timage.getImageData(); + offScreenImageGC.drawImage(timage, 0, 0, + imageData.width, imageData.height, imageData.x, + imageData.y, imageData.width, imageData.height); + } + + /* + * Now loop through the images, creating and drawing + * each one on the off-screen image before drawing it on + * the shell. + */ + while (!stop && !isDisposed() && timage !is null) { + + /* + * Fill with the background color before + * drawing. + */ + display.syncExec(dgRunnable( (ImageData fimageData){ + if (!isDisposed()) { + drawBackground(offScreenImageGC, fimageData.x, + fimageData.y, fimageData.width, + fimageData.height); + } + }, imageData )); + + synchronized (this.outer) { + imageDataIndex = (imageDataIndex + 1) % IMAGE_COUNT; + timage = getImage(imageDataIndex); + imageData = timage.getImageData(); + offScreenImageGC.drawImage(timage, 0, 0, + imageData.width, imageData.height, + imageData.x, imageData.y, imageData.width, + imageData.height); + } + + /* Draw the off-screen image to the shell. */ + animationImage = offScreenImage; + display.syncExec(dgRunnable( { + if (!isDisposed()) + redraw(); + })); + /* + * Sleep for the specified delay time + */ + try { + Thread.sleep(MILLISECONDS_OF_DELAY/1000.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + + } + } catch (Exception e) { + } finally { + display.syncExec(dgRunnable( { + if (offScreenImage !is null + && !offScreenImage.isDisposed()) + offScreenImage.dispose(); + if (offScreenImageGC !is null + && !offScreenImageGC.isDisposed()) + offScreenImageGC.dispose(); + })); + clearImages(); + } + if (busyThread is null) + display.syncExec(dgRunnable( { + animationImage = null; + if (!isDisposed()) + redraw(); + })); + } + + public void setStop(bool stop) { + this.stop = stop; + } + } + + private static const int MARGIN = 0; + private static const int IMAGE_COUNT = 8; + private static const int MILLISECONDS_OF_DELAY = 180; + private Image[] imageCache; + protected Image image; + + protected Image animationImage; + + protected BusyThread busyThread; + + /** + * BusyWidget constructor comment. + * + * @param parent + * dwt.widgets.Composite + * @param style + * int + */ + public this(Composite parent, int style) { + super(parent, style); + + addPaintListener(new class PaintListener { + public void paintControl(PaintEvent event) { + onPaint(event); + } + }); + } + + public Point computeSize(int wHint, int hHint, bool changed) { + Point size = new Point(0, 0); + if (image !is null) { + Rectangle ibounds = image.getBounds(); + size.x = ibounds.width; + size.y = ibounds.height; + } + if (isBusy()) { + Rectangle bounds = getImage(0).getBounds(); + size.x = Math.max(size.x, bounds.width); + size.y = Math.max(size.y, bounds.height); + } + size.x += MARGIN + MARGIN; + size.y += MARGIN + MARGIN; + return size; + } + + /* (non-Javadoc) + * @see dwt.widgets.Control#forceFocus() + */ + public bool forceFocus() { + return false; + } + + /** + * Creates a thread to animate the image. + */ + protected synchronized void createBusyThread() { + if (busyThread !is null) + return; + + Rectangle bounds = getImage(0).getBounds(); + Display display = getDisplay(); + Image offScreenImage = new Image(display, bounds.width, bounds.height); + GC offScreenImageGC = new GC(offScreenImage); + busyThread = new BusyThread(bounds, display, offScreenImageGC, offScreenImage); + busyThread.setPriority(Thread.NORM_PRIORITY + 2); + busyThread.setDaemon(true); + busyThread.start(); + } + + public void dispose() { + if (busyThread !is null) { + busyThread.setStop(true); + busyThread = null; + } + super.dispose(); + } + + /** + * Return the image or <code>null</code>. + */ + public Image getImage() { + return image; + } + + /** + * Returns true if it is currently busy. + * + * @return bool + */ + public bool isBusy() { + return (busyThread !is null); + } + + /* + * Process the paint event + */ + protected void onPaint(PaintEvent event) { + if (animationImage !is null && animationImage.isDisposed()) { + animationImage = null; + } + Rectangle rect = getClientArea(); + if (rect.width is 0 || rect.height is 0) + return; + + GC gc = event.gc; + Image activeImage = animationImage !is null ? animationImage : image; + if (activeImage !is null) { + Rectangle ibounds = activeImage.getBounds(); + gc.drawImage(activeImage, rect.width / 2 - ibounds.width / 2, + rect.height / 2 - ibounds.height / 2); + } + } + + /** + * Sets the indicators busy count up (true) or down (false) one. + * + * @param busy + * bool + */ + public synchronized void setBusy(bool busy) { + if (busy) { + if (busyThread is null) + createBusyThread(); + } else { + if (busyThread !is null) { + busyThread.setStop(true); + busyThread = null; + } + } + } + + /** + * Set the image. The value <code>null</code> clears it. + */ + public void setImage(Image image) { + if (image !is this.image && !isDisposed()) { + this.image = image; + redraw(); + } + } + + + private ImageDescriptor createImageDescriptor(String relativePath) { + //DWT_TODO +// Bundle bundle = Platform.getBundle("dwtx.ui.forms"); //$NON-NLS-1$ +// URL url = FileLocator.find(bundle, new Path(relativePath),null); +// if (url is null) return null; +// try { +// url = FileLocator.resolve(url); +// return ImageDescriptor.createFromURL(url); +// } catch (IOException e) { +// return null; +// } + return null; + } + + private synchronized Image getImage(int index) { + if (imageCache is null) { + imageCache = new Image[IMAGE_COUNT]; + } + if (imageCache[index] is null){ + ImageDescriptor descriptor = createImageDescriptor("$nl$/icons/progress/ani/" ~ to!(String)(index + 1) ~ ".png"); //$NON-NLS-1$ //$NON-NLS-2$ + imageCache[index] = descriptor.createImage(); + } + return imageCache[index]; + } + + private synchronized void clearImages() { + if (busyThread !is null) + return; + if (imageCache !is null) { + for (int index = 0; index < IMAGE_COUNT; index++) { + if (imageCache[index] !is null && !imageCache[index].isDisposed()) { + imageCache[index].dispose(); + imageCache[index] = null; + } + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/ControlSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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.ui.internal.forms.widgets.ControlSegment; + +import dwtx.ui.internal.forms.widgets.ObjectSegment; +import dwtx.ui.internal.forms.widgets.IFocusSelectable; +import dwtx.ui.internal.forms.widgets.Locator; + +import dwt.DWT; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Control; + +public class ControlSegment : ObjectSegment, IFocusSelectable { + private bool fill; + private int width = DWT.DEFAULT; + private int height = DWT.DEFAULT; + + public this() { + } + + public void setFill(bool fill) { + this.fill = fill; + } + + public void setWidth(int width) { + this.width = width; + } + + public void setHeight(int height) { + this.height = height; + } + + public Control getControl(Hashtable resourceTable) { + Object obj = resourceTable.get(getObjectId()); + if ( auto c = cast(Control)obj ) { + if (!c.isDisposed()) + return c; + } + return null; + } + + protected Point getObjectSize(Hashtable resourceTable, int wHint) { + Control control = getControl(resourceTable); + if (controlisnull) + return new Point(0,0); + int realWhint = FormUtil.getWidthHint(wHint, control); + Point size = control.computeSize(realWhint, DWT.DEFAULT); + if (realWhint !is DWT.DEFAULT && fill) + size.x = Math.max(size.x, realWhint); + if (width !is DWT.DEFAULT) + size.x = width; + if (height !is DWT.DEFAULT) + size.y = height; + return size; + } + + public void layout(GC gc, int width, Locator loc, Hashtable resourceTable, + bool selected) { + super.layout(gc, width, loc, resourceTable, selected); + Control control = getControl(resourceTable); + if (control !is null) + control.setBounds(getBounds()); + } + + public bool setFocus(Hashtable resourceTable, bool next) { + Control c = getControl(resourceTable); + if (c !is null) { + return setFocus(c, next); + } + return false; + } + + private bool setFocus(Control c, bool direction) { + if (auto comp = cast(Composite)c ) { + Control [] tabList = comp.getTabList(); + if (direction) { + for (int i=0; i<tabList.length; i++) { + if (setFocus(tabList[i], direction)) + return true; + } + } + else { + for (int i=tabList.length-1; i>=0; i--) { + if (setFocus(tabList[i], direction)) + return true; + } + } + if (!(null !is cast(Canvas)c )) + return false; + } + return c.setFocus(); + } + + public bool isFocusSelectable(Hashtable resourceTable) { + Control c = getControl(resourceTable); + if (c !is null) + return true; + return false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/FormFonts.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2007 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.ui.internal.forms.widgets.FormFonts; + +import tango.util.collection.HashMap; + +import dwt.DWT; +import dwt.graphics.Font; +import dwt.graphics.FontData; +import dwt.widgets.Display; + +import dwt.dwthelper.utils; + +public class FormFonts { + + alias HashMap!(String,Object) HashMapStrToObj; + + private static FormFonts instance; + + public static FormFonts getInstance() { + if (instance is null) + instance = new FormFonts(); + return instance; + } + + private HashMapStrToObj fonts; + private HashMapStrToObj ids; + + private this() { + } + + private class FontIdentifier { + private Display fDisplay; + private Font fFont; + + this (Display display, Font font) { + fDisplay = display; + fFont = font; + } + + public bool equals(Object obj) { + if (auto id = cast(FontIdentifier)obj ) { + return id.fDisplay.opEquals(fDisplay) && id.fFont.opEquals(fFont); + } + return false; + } + + public override hash_t toHash() { + return fDisplay.toHash() * 7 + fFont.toHash(); + } + } + + private class FontReference { + private Font fFont; + private int fCount; + + public this(Font font) { + fFont = font; + fCount = 1; + } + + public Font getFont() { + return fFont; + } + // returns a bool indicating if all clients of this font are finished + // a true result indicates the underlying image should be disposed + public bool decCount() { + return --fCount is 0; + } + public void incCount() { + fCount++; + } + } + + public Font getBoldFont(Display display, Font font) { + checkHashMaps(); + FontIdentifier fid = new FontIdentifier(display, font); + FontReference result = cast(FontReference) fonts.get(fid); + if (result !is null && !result.getFont().isDisposed()) { + result.incCount(); + return result.getFont(); + } + Font boldFont = createBoldFont(display, font); + fonts.put(fid, new FontReference(boldFont)); + ids.put(boldFont, fid); + return boldFont; + } + + public bool markFinished(Font boldFont) { + checkHashMaps(); + FontIdentifier id = cast(FontIdentifier)ids.get(boldFont); + if (id !is null) { + FontReference ref_ = cast(FontReference) fonts.get(id); + if (ref_ !is null) { + if (ref_.decCount()) { + fonts.remove(id); + ids.remove(ref_.getFont()); + ref_.getFont().dispose(); + validateHashMaps(); + } + return true; + } + } + // if the image was not found, dispose of it for the caller + boldFont.dispose(); + return false; + } + + private Font createBoldFont(Display display, Font regularFont) { + FontData[] fontDatas = regularFont.getFontData(); + for (int i = 0; i < fontDatas.length; i++) { + fontDatas[i].setStyle(fontDatas[i].getStyle() | DWT.BOLD); + } + return new Font(display, fontDatas); + } + + private void checkHashMaps() { + if (fonts is null) + fonts = new HashMapStrToObj(); + if (ids is null) + ids = new HashMapStrToObj(); + } + + private void validateHashMaps() { + if (fonts.size() is 0) + fonts = null; + if (ids.size() is 0) + ids = null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/FormHeading.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,1025 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.internal.forms.widgets.FormHeading; + +import dwtx.ui.internal.forms.widgets.TitleRegion; + +import dwt.DWT; +import dwt.custom.CLabel; +import dwt.dnd.DragSourceListener; +import dwt.dnd.DropTargetListener; +import dwt.dnd.Transfer; +import dwt.events.DisposeEvent; +import dwt.events.DisposeListener; +import dwt.events.MouseEvent; +import dwt.events.MouseMoveListener; +import dwt.events.MouseTrackListener; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Layout; +import dwt.widgets.Listener; +import dwt.widgets.ToolBar; +import dwtx.core.runtime.Assert; +import dwtx.core.runtime.ListenerList; +import dwtx.jface.action.IMenuManager; +import dwtx.jface.action.IToolBarManager; +import dwtx.jface.action.ToolBarManager; +import dwtx.jface.dialogs.Dialog; +import dwtx.jface.dialogs.IMessageProvider; +import dwtx.jface.resource.JFaceResources; +import dwtx.ui.forms.IFormColors; +import dwtx.ui.forms.IMessage; +import dwtx.ui.forms.events.IHyperlinkListener; +import dwtx.ui.forms.widgets.Hyperlink; +import dwtx.ui.forms.widgets.ILayoutExtension; +import dwtx.ui.forms.widgets.SizeCache; +import dwtx.ui.internal.forms.IMessageToolTipManager; +import dwtx.ui.internal.forms.MessageManager; + +import dwt.dwthelper.utils; +import tango.util.collection.HashMap; + +/** + * Form header moved out of the form class. + */ +public class FormHeading : Canvas { + private static const int TITLE_HMARGIN = 1; + private static const int SPACING = 5; + private static const int VSPACING = 5; + private static const int HMARGIN = 6; + private static const int VMARGIN = 1; + private static const int CLIENT_MARGIN = 1; + + private static const int SEPARATOR = 1 << 1; + private static const int BOTTOM_TOOLBAR = 1 << 2; + private static const int BACKGROUND_IMAGE_TILED = 1 << 3; + private static const int SEPARATOR_HEIGHT = 2; + private static const int MESSAGE_AREA_LIMIT = 50; + static IMessage[] NULL_MESSAGE_ARRAY; + + public static const String COLOR_BASE_BG = "baseBg"; //$NON-NLS-1$ + + private Image backgroundImage; + + private Image gradientImage; + + HashMap!(String,Object) colors; + + private int flags; + + private GradientInfo gradientInfo; + + private ToolBarManager toolBarManager; + + private SizeCache toolbarCache = new SizeCache(); + + private SizeCache clientCache = new SizeCache(); + + private SizeCache messageCache = new SizeCache(); + + private TitleRegion titleRegion; + + private MessageRegion messageRegion; + + private IMessageToolTipManager messageToolTipManager; + + private Control headClient; + + private class DefaultMessageToolTipManager : + IMessageToolTipManager { + public void createToolTip(Control control, bool imageLabel) { + } + + public void update() { + String details = getMessageType() is 0 ? null : MessageManager + .createDetails(getChildrenMessages()); + if (messageRegion !is null) + messageRegion.updateToolTip(details); + if (getMessageType() > 0 + && (details is null || details.length() is 0)) + details = getMessage(); + titleRegion.updateToolTip(details); + } + } + + private class GradientInfo { + Color[] gradientColors; + + int[] percents; + + bool vertical; + } + + private class FormHeadingLayout : Layout, ILayoutExtension { + public int computeMinimumWidth(Composite composite, bool flushCache) { + return computeSize(composite, 5, DWT.DEFAULT, flushCache).x; + } + + public int computeMaximumWidth(Composite composite, bool flushCache) { + return computeSize(composite, DWT.DEFAULT, DWT.DEFAULT, flushCache).x; + } + + public Point computeSize(Composite composite, int wHint, int hHint, + bool flushCache) { + return layout(composite, false, 0, 0, wHint, hHint, flushCache); + } + + protected void layout(Composite composite, bool flushCache) { + Rectangle rect = composite.getClientArea(); + layout(composite, true, rect.x, rect.y, rect.width, rect.height, + flushCache); + } + + private Point layout(Composite composite, bool move, int x, int y, + int width, int height, bool flushCache) { + Point tsize = null; + Point msize = null; + Point tbsize = null; + Point clsize = null; + + if (flushCache) { + clientCache.flush(); + messageCache.flush(); + toolbarCache.flush(); + } + if (hasToolBar()) { + ToolBar tb = toolBarManager.getControl(); + toolbarCache.setControl(tb); + tbsize = toolbarCache.computeSize(DWT.DEFAULT, DWT.DEFAULT); + } + if (headClient !is null) { + clientCache.setControl(headClient); + int cwhint = width; + if (cwhint !is DWT.DEFAULT) { + cwhint -= HMARGIN * 2; + if (tbsize !is null && getToolBarAlignment() is DWT.BOTTOM) + cwhint -= tbsize.x + SPACING; + } + clsize = clientCache.computeSize(cwhint, DWT.DEFAULT); + } + int totalFlexWidth = width; + int flexWidth = totalFlexWidth; + if (totalFlexWidth !is DWT.DEFAULT) { + totalFlexWidth -= TITLE_HMARGIN * 2; + // complete right margin + if (hasToolBar() && getToolBarAlignment() is DWT.TOP + || hasMessageRegion()) + totalFlexWidth -= SPACING; + // subtract tool bar + if (hasToolBar() && getToolBarAlignment() is DWT.TOP) + totalFlexWidth -= tbsize.x + SPACING; + flexWidth = totalFlexWidth; + if (hasMessageRegion()) { + // remove message region spacing and divide by 2 + flexWidth -= SPACING; + // flexWidth /= 2; + } + } + /* + * // compute text and message sizes tsize = + * titleRegion.computeSize(flexWidth, DWT.DEFAULT); if (flexWidth !is + * DWT.DEFAULT && tsize.x < flexWidth) flexWidth += flexWidth - + * tsize.x; + * + * if (hasMessageRegion()) { + * messageCache.setControl(messageRegion.getMessageControl()); msize = + * messageCache.computeSize(flexWidth, DWT.DEFAULT); int maxWidth = + * messageCache.computeSize(DWT.DEFAULT, DWT.DEFAULT).x; if + * (maxWidth < msize.x) { msize.x = maxWidth; // recompute title + * with the reclaimed width int tflexWidth = totalFlexWidth - + * SPACING - msize.x; tsize = titleRegion.computeSize(tflexWidth, + * DWT.DEFAULT); } } + */ + if (!hasMessageRegion()) { + tsize = titleRegion.computeSize(flexWidth, DWT.DEFAULT); + } else { + // Total flexible area in the first row is flexWidth. + // Try natural widths of title and + Point tsizeNatural = titleRegion.computeSize(DWT.DEFAULT, + DWT.DEFAULT); + messageCache.setControl(messageRegion.getMessageControl()); + Point msizeNatural = messageCache.computeSize(DWT.DEFAULT, + DWT.DEFAULT); + // try to fit all + tsize = tsizeNatural; + msize = msizeNatural; + if (flexWidth !is DWT.DEFAULT) { + int needed = tsizeNatural.x + msizeNatural.x; + if (needed > flexWidth) { + // too big - try to limit the message + int mwidth = flexWidth - tsizeNatural.x; + if (mwidth >= MESSAGE_AREA_LIMIT) { + msize.x = mwidth; + } else { + // message is squeezed to the limit + int flex = flexWidth - MESSAGE_AREA_LIMIT; + tsize = titleRegion.computeSize(flex, DWT.DEFAULT); + msize.x = MESSAGE_AREA_LIMIT; + } + } + } + } + + Point size = new Point(width, height); + if (!move) { + // compute sizes + int width1 = 2 * TITLE_HMARGIN; + width1 += tsize.x; + if (msize !is null) + width1 += SPACING + msize.x; + if (tbsize !is null && getToolBarAlignment() is DWT.TOP) + width1 += SPACING + tbsize.x; + if (msize !is null + || (tbsize !is null && getToolBarAlignment() is DWT.TOP)) + width1 += SPACING; + size.x = width1; + if (clsize !is null) { + int width2 = clsize.x; + if (tbsize !is null && getToolBarAlignment() is DWT.BOTTOM) + width2 += SPACING + tbsize.x; + width2 += 2 * HMARGIN; + size.x = Math.max(width1, width2); + } + // height, first row + size.y = tsize.y; + if (msize !is null) + size.y = Math.max(msize.y, size.y); + if (tbsize !is null && getToolBarAlignment() is DWT.TOP) + size.y = Math.max(tbsize.y, size.y); + if (size.y > 0) + size.y += VMARGIN * 2; + // add second row + int height2 = 0; + if (tbsize !is null && getToolBarAlignment() is DWT.BOTTOM) + height2 = tbsize.y; + if (clsize !is null) + height2 = Math.max(height2, clsize.y); + if (height2 > 0) + size.y += VSPACING + height2 + CLIENT_MARGIN; + // add separator + if (size.y > 0 && isSeparatorVisible()) + size.y += SEPARATOR_HEIGHT; + } else { + // position controls + int xloc = x; + int yloc = y + VMARGIN; + int row1Height = tsize.y; + if (hasMessageRegion()) + row1Height = Math.max(row1Height, msize.y); + if (hasToolBar() && getToolBarAlignment() is DWT.TOP) + row1Height = Math.max(row1Height, tbsize.y); + titleRegion.setBounds(xloc, + // yloc + row1Height / 2 - tsize.y / 2, + yloc, tsize.x, tsize.y); + xloc += tsize.x; + + if (hasMessageRegion()) { + xloc += SPACING; + int messageOffset = 0; + if (tsize.y > 0) { + // space between title area and title text + int titleLeadingSpace = (tsize.y - titleRegion.getFontHeight()) / 2; + // space between message control and message text + int messageLeadingSpace = (msize.y - messageRegion.getFontHeight()) / 2; + // how much to offset the message so baselines align + messageOffset = (titleLeadingSpace + titleRegion.getFontBaselineHeight()) + - (messageLeadingSpace + messageRegion.getFontBaselineHeight()); + } + + messageRegion + .getMessageControl() + .setBounds( + xloc, + tsize.y > 0 ? (yloc + messageOffset) + : (yloc + row1Height / 2 - msize.y / 2), + msize.x, msize.y); + xloc += msize.x; + } + if (toolBarManager !is null) + toolBarManager.getControl().setVisible( + !toolBarManager.isEmpty()); + if (tbsize !is null && getToolBarAlignment() is DWT.TOP) { + ToolBar tbar = toolBarManager.getControl(); + tbar.setBounds(x + width - 1 - tbsize.x - HMARGIN, yloc + + row1Height - 1 - tbsize.y, tbsize.x, tbsize.y); + } + // second row + xloc = HMARGIN; + yloc += row1Height + VSPACING; + int tw = 0; + + if (tbsize !is null && getToolBarAlignment() is DWT.BOTTOM) { + ToolBar tbar = toolBarManager.getControl(); + tbar.setBounds(x + width - 1 - tbsize.x - HMARGIN, yloc, + tbsize.x, tbsize.y); + tw = tbsize.x + SPACING; + } + if (headClient !is null) { + int carea = width - HMARGIN * 2 - tw; + headClient.setBounds(xloc, yloc, carea, clsize.y); + } + } + return size; + } + } + + /* (non-Javadoc) + * @see dwt.widgets.Control#forceFocus() + */ + public bool forceFocus() { + return false; + } + + private bool hasToolBar() { + return toolBarManager !is null && !toolBarManager.isEmpty(); + } + + private bool hasMessageRegion() { + return messageRegion !is null && !messageRegion.isEmpty(); + } + + private class MessageRegion { + private String message; + private int messageType; + private CLabel messageLabel; + private IMessage[] messages; + private Hyperlink messageHyperlink; + private ListenerList listeners; + private Color fg; + private int fontHeight = -1; + private int fontBaselineHeight = -1; + + public this() { + } + + public bool isDisposed() { + Control c = getMessageControl(); + return c !is null && c.isDisposed(); + } + + public bool isEmpty() { + Control c = getMessageControl(); + if (c is null) + return true; + return !c.getVisible(); + } + + public int getFontHeight() { + if (fontHeight is -1) { + Control c = getMessageControl(); + if (c is null) + return 0; + GC gc = new GC(c.getDisplay()); + gc.setFont(c.getFont()); + fontHeight = gc.getFontMetrics().getHeight(); + gc.dispose(); + } + return fontHeight; + } + + public int getFontBaselineHeight() { + if (fontBaselineHeight is -1) { + Control c = getMessageControl(); + if (c is null) + return 0; + GC gc = new GC(c.getDisplay()); + gc.setFont(c.getFont()); + FontMetrics fm = gc.getFontMetrics(); + fontBaselineHeight = fm.getHeight() - fm.getDescent(); + gc.dispose(); + } + return fontBaselineHeight; + } + + public void showMessage(String newMessage, int newType, + IMessage[] messages) { + Control oldControl = getMessageControl(); + int oldType = messageType; + this.message = newMessage; + this.messageType = newType; + this.messages = messages; + if (newMessage is null) { + // clearing of the message + if (oldControl !is null && oldControl.getVisible()) + oldControl.setVisible(false); + return; + } + ensureControlExists(); + if (needHyperlink()) { + messageHyperlink.setText(newMessage); + messageHyperlink.setHref(messages); + } else { + messageLabel.setText(newMessage); + } + if (oldType !is newType) + updateForeground(); + } + + public void updateToolTip(String toolTip) { + Control control = getMessageControl(); + if (control !is null) + control.setToolTipText(toolTip); + } + + public String getMessage() { + return message; + } + + public int getMessageType() { + return messageType; + } + + public IMessage[] getChildrenMessages() { + return messages; + } + + public String getDetailedMessage() { + Control c = getMessageControl(); + if (c !is null) + return c.getToolTipText(); + return null; + } + + public Control getMessageControl() { + if (needHyperlink() && messageHyperlink !is null) + return messageHyperlink; + return messageLabel; + } + + public Image getMessageImage() { + switch (messageType) { + case IMessageProvider.INFORMATION: + return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_INFO); + case IMessageProvider.WARNING: + return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_WARNING); + case IMessageProvider.ERROR: + return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_ERROR); + default: + return null; + } + } + + public void addMessageHyperlinkListener(IHyperlinkListener listener) { + if (listeners is null) + listeners = new ListenerList(); + listeners.add(listener); + ensureControlExists(); + if (messageHyperlink !is null) + messageHyperlink.addHyperlinkListener(listener); + if (listeners.size() is 1) + updateForeground(); + } + + private void removeMessageHyperlinkListener(IHyperlinkListener listener) { + if (listeners !is null) { + listeners.remove(listener); + if (messageHyperlink !is null) + messageHyperlink.removeHyperlinkListener(listener); + if (listeners.isEmpty()) + listeners = null; + ensureControlExists(); + if (listeners is null && !isDisposed()) + updateForeground(); + } + } + + private void ensureControlExists() { + if (needHyperlink()) { + if (messageLabel !is null) + messageLabel.setVisible(false); + if (messageHyperlink is null) { + messageHyperlink = new Hyperlink(this.outer, DWT.NULL); + messageHyperlink.setUnderlined(true); + messageHyperlink.setText(message); + messageHyperlink.setHref(messages); + Object[] llist = listeners.getListeners(); + for (int i = 0; i < llist.length; i++) + messageHyperlink + .addHyperlinkListener(cast(IHyperlinkListener) llist[i]); + if (messageToolTipManager !is null) + messageToolTipManager.createToolTip(messageHyperlink, false); + } else if (!messageHyperlink.getVisible()) { + messageHyperlink.setText(message); + messageHyperlink.setHref(messages); + messageHyperlink.setVisible(true); + } + } else { + // need a label + if (messageHyperlink !is null) + messageHyperlink.setVisible(false); + if (messageLabel is null) { + messageLabel = new CLabel(this.outer, DWT.NULL); + messageLabel.setText(message); + if (messageToolTipManager !is null) + messageToolTipManager.createToolTip(messageLabel, false); + } else if (!messageLabel.getVisible()) { + messageLabel.setText(message); + messageLabel.setVisible(true); + } + } + layout(true); + } + + private bool needHyperlink() { + return messageType > 0 && listeners !is null; + } + + public void setBackground(Color bg) { + if (messageHyperlink !is null) + messageHyperlink.setBackground(bg); + if (messageLabel !is null) + messageLabel.setBackground(bg); + } + + public void setForeground(Color fg) { + this.fg = fg; + } + + private void updateForeground() { + Color theFg; + + switch (messageType) { + case IMessageProvider.ERROR: + theFg = getDisplay().getSystemColor(DWT.COLOR_RED); + break; + case IMessageProvider.WARNING: + theFg = getDisplay().getSystemColor(DWT.COLOR_DARK_YELLOW); + break; + default: + theFg = fg; + } + getMessageControl().setForeground(theFg); + } + } + + /** + * Creates the form content control as a child of the provided parent. + * + * @param parent + * the parent widget + */ + public this(Composite parent, int style) { + colors = new HashMap!(String,Object); + toolbarCache = new SizeCache(); + clientCache = new SizeCache(); + messageCache = new SizeCache(); + messageToolTipManager = new DefaultMessageToolTipManager(); + + super(parent, style); + setBackgroundMode(DWT.INHERIT_DEFAULT); + addListener(DWT.Paint, new class Listener { + public void handleEvent(Event e) { + onPaint(e.gc); + } + }); + addListener(DWT.Dispose, new class Listener { + public void handleEvent(Event e) { + if (gradientImage !is null) { + FormImages.getInstance().markFinished(gradientImage); + gradientImage = null; + } + } + }); + addListener(DWT.Resize, new class Listener { + public void handleEvent(Event e) { + if (gradientInfo !is null + || (backgroundImage !is null && !isBackgroundImageTiled())) + updateGradientImage(); + } + }); + addMouseMoveListener(new class MouseMoveListener { + public void mouseMove(MouseEvent e) { + updateTitleRegionHoverState(e); + } + }); + addMouseTrackListener(new class MouseTrackListener { + public void mouseEnter(MouseEvent e) { + updateTitleRegionHoverState(e); + } + + public void mouseExit(MouseEvent e) { + titleRegion.setHoverState(TitleRegion.STATE_NORMAL); + } + + public void mouseHover(MouseEvent e) { + } + }); + super.setLayout(new FormHeadingLayout()); + titleRegion = new TitleRegion(this); + } + + /** + * Fully delegates the size computation to the internal layout manager. + */ + public final Point computeSize(int wHint, int hHint, bool changed) { + return (cast(FormHeadingLayout) getLayout()).computeSize(this, wHint, + hHint, changed); + } + + /** + * Prevents from changing the custom control layout. + */ + public final void setLayout(Layout layout) { + } + + /** + * Returns the title text that will be rendered at the top of the form. + * + * @return the title text + */ + public String getText() { + return titleRegion.getText(); + } + + /** + * Returns the title image that will be rendered to the left of the title. + * + * @return the title image + * @since 3.2 + */ + public Image getImage() { + return titleRegion.getImage(); + } + + /** + * Sets the background color of the header. + */ + public void setBackground(Color bg) { + super.setBackground(bg); + internalSetBackground(bg); + } + + private void internalSetBackground(Color bg) { + titleRegion.setBackground(bg); + if (messageRegion !is null) + messageRegion.setBackground(bg); + if (toolBarManager !is null) + toolBarManager.getControl().setBackground(bg); + putColor(COLOR_BASE_BG, bg); + } + + /** + * Sets the foreground color of the header. + */ + public void setForeground(Color fg) { + super.setForeground(fg); + titleRegion.setForeground(fg); + if (messageRegion !is null) + messageRegion.setForeground(fg); + } + + /** + * Sets the text to be rendered at the top of the form above the body as a + * title. + * + * @param text + * the title text + */ + public void setText(String text) { + titleRegion.setText(text); + } + + public void setFont(Font font) { + super.setFont(font); + titleRegion.setFont(font); + } + + /** + * Sets the image to be rendered to the left of the title. + * + * @param image + * the title image or <code>null</code> to show no image. + * @since 3.2 + */ + public void setImage(Image image) { + titleRegion.setImage(image); + if (messageRegion !is null) + titleRegion.updateImage(messageRegion.getMessageImage(), true); + else + titleRegion.updateImage(null, true); + } + + public void setTextBackground(Color[] gradientColors, int[] percents, + bool vertical) { + if (gradientColors !is null) { + gradientInfo = new GradientInfo(); + gradientInfo.gradientColors = gradientColors; + gradientInfo.percents = percents; + gradientInfo.vertical = vertical; + setBackground(null); + updateGradientImage(); + } else { + // reset + gradientInfo = null; + if (gradientImage !is null) { + FormImages.getInstance().markFinished(gradientImage); + gradientImage = null; + setBackgroundImage(null); + } + } + } + + public void setHeadingBackgroundImage(Image image) { + this.backgroundImage = image; + if (image !is null) + setBackground(null); + if (isBackgroundImageTiled()) { + setBackgroundImage(image); + } else + updateGradientImage(); + } + + public Image getHeadingBackgroundImage() { + return backgroundImage; + } + + public void setBackgroundImageTiled(bool tiled) { + if (tiled) + flags |= BACKGROUND_IMAGE_TILED; + else + flags &= ~BACKGROUND_IMAGE_TILED; + setHeadingBackgroundImage(this.backgroundImage); + } + + public bool isBackgroundImageTiled() { + return (flags & BACKGROUND_IMAGE_TILED) !is 0; + } + + public void setBackgroundImage(Image image) { + super.setBackgroundImage(image); + if (image !is null) { + internalSetBackground(null); + } + } + + /** + * Returns the tool bar manager that is used to manage tool items in the + * form's title area. + * + * @return form tool bar manager + */ + public IToolBarManager getToolBarManager() { + if (toolBarManager is null) { + toolBarManager = new ToolBarManager(DWT.FLAT); + ToolBar toolbar = toolBarManager.createControl(this); + toolbar.setBackground(getBackground()); + toolbar.setForeground(getForeground()); + toolbar.setCursor(FormsResources.getHandCursor()); + addDisposeListener(new class DisposeListener { + public void widgetDisposed(DisposeEvent e) { + if (toolBarManager !is null) { + toolBarManager.dispose(); + toolBarManager = null; + } + } + }); + } + return toolBarManager; + } + + /** + * Returns the menu manager that is used to manage tool items in the form's + * title area. + * + * @return form drop-down menu manager + * @since 3.3 + */ + public IMenuManager getMenuManager() { + return titleRegion.getMenuManager(); + } + + /** + * Updates the local tool bar manager if used. Does nothing if local tool + * bar manager has not been created yet. + */ + public void updateToolBar() { + if (toolBarManager !is null) + toolBarManager.update(false); + } + + private void onPaint(GC gc) { + if (!isSeparatorVisible() && getBackgroundImage() is null) + return; + Rectangle carea = getClientArea(); + Image buffer = new Image(getDisplay(), carea.width, carea.height); + buffer.setBackground(getBackground()); + GC igc = new GC(buffer); + igc.setBackground(getBackground()); + igc.fillRectangle(0, 0, carea.width, carea.height); + if (getBackgroundImage() !is null) { + if (gradientInfo !is null) + drawBackground(igc, carea.x, carea.y, carea.width, carea.height); + else { + Image bgImage = getBackgroundImage(); + Rectangle ibounds = bgImage.getBounds(); + drawBackground(igc, carea.x, carea.y, ibounds.width, + ibounds.height); + } + } + + if (isSeparatorVisible()) { + // bg separator + if (hasColor(IFormColors.H_BOTTOM_KEYLINE1)) + igc.setForeground(getColor(IFormColors.H_BOTTOM_KEYLINE1)); + else + igc.setForeground(getBackground()); + igc.drawLine(carea.x, carea.height - 2, carea.x + carea.width - 1, + carea.height - 2); + if (hasColor(IFormColors.H_BOTTOM_KEYLINE2)) + igc.setForeground(getColor(IFormColors.H_BOTTOM_KEYLINE2)); + else + igc.setForeground(getForeground()); + igc.drawLine(carea.x, carea.height - 1, carea.x + carea.width - 1, + carea.height - 1); + } + igc.dispose(); + gc.drawImage(buffer, carea.x, carea.y); + buffer.dispose(); + } + + private void updateTitleRegionHoverState(MouseEvent e) { + Rectangle titleRect = titleRegion.getBounds(); + titleRect.width += titleRect.x + 15; + titleRect.height += titleRect.y + 15; + titleRect.x = 0; + titleRect.y = 0; + if (titleRect.contains(e.x, e.y)) + titleRegion.setHoverState(TitleRegion.STATE_HOVER_LIGHT); + else + titleRegion.setHoverState(TitleRegion.STATE_NORMAL); + } + + private void updateGradientImage() { + Rectangle rect = getBounds(); + if (gradientImage !is null) { + FormImages.getInstance().markFinished(gradientImage); + gradientImage = null; + } + if (gradientInfo !is null) { + gradientImage = FormImages.getInstance().getGradient(getDisplay(), gradientInfo.gradientColors, gradientInfo.percents, + gradientInfo.vertical ? rect.height : rect.width, gradientInfo.vertical, getColor(COLOR_BASE_BG)); + } else if (backgroundImage !is null && !isBackgroundImageTiled()) { + gradientImage = new Image(getDisplay(), Math.max(rect.width, 1), + Math.max(rect.height, 1)); + gradientImage.setBackground(getBackground()); + GC gc = new GC(gradientImage); + gc.drawImage(backgroundImage, 0, 0); + gc.dispose(); + } + setBackgroundImage(gradientImage); + } + + public bool isSeparatorVisible() { + return (flags & SEPARATOR) !is 0; + } + + public void setSeparatorVisible(bool addSeparator) { + if (addSeparator) + flags |= SEPARATOR; + else + flags &= ~SEPARATOR; + } + + public void setToolBarAlignment(int alignment) { + if (alignment is DWT.BOTTOM) + flags |= BOTTOM_TOOLBAR; + else + flags &= ~BOTTOM_TOOLBAR; + } + + public int getToolBarAlignment() { + return (flags & BOTTOM_TOOLBAR) !is 0 ? DWT.BOTTOM : DWT.TOP; + } + + public void addMessageHyperlinkListener(IHyperlinkListener listener) { + ensureMessageRegionExists(); + messageRegion.addMessageHyperlinkListener(listener); + } + + public void removeMessageHyperlinkListener(IHyperlinkListener listener) { + if (messageRegion !is null) + messageRegion.removeMessageHyperlinkListener(listener); + } + + public String getMessage() { + return messageRegion !is null ? messageRegion.getMessage() : null; + } + + public int getMessageType() { + return messageRegion !is null ? messageRegion.getMessageType() : 0; + } + + public IMessage[] getChildrenMessages() { + return messageRegion !is null ? messageRegion.getChildrenMessages() + : NULL_MESSAGE_ARRAY; + } + + private void ensureMessageRegionExists() { + // ensure message region exists + if (messageRegion is null) + messageRegion = new MessageRegion(); + } + + public void showMessage(String newMessage, int type, IMessage[] messages) { + if (messageRegion is null) { + // check the trivial case + if (newMessage is null) + return; + } else if (messageRegion.isDisposed()) + return; + ensureMessageRegionExists(); + messageRegion.showMessage(newMessage, type, messages); + titleRegion.updateImage(messageRegion.getMessageImage(), false); + if (messageToolTipManager !is null) + messageToolTipManager.update(); + layout(); + redraw(); + } + + /** + * Tests if the form is in the 'busy' state. + * + * @return <code>true</code> if busy, <code>false</code> otherwise. + */ + + public bool isBusy() { + return titleRegion.isBusy(); + } + + /** + * Sets the form's busy state. Busy form will display 'busy' animation in + * the area of the title image. + * + * @param busy + * the form's busy state + */ + + public void setBusy(bool busy) { + if (titleRegion.setBusy(busy)) + layout(); + } + + public Control getHeadClient() { + return headClient; + } + + public void setHeadClient(Control headClient) { + if (headClient !is null) + Assert.isTrue(headClient.getParent() is this); + this.headClient = headClient; + layout(); + } + + public void putColor(String key, Color color) { + if (color is null) + colors.remove(key); + else + colors.put(key, color); + } + + public Color getColor(String key) { + return cast(Color) colors.get(key); + } + + public bool hasColor(String key) { + return colors.containsKey(key); + } + + public void addDragSupport(int operations, Transfer[] transferTypes, + DragSourceListener listener) { + titleRegion.addDragSupport(operations, transferTypes, listener); + } + + public void addDropSupport(int operations, Transfer[] transferTypes, + DropTargetListener listener) { + titleRegion.addDropSupport(operations, transferTypes, listener); + } + + public IMessageToolTipManager getMessageToolTipManager() { + return messageToolTipManager; + } + + public void setMessageToolTipManager( + IMessageToolTipManager messageToolTipManager) { + this.messageToolTipManager = messageToolTipManager; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/FormImages.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,303 @@ +/******************************************************************************* + * Copyright (c) 2007 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.ui.internal.forms.widgets.FormImages; + +//import dwtx.ui.internal.forms.widgets.FormImages; + +import dwt.graphics.Color; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.widgets.Display; + +import dwt.dwthelper.utils; +import tango.util.collection.HashMap; + +public class FormImages { + private static FormImages instance; + + public static FormImages getInstance() { + if (instance is null) + instance = new FormImages(); + return instance; + } + + private HashMap!(Object,Object) images; + private HashMap!(Object,Object) ids; + + private this() { + } + + private abstract class ImageIdentifier { + Display fDisplay; + Color[] fColors; + int fLength; + + this(Display display, Color[] colors, int length) { + fDisplay = display; + fColors = colors; + fLength = length; + } + + public bool equals(Object obj) { + if (null !is cast(ImageIdentifier)obj ) { + ImageIdentifier id = cast(ImageIdentifier)obj; + if (id.fColors.length is fColors.length) { + bool result = id.fDisplay.equals(fDisplay) && id.fLength is fLength; + for (int i = 0; i < fColors.length && result; i++) { + result = result && id.fColors[i].equals(fColors[i]); + } + return result; + } + } + return false; + } + + public override hash_t toHash() { + int hash = fDisplay.toHash(); + for (int i = 0; i < fColors.length; i++) + hash = hash * 7 + fColors[i].toHash(); + hash = hash * 7 + fLength; + return hash; + } + } + + private class SimpleImageIdentifier : ImageIdentifier{ + private int fTheight; + private int fMarginHeight; + + this (Display display, Color color1, Color color2, + int realtheight, int theight, int marginHeight) { + super(display, [color1, color2], realtheight); + fTheight = theight; + fMarginHeight = marginHeight; + } + + public bool equals(Object obj) { + if (null !is cast(SimpleImageIdentifier)obj ) { + SimpleImageIdentifier id = cast(SimpleImageIdentifier) obj; + if (super.equals(obj) && + id.fTheight is fTheight && id.fMarginHeight is fMarginHeight) + return true; + } + return false; + } + + public override hash_t toHash() { + int hash = super.toHash(); + hash = hash * 7 + (new Integer(fTheight)).toHash(); + hash = hash * 7 + (new Integer(fMarginHeight)).toHash(); + return hash; + } + } + + private class ComplexImageIdentifier : ImageIdentifier { + Color fBg; + bool fVertical; + int[] fPercents; + + public this(Display display, Color[] colors, int length, + int[] percents, bool vertical, Color bg) { + super(display, colors, length); + fBg = bg; + fVertical = vertical; + fPercents = percents; + } + + public bool equals(Object obj) { + if (null !is cast(ComplexImageIdentifier)obj ) { + ComplexImageIdentifier id = cast(ComplexImageIdentifier) obj; + if (super.equals(obj) && + id.fVertical is fVertical && Arrays.equals(id.fPercents, fPercents)) { + if ((id.fBg is null && fBg is null) || + (id.fBg !is null && id.fBg.equals(fBg))) + return true; + // if the only thing that isn't the same is the background color + // still return true if it does not matter (percents add up to 100) + int sum = 0; + for (int i = 0; i < fPercents.length; i++) + sum += fPercents[i]; + if (sum >= 100) + return true; + } + } + return false; + } + + public override hash_t toHash() { + int hash = super.toHash(); + hash = hash * 7 + (new Boolean(fVertical)).toHash(); + for (int i = 0; i < fPercents.length; i++) + hash = hash * 7 + (new Integer(fPercents[i])).toHash(); + return hash; + } + } + + private class ImageReference { + private Image fImage; + private int fCount; + + public this(Image image) { + fImage = image; + fCount = 1; + } + + public Image getImage() { + return fImage; + } + // returns a bool indicating if all clients of this image are finished + // a true result indicates the underlying image should be disposed + public bool decCount() { + return --fCount is 0; + } + public void incCount() { + fCount++; + } + } + + public Image getGradient(Display display, Color color1, Color color2, + int realtheight, int theight, int marginHeight) { + checkHashMaps(); + ImageIdentifier id = new SimpleImageIdentifier(display, color1, color2, realtheight, theight, marginHeight); + ImageReference result = cast(ImageReference) images.get(id); + if (result !is null && !result.getImage().isDisposed()) { + result.incCount(); + return result.getImage(); + } + Image image = createGradient(display, color1, color2, realtheight, theight, marginHeight); + images.put(id, new ImageReference(image)); + ids.put(image, id); + return image; + } + + public Image getGradient(Display display, Color[] colors, int[] percents, + int length, bool vertical, Color bg) { + checkHashMaps(); + ImageIdentifier id = new ComplexImageIdentifier(display, colors, length, percents, vertical, bg); + ImageReference result = cast(ImageReference) images.get(id); + if (result !is null && !result.getImage().isDisposed()) { + result.incCount(); + return result.getImage(); + } + Image image = createGradient(display, colors, percents, length, vertical, bg); + images.put(id, new ImageReference(image)); + ids.put(image, id); + return image; + } + + public bool markFinished(Image image) { + checkHashMaps(); + ImageIdentifier id = cast(ImageIdentifier)ids.get(image); + if (id !is null) { + ImageReference ref_ = cast(ImageReference) images.get(id); + if (ref_ !is null) { + if (ref_.decCount()) { + images.remove(id); + ids.remove(ref_.getImage()); + ref_.getImage().dispose(); + validateHashMaps(); + } + return true; + } + } + // if the image was not found, dispose of it for the caller + image.dispose(); + return false; + } + + private void checkHashMaps() { + if (images is null) + images = new HashMap!(Object,Object); + if (ids is null) + ids = new HashMap!(Object,Object); + } + + private void validateHashMaps() { + if (images.size() is 0) + images = null; + if (ids.size() is 0) + ids = null; + } + + private Image createGradient(Display display, Color color1, Color color2, + int realtheight, int theight, int marginHeight) { + Image image = new Image(display, 1, realtheight); + image.setBackground(color1); + GC gc = new GC(image); + gc.setBackground(color1); + gc.fillRectangle(0, 0, 1, realtheight); + gc.setForeground(color2); + gc.setBackground(color1); + gc.fillGradientRectangle(0, marginHeight + 2, 1, theight - 2, true); + gc.dispose(); + return image; + } + + private Image createGradient(Display display, Color[] colors, int[] percents, + int length, bool vertical, Color bg) { + int width = vertical ? 1 : length; + int height = vertical ? length : 1; + Image gradient = new Image(display, Math.max(width, 1), Math + .max(height, 1)); + GC gc = new GC(gradient); + drawTextGradient(gc, width, height, colors, percents, vertical, bg); + gc.dispose(); + return gradient; + } + + private void drawTextGradient(GC gc, int width, int height, Color[] colors, + int[] percents, bool vertical, Color bg) { + final Color oldBackground = gc.getBackground(); + if (colors.length is 1) { + if (colors[0] !is null) + gc.setBackground(colors[0]); + gc.fillRectangle(0, 0, width, height); + } else { + final Color oldForeground = gc.getForeground(); + Color lastColor = colors[0]; + if (lastColor is null) + lastColor = oldBackground; + int pos = 0; + for (int i = 0; i < percents.length; ++i) { + gc.setForeground(lastColor); + lastColor = colors[i + 1]; + if (lastColor is null) + lastColor = oldBackground; + gc.setBackground(lastColor); + if (vertical) { + int gradientHeight = percents[i] * height / 100; + + gc.fillGradientRectangle(0, pos, width, gradientHeight, + true); + pos += gradientHeight; + } else { + int gradientWidth = percents[i] * height / 100; + + gc.fillGradientRectangle(pos, 0, gradientWidth, height, + false); + pos += gradientWidth; + } + } + if (vertical && pos < height) { + if (bg !is null) + gc.setBackground(bg); + gc.fillRectangle(0, pos, width, height - pos); + } + if (!vertical && pos < width) { + if (bg !is null) + gc.setBackground(bg); + gc.fillRectangle(pos, 0, width - pos, height); + } + gc.setForeground(oldForeground); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/FormTextModel.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,782 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.internal.forms.widgets.FormTextModel; + +import dwtx.ui.internal.forms.widgets.Paragraph; +import dwtx.ui.internal.forms.widgets.IFocusSelectable; +import dwtx.ui.internal.forms.widgets.ParagraphSegment; +import dwtx.ui.internal.forms.widgets.IHyperlinkSegment; +import dwtx.ui.internal.forms.widgets.ControlSegment; +import dwtx.ui.internal.forms.widgets.ImageSegment; +import dwtx.ui.internal.forms.widgets.ObjectSegment; + +import dwt.DWT; +import dwtx.ui.forms.HyperlinkSettings; + +import dwt.dwthelper.utils; +import dwt.dwthelper.InputStream; +pragma(msg,"FIXME temp type "~__FILE__); +public class FormTextModel { + public static const String BOLD_FONT_ID = "f.____bold"; //$NON-NLS-1$ + public this() ; + public Paragraph[] getParagraphs() ; + public String getAccessibleText() ; + public void parseTaggedText(String taggedText, bool expandURLs) ; + public void parseInputStream(InputStream is_, bool expandURLs) ; + public void parseRegularText(String regularText, bool convertURLs) ; + public HyperlinkSettings getHyperlinkSettings() ; + public void setHyperlinkSettings(HyperlinkSettings settings) ; + IFocusSelectable[] getFocusSelectableSegments() ; + public IHyperlinkSegment getHyperlink(int index) ; + public IHyperlinkSegment findHyperlinkAt(int x, int y) ; + public int getHyperlinkCount() ; + public int indexOf(IHyperlinkSegment link) ; + public ParagraphSegment findSegmentAt(int x, int y) ; + public void clearCache(String fontId) ; + public IFocusSelectable getSelectedSegment() ; + public int getSelectedSegmentIndex() ; + public bool linkExists(IHyperlinkSegment link) ; + public bool traverseFocusSelectableObjects(bool next) ; + public IFocusSelectable getNextFocusSegment(bool next) ; + public bool restoreSavedLink() ; + public void selectLink(IHyperlinkSegment link) ; + public void select(IFocusSelectable selectable) ; + public bool hasFocusSegments() ; + public void dispose() ; + public bool isWhitespaceNormalized() ; + public void setWhitespaceNormalized(bool whitespaceNormalized) ; +} + +/++ +static import tango.text.xml.Document; +import tango.util.collection.ArraySeq; +public class FormTextModel { +// private static const DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory +// .newInstance(); + + alias tango.text.xml.Document.Document!(char) Document; + alias Document.NodeImpl Node; + + private bool whitespaceNormalized = true; + + private alias ArraySeq!(Paragraph) TArraySeqParagraph; + private TArraySeqParagraph paragraphs; + + private IFocusSelectable[] selectableSegments; + + private int selectedSegmentIndex = -1; + + private int savedSelectedLinkIndex = -1; + + private HyperlinkSettings hyperlinkSettings; + + public static const String BOLD_FONT_ID = "f.____bold"; //$NON-NLS-1$ + + //private static final int TEXT_ONLY_LINK = 1; + + //private static final int IMG_ONLY_LINK = 2; + + //private static final int TEXT_AND_IMAGES_LINK = 3; + + public this() { + reset(); + } + + /* + * @see ITextModel#getParagraphs() + */ + public Paragraph[] getParagraphs() { + if (paragraphs is null) + return new Paragraph[0]; + return paragraphs + .toArray(); + } + + public String getAccessibleText() { + if (paragraphs is null) + return ""; //$NON-NLS-1$ + StringBuffer sbuf = new StringBuffer(); + for (int i = 0; i < paragraphs.size(); i++) { + Paragraph paragraph = cast(Paragraph) paragraphs.get(i); + String text = paragraph.getAccessibleText(); + sbuf.append(text); + } + return sbuf.toString(); + } + + /* + * @see ITextModel#parse(String) + */ + public void parseTaggedText(String taggedText, bool expandURLs) { + if (taggedText is null) { + reset(); + return; + } + try { + InputStream stream = new ByteArrayInputStream(taggedText + .getBytes("UTF8")); //$NON-NLS-1$ + parseInputStream(stream, expandURLs); + } catch (UnsupportedEncodingException e) { + DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT, e); + } + } + + public void parseInputStream(InputStream is_, bool expandURLs) { + + documentBuilderFactory.setNamespaceAware(true); + documentBuilderFactory.setIgnoringComments(true); + + reset(); + try { + DocumentBuilder parser = documentBuilderFactory + .newDocumentBuilder(); + InputSource source = new InputSource(is_); + Document doc = parser.parse(source); + processDocument(doc, expandURLs); + } catch (ParserConfigurationException e) { + DWT.error(DWT.ERROR_INVALID_ARGUMENT, e); + } catch (SAXException e) { + DWT.error(DWT.ERROR_INVALID_ARGUMENT, e); + } catch (IOException e) { + DWT.error(DWT.ERROR_IO, e); + } + } + + private void processDocument(Document doc, bool expandURLs) { + Node root = doc.getDocumentElement(); + NodeList children = root.getChildNodes(); + processSubnodes(paragraphs, children, expandURLs); + } + + private void processSubnodes(TArraySeqParagraph plist, NodeList children, bool expandURLs) { + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeType() is Node.TEXT_NODE) { + // Make an implicit paragraph + String text = getSingleNodeText(child); + if (text !is null && !isIgnorableWhiteSpace(text, true)) { + Paragraph p = new Paragraph(true); + p.parseRegularText(text, expandURLs, true, + getHyperlinkSettings(), null); + plist.add(p); + } + } else if (child.getNodeType() is Node.ELEMENT_NODE) { + String tag = child.getNodeName().toLowerCase(); + if (tag.equals("p")) { //$NON-NLS-1$ + Paragraph p = processParagraph(child, expandURLs); + if (p !is null) + plist.add(p); + } else if (tag.equals("li")) { //$NON-NLS-1$ + Paragraph p = processListItem(child, expandURLs); + if (p !is null) + plist.add(p); + } + } + } + } + + private Paragraph processParagraph(Node paragraph, bool expandURLs) { + NodeList children = paragraph.getChildNodes(); + NamedNodeMap atts = paragraph.getAttributes(); + Node addSpaceAtt = atts.getNamedItem("addVerticalSpace"); //$NON-NLS-1$ + bool addSpace = true; + + if (addSpaceAtt is null) + addSpaceAtt = atts.getNamedItem("vspace"); //$NON-NLS-1$ + + if (addSpaceAtt !is null) { + String value = addSpaceAtt.getNodeValue(); + addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$ + } + Paragraph p = new Paragraph(addSpace); + + processSegments(p, children, expandURLs); + return p; + } + + private Paragraph processListItem(Node listItem, bool expandURLs) { + NodeList children = listItem.getChildNodes(); + NamedNodeMap atts = listItem.getAttributes(); + Node addSpaceAtt = atts.getNamedItem("addVerticalSpace");//$NON-NLS-1$ + Node styleAtt = atts.getNamedItem("style");//$NON-NLS-1$ + Node valueAtt = atts.getNamedItem("value");//$NON-NLS-1$ + Node indentAtt = atts.getNamedItem("indent");//$NON-NLS-1$ + Node bindentAtt = atts.getNamedItem("bindent");//$NON-NLS-1$ + int style = BulletParagraph.CIRCLE; + int indent = -1; + int bindent = -1; + String text = null; + bool addSpace = true; + + if (addSpaceAtt !is null) { + String value = addSpaceAtt.getNodeValue(); + addSpace = value.equalsIgnoreCase("true"); //$NON-NLS-1$ + } + if (styleAtt !is null) { + String value = styleAtt.getNodeValue(); + if (value.equalsIgnoreCase("text")) { //$NON-NLS-1$ + style = BulletParagraph.TEXT; + } else if (value.equalsIgnoreCase("image")) { //$NON-NLS-1$ + style = BulletParagraph.IMAGE; + } else if (value.equalsIgnoreCase("bullet")) { //$NON-NLS-1$ + style = BulletParagraph.CIRCLE; + } + } + if (valueAtt !is null) { + text = valueAtt.getNodeValue(); + if (style is BulletParagraph.IMAGE) + text = "i." + text; //$NON-NLS-1$ + } + if (indentAtt !is null) { + String value = indentAtt.getNodeValue(); + try { + indent = Integer.parseInt(value); + } catch (NumberFormatException e) { + } + } + if (bindentAtt !is null) { + String value = bindentAtt.getNodeValue(); + try { + bindent = Integer.parseInt(value); + } catch (NumberFormatException e) { + } + } + + BulletParagraph p = new BulletParagraph(addSpace); + p.setIndent(indent); + p.setBulletIndent(bindent); + p.setBulletStyle(style); + p.setBulletText(text); + + processSegments(p, children, expandURLs); + return p; + } + + private void processSegments(Paragraph p, NodeList children, + bool expandURLs) { + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + ParagraphSegment segment = null; + + if (child.getNodeType() is Node.TEXT_NODE) { + String value = getSingleNodeText(child); + + if (value !is null && !isIgnorableWhiteSpace(value, false)) { + p.parseRegularText(value, expandURLs, true, + getHyperlinkSettings(), null); + } + } else if (child.getNodeType() is Node.ELEMENT_NODE) { + String name = child.getNodeName(); + if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$ + segment = processImageSegment(child); + } else if (name.equalsIgnoreCase("a")) { //$NON-NLS-1$ + segment = processHyperlinkSegment(child, + getHyperlinkSettings()); + } else if (name.equalsIgnoreCase("span")) { //$NON-NLS-1$ + processTextSegment(p, expandURLs, child); + } else if (name.equalsIgnoreCase("b")) { //$NON-NLS-1$ + String text = getNodeText(child); + String fontId = BOLD_FONT_ID; + p.parseRegularText(text, expandURLs, true, + getHyperlinkSettings(), fontId); + } else if (name.equalsIgnoreCase("br")) { //$NON-NLS-1$ + segment = new BreakSegment(); + } else if (name.equalsIgnoreCase("control")) { //$NON-NLS-1$ + segment = processControlSegment(child); + } + } + if (segment !is null) { + p.addSegment(segment); + } + } + } + + private bool isIgnorableWhiteSpace(String text, bool ignoreSpaces) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (ignoreSpaces && c is ' ') + continue; + if (c is '\n' || c is '\r' || c is '\f') + continue; + return false; + } + return true; + } + + private ImageSegment processImageSegment(Node image) { + ImageSegment segment = new ImageSegment(); + processObjectSegment(segment, image, "i."); //$NON-NLS-1$ + return segment; + } + + private ControlSegment processControlSegment(Node control) { + ControlSegment segment = new ControlSegment(); + processObjectSegment(segment, control, "o."); //$NON-NLS-1$ + Node fill = control.getAttributes().getNamedItem("fill"); //$NON-NLS-1$ + if (fill !is null) { + String value = fill.getNodeValue(); + bool doFill = value.equalsIgnoreCase("true"); //$NON-NLS-1$ + segment.setFill(doFill); + } + try { + Node width = control.getAttributes().getNamedItem("width"); //$NON-NLS-1$ + if (width !is null) { + String value = width.getNodeValue(); + int doWidth = Integer.parseInt(value); + segment.setWidth(doWidth); + } + Node height = control.getAttributes().getNamedItem("height"); //$NON-NLS-1$ + if (height !is null) { + String value = height.getNodeValue(); + int doHeight = Integer.parseInt(value); + segment.setHeight(doHeight); + } + } + catch (NumberFormatException e) { + // ignore invalid width or height + } + return segment; + } + + private void processObjectSegment(ObjectSegment segment, Node object, String prefix) { + NamedNodeMap atts = object.getAttributes(); + Node id = atts.getNamedItem("href"); //$NON-NLS-1$ + Node align_ = atts.getNamedItem("align"); //$NON-NLS-1$ + if (id !is null) { + String value = id.getNodeValue(); + segment.setObjectId(prefix + value); + } + if (align_ !is null) { + String value = align_.getNodeValue().toLowerCase(); + if (value.equals("top")) //$NON-NLS-1$ + segment.setVerticalAlignment(ImageSegment.TOP); + else if (value.equals("middle")) //$NON-NLS-1$ + segment.setVerticalAlignment(ImageSegment.MIDDLE); + else if (value.equals("bottom")) //$NON-NLS-1$ + segment.setVerticalAlignment(ImageSegment.BOTTOM); + } + } + + private void appendText(String value, StringBuffer buf, int[] spaceCounter) { + if (!whitespaceNormalized) + buf.append(value); + else { + for (int j = 0; j < value.length(); j++) { + char c = value.charAt(j); + if (c is ' ' || c is '\t') { + // space + if (++spaceCounter[0] is 1) { + buf.append(c); + } + } else if (c is '\n' || c is '\r' || c is '\f') { + // new line + if (++spaceCounter[0] is 1) { + buf.append(' '); + } + } else { + // other characters + spaceCounter[0] = 0; + buf.append(c); + } + } + } + } + + private String getNormalizedText(String text) { + int[] spaceCounter = new int[1]; + StringBuffer buf = new StringBuffer(); + + if (text is null) + return null; + appendText(text, buf, spaceCounter); + return buf.toString(); + } + + private String getSingleNodeText(Node node) { + return getNormalizedText(node.getNodeValue()); + } + + private String getNodeText(Node node) { + NodeList children = node.getChildNodes(); + StringBuffer buf = new StringBuffer(); + int[] spaceCounter = new int[1]; + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeType() is Node.TEXT_NODE) { + String value = child.getNodeValue(); + appendText(value, buf, spaceCounter); + } + } + return buf.toString().trim(); + } + + private ParagraphSegment processHyperlinkSegment(Node link, + HyperlinkSettings settings) { + NamedNodeMap atts = link.getAttributes(); + String href = null; + bool wrapAllowed = true; + String boldFontId = null; + + Node hrefAtt = atts.getNamedItem("href"); //$NON-NLS-1$ + if (hrefAtt !is null) { + href = hrefAtt.getNodeValue(); + } + Node boldAtt = atts.getNamedItem("bold"); //$NON-NLS-1$ + if (boldAtt !is null) { + boldFontId = BOLD_FONT_ID; + } + Node nowrap = atts.getNamedItem("nowrap"); //$NON-NLS-1$ + if (nowrap !is null) { + String value = nowrap.getNodeValue(); + if (value !is null && value.equalsIgnoreCase("true")) //$NON-NLS-1$ + wrapAllowed = false; + } + Object status = checkChildren(link); + if ( auto child = cast(Node)status ) { + ImageHyperlinkSegment segment = new ImageHyperlinkSegment(); + segment.setHref(href); + segment.setWordWrapAllowed(wrapAllowed); + Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$ + if (alt !is null) + segment.setTooltipText(alt.getNodeValue()); + Node text = child.getAttributes().getNamedItem("text"); //$NON-NLS-1$ + if (text !is null) + segment.setText(text.getNodeValue()); + processObjectSegment(segment, child, "i."); //$NON-NLS-1$ + return segment; + } else if ( auto textObj = cast(ArrayWrapperString)status ) { + String text = textObj.array; + TextHyperlinkSegment segment = new TextHyperlinkSegment(text, + settings, null); + segment.setHref(href); + segment.setFontId(boldFontId); + Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$ + if (alt !is null) + segment.setTooltipText(alt.getNodeValue()); + segment.setWordWrapAllowed(wrapAllowed); + return segment; + } else { + AggregateHyperlinkSegment parent = new AggregateHyperlinkSegment(); + parent.setHref(href); + NodeList children = link.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeType() is Node.TEXT_NODE) { + String value = child.getNodeValue(); + TextHyperlinkSegment ts = new TextHyperlinkSegment( + getNormalizedText(value), settings, null); + Node alt = atts.getNamedItem("alt"); //$NON-NLS-1$ + if (alt !is null) + ts.setTooltipText(alt.getNodeValue()); + ts.setWordWrapAllowed(wrapAllowed); + parent.add(ts); + } else if (child.getNodeType() is Node.ELEMENT_NODE) { + String name = child.getNodeName(); + if (name.equalsIgnoreCase("img")) { //$NON-NLS-1$ + ImageHyperlinkSegment is_ = new ImageHyperlinkSegment(); + processObjectSegment(is_, child, "i."); //$NON-NLS-1$ + Node alt = child.getAttributes().getNamedItem("alt"); //$NON-NLS-1$ + if (alt !is null) + is_.setTooltipText(alt.getNodeValue()); + parent.add(is_); + is_.setWordWrapAllowed(wrapAllowed); + } + } + } + return parent; + } + } + + private Object checkChildren(Node node) { + bool text = false; + Node imgNode = null; + //int status = 0; + + NodeList children = node.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeType() is Node.TEXT_NODE) + text = true; + else if (child.getNodeType() is Node.ELEMENT_NODE + && child.getNodeName().equalsIgnoreCase("img")) { //$NON-NLS-1$ + imgNode = child; + } + } + if (text && imgNode is null) + return getNodeText(node); + else if (!text && imgNode !is null) + return imgNode; + else return null; + } + + private void processTextSegment(Paragraph p, bool expandURLs, + Node textNode) { + String text = getNodeText(textNode); + + NamedNodeMap atts = textNode.getAttributes(); + Node font = atts.getNamedItem("font"); //$NON-NLS-1$ + Node color = atts.getNamedItem("color"); //$NON-NLS-1$ + bool wrapAllowed=true; + Node nowrap = atts.getNamedItem("nowrap"); //$NON-NLS-1$ + if (nowrap !is null) { + String value = nowrap.getNodeValue(); + if (value !is null && value.equalsIgnoreCase("true")) //$NON-NLS-1$ + wrapAllowed = false; + } + String fontId = null; + String colorId = null; + if (font !is null) { + fontId = "f." + font.getNodeValue(); //$NON-NLS-1$ + } + if (color !is null) { + colorId = "c." + color.getNodeValue(); //$NON-NLS-1$ + } + p.parseRegularText(text, expandURLs, wrapAllowed, getHyperlinkSettings(), fontId, + colorId); + } + + public void parseRegularText(String regularText, bool convertURLs) { + reset(); + + if (regularText is null) + return; + + regularText = getNormalizedText(regularText); + + Paragraph p = new Paragraph(true); + paragraphs.append(p); + int pstart = 0; + + for (int i = 0; i < regularText.length(); i++) { + char c = regularText.charAt(i); + if (p is null) { + p = new Paragraph(true); + paragraphs.append(p); + } + if (c is '\n') { + String text = regularText.substring(pstart, i); + pstart = i + 1; + p.parseRegularText(text, convertURLs, true, getHyperlinkSettings(), + null); + p = null; + } + } + if (p !is null) { + // no new line + String text = regularText.substring(pstart); + p.parseRegularText(text, convertURLs, true, getHyperlinkSettings(), null); + } + } + + public HyperlinkSettings getHyperlinkSettings() { + // #132723 cannot have null settings + if (hyperlinkSettingsisnull) + hyperlinkSettings = new HyperlinkSettings(SWTUtil.getStandardDisplay()); + return hyperlinkSettings; + } + + public void setHyperlinkSettings(HyperlinkSettings settings) { + this.hyperlinkSettings = settings; + } + + private void reset() { + if (paragraphs is null) + paragraphs = new TArraySeqParagraph; + paragraphs.clear(); + selectedSegmentIndex = -1; + savedSelectedLinkIndex = -1; + selectableSegments = null; + } + + IFocusSelectable[] getFocusSelectableSegments() { + if (selectableSegments !is null || paragraphs is null) + return selectableSegments; + IFocusSelectable[] result; + for (int i = 0; i < paragraphs.size(); i++) { + Paragraph p = cast(Paragraph) paragraphs.get(i); + ParagraphSegment[] segments = p.getSegments(); + for (int j = 0; j < segments.length; j++) { + if (null !is cast(IFocusSelectable)segments[j] ) + result ~= segments[j]; + } + } + selectableSegments = result; + return selectableSegments; + } + + public IHyperlinkSegment getHyperlink(int index) { + IFocusSelectable[] selectables = getFocusSelectableSegments(); + if (selectables.length>index) { + IFocusSelectable link = selectables[index]; + if (auto l = cast(IHyperlinkSegment)link ) + return l; + } + return null; + } + + public IHyperlinkSegment findHyperlinkAt(int x, int y) { + IFocusSelectable[] selectables = getFocusSelectableSegments(); + for (int i = 0; i < selectables.length; i++) { + IFocusSelectable segment = selectables[i]; + if ( auto link = cast(IHyperlinkSegment)segment ) { + if (link.contains(x, y)) + return link; + } + } + return null; + } + + public int getHyperlinkCount() { + return getFocusSelectableSegments().length; + } + + public int indexOf(IHyperlinkSegment link) { + IFocusSelectable[] selectables = getFocusSelectableSegments(); + for (int i = 0; i < selectables.length; i++) { + IFocusSelectable segment = selectables[i]; + if (auto l = cast(IHyperlinkSegment)segment ) { + if (link is l) + return i; + } + } + return -1; + } + + public ParagraphSegment findSegmentAt(int x, int y) { + for (int i = 0; i < paragraphs.size(); i++) { + Paragraph p = cast(Paragraph) paragraphs.get(i); + ParagraphSegment segment = p.findSegmentAt(x, y); + if (segment !is null) + return segment; + } + return null; + } + + public void clearCache(String fontId) { + for (int i = 0; i < paragraphs.size(); i++) { + Paragraph p = cast(Paragraph) paragraphs.get(i); + p.clearCache(fontId); + } + } + + public IFocusSelectable getSelectedSegment() { + if (selectableSegments is null || selectedSegmentIndex is -1) + return null; + return selectableSegments[selectedSegmentIndex]; + } + + public int getSelectedSegmentIndex() { + return selectedSegmentIndex; + } + + public bool linkExists(IHyperlinkSegment link) { + if (selectableSegmentsisnull) + return false; + for (int i=0; i<selectableSegments.length; i++) { + if (selectableSegments[i] is link) + return true; + } + return false; + } + + public bool traverseFocusSelectableObjects(bool next) { + IFocusSelectable[] selectables = getFocusSelectableSegments(); + if (selectables is null) + return false; + int size = selectables.length; + if (next) { + selectedSegmentIndex++; + } else + selectedSegmentIndex--; + + if (selectedSegmentIndex < 0 || selectedSegmentIndex > size - 1) { + selectedSegmentIndex = -1; + } + return selectedSegmentIndex !is -1; + } + + public IFocusSelectable getNextFocusSegment(bool next) { + IFocusSelectable[] selectables = getFocusSelectableSegments(); + if (selectables is null) + return null; + int nextIndex = next?selectedSegmentIndex+1:selectedSegmentIndex-1; + + if (nextIndex < 0 || nextIndex > selectables.length - 1) { + return null; + } + return selectables[nextIndex]; + } + + public bool restoreSavedLink() { + if (savedSelectedLinkIndex!is -1) { + selectedSegmentIndex = savedSelectedLinkIndex; + return true; + } + return false; + } + + public void selectLink(IHyperlinkSegment link) { + if (link is null) { + savedSelectedLinkIndex = selectedSegmentIndex; + selectedSegmentIndex = -1; + } + else { + select(link); + + } + } + + public void select(IFocusSelectable selectable) { + IFocusSelectable[] selectables = getFocusSelectableSegments(); + selectedSegmentIndex = -1; + if (selectables is null) + return; + for (int i = 0; i < selectables.length; i++) { + if (selectables[i].equals(selectable)) { + selectedSegmentIndex = i; + break; + } + } + } + + public bool hasFocusSegments() { + IFocusSelectable[] segments = getFocusSelectableSegments(); + if (segments.length > 0) + return true; + return false; + } + + public void dispose() { + paragraphs = null; + selectedSegmentIndex = -1; + savedSelectedLinkIndex = -1; + selectableSegments = null; + } + + /** + * @return Returns the whitespaceNormalized. + */ + public bool isWhitespaceNormalized() { + return whitespaceNormalized; + } + + /** + * @param whitespaceNormalized + * The whitespaceNormalized to set. + */ + public void setWhitespaceNormalized(bool whitespaceNormalized) { + this.whitespaceNormalized = whitespaceNormalized; + } +} +++/ \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/FormUtil.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,554 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + * Chriss Gross (schtoo@schtoo.com) - fix for 61670 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.ui.internal.forms.widgets.FormUtil; + + +import dwt.DWT; +import dwt.custom.ScrolledComposite; +import dwt.events.MouseEvent; +import dwt.graphics.Device; +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.ImageData; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.layout.GridData; +import dwt.widgets.Combo; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Label; +import dwt.widgets.Layout; +import dwt.widgets.ScrollBar; +import dwt.widgets.Text; +import dwtx.ui.forms.widgets.ColumnLayout; +import dwtx.ui.forms.widgets.Form; +import dwtx.ui.forms.widgets.FormText; +import dwtx.ui.forms.widgets.FormToolkit; +import dwtx.ui.forms.widgets.ILayoutExtension; + +import dwt.dwthelper.utils; + +//import com.ibm.icu.text.BreakIterator; +public class FormUtil { + + //DWT_TODO temp type + static class BreakIterator{ + + public static const int DONE = 0; + + public static BreakIterator getWordInstance() { + // TODO Auto-generated method stub + return null; + } + + public void setText(String text) { + // TODO Auto-generated method stub + + } + + public int first() { + // TODO Auto-generated method stub + return 0; + } + + public int next() { + // TODO Auto-generated method stub + return 0; + } + + } + public static const String PLUGIN_ID = "dwtx.ui.forms"; //$NON-NLS-1$ + + static const int H_SCROLL_INCREMENT = 5; + + static const int V_SCROLL_INCREMENT = 64; + + public static const String DEBUG = PLUGIN_ID + "/debug"; //$NON-NLS-1$ + + public static const String DEBUG_TEXT = DEBUG + "/text"; //$NON-NLS-1$ + public static const String DEBUG_TEXTSIZE = DEBUG + "/textsize"; //$NON-NLS-1$ + + public static const String DEBUG_FOCUS = DEBUG + "/focus"; //$NON-NLS-1$ + + public static const String FOCUS_SCROLLING = "focusScrolling"; //$NON-NLS-1$ + + public static const String IGNORE_BODY = "__ignore_body__"; //$NON-NLS-1$ + + public static Text createText(Composite parent, String label, + FormToolkit factory) { + return createText(parent, label, factory, 1); + } + + public static Text createText(Composite parent, String label, + FormToolkit factory, int span) { + factory.createLabel(parent, label); + Text text = factory.createText(parent, ""); //$NON-NLS-1$ + int hfill = span is 1 ? GridData.FILL_HORIZONTAL + : GridData.HORIZONTAL_ALIGN_FILL; + GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); + gd.horizontalSpan = span; + text.setLayoutData(gd); + return text; + } + + public static Text createText(Composite parent, String label, + FormToolkit factory, int span, int style) { + Label l = factory.createLabel(parent, label); + if ((style & DWT.MULTI) !is 0) { + GridData gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING); + l.setLayoutData(gd); + } + Text text = factory.createText(parent, "", style); //$NON-NLS-1$ + int hfill = span is 1 ? GridData.FILL_HORIZONTAL + : GridData.HORIZONTAL_ALIGN_FILL; + GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); + gd.horizontalSpan = span; + text.setLayoutData(gd); + return text; + } + + public static Text createText(Composite parent, FormToolkit factory, + int span) { + Text text = factory.createText(parent, ""); //$NON-NLS-1$ + int hfill = span is 1 ? GridData.FILL_HORIZONTAL + : GridData.HORIZONTAL_ALIGN_FILL; + GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); + gd.horizontalSpan = span; + text.setLayoutData(gd); + return text; + } + + public static int computeMinimumWidth(GC gc, String text) { + BreakIterator wb = BreakIterator.getWordInstance(); + wb.setText(text); + int last = 0; + + int width = 0; + + for (int loc = wb.first(); loc !is BreakIterator.DONE; loc = wb.next()) { + String word = text.substring(last, loc); + Point extent = gc.textExtent(word); + width = Math.max(width, extent.x); + last = loc; + } + String lastWord = text.substring(last); + Point extent = gc.textExtent(lastWord); + width = Math.max(width, extent.x); + return width; + } + + public static Point computeWrapSize(GC gc, String text, int wHint) { + BreakIterator wb = BreakIterator.getWordInstance(); + wb.setText(text); + FontMetrics fm = gc.getFontMetrics(); + int lineHeight = fm.getHeight(); + + int saved = 0; + int last = 0; + int height = lineHeight; + int maxWidth = 0; + for (int loc = wb.first(); loc !is BreakIterator.DONE; loc = wb.next()) { + String word = text.substring(saved, loc); + Point extent = gc.textExtent(word); + if (extent.x > wHint) { + // overflow + saved = last; + height += extent.y; + // switch to current word so maxWidth will accommodate very long single words + word = text.substring(last, loc); + extent = gc.textExtent(word); + } + maxWidth = Math.max(maxWidth, extent.x); + last = loc; + } + /* + * Correct the height attribute in case it was calculated wrong due to wHint being less than maxWidth. + * The recursive call proved to be the only thing that worked in all cases. Some attempts can be made + * to estimate the height, but the algorithm needs to be run again to be sure. + */ + if (maxWidth > wHint) + return computeWrapSize(gc, text, maxWidth); + return new Point(maxWidth, height); + } + + public static void paintWrapText(GC gc, String text, Rectangle bounds) { + paintWrapText(gc, text, bounds, false); + } + + public static void paintWrapText(GC gc, String text, Rectangle bounds, + bool underline) { + BreakIterator wb = BreakIterator.getWordInstance(); + wb.setText(text); + FontMetrics fm = gc.getFontMetrics(); + int lineHeight = fm.getHeight(); + int descent = fm.getDescent(); + + int saved = 0; + int last = 0; + int y = bounds.y; + int width = bounds.width; + + for (int loc = wb.first(); loc !is BreakIterator.DONE; loc = wb.next()) { + String line = text.substring(saved, loc); + Point extent = gc.textExtent(line); + + if (extent.x > width) { + // overflow + String prevLine = text.substring(saved, last); + gc.drawText(prevLine, bounds.x, y, true); + if (underline) { + Point prevExtent = gc.textExtent(prevLine); + int lineY = y + lineHeight - descent + 1; + gc + .drawLine(bounds.x, lineY, bounds.x + prevExtent.x, + lineY); + } + + saved = last; + y += lineHeight; + } + last = loc; + } + // paint the last line + String lastLine = text.substring(saved, last); + gc.drawText(lastLine, bounds.x, y, true); + if (underline) { + int lineY = y + lineHeight - descent + 1; + Point lastExtent = gc.textExtent(lastLine); + gc.drawLine(bounds.x, lineY, bounds.x + lastExtent.x, lineY); + } + } + + public static ScrolledComposite getScrolledComposite(Control c) { + Composite parent = c.getParent(); + + while (parent !is null) { + if ( auto sc = cast(ScrolledComposite)parent ) { + return sc; + } + parent = parent.getParent(); + } + return null; + } + + public static void ensureVisible(Control c) { + ScrolledComposite scomp = getScrolledComposite(c); + if (scomp !is null) { + Object data = scomp.getData(FOCUS_SCROLLING); + if (data is null || !data.equals(Boolean.FALSE)) + FormUtil.ensureVisible(scomp, c); + } + } + + public static void ensureVisible(ScrolledComposite scomp, Control control) { + // if the control is a FormText we do not need to scroll since it will + // ensure visibility of its segments as necessary + if ( auto ft = cast(FormText)control ) + return; + Point controlSize = control.getSize(); + Point controlOrigin = getControlLocation(scomp, control); + ensureVisible(scomp, controlOrigin, controlSize); + } + + public static void ensureVisible(ScrolledComposite scomp, + Point controlOrigin, Point controlSize) { + Rectangle area = scomp.getClientArea(); + Point scompOrigin = scomp.getOrigin(); + + int x = scompOrigin.x; + int y = scompOrigin.y; + + // horizontal right, but only if the control is smaller + // than the client area + if (controlSize.x < area.width + && (controlOrigin.x + controlSize.x > scompOrigin.x + + area.width)) { + x = controlOrigin.x + controlSize.x - area.width; + } + // horizontal left - make sure the left edge of + // the control is showing + if (controlOrigin.x < x) { + if (controlSize.x < area.width) + x = controlOrigin.x + controlSize.x - area.width; + else + x = controlOrigin.x; + } + // vertical bottom + if (controlSize.y < area.height + && (controlOrigin.y + controlSize.y > scompOrigin.y + + area.height)) { + y = controlOrigin.y + controlSize.y - area.height; + } + // vertical top - make sure the top of + // the control is showing + if (controlOrigin.y < y) { + if (controlSize.y < area.height) + y = controlOrigin.y + controlSize.y - area.height; + else + y = controlOrigin.y; + } + + if (scompOrigin.x !is x || scompOrigin.y !is y) { + // scroll to reveal + scomp.setOrigin(x, y); + } + } + + public static void ensureVisible(ScrolledComposite scomp, Control control, + MouseEvent e) { + Point controlOrigin = getControlLocation(scomp, control); + int rX = controlOrigin.x + e.x; + int rY = controlOrigin.y + e.y; + Rectangle area = scomp.getClientArea(); + Point scompOrigin = scomp.getOrigin(); + + int x = scompOrigin.x; + int y = scompOrigin.y; + // System.out.println("Ensure: area="+area+", origin="+scompOrigin+", + // cloc="+controlOrigin+", csize="+controlSize+", x="+x+", y="+y); + + // horizontal right + if (rX > scompOrigin.x + area.width) { + x = rX - area.width; + } + // horizontal left + else if (rX < x) { + x = rX; + } + // vertical bottom + if (rY > scompOrigin.y + area.height) { + y = rY - area.height; + } + // vertical top + else if (rY < y) { + y = rY; + } + + if (scompOrigin.x !is x || scompOrigin.y !is y) { + // scroll to reveal + scomp.setOrigin(x, y); + } + } + + public static Point getControlLocation(ScrolledComposite scomp, + Control control) { + int x = 0; + int y = 0; + Control content = scomp.getContent(); + Control currentControl = control; + for (;;) { + if (currentControl is content) + break; + Point location = currentControl.getLocation(); + // if (location.x > 0) + // x += location.x; + // if (location.y > 0) + // y += location.y; + x += location.x; + y += location.y; + currentControl = currentControl.getParent(); + } + return new Point(x, y); + } + + static void scrollVertical(ScrolledComposite scomp, bool up) { + scroll(scomp, 0, up ? -V_SCROLL_INCREMENT : V_SCROLL_INCREMENT); + } + + static void scrollHorizontal(ScrolledComposite scomp, bool left) { + scroll(scomp, left ? -H_SCROLL_INCREMENT : H_SCROLL_INCREMENT, 0); + } + + static void scrollPage(ScrolledComposite scomp, bool up) { + Rectangle clientArea = scomp.getClientArea(); + int increment = up ? -clientArea.height : clientArea.height; + scroll(scomp, 0, increment); + } + + 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 updatePageIncrement(ScrolledComposite scomp) { + ScrollBar vbar = scomp.getVerticalBar(); + if (vbar !is null) { + Rectangle clientArea = scomp.getClientArea(); + int increment = clientArea.height - 5; + vbar.setPageIncrement(increment); + } + ScrollBar hbar = scomp.getHorizontalBar(); + if (hbar !is null) { + Rectangle clientArea = scomp.getClientArea(); + int increment = clientArea.width - 5; + hbar.setPageIncrement(increment); + } + } + + public static void processKey(int keyCode, Control c) { + ScrolledComposite scomp = FormUtil.getScrolledComposite(c); + if (scomp !is null) { + if (null !is cast(Combo)c ) + return; + switch (keyCode) { + case DWT.ARROW_DOWN: + if (scomp.getData("novarrows") is null) //$NON-NLS-1$ + FormUtil.scrollVertical(scomp, false); + break; + case DWT.ARROW_UP: + if (scomp.getData("novarrows") is null) //$NON-NLS-1$ + FormUtil.scrollVertical(scomp, true); + break; + case DWT.ARROW_LEFT: + FormUtil.scrollHorizontal(scomp, true); + break; + case DWT.ARROW_RIGHT: + FormUtil.scrollHorizontal(scomp, false); + break; + case DWT.PAGE_UP: + FormUtil.scrollPage(scomp, true); + break; + case DWT.PAGE_DOWN: + FormUtil.scrollPage(scomp, false); + break; + } + } + } + + public static bool isWrapControl(Control c) { + if ((c.getStyle() & DWT.WRAP) !is 0) + return true; + if (auto comp = cast(Composite)c ) { + return ( null !is cast(ILayoutExtension)( comp.getLayout() )); + } + return false; + } + + public static int getWidthHint(int wHint, Control c) { + bool wrap = isWrapControl(c); + return wrap ? wHint : DWT.DEFAULT; + } + + public static int getHeightHint(int hHint, Control c) { + if ( auto comp = cast(Composite)c ) { + Layout layout = comp.getLayout(); + if (null !is cast(ColumnLayout)layout ) + return hHint; + } + return DWT.DEFAULT; + } + + public static int computeMinimumWidth(Control c, bool changed) { + if ( auto comp = cast(Composite)c ) { + Layout layout = comp.getLayout(); + if ( auto le = cast(ILayoutExtension)layout ) + return le.computeMinimumWidth( + comp, changed); + } + return c.computeSize(FormUtil.getWidthHint(5, c), DWT.DEFAULT, changed).x; + } + + public static int computeMaximumWidth(Control c, bool changed) { + if ( auto comp = cast(Composite)c ) { + Layout layout = comp.getLayout(); + if ( auto le = cast(ILayoutExtension)layout ) + return le.computeMaximumWidth( + comp, changed); + } + return c.computeSize(DWT.DEFAULT, DWT.DEFAULT, changed).x; + } + + public static Form getForm(Control c) { + Composite parent = c.getParent(); + while (parent !is null) { + if ( auto frm = cast(Form)parent ) { + return frm; + } + parent = parent.getParent(); + } + return null; + } + + public static Image createAlphaMashImage(Device device, Image srcImage) { + Rectangle bounds = srcImage.getBounds(); + int alpha = 0; + int calpha = 0; + ImageData data = srcImage.getImageData(); + // Create a new image with alpha values alternating + // between fully transparent (0) and fully opaque (255). + // This image will show the background through the + // transparent pixels. + for (int i = 0; i < bounds.height; i++) { + // scan line + alpha = calpha; + for (int j = 0; j < bounds.width; j++) { + // column + data.setAlpha(j, i, alpha); + alpha = alpha is 255 ? 0 : 255; + } + calpha = calpha is 255 ? 0 : 255; + } + return new Image(device, data); + } + + public static bool mnemonicMatch(String text, char key) { + char mnemonic = findMnemonic(text); + if (mnemonic is '\0') + return false; + return Character.toUpperCase(key) is Character.toUpperCase(mnemonic); + } + + private static char findMnemonic(String string) { + int index = 0; + int length = string.length(); + do { + while (index < length && string.charAt(index) !is '&') + index++; + if (++index >= length) + return '\0'; + if (string.charAt(index) !is '&') + return string.charAt(index); + index++; + } while (index < length); + return '\0'; + } + + public static void setFocusScrollingEnabled(Control c, bool enabled) { + ScrolledComposite scomp = null; + + if ( auto sc = cast(ScrolledComposite)c ) + scomp = sc; + else + scomp = getScrolledComposite(c); + if (scomp !is null) + scomp.setData(FormUtil.FOCUS_SCROLLING, enabled ? null : Boolean.FALSE); + } + + public static void setAntialias(GC gc, int style) { + if (!gc.getAdvanced()) { + gc.setAdvanced(true); + if (!gc.getAdvanced()) + return; + } + gc.setAntialias(style); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/FormsResources.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2003, 2005 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.ui.internal.forms.widgets.FormsResources; + +import dwt.DWT; +import dwt.graphics.Cursor; +import dwt.widgets.Display; + +/** + * Utility methods to access shared form-specific resources. + * <p> + * All methods declared on this class are static. This + * class cannot be instantiated. + * </p> + * <p> + * </p> + */ +public class FormsResources { + private static Cursor busyCursor; + private static Cursor handCursor; + private static Cursor textCursor; + + public static Cursor getBusyCursor() { + if (busyCursorisnull) + busyCursor = new Cursor(Display.getCurrent(), DWT.CURSOR_WAIT); + return busyCursor; + } + public static Cursor getHandCursor() { + if (handCursorisnull) + handCursor = new Cursor(Display.getCurrent(), DWT.CURSOR_HAND); + return handCursor; + } + public static Cursor getTextCursor() { + if (textCursorisnull) + textCursor = new Cursor(Display.getCurrent(), DWT.CURSOR_IBEAM); + return textCursor; + } + + public static int getProgressDelay(int index) { + /* + if (progressDelaysisnull) + return 0; + return progressDelays[index]; + */ + return 100; + } + + public static void shutdown() { + if (busyCursor !is null) + busyCursor.dispose(); + if (handCursor !is null) + handCursor.dispose(); + if (textCursor !is null) + textCursor.dispose(); + busyCursor=null; + handCursor=null; + textCursor=null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/IFocusSelectable.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2003, 2005 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.ui.internal.forms.widgets.IFocusSelectable; + +import tango.util.collection.HashMap; + +import dwt.graphics.Rectangle; +import dwt.dwthelper.utils; + +public interface IFocusSelectable { + alias HashMap!(String,Object) Hashtable; + bool isFocusSelectable(Hashtable resourceTable); + bool setFocus(Hashtable resourceTable, bool direction); + Rectangle getBounds(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/IHyperlinkSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 + * Chriss Gross (schtoo@schtoo.com) - fix for 61670 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.ui.internal.forms.widgets.IHyperlinkSegment; + +import dwtx.ui.internal.forms.widgets.IFocusSelectable; + +import dwt.graphics.Color; +import dwt.graphics.GC; +import dwt.graphics.Rectangle; + +import dwt.dwthelper.utils; + +public interface IHyperlinkSegment : IFocusSelectable { + String getHref(); + String getText(); + void paintFocus(GC gc, Color bg, Color fg, bool selected, Rectangle repaintRegion); + bool contains(int x, int y); + bool intersects(Rectangle rect); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/ImageHyperlinkSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2003, 2005 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.ui.internal.forms.widgets.ImageHyperlinkSegment; + +import java.util.Hashtable; + +import dwt.graphics.Color; +import dwt.graphics.GC; +import dwt.graphics.Rectangle; + +public class ImageHyperlinkSegment extends ImageSegment implements + IHyperlinkSegment { + private String href; + private String text; + + private String tooltipText; + + public ImageHyperlinkSegment() { + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#setHref(java.lang.String) + */ + public void setHref(String href) { + this.href = href; + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#getHref() + */ + public String getHref() { + return href; + } + + public void paintFocus(GC gc, Color bg, Color fg, bool selected, + Rectangle repaintRegion) { + Rectangle bounds = getBounds(); + if (bounds is null) + return; + if (selected) { + gc.setBackground(bg); + gc.setForeground(fg); + gc.drawFocus(bounds.x, bounds.y, bounds.width, bounds.height); + } else { + gc.setForeground(bg); + gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, + bounds.height - 1); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#isWordWrapAllowed() + */ + public bool isWordWrapAllowed() { + return !isNowrap(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#setWordWrapAllowed(bool) + */ + public void setWordWrapAllowed(bool value) { + setNowrap(!value); + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.IHyperlinkSegment#getText() + */ + public String getText() { + return text!isnull?text:""; //$NON-NLS-1$ + } + + public void setText(String text) { + this.text = text; + } + + /** + * @return Returns the tooltipText. + */ + public String getTooltipText() { + return tooltipText; + } + + /** + * @param tooltipText + * The tooltipText to set. + */ + public void setTooltipText(String tooltipText) { + this.tooltipText = tooltipText; + } + + public bool isSelectable() { + return true; + } + + public bool isFocusSelectable(Hashtable resourceTable) { + return true; + } + + public bool setFocus(Hashtable resourceTable, bool direction) { + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/ImageSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.internal.forms.widgets.ImageSegment; + +import dwtx.ui.internal.forms.widgets.ObjectSegment; +import dwtx.ui.internal.forms.widgets.SelectionData; + +import dwt.graphics.Color; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; + +import dwt.dwthelper.utils; +import tango.util.collection.HashMap; + +/** + * @version 1.0 + * @author + */ +public class ImageSegment : ObjectSegment { + alias HashMap!(String,Object) Hashtable; + public static const String SEL_IMAGE_PREFIX = "isel."; //$NON-NLS-1$ + + public Image getImage(Hashtable objectTable) { + return getImage(getObjectId(), objectTable); + } + + private Image getImage(String key, Hashtable objectTable) { + if (key is null) + return null; + Object obj = objectTable.get(key); + if (obj is null) + return null; + if ( auto image = cast(Image)obj ) + return image; + return null; + } + + private Image getSelectedImage(Hashtable objectTable, SelectionData selData) { + String key = SEL_IMAGE_PREFIX + getObjectId(); + Image image = getImage(key, objectTable); + if (image is null) { + image = FormUtil.createAlphaMashImage(selData.display, getImage(objectTable)); + if (image !is null) + objectTable.put(key, image); + } + return image; + } +/* + private String getSelectedImageId() { + if (getObjectId() is null) + return null; + return SEL_IMAGE_PREFIX + getObjectId(); + } +*/ + + public void paint(GC gc, bool hover, Hashtable resourceTable, bool selected, SelectionData selData, Rectangle repaintRegion) { + Image image = getImage(resourceTable); + int iwidth = 0; + int iheight = 0; + if (image !is null) { + Rectangle rect = image.getBounds(); + iwidth = rect.width + (isSelectable()?2:0); + iheight = rect.height + (isSelectable()?2:0); + } else + return; + Rectangle bounds = getBounds(); + int ix = bounds.x+(isSelectable()?1:0); + int iy = bounds.y+(isSelectable()?1:0); + + if (selData !is null) { + int leftOffset = selData.getLeftOffset(bounds.height); + int rightOffset = selData.getRightOffset(bounds.height); + bool firstRow = selData.isFirstSelectionRow(bounds.y, + bounds.height); + bool lastRow = selData.isLastSelectionRow(bounds.y, + bounds.height); + bool selectedRow = selData + .isSelectedRow(bounds.y, bounds.height); + if (selectedRow) { + if ((firstRow && leftOffset > ix) || + (lastRow && rightOffset < ix + iwidth/2)) { + drawClipImage(gc, image, ix, iy, repaintRegion); + } + else { + Color savedBg = gc.getBackground(); + gc.setBackground(selData.bg); + int sx = ix; + int sy = iy; + if (repaintRegion !is null) { + sx -= repaintRegion.x; + sy -= repaintRegion.y; + } + gc.fillRectangle(sx, sy, iwidth, iheight); + Image selImage = getSelectedImage(resourceTable, selData); + gc.drawImage(selImage, sx, sy); + gc.setBackground(savedBg); + } + } + else + drawClipImage(gc, image, ix, iy, repaintRegion); + } else + drawClipImage(gc, image, ix, iy, repaintRegion); + if (selected) { + int fx = bounds.x; + int fy = bounds.y; + if (repaintRegion !is null) { + fx -= repaintRegion.x; + fy -= repaintRegion.y; + } + Color fg = gc.getForeground(); + gc.setForeground(gc.getBackground()); + // Clean up to avoid canceling out XOR if it is already + // selected. + gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, + bounds.height - 1); + gc.setForeground(fg); + gc.drawFocus(fx, fy, bounds.width, bounds.height); + } + } + private void drawClipImage(GC gc, Image image, int ix, int iy, Rectangle repaintRegion) { + if (repaintRegion !is null) { + ix -= repaintRegion.x; + iy -= repaintRegion.y; + } + gc.drawImage(image, ix, iy); + } + + protected Point getObjectSize(Hashtable resourceTable, int wHint) { + Image image = getImage(resourceTable); + if (image is null) + return new Point(0, 0); + Rectangle ibounds = image.getBounds(); + return new Point(ibounds.width, ibounds.height); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/Locator.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.internal.forms.widgets.Locator; + +import dwt.dwthelper.utils; +import tango.util.collection.ArraySeq; + +public class Locator : Cloneable { + public int indent; + public int x, y; + public int width; + public int leading; + public int rowHeight; + public int marginWidth; + public int marginHeight; + public int rowCounter; + public ArraySeq!( ArrayWrapperInt ) heights; + + public Locator clone(){ + auto res = new Locator(); + res.indent = indent; + res.x = x; + res.y = y; + res.width = width; + res.leading = leading; + res.rowHeight = rowHeight; + res.marginWidth = marginWidth; + res.marginHeight = marginHeight; + res.rowCounter = rowCounter; + res.heights = heights; + return res; + } + + public void newLine() { + resetCaret(); + y += rowHeight; + rowHeight = 0; + } + + public Locator create() { + try { + return cast(Locator)clone(); + } + catch (CloneNotSupportedException e) { + return null; + } + } + public void collectHeights() { + heights.add(new ArrayWrapperInt( [ rowHeight, leading ] ) ); + rowCounter++; + } + public int getBaseline(int segmentHeight) { + return getBaseline(segmentHeight, true); + + } + public int getMiddle(int segmentHeight, bool text) { + if (heights !is null && heights.size() > rowCounter) { + int [] rdata = heights.get(rowCounter).array; + int rheight = rdata[0]; + int rleading = rdata[1]; + if (text) + return y + rheight/2 - segmentHeight/2 - rleading; + return y + rheight/2 - segmentHeight/2; + } + return y; + } + public int getBaseline(int segmentHeight, bool text) { + if (heights !is null && heights.size()>rowCounter) { + int [] rdata = heights.get(rowCounter).array; + int rheight = rdata[0]; + int rleading = rdata[1]; + if (text) + return y + rheight - segmentHeight - rleading; + return y + rheight - segmentHeight; + } + return y; + } + + public void resetCaret() { + x = getStartX(); + } + public int getStartX() { + return marginWidth + indent; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/ObjectSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.internal.forms.widgets.ObjectSegment; + +import dwtx.ui.internal.forms.widgets.ParagraphSegment; +import dwtx.ui.internal.forms.widgets.Locator; +import dwtx.ui.internal.forms.widgets.SelectionData; + +import dwt.DWT; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; + +import dwt.dwthelper.utils; + +public abstract class ObjectSegment : ParagraphSegment { + public static const int TOP = 1; + + public static const int MIDDLE = 2; + + public static const int BOTTOM = 3; + + private int alignment = BOTTOM; + private bool nowrap=false; + private Rectangle bounds; + private String objectId; + + public int getVerticalAlignment() { + return alignment; + } + + void setVerticalAlignment(int alignment) { + this.alignment = alignment; + } + + public String getObjectId() { + return objectId; + } + + void setObjectId(String objectId) { + this.objectId = objectId; + } + + protected abstract Point getObjectSize(Hashtable resourceTable, int wHint); + + public bool advanceLocator(GC gc, int wHint, Locator loc, + Hashtable objectTable, bool computeHeightOnly) { + Point objectSize = getObjectSize(objectTable, wHint); + int iwidth = 0; + int iheight = 0; + bool newLine = false; + + if (objectSize !is null) { + iwidth = objectSize.x + (isSelectable()?2:0); + iheight = objectSize.y + (isSelectable()?2:0); + } + if (wHint !is DWT.DEFAULT && !nowrap && loc.x + iwidth > wHint) { + // new line + if (computeHeightOnly) + loc.collectHeights(); + loc.x = loc.indent; + loc.x += iwidth; + loc.y += loc.rowHeight; + loc.width = loc.indent + iwidth; + loc.rowHeight = iheight; + loc.leading = 0; + newLine = true; + } else { + loc.x += iwidth; + loc.width += iwidth; + loc.rowHeight = Math.max(loc.rowHeight, iheight); + } + return newLine; + } + + public bool contains(int x, int y) { + if (boundsisnull) + return false; + return bounds.contains(x, y); + } + public bool intersects(Rectangle rect) { + if (boundsisnull) + return false; + return bounds.intersects(rect); + } + + public Rectangle getBounds() { + return bounds; + } + + public bool isSelectable() { + return false; + } + /** + * @return Returns the nowrap. + */ + public bool isNowrap() { + return nowrap; + } + /** + * @param nowrap The nowrap to set. + */ + public void setNowrap(bool nowrap) { + this.nowrap = nowrap; + } + public void paint(GC gc, bool hover, Hashtable resourceTable, bool selected, SelectionData selData, Rectangle repaintRegion) { + } + + /* (non-Javadoc) + * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#layout(dwt.graphics.GC, int, dwtx.ui.internal.forms.widgets.Locator, java.util.Hashtable, bool, dwtx.ui.internal.forms.widgets.SelectionData) + */ + public void layout(GC gc, int width, Locator loc, Hashtable resourceTable, + bool selected) { + Point size = getObjectSize(resourceTable, width); + + int objWidth = 0; + int objHeight = 0; + if (size !is null) { + objWidth = size.x + (isSelectable()?2:0); + objHeight = size.y + (isSelectable()?2:0); + } else + return; + loc.width = objWidth; + + if (!nowrap && loc.x + objWidth > width) { + // new row + loc.newLine(); + loc.rowCounter++; + } + int ix = loc.x; + int iy = loc.y; + + if (alignmentisMIDDLE) + iy = loc.getMiddle(objHeight, false); + else if (alignmentisBOTTOM) + iy = loc.getBaseline(objHeight, false); + loc.x += objWidth; + loc.rowHeight = Math.max(loc.rowHeight, objHeight); + bounds = new Rectangle(ix, iy, objWidth, objHeight); + } + /* (non-Javadoc) + * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#computeSelection(dwt.graphics.GC, java.util.Hashtable, bool, dwtx.ui.internal.forms.widgets.SelectionData) + */ + public void computeSelection(GC gc, Hashtable resourceTable, SelectionData selData) { + // TODO we should add this to the selection + // if we want to support rich text + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/Paragraph.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.internal.forms.widgets.Paragraph; + +import dwtx.ui.internal.forms.widgets.ParagraphSegment; +import dwtx.ui.internal.forms.widgets.Locator; +import dwtx.ui.internal.forms.widgets.IHyperlinkSegment; +import dwtx.ui.internal.forms.widgets.SelectionData; + +import dwt.graphics.GC; +import dwt.graphics.Rectangle; +import dwtx.ui.forms.HyperlinkSettings; + +import dwt.dwthelper.utils; +import tango.util.collection.ArraySeq; +import tango.util.collection.HashMap; + +/** + * @version 1.0 + * @author + */ +public class Paragraph { + public static const String HTTP = "http://"; //$NON-NLS-1$ + + alias ArraySeq!(ParagraphSegment) TArraySeqParagraphSegment; + alias HashMap!(String,Object) Hashtable; + + private TArraySeqParagraphSegment segments; + + private bool addVerticalSpace = true; + + public this(bool addVerticalSpace) { + this.addVerticalSpace = addVerticalSpace; + } + + public int getIndent() { + return 0; + } + + public bool getAddVerticalSpace() { + return addVerticalSpace; + } + + /* + * @see IParagraph#getSegments() + */ + public ParagraphSegment[] getSegments() { + if (segments is null) + return new TArraySeqParagraphSegment; + return segments + .toArray(); + } + + public void addSegment(ParagraphSegment segment) { + if (segments is null) + segments = new TArraySeqParagraphSegment; + segments.append(segment); + } + + public void parseRegularText(String text, bool expandURLs, bool wrapAllowed, + HyperlinkSettings settings, String fontId) { + parseRegularText(text, expandURLs, wrapAllowed, settings, fontId, null); + } + + public void parseRegularText(String text, bool expandURLs, bool wrapAllowed, + HyperlinkSettings settings, String fontId, String colorId) { + if (text.length() is 0) + return; + if (expandURLs) { + int loc = text.indexOf(HTTP); + + if (loc is -1) + addSegment(new TextSegment(text, fontId, colorId, wrapAllowed)); + else { + int textLoc = 0; + while (loc !is -1) { + addSegment(new TextSegment(text.substring(textLoc, loc), + fontId, colorId, wrapAllowed)); + bool added = false; + for (textLoc = loc; textLoc < text.length(); textLoc++) { + char c = text.charAt(textLoc); + if (Character.isSpaceChar(c)) { + addHyperlinkSegment(text.substring(loc, textLoc), + settings, fontId); + added = true; + break; + } + } + if (!added) { + // there was no space - just end of text + addHyperlinkSegment(text.substring(loc), settings, + fontId); + break; + } + loc = text.indexOf(HTTP, textLoc); + } + if (textLoc < text.length()) { + addSegment(new TextSegment(text.substring(textLoc), fontId, + colorId, wrapAllowed)); + } + } + } else { + addSegment(new TextSegment(text, fontId, colorId, wrapAllowed)); + } + } + + private void addHyperlinkSegment(String text, HyperlinkSettings settings, + String fontId) { + TextHyperlinkSegment hs = new TextHyperlinkSegment(text, settings, + fontId); + hs.setWordWrapAllowed(false); + hs.setHref(text); + addSegment(hs); + } + + protected void computeRowHeights(GC gc, int width, Locator loc, + int lineHeight, Hashtable resourceTable) { + ParagraphSegment[] segments = getSegments(); + // compute heights + Locator hloc = loc.create(); + ArrayList heights = new ArrayList(); + hloc.heights = heights; + hloc.rowCounter = 0; + int innerWidth = width - loc.marginWidth*2; + for (int j = 0; j < segments.length; j++) { + ParagraphSegment segment = segments[j]; + segment.advanceLocator(gc, innerWidth, hloc, resourceTable, true); + } + hloc.collectHeights(); + loc.heights = heights; + loc.rowCounter = 0; + } + + public void layout(GC gc, int width, Locator loc, int lineHeight, + Hashtable resourceTable, IHyperlinkSegment selectedLink) { + ParagraphSegment[] segments = getSegments(); + //int height; + if (segments.length > 0) { + /* + if (segments[0] instanceof TextSegment + && ((TextSegment) segments[0]).isSelectable()) + loc.x += 1; + */ + // compute heights + if (loc.heights is null) + computeRowHeights(gc, width, loc, lineHeight, resourceTable); + for (int j = 0; j < segments.length; j++) { + ParagraphSegment segment = segments[j]; + bool doSelect = false; + if (selectedLink !is null && segment.equals(selectedLink)) + doSelect = true; + segment.layout(gc, width, loc, resourceTable, doSelect); + } + loc.heights = null; + loc.y += loc.rowHeight; + } else { + loc.y += lineHeight; + } + } + + public void paint(GC gc, Rectangle repaintRegion, + Hashtable resourceTable, IHyperlinkSegment selectedLink, + SelectionData selData) { + ParagraphSegment[] segments = getSegments(); + + for (int i = 0; i < segments.length; i++) { + ParagraphSegment segment = segments[i]; + if (!segment.intersects(repaintRegion)) + continue; + bool doSelect = false; + if (selectedLink !is null && segment.equals(selectedLink)) + doSelect = true; + segment.paint(gc, false, resourceTable, doSelect, selData, repaintRegion); + } + } + + public void computeSelection(GC gc, Hashtable resourceTable, IHyperlinkSegment selectedLink, + SelectionData selData) { + ParagraphSegment[] segments = getSegments(); + + for (int i = 0; i < segments.length; i++) { + ParagraphSegment segment = segments[i]; + //bool doSelect = false; + //if (selectedLink !is null && segment.equals(selectedLink)) + //doSelect = true; + segment.computeSelection(gc, resourceTable, selData); + } + } + + public String getAccessibleText() { + ParagraphSegment[] segments = getSegments(); + StringWriter swriter = new StringWriter(); + PrintWriter writer = new PrintWriter(swriter); + for (int i = 0; i < segments.length; i++) { + ParagraphSegment segment = segments[i]; + if ( auto ts = cast(TextSegment)segment ) { + String text = ts.getText(); + writer.print(text); + } + } + writer.println(); + swriter.flush(); + return swriter.toString(); + } + + public ParagraphSegment findSegmentAt(int x, int y) { + if (segments !is null) { + for (int i = 0; i < segments.size(); i++) { + ParagraphSegment segment = cast(ParagraphSegment) segments.get(i); + if (segment.contains(x, y)) + return segment; + } + } + return null; + } + public void clearCache(String fontId) { + if (segments !is null) { + for (int i = 0; i < segments.size(); i++) { + ParagraphSegment segment = cast(ParagraphSegment) segments.get(i); + segment.clearCache(fontId); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/ParagraphSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.internal.forms.widgets.ParagraphSegment; + +import dwtx.ui.internal.forms.widgets.Locator; +import dwtx.ui.internal.forms.widgets.SelectionData; + +import dwt.graphics.GC; +import dwt.graphics.Rectangle; + +import dwt.dwthelper.utils; + +import tango.util.collection.HashMap; + +/** + * @version 1.0 + * @author + */ +public abstract class ParagraphSegment { + + alias HashMap!(String,Object) Hashtable; + /** + * Moves the locator according to the content of this segment. + * @param gc + * @param wHint + * @param loc + * @param objectTable + * @param computeHeightOnly + * @return <code>true</code> if text wrapped to the new line, <code>false</code> otherwise. + */ + public abstract bool advanceLocator(GC gc, int wHint, Locator loc, Hashtable objectTable, bool computeHeightOnly); + /** + * Computes bounding rectangles and row heights of this segments. + * @param gc + * @param width + * @param loc + * @param resourceTable + * @param selected + */ + public abstract void layout(GC gc, int width, Locator loc, Hashtable resourceTable, bool selected); + /** + * Paints this segment. + * @param gc + * @param hover + * @param resourceTable + * @param selected + * @param selData + * @param region + */ + public abstract void paint(GC gc, bool hover, Hashtable resourceTable, bool selected, SelectionData selData, Rectangle region); + /** + * Paints this segment. + * @param gc + * @param resourceTable + * @param selData + */ + public abstract void computeSelection(GC gc, Hashtable resourceTable, SelectionData selData); + /** + * Tests if the coordinates are contained in one of the + * bounding rectangles of this segment. + * @param x + * @param y + * @return true if inside the bounding rectangle, false otherwise. + */ + public abstract bool contains(int x, int y); + /** + * Tests if the source rectangle intersects with + * one of the bounding rectangles of this segment. + * @param rect + * @return true if the two rectangles intersect, false otherwise. + */ + public abstract bool intersects(Rectangle rect); + /** + * Returns the tool tip of this segment or <code>null</code> + * if not defined. + * @return tooltip or <code>null</code>. + */ + public String getTooltipText() { + return null; + } + /** + * Clears the text metrics cache for the provided font id. + * @param fontId the id of the font that the cache is kept for. + * + */ + public void clearCache(String fontId) { + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/PixelConverter.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.internal.forms.widgets.PixelConverter; + +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.widgets.Control; + +public class PixelConverter { + /** + * Number of horizontal dialog units per character, value <code>4</code>. + */ + private static final int HORIZONTAL_DIALOG_UNIT_PER_CHAR = 4; + + private FontMetrics fFontMetrics; + + public PixelConverter(Control control) { + GC gc = new GC(control); + gc.setFont(control.getFont()); + fFontMetrics = gc.getFontMetrics(); + gc.dispose(); + } + + public int convertHorizontalDLUsToPixels(int dlus) { + // round to the nearest pixel + return (fFontMetrics.getAverageCharWidth() * dlus + HORIZONTAL_DIALOG_UNIT_PER_CHAR / 2) + / HORIZONTAL_DIALOG_UNIT_PER_CHAR; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/SWTUtil.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.internal.forms.widgets.SWTUtil; +import dwt.dnd.DragSource; +import dwt.dnd.DropTarget; +import dwt.widgets.Caret; +import dwt.widgets.Control; +import dwt.widgets.Display; +import dwt.widgets.Menu; +import dwt.widgets.ScrollBar; +import dwt.widgets.Shell; +import dwt.widgets.Widget; + +/** + * Utility class to simplify access to some DWT resources. + */ +public class SWTUtil { + + /** + * Returns the standard display to be used. The method first checks, if + * the thread calling this method has an associated disaply. If so, this + * display is returned. Otherwise the method returns the default display. + */ + public static Display getStandardDisplay() { + Display display; + display = Display.getCurrent(); + if (display is null) + display = Display.getDefault(); + return display; + } + + /** + * Returns the shell for the given widget. If the widget doesn't represent + * a DWT object that manage a shell, <code>null</code> is returned. + * + * @return the shell for the given widget + */ + public static Shell getShell(Widget widget) { + if (widget instanceof Control) + return ((Control) widget).getShell(); + if (widget instanceof Caret) + return ((Caret) widget).getParent().getShell(); + if (widget instanceof DragSource) + return ((DragSource) widget).getControl().getShell(); + if (widget instanceof DropTarget) + return ((DropTarget) widget).getControl().getShell(); + if (widget instanceof Menu) + return ((Menu) widget).getParent().getShell(); + if (widget instanceof ScrollBar) + return ((ScrollBar) widget).getParent().getShell(); + + return null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/SelectionData.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.internal.forms.widgets.SelectionData; + +import dwtx.ui.internal.forms.widgets.Locator; + +import dwt.DWT; +import dwt.events.MouseEvent; +import dwt.graphics.Color; +import dwt.graphics.Point; +import dwt.widgets.Display; + +import dwt.dwthelper.utils; +import tango.util.collection.ArraySeq; +static import tango.text.Text; + +public class SelectionData { + public Display display; + public Color bg; + public Color fg; + private Point start; + private Point stop; + private ArraySeq!(String) segments; + private bool newLineNeeded; + + public this(MouseEvent e) { + display = e.display; + segments = new ArraySeq!(String); + start = new Point(e.x, e.y); + stop = new Point(e.x, e.y); + bg = e.display.getSystemColor(DWT.COLOR_LIST_SELECTION); + fg = e.display.getSystemColor(DWT.COLOR_LIST_SELECTION_TEXT); + } + + public void markNewLine() { + newLineNeeded=true; + } + public void addSegment(String text) { + if (newLineNeeded) { + segments.append(System.getProperty("line.separator")); //$NON-NLS-1$ + newLineNeeded=false; + } + segments.append(text); + } + + public void update(MouseEvent e) { + //Control c = (Control)e.widget; + stop.x = e.x; + stop.y = e.y; + } + public void reset() { + segments.clear(); + } + public String getSelectionText() { + auto buf = new tango.text.Text.Text!(char); + for (int i=0; i<segments.size(); i++) { + buf.append(segments.get(i)); + } + return buf.toString(); + } + public bool canCopy() { + return segments.size()>0; + } + + private int getTopOffset() { + return start.y<stop.y?start.y:stop.y; + } + private int getBottomOffset() { + return start.y>stop.y?start.y:stop.y; + } + public int getLeftOffset(Locator locator) { + return isInverted(locator)? stop.x:start.x; + } + public int getLeftOffset(int rowHeight) { + return isInverted(rowHeight) ? stop.x:start.x; + } + public int getRightOffset(Locator locator) { + return isInverted(locator)? start.x: stop.x; + } + public int getRightOffset(int rowHeight) { + return isInverted(rowHeight) ? start.x:stop.x; + } + private bool isInverted(Locator locator) { + int rowHeight = locator.heights.get(locator.rowCounter).array[0]; + return isInverted(rowHeight); + } + private bool isInverted(int rowHeight) { + int deltaY = start.y - stop.y; + if (Math.abs(deltaY) > rowHeight) { + // inter-row selection + return deltaY>0; + } + // intra-row selection + return start.x > stop.x; + } + public bool isEnclosed() { + return !start.equals(stop); + } + + public bool isSelectedRow(Locator locator) { + if (!isEnclosed()) + return false; + int rowHeight = locator.heights.get(locator.rowCounter).array[0]; + return isSelectedRow(locator.y, rowHeight); + } + public bool isSelectedRow(int y, int rowHeight) { + if (!isEnclosed()) + return false; + return (y + rowHeight >= getTopOffset() && + y <= getBottomOffset()); + } + public bool isFirstSelectionRow(Locator locator) { + if (!isEnclosed()) + return false; + int rowHeight = locator.heights.get(locator.rowCounter).array[0]; + return (locator.y + rowHeight >= getTopOffset() && + locator.y <= getTopOffset()); + } + public bool isFirstSelectionRow(int y, int rowHeight) { + if (!isEnclosed()) + return false; + return (y + rowHeight >= getTopOffset() && + y <= getTopOffset()); + } + public bool isLastSelectionRow(Locator locator) { + if (!isEnclosed()) + return false; + int rowHeight = locator.heights.get(locator.rowCounter).array[0]; + return (locator.y + rowHeight >=getBottomOffset() && + locator.y <= getBottomOffset()); + } + public bool isLastSelectionRow(int y, int rowHeight) { + if (!isEnclosed()) + return false; + return (y + rowHeight >=getBottomOffset() && + y <= getBottomOffset()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/TextHyperlinkSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.ui.internal.forms.widgets.TextHyperlinkSegment; + +import java.util.Hashtable; + +import dwt.graphics.Color; +import dwt.graphics.GC; +import dwt.graphics.Rectangle; +import dwtx.ui.forms.HyperlinkSettings; + +/** + * @version 1.0 + * @author + */ +public class TextHyperlinkSegment extends TextSegment implements + IHyperlinkSegment { + private String href; + + private String tooltipText; + + //private static final String LINK_FG = "c.___link_fg"; + + private HyperlinkSettings settings; + + public TextHyperlinkSegment(String text, HyperlinkSettings settings, + String fontId) { + super(text, fontId); + this.settings = settings; + underline = settings.getHyperlinkUnderlineMode() is HyperlinkSettings.UNDERLINE_ALWAYS; + } + + /* + * @see IObjectReference#getObjectId() + */ + public String getHref() { + return href; + } + + public void setHref(String href) { + this.href = href; + } + + /* + * public void paint(GC gc, int width, Locator locator, Hashtable + * resourceTable, bool selected, SelectionData selData) { + * resourceTable.put(LINK_FG, settings.getForeground()); + * setColorId(LINK_FG); super.paint(gc, width, locator, resourceTable, + * selected, selData); } + */ + + public void paint(GC gc, bool hover, Hashtable resourceTable, + bool selected, SelectionData selData, Rectangle repaintRegion) { + bool rolloverMode = settings.getHyperlinkUnderlineMode() is HyperlinkSettings.UNDERLINE_HOVER; + Color savedFg = gc.getForeground(); + Color newFg = hover ? settings.getActiveForeground() : settings + .getForeground(); + if (newFg!isnull) + gc.setForeground(newFg); + super.paint(gc, hover, resourceTable, selected, rolloverMode, selData, + repaintRegion); + gc.setForeground(savedFg); + } + + public String getTooltipText() { + return tooltipText; + } + + public void setTooltipText(String tooltip) { + this.tooltipText = tooltip; + } + + public bool isSelectable() { + return true; + } + + public bool isFocusSelectable(Hashtable resourceTable) { + return true; + } + + public bool setFocus(Hashtable resourceTable, bool direction) { + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/TextSegment.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,755 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.internal.forms.widgets.TextSegment; + +import dwtx.ui.internal.forms.widgets.ParagraphSegment; +import dwtx.ui.internal.forms.widgets.Locator; +import dwtx.ui.internal.forms.widgets.SelectionData; + +import dwt.DWT; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; + +import dwt.dwthelper.utils; +import tango.util.collection.ArraySeq; + +/** + * @version 1.0 + * @author + */ +public class TextSegment : ParagraphSegment { + + //DWT_TODO temp type + static class BreakIterator{ + + public static const int DONE = 0; + + public static BreakIterator getLineInstance() { + // TODO Auto-generated method stub + return null; + } + + public void setText(String text) { + // TODO Auto-generated method stub + + } + + public int first() { + // TODO Auto-generated method stub + return 0; + } + + public int next() { + // TODO Auto-generated method stub + return 0; + } + + } + private String colorId; + + private String fontId; + + private String text; + + protected bool underline; + + private bool wrapAllowed = true; + + protected ArraySeq!(Object) areaRectangles; + + private TextFragment[] textFragments; + + class AreaRectangle { + Rectangle rect; + + int from, to; + + public this(Rectangle rect, int from, int to) { + this.rect = rect; + this.from = from; + this.to = to; + } + + public bool contains(int x, int y) { + return rect.contains(x, y); + } + + public bool intersects(Rectangle region) { + return rect.intersects(region); + } + + public String getText() { + if (from is 0 && to is -1) + return this.outer.getText(); + if (from > 0 && to is -1) + return this.outer.getText().substring(from); + return this.outer.getText().substring(from, to); + } + } + + static class SelectionRange { + public int start; + + public int stop; + + public this() { + reset(); + } + + public void reset() { + start = -1; + stop = -1; + } + } + + static class TextFragment { + short index; + + short length; + + public this(short index, short length) { + this.index = index; + this.length = length; + } + } + + public this(String text, String fontId) { + this(text, fontId, null, true); + } + + public this(String text, String fontId, String colorId) { + this(text, fontId, colorId, true); + } + + public this(String text, String fontId, String colorId, bool wrapAllowed) { + areaRectangles = new ArraySeq!(Object); + this.text = cleanup(text); + this.fontId = fontId; + this.colorId = colorId; + this.wrapAllowed = wrapAllowed; + } + + private String cleanup(String text) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c is '\n' || c is '\r' || c is '\f') { + if (i > 0) + buf.append(' '); + } else + buf.append(c); + } + return buf.toString(); + } + + public void setWordWrapAllowed(bool value) { + wrapAllowed = value; + } + + public bool isWordWrapAllowed() { + return wrapAllowed; + } + + public bool isSelectable() { + return false; + } + + public String getColorId() { + return colorId; + } + + public String getText() { + return text; + } + + void setText(String text) { + this.text = cleanup(text); + textFragments = null; + } + + void setColorId(String colorId) { + this.colorId = colorId; + } + + void setFontId(String fontId) { + this.fontId = fontId; + textFragments = null; + } + + public bool contains(int x, int y) { + for (int i = 0; i < areaRectangles.size(); i++) { + AreaRectangle ar = cast(AreaRectangle) areaRectangles.get(i); + if (ar.contains(x, y)) + return true; + if (i<areaRectangles.size()-1) { + // test the gap + Rectangle top = ar.rect; + Rectangle bot = (cast(AreaRectangle)areaRectangles.get(i+1)).rect; + if (y >= top.y+top.height && y < bot.y) { + // in the gap + int left = Math.max(top.x, bot.x); + int right = Math.min(top.x+top.width, bot.x+bot.width); + if (x>=left && x<=right) { + return true; + } + } + } + } + return false; + } + + public bool intersects(Rectangle rect) { + for (int i = 0; i < areaRectangles.size(); i++) { + AreaRectangle ar = cast(AreaRectangle) areaRectangles.get(i); + if (ar.intersects(rect)) + return true; + if (i<areaRectangles.size()-1) { + // test the gap + Rectangle top = ar.rect; + Rectangle bot = (cast(AreaRectangle)areaRectangles.get(i+1)).rect; + if (top.y+top.height < bot.y) { + int y = top.y+top.height; + int height = bot.y-y; + int left = Math.max(top.x, bot.x); + int right = Math.min(top.x+top.width, bot.x+bot.width); + Rectangle gap = new Rectangle(left, y, right-left, height); + if (gap.intersects(rect)) + return true; + } + } + } + return false; + } + + public Rectangle getBounds() { + int x = 0, y = 0; + int width = 0, height = 0; + + for (int i = 0; i < areaRectangles.size(); i++) { + AreaRectangle ar = cast(AreaRectangle) areaRectangles.get(i); + if (i is 0) { + x = ar.rect.x; + y = ar.rect.y; + } else + x = Math.min(ar.rect.x, x); + width = Math.max(ar.rect.width, width); + height += ar.rect.height; + } + return new Rectangle(x, y, width, height); + } + + public bool advanceLocator(GC gc, int wHint, Locator locator, + Hashtable objectTable, bool computeHeightOnly) { + Font oldFont = null; + if (fontId !is null) { + oldFont = gc.getFont(); + Font newFont = cast(Font) objectTable.get(fontId); + if (newFont !is null) + gc.setFont(newFont); + } + FontMetrics fm = gc.getFontMetrics(); + int lineHeight = fm.getHeight(); + bool newLine = false; + + if (wHint is DWT.DEFAULT || !wrapAllowed) { + Point extent = gc.textExtent(text); + int totalExtent = locator.x+extent.x; + if (isSelectable()) + totalExtent+=1; + + if (wHint !is DWT.DEFAULT && totalExtent > wHint) { + // new line + locator.x = locator.indent; + locator.y += locator.rowHeight; + if (computeHeightOnly) + locator.collectHeights(); + locator.rowHeight = 0; + locator.leading = 0; + newLine = true; + } + int width = extent.x; + if (isSelectable()) + width += 1; + locator.x += width; + locator.width = locator.indent + width; + locator.rowHeight = Math.max(locator.rowHeight, extent.y); + locator.leading = Math.max(locator.leading, fm.getLeading()); + return newLine; + } + + computeTextFragments(gc); + + int width = 0; + Point lineExtent = new Point(0, 0); + + for (int i = 0; i < textFragments.length; i++) { + TextFragment textFragment = textFragments[i]; + int currentExtent = locator.x + lineExtent.x; + + if (isSelectable()) + currentExtent += 1; + + if (i !is 0 && currentExtent + textFragment.length > wHint) { + // overflow + int lineWidth = currentExtent; + locator.rowHeight = Math.max(locator.rowHeight, lineExtent.y); + locator.leading = Math.max(locator.leading, fm.getLeading()); + if (computeHeightOnly) + locator.collectHeights(); + locator.x = locator.indent; + locator.y += locator.rowHeight; + locator.rowHeight = 0; + locator.leading = 0; + lineExtent.x = 0; + lineExtent.y = 0; + width = Math.max(width, lineWidth); + newLine = true; + } + lineExtent.x += textFragment.length; + lineExtent.y = Math.max(lineHeight, lineExtent.y); + } + int lineWidth = lineExtent.x; + if (isSelectable()) + lineWidth += 1; + locator.x += lineWidth; + locator.width = width; + locator.rowHeight = Math.max(locator.rowHeight, lineExtent.y); + locator.leading = Math.max(locator.leading, fm.getLeading()); + if (oldFont !is null) { + gc.setFont(oldFont); + } + return newLine; + } + + /** + * @param gc + * @param width + * @param locator + * @param selected + * @param selData + * @param color + * @param fm + * @param lineHeight + * @param descent + */ + private void layoutWithoutWrapping(GC gc, int width, Locator locator, + bool selected, FontMetrics fm, int lineHeight, int descent) { + Point extent = gc.textExtent(text); + int ewidth = extent.x; + if (isSelectable()) + ewidth += 1; + if (locator.x + ewidth > width-locator.marginWidth) { + // new line + locator.resetCaret(); + locator.y += locator.rowHeight; + locator.rowHeight = 0; + locator.rowCounter++; + } + int ly = locator.getBaseline(fm.getHeight() - fm.getLeading()); + //int lineY = ly + lineHeight - descent + 1; + Rectangle br = new Rectangle(locator.x, ly, ewidth, + lineHeight - descent + 3); + areaRectangles.add(new AreaRectangle(br, 0, -1)); + locator.x += ewidth; + locator.width = ewidth; + locator.rowHeight = Math.max(locator.rowHeight, extent.y); + } + + protected int convertOffsetToStringIndex(GC gc, String s, int x, + int swidth, int selOffset) { + int index = s.length(); + while (index > 0 && x + swidth > selOffset) { + index--; + String ss = s.substring(0, index); + swidth = gc.textExtent(ss).x; + } + return index; + } + + public void paintFocus(GC gc, Color bg, Color fg, bool selected, + Rectangle repaintRegion) { + if (areaRectangles is null) + return; + for (int i = 0; i < areaRectangles.size(); i++) { + AreaRectangle areaRectangle = cast(AreaRectangle) areaRectangles.get(i); + Rectangle br = areaRectangle.rect; + int bx = br.x; + int by = br.y; + if (repaintRegion !is null) { + bx -= repaintRegion.x; + by -= repaintRegion.y; + } + if (selected) { + gc.setBackground(bg); + gc.setForeground(fg); + gc.drawFocus(bx, by, br.width, br.height); + } else { + gc.setForeground(bg); + gc.drawRectangle(bx, by, br.width - 1, br.height - 1); + } + } + } + + public void paint(GC gc, bool hover, Hashtable resourceTable, + bool selected, SelectionData selData, Rectangle repaintRegion) { + this.paint(gc, hover, resourceTable, selected, false, selData, + repaintRegion); + } + + protected void paint(GC gc, bool hover, Hashtable resourceTable, + bool selected, bool rollover, SelectionData selData, + Rectangle repaintRegion) { + Font oldFont = null; + Color oldColor = null; + Color oldBg = null; + + // apply segment-specific font, color and background + if (fontId !is null) { + oldFont = gc.getFont(); + Font newFont = cast(Font) resourceTable.get(fontId); + if (newFont !is null) + gc.setFont(newFont); + } + if (!hover && colorId !is null) { + oldColor = gc.getForeground(); + Color newColor = cast(Color) resourceTable.get(colorId); + if (newColor !is null) + gc.setForeground(newColor); + } + oldBg = gc.getBackground(); + + FontMetrics fm = gc.getFontMetrics(); + int lineHeight = fm.getHeight(); + int descent = fm.getDescent(); + + // paint area rectangles of the segment + for (int i = 0; i < areaRectangles.size(); i++) { + AreaRectangle areaRectangle = cast(AreaRectangle) areaRectangles.get(i); + Rectangle rect = areaRectangle.rect; + String text = areaRectangle.getText(); + Point extent = gc.textExtent(text); + int textX = rect.x + (isSelectable()?1:0); + int lineY = rect.y + lineHeight - descent + 1; + paintString(gc, text, extent.x, textX, rect.y, lineY, selData, + rect, hover, rollover, repaintRegion); + if (selected) { + int fx = rect.x; + int fy = rect.y; + if (repaintRegion !is null) { + fx -= repaintRegion.x; + fy -= repaintRegion.y; + } + //To avoid partially cancelling the focus by painting over + //X-ORed pixels, first cancel it yourself + Color fg = gc.getForeground(); + gc.setForeground(oldBg); + gc.drawRectangle(fx, fy, rect.width - 1, rect.height - 1); + gc.setForeground(fg); + gc.drawFocus(fx, fy, rect.width, rect.height); + } + } + // restore GC resources + if (oldFont !is null) { + gc.setFont(oldFont); + } + if (oldColor !is null) { + gc.setForeground(oldColor); + } + if (oldBg !is null) { + gc.setBackground(oldBg); + } + } + + public void computeSelection(GC gc, Hashtable resourceTable, SelectionData selData) { + Font oldFont = null; + + if (fontId !is null) { + oldFont = gc.getFont(); + Font newFont = cast(Font) resourceTable.get(fontId); + if (newFont !is null) + gc.setFont(newFont); + } + + for (int i = 0; i < areaRectangles.size(); i++) { + AreaRectangle areaRectangle = cast(AreaRectangle) areaRectangles.get(i); + Rectangle rect = areaRectangle.rect; + String text = areaRectangle.getText(); + Point extent = gc.textExtent(text); + computeSelection(gc, text, extent.x, selData, + rect); + } + // restore GC resources + if (oldFont !is null) { + gc.setFont(oldFont); + } + } + + private void paintString(GC gc, String s, int swidth, int x, int y, + int lineY, SelectionData selData, Rectangle bounds, bool hover, + bool rolloverMode, Rectangle repaintRegion) { + // repaints one area rectangle + if (selData !is null && selData.isEnclosed()) { + Color savedBg = gc.getBackground(); + Color savedFg = gc.getForeground(); + int leftOffset = selData.getLeftOffset(bounds.height); + int rightOffset = selData.getRightOffset(bounds.height); + bool firstRow = selData.isFirstSelectionRow(bounds.y, + bounds.height); + bool lastRow = selData.isLastSelectionRow(bounds.y, + bounds.height); + bool selectedRow = selData + .isSelectedRow(bounds.y, bounds.height); + + int sstart = -1; + int sstop = -1; + + if ((firstRow && x + swidth < leftOffset) + || (lastRow && x > rightOffset)) { + paintStringSegment(gc, s, gc.textExtent(s).x, x, y, lineY, + hover, rolloverMode, repaintRegion); + return; + } + + if (firstRow && bounds.x + swidth > leftOffset) { + sstart = convertOffsetToStringIndex(gc, s, bounds.x, swidth, + leftOffset); + } + if (lastRow && bounds.x + swidth > rightOffset) { + sstop = convertOffsetToStringIndex(gc, s, bounds.x, swidth, + rightOffset); + } + + if (firstRow && sstart !is -1) { + String left = s.substring(0, sstart); + int width = gc.textExtent(left).x; + paintStringSegment(gc, left, width, x, y, lineY, hover, + rolloverMode, repaintRegion); + x += width; + } + if (selectedRow) { + int lindex = sstart !is -1 ? sstart : 0; + int rindex = sstop !is -1 ? sstop : s.length(); + String mid = s.substring(lindex, rindex); + Point extent = gc.textExtent(mid); + gc.setForeground(selData.fg); + gc.setBackground(selData.bg); + gc.fillRectangle(x, y, extent.x, extent.y); + paintStringSegment(gc, mid, extent.x, x, y, lineY, hover, + rolloverMode, repaintRegion); + x += extent.x; + gc.setForeground(savedFg); + gc.setBackground(savedBg); + } else { + paintStringSegment(gc, s, gc.textExtent(s).x, x, y, lineY, + hover, rolloverMode, repaintRegion); + } + if (lastRow && sstop !is -1) { + String right = s.substring(sstop); + paintStringSegment(gc, right, gc.textExtent(right).x, x, y, + lineY, hover, rolloverMode, repaintRegion); + } + } else { + paintStringSegment(gc, s, gc.textExtent(s).x, x, y, lineY, hover, + rolloverMode, repaintRegion); + } + } + + private void computeSelection(GC gc, String s, int swidth, SelectionData selData, Rectangle bounds) { + int leftOffset = selData.getLeftOffset(bounds.height); + int rightOffset = selData.getRightOffset(bounds.height); + bool firstRow = selData.isFirstSelectionRow(bounds.y, bounds.height); + bool lastRow = selData.isLastSelectionRow(bounds.y, bounds.height); + bool selectedRow = selData.isSelectedRow(bounds.y, bounds.height); + + int sstart = -1; + int sstop = -1; + + if (firstRow && bounds.x + swidth > leftOffset) { + sstart = convertOffsetToStringIndex(gc, s, bounds.x, swidth, + leftOffset); + } + if (lastRow && bounds.x + swidth > rightOffset) { + sstop = convertOffsetToStringIndex(gc, s, bounds.x, swidth, + rightOffset); + } + + if (selectedRow) { + int lindex = sstart !is -1 ? sstart : 0; + int rindex = sstop !is -1 ? sstop : s.length(); + String mid = s.substring(lindex, rindex); + selData.addSegment(mid); + } + } + + /** + * @param gc + * @param s + * @param x + * @param y + * @param lineY + * @param hover + * @param rolloverMode + */ + private void paintStringSegment(GC gc, String s, int swidth, int x, int y, + int lineY, bool hover, bool rolloverMode, + Rectangle repaintRegion) { + bool reverse = false; + int clipX = x; + int clipY = y; + int clipLineY = lineY; + if (repaintRegion !is null) { + clipX -= repaintRegion.x; + clipY -= repaintRegion.y; + clipLineY -= repaintRegion.y; + } + if (underline || hover || rolloverMode) { + if (rolloverMode && !hover) + reverse = true; + } + if (reverse) { + drawUnderline(gc, swidth, clipX, clipLineY, hover, rolloverMode); + gc.drawString(s, clipX, clipY, false); + } else { + gc.drawString(s, clipX, clipY, false); + drawUnderline(gc, swidth, clipX, clipLineY, hover, rolloverMode); + } + } + + private void drawUnderline(GC gc, int swidth, int x, int y, bool hover, + bool rolloverMode) { + if (underline || hover || rolloverMode) { + Color saved = null; + if (rolloverMode && !hover) { + saved = gc.getForeground(); + gc.setForeground(gc.getBackground()); + } + gc.drawLine(x, y, x + swidth-1, y); + if (saved !is null) + gc.setForeground(saved); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.ui.internal.forms.widgets.ParagraphSegment#layout(dwt.graphics.GC, + * int, dwtx.ui.internal.forms.widgets.Locator, + * java.util.Hashtable, bool, + * dwtx.ui.internal.forms.widgets.SelectionData) + */ + public void layout(GC gc, int width, Locator locator, + Hashtable resourceTable, bool selected) { + Font oldFont = null; + + areaRectangles.clear(); + + if (fontId !is null) { + oldFont = gc.getFont(); + Font newFont = cast(Font) resourceTable.get(fontId); + if (newFont !is null) + gc.setFont(newFont); + } + FontMetrics fm = gc.getFontMetrics(); + int lineHeight = fm.getHeight(); + int descent = fm.getDescent(); + + if (!wrapAllowed) { + layoutWithoutWrapping(gc, width, locator, selected, fm, lineHeight, + descent); + } else { + int lineStart = 0; + int lastLoc = 0; + Point lineExtent = new Point(0, 0); + computeTextFragments(gc); + int rightEdge = width-locator.marginWidth; + for (int i = 0; i < textFragments.length; i++) { + TextFragment fragment = textFragments[i]; + int breakLoc = fragment.index; + if (breakLoc is 0) + continue; + if (i !is 0 && locator.x + lineExtent.x + fragment.length > rightEdge) { + // overflow + int lineWidth = locator.x + lineExtent.x; + if (isSelectable()) + lineWidth += 1; + int ly = locator.getBaseline(lineHeight - fm.getLeading()); + Rectangle br = new Rectangle(isSelectable()? + locator.x - 1:locator.x, ly, + isSelectable()?lineExtent.x + 1:lineExtent.x, lineHeight - descent + 3); + areaRectangles + .add(new AreaRectangle(br, lineStart, lastLoc)); + + locator.rowHeight = Math.max(locator.rowHeight, + lineExtent.y); + locator.resetCaret(); + if (isSelectable()) + locator.x += 1; + locator.y += locator.rowHeight; + locator.rowCounter++; + locator.rowHeight = 0; + lineStart = lastLoc; + lineExtent.x = 0; + lineExtent.y = 0; + } + lastLoc = breakLoc; + lineExtent.x += fragment.length; + lineExtent.y = Math.max(lineHeight, lineExtent.y); + } + //String lastLine = text.substring(lineStart, lastLoc); + int ly = locator.getBaseline(lineHeight - fm.getLeading()); + int lastWidth = lineExtent.x; + if (isSelectable()) + lastWidth += 1; + Rectangle br = new Rectangle(isSelectable()?locator.x - 1:locator.x, ly, + isSelectable()?lineExtent.x + 1:lineExtent.x, + lineHeight - descent + 3); + //int lineY = ly + lineHeight - descent + 1; + areaRectangles.add(new AreaRectangle(br, lineStart, lastLoc)); + locator.x += lastWidth; + locator.rowHeight = Math.max(locator.rowHeight, lineExtent.y); + } + if (oldFont !is null) { + gc.setFont(oldFont); + } + } + + private void computeTextFragments(GC gc) { + if (textFragments !is null) + return; + ArrayList list = new ArrayList(); + BreakIterator wb = BreakIterator.getLineInstance(); + wb.setText(getText()); + int cursor = 0; + for (int loc = wb.first(); loc !is BreakIterator.DONE; loc = wb.next()) { + if (loc is 0) + continue; + String word = text.substring(cursor, loc); + Point extent = gc.textExtent(word); + list.add(new TextFragment(cast(short) loc, cast(short) extent.x)); + cursor = loc; + } + textFragments = cast(TextFragment[]) list.toArray(new TextFragment[list + .size()]); + } + + public void clearCache(String fontId) { + if (fontIdisnull && (this.fontId is null||this.fontId.equals(FormTextModel.BOLD_FONT_ID))) + textFragments = null; + else if (fontId !is null && this.fontId !is null && fontId.equals(this.fontId)) + textFragments = null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/TitleRegion.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,544 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.ui.internal.forms.widgets.TitleRegion; + +import dwtx.ui.internal.forms.widgets.BusyIndicator; + +import dwt.DWT; +import dwt.dnd.DragSource; +import dwt.dnd.DragSourceEffect; +import dwt.dnd.DragSourceEvent; +import dwt.dnd.DragSourceListener; +import dwt.dnd.DropTarget; +import dwt.dnd.DropTargetListener; +import dwt.dnd.Transfer; +import dwt.events.MouseEvent; +import dwt.events.MouseMoveListener; +import dwt.events.MouseTrackListener; +import dwt.events.PaintEvent; +import dwt.events.PaintListener; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.FontMetrics; +import dwt.graphics.GC; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Canvas; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Label; +import dwt.widgets.Layout; +import dwt.widgets.Listener; +import dwt.widgets.Menu; +import dwtx.jface.action.IMenuManager; +import dwtx.jface.action.MenuManager; +import dwtx.ui.forms.IFormColors; +import dwtx.ui.forms.widgets.ILayoutExtension; +import dwtx.ui.forms.widgets.SizeCache; +import dwtx.ui.forms.widgets.Twistie; +import dwtx.ui.internal.forms.IMessageToolTipManager; + +import dwt.dwthelper.utils; + +/** + * Form heading title. + */ +public class TitleRegion : Canvas { + public static const int STATE_NORMAL = 0; + public static const int STATE_HOVER_LIGHT = 1; + public static const int STATE_HOVER_FULL = 2; + private int hoverState; + private static const int HMARGIN = 1; + private static const int VMARGIN = 5; + private static const int SPACING = 5; + private static const int ARC_WIDTH = 20; + private static const int ARC_HEIGHT = 20; + private Image image; + private BusyIndicator busyLabel; + private Label titleLabel; + private SizeCache titleCache; + private int fontHeight = -1; + private int fontBaselineHeight = -1; + private MenuHyperlink menuHyperlink; + private MenuManager menuManager; + private bool dragSupport; + private int dragOperations; + private Transfer[] dragTransferTypes; + private DragSourceListener dragListener; + private DragSource dragSource; + private Image dragImage; + + private class HoverListener : MouseTrackListener, + MouseMoveListener { + + public void mouseEnter(MouseEvent e) { + setHoverState(STATE_HOVER_FULL); + } + + public void mouseExit(MouseEvent e) { + setHoverState(STATE_NORMAL); + } + + public void mouseHover(MouseEvent e) { + } + + public void mouseMove(MouseEvent e) { + if (e.button > 0) + setHoverState(STATE_NORMAL); + else + setHoverState(STATE_HOVER_FULL); + } + } + + private class MenuHyperlink : Twistie { + private bool firstTime = true; + + public this(Composite parent, int style) { + super(parent, style); + setExpanded(true); + } + + public void setExpanded(bool expanded) { + if (firstTime) { + super.setExpanded(expanded); + firstTime = false; + } else { + Menu menu = menuManager.createContextMenu(menuHyperlink); + menu.setVisible(true); + } + } + } + + private class TitleRegionLayout : Layout, ILayoutExtension { + + protected Point computeSize(Composite composite, int wHint, int hHint, + bool flushCache) { + return layout(composite, false, 0, 0, wHint, hHint, flushCache); + } + + protected void layout(Composite composite, bool flushCache) { + Rectangle carea = composite.getClientArea(); + layout(composite, true, carea.x, carea.y, carea.width, + carea.height, flushCache); + } + + private Point layout(Composite composite, bool move, int x, int y, + int width, int height, bool flushCache) { + int iwidth = width is DWT.DEFAULT ? DWT.DEFAULT : width - HMARGIN + * 2; + Point bsize = null; + Point tsize = null; + Point msize = null; + + if (busyLabel !is null) { + bsize = busyLabel.computeSize(DWT.DEFAULT, DWT.DEFAULT); + } + if (menuManager !is null) { + menuHyperlink.setVisible(!menuManager.isEmpty() + && titleLabel.getVisible()); + if (menuHyperlink.getVisible()) + msize = menuHyperlink.computeSize(DWT.DEFAULT, DWT.DEFAULT); + } + if (flushCache) + titleCache.flush(); + titleCache.setControl(titleLabel); + int twidth = iwidth is DWT.DEFAULT ? iwidth : iwidth - SPACING * 2; + if (bsize !is null && twidth !is DWT.DEFAULT) + twidth -= bsize.x + SPACING; + if (msize !is null && twidth !is DWT.DEFAULT) + twidth -= msize.x + SPACING; + if (titleLabel.getVisible()) { + tsize = titleCache.computeSize(twidth, DWT.DEFAULT); + if (twidth !is DWT.DEFAULT) { + // correct for the case when width hint is larger + // than the maximum width - this is when the text + // can be rendered on one line with width to spare + int maxWidth = titleCache.computeSize(DWT.DEFAULT, + DWT.DEFAULT).x; + tsize.x = Math.min(tsize.x, maxWidth); + // System.out.println("twidth="+twidth+", + // tsize.x="+tsize.x); //$NON-NLS-1$//$NON-NLS-2$ + } + } else + tsize = new Point(0, 0); + Point size = new Point(width, height); + if (!move) { + // compute size + size.x = tsize.x > 0 ? HMARGIN * 2 + SPACING * 2 + tsize.x : 0; + size.y = tsize.y; + if (bsize !is null) { + size.x += bsize.x + SPACING; + size.y = Math.max(size.y, bsize.y); + } + if (msize !is null) { + size.x += msize.x + SPACING; + size.y = Math.max(size.y, msize.y); + } + if (size.y > 0) + size.y += VMARGIN * 2; + // System.out.println("Compute size: width="+width+", + // size.x="+size.x); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + // position controls + int xloc = x + HMARGIN + SPACING; + int yloc = y + VMARGIN; + if (bsize !is null) { + busyLabel.setBounds(xloc, + // yloc + height / 2 - bsize.y / 2, + yloc + (getFontHeight() >= bsize.y ? getFontHeight() : bsize.y) - 1 - bsize.y, + bsize.x, bsize.y); + xloc += bsize.x + SPACING; + } + if (titleLabel.getVisible()) { + int tw = width - HMARGIN * 2 - SPACING * 2; + if (bsize !is null) + tw -= bsize.x + SPACING; + if (msize !is null) + tw -= msize.x + SPACING; + titleLabel.setBounds(xloc, + // yloc + height / 2 - tsize.y / 2, + yloc, tw, tsize.y); + // System.out.println("tw="+tw); //$NON-NLS-1$ + xloc += tw + SPACING; + } + if (msize !is null) { + menuHyperlink.setBounds(xloc, yloc + + getFontHeight() / 2 - msize.y / 2, + msize.x, msize.y); + } + } + return size; + } + + public int computeMaximumWidth(Composite parent, bool changed) { + return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x; + } + + public int computeMinimumWidth(Composite parent, bool changed) { + return computeSize(parent, 0, DWT.DEFAULT, changed).x; + } + } + + public this(Composite parent) { + super(parent, DWT.NULL); + titleLabel = new Label(this, DWT.WRAP); + titleLabel.setVisible(false); + titleCache = new SizeCache(); + super.setLayout(new TitleRegionLayout()); + hookHoverListeners(); + addListener(DWT.Dispose, new class Listener { + public void handleEvent(Event e) { + if (dragImage !is null) { + dragImage.dispose(); + dragImage = null; + } + } + }); + } + + /* (non-Javadoc) + * @see dwt.widgets.Control#forceFocus() + */ + public bool forceFocus() { + return false; + } + + private Color getColor(String key) { + return cast(Color) (cast(FormHeading) getParent()).colors.get(key); + } + + private void hookHoverListeners() { + HoverListener listener = new HoverListener(); + addMouseTrackListener(listener); + addMouseMoveListener(listener); + titleLabel.addMouseTrackListener(listener); + titleLabel.addMouseMoveListener(listener); + addPaintListener(new class PaintListener { + public void paintControl(PaintEvent e) { + onPaint(e); + } + }); + } + + private void onPaint(PaintEvent e) { + if (hoverState is STATE_NORMAL) + return; + GC gc = e.gc; + Rectangle carea = getClientArea(); + gc.setBackground(getHoverBackground()); + int savedAntialias = gc.getAntialias(); + FormUtil.setAntialias(gc, DWT.ON); + gc.fillRoundRectangle(carea.x + HMARGIN, carea.y + 2, carea.width + - HMARGIN * 2, carea.height - 4, ARC_WIDTH, ARC_HEIGHT); + FormUtil.setAntialias(gc, savedAntialias); + } + + private Color getHoverBackground() { + if (hoverState is STATE_NORMAL) + return null; + Color color = getColor(hoverState is STATE_HOVER_FULL ? IFormColors.H_HOVER_FULL + : IFormColors.H_HOVER_LIGHT); + if (color is null) + color = getDisplay() + .getSystemColor( + hoverState is STATE_HOVER_FULL ? DWT.COLOR_WIDGET_BACKGROUND + : DWT.COLOR_WIDGET_LIGHT_SHADOW); + return color; + } + + public void setHoverState(int state) { + if (dragSource is null || this.hoverState is state) + return; + this.hoverState = state; + Color color = getHoverBackground(); + titleLabel.setBackground(color !is null ? color + : getColor(FormHeading.COLOR_BASE_BG)); + if (busyLabel !is null) + busyLabel.setBackground(color !is null ? color + : getColor(FormHeading.COLOR_BASE_BG)); + if (menuHyperlink !is null) + menuHyperlink.setBackground(color !is null ? color + : getColor(FormHeading.COLOR_BASE_BG)); + redraw(); + } + + /** + * Fully delegates the size computation to the internal layout manager. + */ + public final Point computeSize(int wHint, int hHint, bool changed) { + return (cast(TitleRegionLayout) getLayout()).computeSize(this, wHint, + hHint, changed); + } + + public final void setLayout(Layout layout) { + // do nothing + } + + public Image getImage() { + return image; + } + + public void setImage(Image image) { + this.image = image; + } + + public void updateImage(Image newImage, bool doLayout) { + Image theImage = newImage !is null ? newImage : this.image; + + if (theImage !is null) { + ensureBusyLabelExists(); + } else if (busyLabel !is null) { + if (!busyLabel.isBusy()) { + busyLabel.dispose(); + busyLabel = null; + } + } + if (busyLabel !is null) { + busyLabel.setImage(theImage); + } + if (doLayout) + layout(); + } + + public void updateToolTip(String toolTip) { + if (busyLabel !is null) + busyLabel.setToolTipText(toolTip); + } + + public void setBackground(Color bg) { + super.setBackground(bg); + titleLabel.setBackground(bg); + if (busyLabel !is null) + busyLabel.setBackground(bg); + if (menuHyperlink !is null) + menuHyperlink.setBackground(bg); + } + + public void setForeground(Color fg) { + super.setForeground(fg); + titleLabel.setForeground(fg); + if (menuHyperlink !is null) + menuHyperlink.setForeground(fg); + } + + public void setText(String text) { + if (text !is null) + titleLabel.setText(text); + titleLabel.setVisible(text !is null); + layout(); + redraw(); + } + + public String getText() { + return titleLabel.getText(); + } + + public void setFont(Font font) { + super.setFont(font); + titleLabel.setFont(font); + fontHeight = -1; + fontBaselineHeight = -1; + layout(); + } + + private void ensureBusyLabelExists() { + if (busyLabel is null) { + busyLabel = new BusyIndicator(this, DWT.NULL); + busyLabel.setBackground(getColor(FormHeading.COLOR_BASE_BG)); + HoverListener listener = new HoverListener(); + busyLabel.addMouseTrackListener(listener); + busyLabel.addMouseMoveListener(listener); + if (menuManager !is null) + busyLabel.setMenu(menuManager.createContextMenu(this)); + if (dragSupport) + addDragSupport(busyLabel, dragOperations, dragTransferTypes, dragListener); + IMessageToolTipManager mng = (cast(FormHeading) getParent()) + .getMessageToolTipManager(); + if (mng !is null) + mng.createToolTip(busyLabel, true); + } + } + + private void createMenuHyperlink() { + menuHyperlink = new MenuHyperlink(this, DWT.NULL); + menuHyperlink.setBackground(getColor(FormHeading.COLOR_BASE_BG)); + menuHyperlink.setDecorationColor(getForeground()); + menuHyperlink.setHoverDecorationColor(getDisplay().getSystemColor(DWT.COLOR_LIST_FOREGROUND)); + HoverListener listener = new HoverListener(); + menuHyperlink.addMouseTrackListener(listener); + menuHyperlink.addMouseMoveListener(listener); + if (dragSupport) + addDragSupport(menuHyperlink, dragOperations, dragTransferTypes, dragListener); + } + + /** + * Sets the form's busy state. Busy form will display 'busy' animation in + * the area of the title image. + * + * @param busy + * the form's busy state + */ + + public bool setBusy(bool busy) { + if (busy) + ensureBusyLabelExists(); + else if (busyLabel is null) + return false; + if (busy is busyLabel.isBusy()) + return false; + busyLabel.setBusy(busy); + if (busyLabel.getImage() is null) { + layout(); + return true; + } + return false; + } + + public bool isBusy() { + return busyLabel !is null && busyLabel.isBusy(); + } + + /* + * Returns the complete height of the font. + */ + public int getFontHeight() { + if (fontHeight is -1) { + Font font = getFont(); + GC gc = new GC(getDisplay()); + gc.setFont(font); + FontMetrics fm = gc.getFontMetrics(); + fontHeight = fm.getHeight(); + gc.dispose(); + } + return fontHeight; + } + + /* + * Returns the height of the font starting at the baseline, + * i.e. without the descent. + */ + public int getFontBaselineHeight() { + if (fontBaselineHeight is -1) { + Font font = getFont(); + GC gc = new GC(getDisplay()); + gc.setFont(font); + FontMetrics fm = gc.getFontMetrics(); + fontBaselineHeight = fm.getHeight() - fm.getDescent(); + gc.dispose(); + } + return fontBaselineHeight; + } + + public IMenuManager getMenuManager() { + if (menuManager is null) { + menuManager = new MenuManager(); + Menu menu = menuManager.createContextMenu(this); + setMenu(menu); + titleLabel.setMenu(menu); + if (busyLabel !is null) + busyLabel.setMenu(menu); + createMenuHyperlink(); + } + return menuManager; + } + + public void addDragSupport(int operations, Transfer[] transferTypes, + DragSourceListener listener) { + dragSupport = true; + dragOperations = operations; + dragTransferTypes = transferTypes; + dragListener = listener; + dragSource = addDragSupport(titleLabel, operations, transferTypes, + listener); + addDragSupport(this, operations, transferTypes, listener); + if (busyLabel !is null) + addDragSupport(busyLabel, operations, transferTypes, listener); + if (menuHyperlink !is null) + addDragSupport(menuHyperlink, operations, transferTypes, listener); + } + + private DragSource addDragSupport(Control control, int operations, + Transfer[] transferTypes, DragSourceListener listener) { + DragSource source = new DragSource(control, operations); + source.setTransfer(transferTypes); + source.addDragListener(listener); + source.setDragSourceEffect(new class(control) DragSourceEffect { + this(Control c){ + super(c); + } + public void dragStart(DragSourceEvent event) { + event.image = createDragEffectImage(); + } + }); + return source; + } + + private Image createDragEffectImage() { + /* + * if (dragImage !is null) { dragImage.dispose(); } GC gc = new GC(this); + * Point size = getSize(); dragImage = new Image(getDisplay(), size.x, + * size.y); gc.copyArea(dragImage, 0, 0); gc.dispose(); return + * dragImage; + */ + return null; + } + + public void addDropSupport(int operations, Transfer[] transferTypes, + DropTargetListener listener) { + final DropTarget target = new DropTarget(this, operations); + target.setTransfer(transferTypes); + target.addDropListener(listener); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/ui/internal/forms/widgets/WrappedPageBook.d Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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.ui.internal.forms.widgets.WrappedPageBook; + +import dwt.DWT; +import dwt.graphics.Point; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Layout; +import dwtx.ui.forms.widgets.ILayoutExtension; + +import dwt.dwthelper.utils; + +/** + * A pagebook is a composite control where only a single control is visible at + * a time. It is similar to a notebook, but without tabs. + * <p> + * This class may be instantiated; it is not intended to be subclassed. + * </p> + */ +public class WrappedPageBook : Composite { + class PageBookLayout : Layout, ILayoutExtension { + protected Point computeSize(Composite composite, int wHint, int hHint, + bool flushCache) { + if (wHint !is DWT.DEFAULT && hHint !is DWT.DEFAULT) + return new Point(wHint, hHint); + Point result = null; + if (currentPage !is null) { + result = currentPage.computeSize(wHint, hHint, flushCache); + } else { + result = new Point(0, 0); + } + return result; + } + protected void layout(Composite composite, bool flushCache) { + if (currentPage !is null) { + currentPage.setBounds(composite.getClientArea()); + } + } + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.widgets.ILayoutExtension#computeMaximumWidth(dwt.widgets.Composite, + * bool) + */ + public int computeMaximumWidth(Composite parent, bool changed) { + return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x; + } + /* + * (non-Javadoc) + * + * @see dwtx.ui.forms.widgets.ILayoutExtension#computeMinimumWidth(dwt.widgets.Composite, + * bool) + */ + public int computeMinimumWidth(Composite parent, bool changed) { + return computeSize(parent, 0, DWT.DEFAULT, changed).x; + } + } + /** + * The current control; <code>null</code> if none. + */ + private Control currentPage = null; + /** + * Creates a new empty pagebook. + * + * @param parent + * the parent composite + * @param style + * the DWT style bits + */ + public this(Composite parent, int style) { + super(parent, style); + setLayout(new PageBookLayout()); + } + /** + * Shows the given page. This method has no effect if the given page is not + * contained in this pagebook. + * + * @param page + * the page to show + */ + public void showPage(Control page) { + if (page is currentPage) + return; + if (page.getParent() !is this) + return; + Control oldPage = currentPage; + currentPage = page; + // show new page + if (page !is null) { + if (!page.isDisposed()) { + //page.setVisible(true); + layout(true); + page.setVisible(true); + } + } + // hide old *after* new page has been made visible in order to avoid + // flashing + if (oldPage !is null && !oldPage.isDisposed()) + oldPage.setVisible(false); + } + public Point computeSize(int wHint, int hHint, bool changed) { + return (cast(PageBookLayout) getLayout()).computeSize(this, wHint, hHint, + changed); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/res/dwtx.ui.internal.forms.Messages.properties Sat May 24 05:11:16 2008 +0200 @@ -0,0 +1,24 @@ +############################################################################### +# Copyright (c) 2000, 2007 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 +############################################################################### +FormText_copy=&Copy +Form_tooltip_minimize=Minimize +FormDialog_defaultTitle=Form Dialog +Form_tooltip_restore=Restore + +#Message manager +MessageManager_sMessageSummary = 1 message detected +MessageManager_sWarningSummary = 1 warning detected +MessageManager_sErrorSummary = 1 error detected +MessageManager_pMessageSummary = {0} messages detected +MessageManager_pWarningSummary = {0} warnings detected +MessageManager_pErrorSummary = {0} errors detected +ToggleHyperlink_accessibleName=Expandable +ToggleHyperlink_accessibleColumn=: