--- /dev/null
+package org.argeo.eclipse.ui.forms;
+/**
+ * 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 org.eclipse.ui.forms.widgets.Section
+ * @since 1.0
+ */
+public abstract class AbstractFormPart implements IFormPart {
+ private IManagedForm managedForm;
+ private boolean dirty = false;
+ private boolean stale = true;
+ /**
+ * @see org.eclipse.ui.forms.IFormPart#initialize(org.eclipse.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(boolean 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 boolean 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 boolean 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 boolean 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
+package org.argeo.eclipse.ui.forms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.RGB;
+//import org.eclipse.swt.internal.graphics.Graphics;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * 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 1.0
+ */
+public class FormColors {
+ /**
+ * Key for the form title foreground color.
+ *
+ * @deprecated use <code>IFormColors.TITLE</code>.
+ */
+ public static final String TITLE = IFormColors.TITLE;
+
+ /**
+ * Key for the tree/table border color.
+ *
+ * @deprecated use <code>IFormColors.BORDER</code>
+ */
+ public static final String BORDER = IFormColors.BORDER;
+
+ /**
+ * Key for the section separator color.
+ *
+ * @deprecated use <code>IFormColors.SEPARATOR</code>.
+ */
+ public static final String SEPARATOR = IFormColors.SEPARATOR;
+
+ /**
+ * Key for the section title bar background.
+ *
+ * @deprecated use <code>IFormColors.TB_BG
+ */
+ public static final String TB_BG = IFormColors.TB_BG;
+
+ /**
+ * Key for the section title bar foreground.
+ *
+ * @deprecated use <code>IFormColors.TB_FG</code>
+ */
+ public static final String TB_FG = IFormColors.TB_FG;
+
+ /**
+ * Key for the section title bar gradient.
+ *
+ * @deprecated use <code>IFormColors.TB_GBG</code>
+ */
+ public static final String TB_GBG = IFormColors.TB_GBG;
+
+ /**
+ * Key for the section title bar border.
+ *
+ * @deprecated use <code>IFormColors.TB_BORDER</code>.
+ */
+ public static final 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 final String TB_TOGGLE = IFormColors.TB_TOGGLE;
+
+ /**
+ * Key for the section toggle hover color.
+ *
+ * @deprecated use <code>IFormColors.TB_TOGGLE_HOVER</code>.
+ */
+ public static final String TB_TOGGLE_HOVER = IFormColors.TB_TOGGLE_HOVER;
+
+ protected Map colorRegistry = new HashMap(10);
+
+ private LocalResourceManager resources;
+
+ protected Color background;
+
+ protected Color foreground;
+
+ private boolean shared;
+
+ protected Display display;
+
+ protected Color border;
+
+ /**
+ * Creates form colors using the provided display.
+ *
+ * @param display
+ * the display to use
+ */
+ public FormColors(Display display) {
+ 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(SWT.COLOR_LIST_BACKGROUND);
+ foreground = display.getSystemColor(SWT.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(SWT.COLOR_BLACK);
+ RGB borderRGB = getSystemColor(SWT.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.
+ */
+ 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>SWT</code> class.
+ *
+ * @param code
+ * the system color constant as defined in <code>SWT</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) {
+ // RAP [rh] changes due to missing Color constructor
+// Color c = getResourceManager().createColor(rgb);
+// Color prevC = (Color) colorRegistry.get(key);
+// if (prevC != null && !prevC.isDisposed())
+// getResourceManager().destroyColor(prevC.getRGB());
+// Color c = Graphics.getColor(rgb);
+ Color c = new Color(display, rgb);
+ colorRegistry.put(key, c);
+ return c;
+ }
+
+ /**
+ * 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
+ */
+ public Color getInactiveBackground() {
+ String key = "__ncbg__"; //$NON-NLS-1$
+ Color color = getColor(key);
+ if (color == null) {
+ RGB sel = getSystemColor(SWT.COLOR_LIST_SELECTION);
+ // a blend of 95% white and 5% list selection system color
+ RGB ncbg = blend(sel, getSystemColor(SWT.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) {
+ return createColor(key, new RGB(r,g,b));
+ }
+
+ /**
+ * 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(SWT.COLOR_WIDGET_BACKGROUND);
+ Color bg = getImpliedBackground();
+ if (border.getRed() == bg.getRed()
+ && border.getGreen() == bg.getGreen()
+ && border.getBlue() == bg.getBlue())
+ border = display.getSystemColor(SWT.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 boolean isWhiteBackground() {
+ Color bg = getImpliedBackground();
+ return bg.getRed() == 255 && bg.getGreen() == 255
+ && bg.getBlue() == 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 (Color) colorRegistry.get(key);
+ }
+
+ /**
+ * Disposes all the colors in the registry.
+ */
+ public void dispose() {
+ if (resources != null)
+ resources.dispose();
+ resources = null;
+ 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 boolean 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
+ */
+ 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.
+ */
+ public static boolean 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.
+ */
+ public static boolean 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() != null)
+ return getBackground();
+ return getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+ }
+
+ private static boolean testPrimaryColor(int value, int from, int to) {
+ return value > from && value < to;
+ }
+
+ private void createTitleColor() {
+ /*
+ * RGB rgb = getSystemColor(SWT.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(SWT.COLOR_LIST_SELECTION);
+ RGB listForeground = getSystemColor(SWT.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 == 0 && bg.green == 0 && bg.blue == 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(SWT.COLOR_WHITE);
+ createColor(TB_TOGGLE, rgb);
+ rgb = blend(rgb, white, 60);
+ createColor(TB_TOGGLE_HOVER, rgb);
+ }
+
+ private void createTitleBarGradientColors() {
+ RGB tbBg = getSystemColor(SWT.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(SWT.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 != null) {
+ colorRegistry.remove(key);
+ // RAP [rh] changes due to missing Color#dispose()
+// color.dispose();
+ }
+ }
+
+ private void createFormHeaderColors() {
+ createFormHeaderGradientColors();
+ createFormHeaderKeylineColors();
+ createFormHeaderDNDColors();
+ }
+
+ private void createFormHeaderGradientColors() {
+ RGB titleBg = getSystemColor(SWT.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(SWT.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(SWT.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);
+ }
+
+ private LocalResourceManager getResourceManager() {
+ if (resources == null)
+ resources = new LocalResourceManager(JFaceResources.getResources());
+ return resources;
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.forms;
+
+import java.util.HashMap;
+
+import org.eclipse.jface.resource.DeviceResourceException;
+import org.eclipse.jface.resource.FontDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontData;
+//import org.eclipse.swt.internal.graphics.Graphics;
+import org.eclipse.swt.widgets.Display;
+
+public class FormFonts {
+ private static FormFonts instance;
+
+ public static FormFonts getInstance() {
+ if (instance == null)
+ instance = new FormFonts();
+ return instance;
+ }
+
+ private LocalResourceManager resources;
+ private HashMap descriptors;
+
+ private FormFonts() {
+ }
+
+ private class BoldFontDescriptor extends FontDescriptor {
+ private FontData[] fFontData;
+
+ BoldFontDescriptor(Font font) {
+ // RAP [if] Changes due to different way of creating fonts
+ // fFontData = font.getFontData();
+ // for (int i = 0; i < fFontData.length; i++) {
+ // fFontData[i].setStyle(fFontData[i].getStyle() | SWT.BOLD);
+ // }
+ FontData fontData = font.getFontData()[0];
+ // Font boldFont = Graphics.getFont( fontData.getName(),
+ // fontData.getHeight(),
+ // fontData.getStyle() | SWT.BOLD );
+ Font boldFont = new Font(Display.getCurrent(), fontData.getName(), fontData.getHeight(),
+ fontData.getStyle() | SWT.BOLD);
+ fFontData = boldFont.getFontData();
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof BoldFontDescriptor) {
+ BoldFontDescriptor desc = (BoldFontDescriptor) obj;
+ if (desc.fFontData.length != fFontData.length)
+ return false;
+ for (int i = 0; i < fFontData.length; i++)
+ if (!fFontData[i].equals(desc.fFontData[i]))
+ return false;
+ return true;
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ int hash = 0;
+ for (int i = 0; i < fFontData.length; i++)
+ hash = hash * 7 + fFontData[i].hashCode();
+ return hash;
+ }
+
+ public Font createFont(Device device) throws DeviceResourceException {
+ // RAP [if] Changes due to different way of creating fonts
+ return new Font(device, fFontData[0]);
+ // return Graphics.getFont( fFontData[ 0 ] );
+ }
+
+ public void destroyFont(Font previouslyCreatedFont) {
+ // RAP [if] unnecessary
+ // previouslyCreatedFont.dispose();
+ }
+ }
+
+ public Font getBoldFont(Display display, Font font) {
+ checkHashMaps();
+ BoldFontDescriptor desc = new BoldFontDescriptor(font);
+ Font result = getResourceManager().createFont(desc);
+ descriptors.put(result, desc);
+ return result;
+ }
+
+ public boolean markFinished(Font boldFont) {
+ checkHashMaps();
+ BoldFontDescriptor desc = (BoldFontDescriptor) descriptors.get(boldFont);
+ if (desc != null) {
+ getResourceManager().destroyFont(desc);
+ if (getResourceManager().find(desc) == null) {
+ descriptors.remove(boldFont);
+ validateHashMaps();
+ }
+ return true;
+
+ }
+ // if the image was not found, dispose of it for the caller
+ // RAP [if] unnecessary
+ // boldFont.dispose();
+ return false;
+ }
+
+ private LocalResourceManager getResourceManager() {
+ if (resources == null)
+ resources = new LocalResourceManager(JFaceResources.getResources());
+ return resources;
+ }
+
+ private void checkHashMaps() {
+ if (descriptors == null)
+ descriptors = new HashMap();
+ }
+
+ private void validateHashMaps() {
+ if (descriptors.size() == 0)
+ descriptors = null;
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.forms;
+
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+//import org.eclipse.swt.custom.CCombo;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.FocusAdapter;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+// RAP [rh] Paint events missing
+//import org.eclipse.swt.events.PaintEvent;
+//import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+//RAP [rh] GC missing
+//import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+//import org.eclipse.swt.graphics.RGB;
+//import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+//import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+//import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.Widget;
+//import org.eclipse.ui.forms.FormColors;
+//import org.eclipse.ui.forms.HyperlinkGroup;
+//import org.eclipse.ui.forms.IFormColors;
+//import org.eclipse.ui.internal.forms.widgets.FormFonts;
+//import org.eclipse.ui.internal.forms.widgets.FormUtil;
+
+/**
+ * The toolkit is responsible for creating SWT 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 1.0
+ */
+public class FormToolkit {
+ public static final String KEY_DRAW_BORDER = "FormWidgetFactory.drawBorder"; //$NON-NLS-1$
+
+ public static final String TREE_BORDER = "treeBorder"; //$NON-NLS-1$
+
+ public static final String TEXT_BORDER = "textBorder"; //$NON-NLS-1$
+
+ private int borderStyle = SWT.NULL;
+
+ private FormColors colors;
+
+ private int orientation = Window.getDefaultOrientation();
+
+ // private KeyListener deleteListener;
+ // RAP [rh] Paint events missing
+// private BorderPainter borderPainter;
+
+ private BoldFontHolder boldFontHolder;
+
+// private HyperlinkGroup hyperlinkGroup;
+
+ private boolean isDisposed = false;
+
+ /* default */
+ VisibilityHandler visibilityHandler;
+
+ /* default */
+ KeyboardHandler keyboardHandler;
+
+ // RAP [rh] Paint events missing
+// private class BorderPainter implements PaintListener {
+// public void paintControl(PaintEvent event) {
+// Composite composite = (Composite) event.widget;
+// Control[] children = composite.getChildren();
+// for (int i = 0; i < children.length; i++) {
+// Control c = children[i];
+// boolean inactiveBorder = false;
+// boolean textBorder = false;
+// if (!c.isVisible())
+// continue;
+// /*
+// * if (c.getEnabled() == false && !(c instanceof CCombo))
+// * continue;
+// */
+// if (c instanceof Hyperlink)
+// continue;
+// Object flag = c.getData(KEY_DRAW_BORDER);
+// if (flag != null) {
+// if (flag.equals(Boolean.FALSE))
+// continue;
+// if (flag.equals(TREE_BORDER))
+// inactiveBorder = true;
+// else if (flag.equals(TEXT_BORDER))
+// textBorder = true;
+// }
+// if (getBorderStyle() == SWT.BORDER) {
+// if (!inactiveBorder && !textBorder) {
+// continue;
+// }
+// if (c instanceof Text || c instanceof Table
+// || c instanceof Tree)
+// continue;
+// }
+// if (!inactiveBorder
+// && (c instanceof Text || c instanceof CCombo || 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() == SWT.BORDER ? colors
+// // .getBorderColor() : colors.getForeground());
+// gc.setForeground(colors.getBorderColor());
+// if (c instanceof CCombo)
+// 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 || c instanceof Table
+// || c instanceof Tree) {
+// 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 extends FocusAdapter {
+ public void focusGained(FocusEvent e) {
+ Widget w = e.widget;
+ if (w instanceof Control) {
+ FormUtil.ensureVisible((Control) w);
+ }
+ }
+ }
+
+ private static class KeyboardHandler extends KeyAdapter {
+ public void keyPressed(KeyEvent e) {
+ Widget w = e.widget;
+ if (w instanceof Control) {
+ if (e.doit)
+ FormUtil.processKey(e.keyCode, (Control) w);
+ }
+ }
+ }
+
+ private class BoldFontHolder {
+ private Font normalFont;
+
+ private Font boldFont;
+
+ public BoldFontHolder() {
+ }
+
+ public Font getBoldFont(Font font) {
+ createBoldFont(font);
+ return boldFont;
+ }
+
+ private void createBoldFont(Font font) {
+ if (normalFont == null || !normalFont.equals(font)) {
+ normalFont = font;
+ dispose();
+ }
+ if (boldFont == null) {
+ boldFont = FormFonts.getInstance().getBoldFont(colors.getDisplay(),
+ normalFont);
+ }
+ }
+
+ public void dispose() {
+ if (boldFont != null) {
+ FormFonts.getInstance().markFinished(boldFont);
+ boldFont = null;
+ }
+ }
+ }
+
+ /**
+ * Creates a toolkit that is self-sufficient (will manage its own colors).
+ * <p>
+ * Clients that call this method must call {@link #dispose()} when they
+ * are finished using the toolkit.
+ *
+ */
+ public FormToolkit(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.
+ * <p>
+ * Clients that call this method must call {@link #dispose()} when they
+ * are finished using the toolkit.
+ *
+ * @param colors
+ * the shared colors
+ */
+ public FormToolkit(FormColors colors) {
+ 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>SWT.PUSH</code>)
+ * @return the button widget
+ */
+ public Button createButton(Composite parent, String text, int style) {
+ Button button = new Button(parent, style | SWT.FLAT | orientation);
+ if (text != 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, SWT.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);
+ Composite composite = new Composite(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
+ */
+// RAP [rh] createCompositeSeparator: currently no useful implementation possible, delete?
+ public Composite createCompositeSeparator(Composite parent) {
+ final Composite composite = new Composite(parent, orientation);
+// RAP [rh] GC and paint events missing
+// composite.addListener(SWT.Paint, new Listener() {
+// public void handleEvent(Event e) {
+// if (composite.isDisposed())
+// return;
+// Rectangle bounds = composite.getBounds();
+// GC gc = e.gc;
+// gc.setForeground(colors.getColor(IFormColors.SEPARATOR));
+// if (colors.getBackground() != null)
+// gc.setBackground(colors.getBackground());
+// gc.fillGradientRectangle(0, 0, bounds.width, bounds.height,
+// false);
+// }
+// });
+// if (parent instanceof Section)
+// ((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, SWT.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 != 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 != 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
+ * @since 1.2
+ */
+// public FormText createFormText(Composite parent, boolean trackFocus) {
+// FormText engine = new FormText(parent, SWT.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, boolean trackFocus, boolean trackKeyboard) {
+ control.setBackground(colors.getBackground());
+ control.setForeground(colors.getForeground());
+// if (control instanceof ExpandableComposite) {
+// ExpandableComposite ec = (ExpandableComposite) control;
+// if (ec.toggle != null) {
+// if (trackFocus)
+// ec.toggle.addFocusListener(visibilityHandler);
+// if (trackKeyboard)
+// ec.toggle.addKeyListener(keyboardHandler);
+// }
+// if (ec.textLabel != null) {
+// if (trackFocus)
+// ec.textLabel.addFocusListener(visibilityHandler);
+// if (trackKeyboard)
+// ec.textLabel.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 MouseAdapter() {
+ public void mouseDown(MouseEvent e) {
+ ((Control) e.widget).setFocus();
+ }
+ });
+ if (composite.getParent() != null)
+ 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 != null) {
+// section.toggle.setHoverDecorationColor(colors
+// .getColor(IFormColors.TB_TOGGLE_HOVER));
+// section.toggle.setDecorationColor(colors
+// .getColor(IFormColors.TB_TOGGLE));
+// }
+// section.setFont(boldFontHolder.getBoldFont(parent.getFont()));
+// if ((sectionStyle & Section.TITLE_BAR) != 0
+// || (sectionStyle & Section.SHORT_TITLE_BAR) != 0) {
+// colors.initializeSectionToolBarColors();
+// section.setTitleBarBackground(colors.getColor(IFormColors.TB_BG));
+// section.setTitleBarBorderColor(colors
+// .getColor(IFormColors.TB_BORDER));
+// }
+// // call setTitleBarForeground regardless as it also sets the label color
+// 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, SWT.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, SWT.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 != 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 ScrolledComposite createScrolledForm(Composite parent) {
+ ScrolledComposite form = new ScrolledComposite(parent, SWT.V_SCROLL
+ | SWT.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.
+ *
+ * @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(new Color[] { top, bot }, new int[] { 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 (isDisposed) {
+ return;
+ }
+ isDisposed = true;
+ if (colors.isShared() == 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());
+// }
+
+// RAP [rh] paintBordersFor not useful as no GC to actually paint borders
+// /**
+// * Paints flat borders for widgets created by this toolkit within the
+// * provided parent. Borders will not be painted if the global border style
+// * is SWT.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 == SWT.BORDER)
+// // return;
+// if (borderPainter == 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 SWT.BORDER and don't paint the borders
+ * ourselves. Otherwise, the style is set to SWT.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
+ */
+ public int getBorderMargin() {
+ return getBorderStyle() == SWT.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>SWT.BORDER</code> or <code>SWT.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.
+ */
+ public static void setControlVisible(Control c, boolean verticalOnly) {
+ ScrolledComposite scomp = FormUtil.getScrolledComposite(c);
+ if (scomp == 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();
+ }
+
+// RAP [rh] revise detection of border style: can't ask OS here
+ 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(SWT.COLOR_WIDGET_BACKGROUND);
+// if (rgb.red != 212 || rgb.green != 208 || rgb.blue != 200)
+// borderStyle = SWT.BORDER;
+// } else if (osname.startsWith("Mac")) //$NON-NLS-1$
+// borderStyle = SWT.BORDER;
+
+ borderStyle = SWT.BORDER;
+ }
+
+ /**
+ * Returns the orientation that all the widgets created by this toolkit will
+ * inherit, if set. Can be <code>SWT.NULL</code>,
+ * <code>SWT.LEFT_TO_RIGHT</code> and <code>SWT.RIGHT_TO_LEFT</code>.
+ *
+ * @return orientation style for this toolkit, or <code>SWT.NULL</code> if
+ * not set. The default orientation is inherited from the Window
+ * default orientation.
+ * @see org.eclipse.jface.window.Window#getDefaultOrientation()
+ */
+
+ public int getOrientation() {
+ return orientation;
+ }
+
+ /**
+ * Sets the orientation that all the widgets created by this toolkit will
+ * inherit. Can be <code>SWT.NULL</code>, <code>SWT.LEFT_TO_RIGHT</code>
+ * and <code>SWT.RIGHT_TO_LEFT</code>.
+ *
+ * @param orientation
+ * style for this toolkit.
+ */
+
+ public void setOrientation(int orientation) {
+ this.orientation = orientation;
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.forms;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.MouseEvent;
+//import org.eclipse.swt.graphics.Device;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+//import org.eclipse.swt.graphics.Image;
+//import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Layout;
+//import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Text;
+//import org.eclipse.ui.forms.widgets.ColumnLayout;
+//import org.eclipse.ui.forms.widgets.Form;
+//import org.eclipse.ui.forms.widgets.FormText;
+//import org.eclipse.ui.forms.widgets.FormToolkit;
+//import org.eclipse.ui.forms.widgets.ILayoutExtension;
+//
+import com.ibm.icu.text.BreakIterator;
+
+public class FormUtil {
+ public static final String PLUGIN_ID = "org.eclipse.ui.forms"; //$NON-NLS-1$
+
+ static final int H_SCROLL_INCREMENT = 5;
+
+ static final int V_SCROLL_INCREMENT = 64;
+
+ public static final String DEBUG = PLUGIN_ID + "/debug"; //$NON-NLS-1$
+
+ public static final String DEBUG_TEXT = DEBUG + "/text"; //$NON-NLS-1$
+ public static final String DEBUG_TEXTSIZE = DEBUG + "/textsize"; //$NON-NLS-1$
+
+ public static final String DEBUG_FOCUS = DEBUG + "/focus"; //$NON-NLS-1$
+
+ public static final String FOCUS_SCROLLING = "focusScrolling"; //$NON-NLS-1$
+
+ public static final 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 == 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 & SWT.MULTI) != 0) {
+ GridData gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
+ l.setLayoutData(gd);
+ }
+ Text text = factory.createText(parent, "", style); //$NON-NLS-1$
+ int hfill = span == 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 == 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 != 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 != 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);
+ }
+
+// RAP [rh] paintWrapText unnecessary
+// public static void paintWrapText(GC gc, String text, Rectangle bounds) {
+// paintWrapText(gc, text, bounds, false);
+// }
+
+// RAP [rh] paintWrapText unnecessary
+// public static void paintWrapText(GC gc, String text, Rectangle bounds,
+// boolean 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 != 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 != null) {
+ if (parent instanceof ScrolledComposite) {
+ return (ScrolledComposite) parent;
+ }
+ parent = parent.getParent();
+ }
+ return null;
+ }
+
+ public static void ensureVisible(Control c) {
+ ScrolledComposite scomp = getScrolledComposite(c);
+ if (scomp != null) {
+ Object data = scomp.getData(FOCUS_SCROLLING);
+ if (data == 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 (control instanceof FormText)
+// 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 != x || scompOrigin.y != 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 != x || scompOrigin.y != 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 == 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, boolean up) {
+ scroll(scomp, 0, up ? -V_SCROLL_INCREMENT : V_SCROLL_INCREMENT);
+ }
+
+ static void scrollHorizontal(ScrolledComposite scomp, boolean left) {
+ scroll(scomp, left ? -H_SCROLL_INCREMENT : H_SCROLL_INCREMENT, 0);
+ }
+
+ static void scrollPage(ScrolledComposite scomp, boolean 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);
+ }
+
+// RAP [rh] FormUtil#updatePageIncrement: empty implementation
+ public static void updatePageIncrement(ScrolledComposite scomp) {
+// ScrollBar vbar = scomp.getVerticalBar();
+// if (vbar != null) {
+// Rectangle clientArea = scomp.getClientArea();
+// int increment = clientArea.height - 5;
+// vbar.setPageIncrement(increment);
+// }
+// ScrollBar hbar = scomp.getHorizontalBar();
+// if (hbar != null) {
+// Rectangle clientArea = scomp.getClientArea();
+// int increment = clientArea.width - 5;
+// hbar.setPageIncrement(increment);
+// }
+ }
+
+ public static void processKey(int keyCode, Control c) {
+ if (c.isDisposed()) {
+ return;
+ }
+ ScrolledComposite scomp = FormUtil.getScrolledComposite(c);
+ if (scomp != null) {
+ if (c instanceof Combo)
+ return;
+ switch (keyCode) {
+ case SWT.ARROW_DOWN:
+ if (scomp.getData("novarrows") == null) //$NON-NLS-1$
+ FormUtil.scrollVertical(scomp, false);
+ break;
+ case SWT.ARROW_UP:
+ if (scomp.getData("novarrows") == null) //$NON-NLS-1$
+ FormUtil.scrollVertical(scomp, true);
+ break;
+ case SWT.ARROW_LEFT:
+ FormUtil.scrollHorizontal(scomp, true);
+ break;
+ case SWT.ARROW_RIGHT:
+ FormUtil.scrollHorizontal(scomp, false);
+ break;
+ case SWT.PAGE_UP:
+ FormUtil.scrollPage(scomp, true);
+ break;
+ case SWT.PAGE_DOWN:
+ FormUtil.scrollPage(scomp, false);
+ break;
+ }
+ }
+ }
+
+ public static boolean isWrapControl(Control c) {
+ if ((c.getStyle() & SWT.WRAP) != 0)
+ return true;
+ if (c instanceof Composite) {
+ return false;
+// return ((Composite) c).getLayout() instanceof ILayoutExtension;
+ }
+ return false;
+ }
+
+ public static int getWidthHint(int wHint, Control c) {
+ boolean wrap = isWrapControl(c);
+ return wrap ? wHint : SWT.DEFAULT;
+ }
+
+ public static int getHeightHint(int hHint, Control c) {
+ if (c instanceof Composite) {
+ Layout layout = ((Composite) c).getLayout();
+// if (layout instanceof ColumnLayout)
+// return hHint;
+ }
+ return SWT.DEFAULT;
+ }
+
+ public static int computeMinimumWidth(Control c, boolean changed) {
+ if (c instanceof Composite) {
+ Layout layout = ((Composite) c).getLayout();
+// if (layout instanceof ILayoutExtension)
+// return ((ILayoutExtension) layout).computeMinimumWidth(
+// (Composite) c, changed);
+ }
+ return c.computeSize(FormUtil.getWidthHint(5, c), SWT.DEFAULT, changed).x;
+ }
+
+ public static int computeMaximumWidth(Control c, boolean changed) {
+ if (c instanceof Composite) {
+ Layout layout = ((Composite) c).getLayout();
+// if (layout instanceof ILayoutExtension)
+// return ((ILayoutExtension) layout).computeMaximumWidth(
+// (Composite) c, changed);
+ }
+ return c.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed).x;
+ }
+
+// public static Form getForm(Control c) {
+// Composite parent = c.getParent();
+// while (parent != null) {
+// if (parent instanceof Form) {
+// return (Form) parent;
+// }
+// parent = parent.getParent();
+// }
+// return null;
+// }
+
+// RAP [rh] FormUtil#createAlphaMashImage unnecessary
+// 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 == 255 ? 0 : 255;
+// }
+// calpha = calpha == 255 ? 0 : 255;
+// }
+// return new Image(device, data);
+// }
+
+ public static boolean mnemonicMatch(String text, char key) {
+ char mnemonic = findMnemonic(text);
+ if (mnemonic == '\0')
+ return false;
+ return Character.toUpperCase(key) == Character.toUpperCase(mnemonic);
+ }
+
+ private static char findMnemonic(String string) {
+ int index = 0;
+ int length = string.length();
+ do {
+ while (index < length && string.charAt(index) != '&')
+ index++;
+ if (++index >= length)
+ return '\0';
+ if (string.charAt(index) != '&')
+ return string.charAt(index);
+ index++;
+ } while (index < length);
+ return '\0';
+ }
+
+ public static void setFocusScrollingEnabled(Control c, boolean enabled) {
+ ScrolledComposite scomp = null;
+
+ if (c instanceof ScrolledComposite)
+ scomp = (ScrolledComposite)c;
+ else
+ scomp = getScrolledComposite(c);
+ if (scomp!=null)
+ scomp.setData(FormUtil.FOCUS_SCROLLING, enabled?null:Boolean.FALSE);
+ }
+
+ // RAP [rh] FormUtil#setAntialias unnecessary
+// public static void setAntialias(GC gc, int style) {
+// if (!gc.getAdvanced()) {
+// gc.setAdvanced(true);
+// if (!gc.getAdvanced())
+// return;
+// }
+// gc.setAntialias(style);
+// }
+}
--- /dev/null
+package org.argeo.eclipse.ui.forms;
+
+/**
+ * A place to hold all the color constants used in the forms package.
+ *
+ * @since 1.0
+ */
+
+public interface IFormColors {
+ /**
+ * A prefix for all the keys.
+ */
+ String PREFIX = "org.eclipse.ui.forms."; //$NON-NLS-1$
+ /**
+ * Key for the form title foreground color.
+ */
+ String TITLE = PREFIX + "TITLE"; //$NON-NLS-1$
+
+ /**
+ * A prefix for the header color constants.
+ */
+ String H_PREFIX = PREFIX + "H_"; //$NON-NLS-1$
+ /*
+ * A prefix for the section title bar color constants.
+ */
+ String TB_PREFIX = PREFIX + "TB_"; //$NON-NLS-1$
+ /**
+ * Key for the form header background gradient ending color.
+ */
+ String H_GRADIENT_END = H_PREFIX + "GRADIENT_END"; //$NON-NLS-1$
+
+ /**
+ * Key for the form header background gradient starting color.
+ *
+ */
+ String H_GRADIENT_START = H_PREFIX + "GRADIENT_START"; //$NON-NLS-1$
+ /**
+ * Key for the form header bottom keyline 1 color.
+ *
+ */
+ String H_BOTTOM_KEYLINE1 = H_PREFIX + "BOTTOM_KEYLINE1"; //$NON-NLS-1$
+ /**
+ * Key for the form header bottom keyline 2 color.
+ *
+ */
+ String H_BOTTOM_KEYLINE2 = H_PREFIX + "BOTTOM_KEYLINE2"; //$NON-NLS-1$
+ /**
+ * Key for the form header light hover color.
+ *
+ */
+ String H_HOVER_LIGHT = H_PREFIX + "H_HOVER_LIGHT"; //$NON-NLS-1$
+ /**
+ * Key for the form header full hover color.
+ *
+ */
+ String H_HOVER_FULL = H_PREFIX + "H_HOVER_FULL"; //$NON-NLS-1$
+
+ /**
+ * Key for the tree/table border color.
+ */
+ String BORDER = PREFIX + "BORDER"; //$NON-NLS-1$
+
+ /**
+ * Key for the section separator color.
+ */
+ String SEPARATOR = PREFIX + "SEPARATOR"; //$NON-NLS-1$
+
+ /**
+ * Key for the section title bar background.
+ */
+ String TB_BG = TB_PREFIX + "BG"; //$NON-NLS-1$
+
+ /**
+ * Key for the section title bar foreground.
+ */
+ 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.
+ */
+ String TB_GBG = TB_BG;
+
+ /**
+ * Key for the section title bar border.
+ */
+ 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.
+ */
+ String TB_TOGGLE = TB_PREFIX + "TOGGLE"; //$NON-NLS-1$
+
+ /**
+ * Key for the section toggle hover color.
+ *
+ */
+ String TB_TOGGLE_HOVER = TB_PREFIX + "TOGGLE_HOVER"; //$NON-NLS-1$
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.eclipse.ui.forms;
+
+/**
+ * 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 1.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
+ */
+ boolean 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(boolean 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.
+ */
+ boolean 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.
+ */
+ boolean 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
+package org.argeo.eclipse.ui.forms;
+
+import org.eclipse.jface.viewers.ISelection;
+//import org.eclipse.ui.forms.widgets.FormToolkit;
+//import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+/**
+ * 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 1.0
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @noextend This interface is not intended to be extended by clients.
+ */
+public interface IManagedForm {
+ /**
+ * Initializes the form by looping through the managed parts and
+ * initializing them. Has no effect if already called once.
+ */
+ 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(boolean 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.
+ */
+ boolean 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.
+ */
+ boolean 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(boolean 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.
+ */
+ boolean 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
+ */
+// IMessageManager getMessageManager();
+}
--- /dev/null
+package org.argeo.eclipse.ui.forms;
+
+import java.util.Vector;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.widgets.Composite;
+//import org.eclipse.ui.forms.widgets.FormToolkit;
+//import org.eclipse.ui.forms.widgets.ScrolledForm;
+
+/**
+ * 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 1.0
+ */
+public class ManagedForm implements IManagedForm {
+ private Object input;
+
+ private ScrolledComposite form;
+
+ private FormToolkit toolkit;
+
+ private Object container;
+
+ private boolean ownsToolkit;
+
+ private boolean initialized;
+
+ private Vector parts = new Vector();
+
+ /**
+ * 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 ManagedForm(Composite parent) {
+ 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 ManagedForm(FormToolkit toolkit, ScrolledComposite form) {
+ this.form = form;
+ this.toolkit = toolkit;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#addPart(org.eclipse.ui.forms.IFormPart)
+ */
+ public void addPart(IFormPart part) {
+ parts.add(part);
+ part.initialize(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#removePart(org.eclipse.ui.forms.IFormPart)
+ */
+ public void removePart(IFormPart part) {
+ parts.remove(part);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#getParts()
+ */
+ public IFormPart[] getParts() {
+ return (IFormPart[]) parts.toArray(new IFormPart[parts.size()]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#getToolkit()
+ */
+ public FormToolkit getToolkit() {
+ return toolkit;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#getForm()
+ */
+ public ScrolledComposite getForm() {
+ return form;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#reflow(boolean)
+ */
+ public void reflow(boolean 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 = (IFormPart) parts.get(i);
+ if (part.equals(cpart))
+ continue;
+// if (cpart instanceof IPartSelectionListener) {
+// ((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 = (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 = (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.currentThread();
+ Thread dt = toolkit.getColors().getDisplay().getThread();
+ if (t.equals(dt))
+ doRefresh();
+ else {
+ toolkit.getColors().getDisplay().asyncExec(new Runnable() {
+ public void run() {
+ doRefresh();
+ }
+ });
+ }
+ }
+
+ private void doRefresh() {
+ int nrefreshed = 0;
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ if (part.isStale()) {
+ part.refresh();
+ nrefreshed++;
+ }
+ }
+// if (nrefreshed > 0)
+// form.reflow(true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#commit(boolean)
+ */
+ public void commit(boolean onSave) {
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ if (part.isDirty())
+ part.commit(onSave);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#setInput(java.lang.Object)
+ */
+ public boolean setInput(Object input) {
+ boolean pageResult = false;
+
+ this.input = input;
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ boolean result = part.setFormInput(input);
+ if (result)
+ pageResult = true;
+ }
+ return pageResult;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.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 = (IFormPart) parts.get(0);
+ part.setFocus();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#isDirty()
+ */
+ public boolean isDirty() {
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ if (part.isDirty())
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#isStale()
+ */
+ public boolean isStale() {
+ for (int i = 0; i < parts.size(); i++) {
+ IFormPart part = (IFormPart) parts.get(i);
+ if (part.isStale())
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#dirtyStateChanged()
+ */
+ public void dirtyStateChanged() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#staleStateChanged()
+ */
+ public void staleStateChanged() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#getContainer()
+ */
+ public Object getContainer() {
+ return container;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.ui.forms.IManagedForm#setContainer(java.lang.Object)
+ */
+ public void setContainer(Object container) {
+ this.container = container;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ui.forms.IManagedForm#getMessageManager()
+ */
+// public IMessageManager getMessageManager() {
+// return form.getMessageManager();
+// }
+}
--- /dev/null
+package org.argeo.eclipse.ui.forms.editor;
+
+import org.argeo.eclipse.ui.forms.FormToolkit;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.jface.dialogs.IPageChangeProvider;
+import org.eclipse.jface.dialogs.IPageChangedListener;
+import org.eclipse.jface.dialogs.PageChangedEvent;
+import org.eclipse.jface.util.SafeRunnable;
+
+/**
+ * This class forms a base of multi-page form editors that typically use one or
+ * more pages with forms and one page for raw source of the editor input.
+ * <p>
+ * Pages are added 'lazily' i.e. adding a page reserves a tab for it but does
+ * not cause the page control to be created. Page control is created when an
+ * attempt is made to select the page in question. This allows editors with
+ * several tabs and complex pages to open quickly.
+ * <p>
+ * Subclasses should extend this class and implement <code>addPages</code>
+ * method. One of the two <code>addPage</code> methods should be called to
+ * contribute pages to the editor. One adds complete (standalone) editors as
+ * nested tabs. These editors will be created right away and will be hooked so
+ * that key bindings, selection service etc. is compatible with the one for the
+ * standalone case. The other method adds classes that implement
+ * <code>IFormPage</code> interface. These pages will be created lazily and
+ * they will share the common key binding and selection service. Since 3.1,
+ * FormEditor is a page change provider. It allows listeners to attach to it and
+ * get notified when pages are changed. This new API in JFace allows dynamic
+ * help to update on page changes.
+ *
+ * @since 1.0
+ */
+// RAP [if] As RAP is still using workbench 3.4, the implementation of
+// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code
+// with the adoption of workbench > 3.5
+//public abstract class FormEditor extends MultiPageEditorPart {
+public abstract class FormEditor implements
+ IPageChangeProvider {
+ private FormToolkit formToolkit;
+
+
+public FormToolkit getToolkit() {
+ return formToolkit;
+ }
+
+public void editorDirtyStateChanged() {
+
+}
+
+public FormPage getActivePageInstance() {
+ return null;
+}
+
+ // RAP [if] As RAP is still using workbench 3.4, the implementation of
+// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code
+// with the adoption of workbench > 3.5
+ private ListenerList pageListeners = new ListenerList();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.dialogs.IPageChangeProvider#addPageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
+ */
+ public void addPageChangedListener(IPageChangedListener listener) {
+ pageListeners.add(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.dialogs.IPageChangeProvider#removePageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener)
+ */
+ public void removePageChangedListener(IPageChangedListener listener) {
+ pageListeners.remove(listener);
+ }
+
+ private void firePageChanged(final PageChangedEvent event) {
+ Object[] listeners = pageListeners.getListeners();
+ for (int i = 0; i < listeners.length; ++i) {
+ final IPageChangedListener l = (IPageChangedListener) listeners[i];
+ SafeRunnable.run(new SafeRunnable() {
+ public void run() {
+ l.pageChanged(event);
+ }
+ });
+ }
+ }
+// RAPEND [if]
+}
--- /dev/null
+package org.argeo.eclipse.ui.forms.editor;
+import org.argeo.eclipse.ui.forms.IManagedForm;
+import org.argeo.eclipse.ui.forms.ManagedForm;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.swt.custom.BusyIndicator;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+/**
+ * A base class that all pages that should be added to FormEditor must subclass.
+ * Form page has an instance of PageForm that extends managed form. Subclasses
+ * should override method 'createFormContent(ManagedForm)' to fill the form with
+ * content. Note that page itself can be loaded lazily (on first open).
+ * Consequently, the call to create the form content can come after the editor
+ * has been opened for a while (in fact, it is possible to open and close the
+ * editor and never create the form because no attempt has been made to show the
+ * page).
+ *
+ * @since 1.0
+ */
+public class FormPage implements IFormPage {
+ private FormEditor editor;
+ private PageForm mform;
+ private int index;
+ private String id;
+
+ private String partName;
+
+
+
+ public void setPartName(String partName) {
+ this.partName = partName;
+ }
+ private static class PageForm extends ManagedForm {
+ public PageForm(FormPage page, ScrolledComposite form) {
+ super(page.getEditor().getToolkit(), form);
+ setContainer(page);
+ }
+
+ public FormPage getPage() {
+ return (FormPage)getContainer();
+ }
+ public void dirtyStateChanged() {
+ getPage().getEditor().editorDirtyStateChanged();
+ }
+ public void staleStateChanged() {
+ if (getPage().isActive())
+ refresh();
+ }
+ }
+ /**
+ * A constructor that creates the page and initializes it with the editor.
+ *
+ * @param editor
+ * the parent editor
+ * @param id
+ * the unique identifier
+ * @param title
+ * the page title
+ */
+ public FormPage(FormEditor editor, String id, String title) {
+ this(id, title);
+ initialize(editor);
+ }
+ /**
+ * The constructor. The parent editor need to be passed in the
+ * <code>initialize</code> method if this constructor is used.
+ *
+ * @param id
+ * a unique page identifier
+ * @param title
+ * a user-friendly page title
+ */
+ public FormPage(String id, String title) {
+ this.id = id;
+ setPartName(title);
+ }
+ /**
+ * Initializes the form page.
+ *
+ * @see IEditorPart#init
+ */
+// public void init(IEditorSite site, IEditorInput input) {
+// setSite(site);
+// setInput(input);
+// }
+ /**
+ * Primes the form page with the parent editor instance.
+ *
+ * @param editor
+ * the parent editor
+ */
+ public void initialize(FormEditor editor) {
+ this.editor = editor;
+ }
+ /**
+ * Returns the parent editor.
+ *
+ * @return parent editor instance
+ */
+ public FormEditor getEditor() {
+ return editor;
+ }
+ /**
+ * Returns the managed form owned by this page.
+ *
+ * @return the managed form
+ */
+ public IManagedForm getManagedForm() {
+ return mform;
+ }
+ /**
+ * Implements the required method by refreshing the form when set active.
+ * Subclasses must call super when overriding this method.
+ */
+ public void setActive(boolean active) {
+ if (active) {
+ // We are switching to this page - refresh it
+ // if needed.
+ if (mform != null)
+ mform.refresh();
+ }
+ }
+ /**
+ * Tests if the page is active by asking the parent editor if this page is
+ * the currently active page.
+ *
+ * @return <code>true</code> if the page is currently active,
+ * <code>false</code> otherwise.
+ */
+ public boolean isActive() {
+ return this.equals(editor.getActivePageInstance());
+ }
+ /**
+ * Creates the part control by creating the managed form using the parent
+ * editor's toolkit. Subclasses should override
+ * <code>createFormContent(IManagedForm)</code> to populate the form with
+ * content.
+ *
+ * @param parent
+ * the page parent composite
+ */
+ public void createPartControl(Composite parent) {
+ ScrolledComposite form = editor.getToolkit().createScrolledForm(parent);
+ mform = new PageForm(this, form);
+ BusyIndicator.showWhile(parent.getDisplay(), new Runnable() {
+ public void run() {
+ createFormContent(mform);
+ }
+ });
+ }
+ /**
+ * Subclasses should override this method to create content in the form
+ * hosted in this page.
+ *
+ * @param managedForm
+ * the form hosted in this page.
+ */
+ protected void createFormContent(IManagedForm managedForm) {
+ }
+ /**
+ * Returns the form page control.
+ *
+ * @return managed form's control
+ */
+ public Control getPartControl() {
+ return mform != null ? mform.getForm() : null;
+ }
+ /**
+ * Disposes the managed form.
+ */
+ public void dispose() {
+ if (mform != null)
+ mform.dispose();
+ }
+ /**
+ * Returns the unique identifier that can be used to reference this page.
+ *
+ * @return the unique page identifier
+ */
+ public String getId() {
+ return id;
+ }
+ /**
+ * Returns <code>null</code>- form page has no title image. Subclasses
+ * may override.
+ *
+ * @return <code>null</code>
+ */
+ public Image getTitleImage() {
+ return null;
+ }
+ /**
+ * Sets the focus by delegating to the managed form.
+ */
+ public void setFocus() {
+ if (mform != null)
+ mform.setFocus();
+ }
+ /**
+ * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
+ */
+ public void doSave(IProgressMonitor monitor) {
+ if (mform != null)
+ mform.commit(true);
+ }
+ /**
+ * @see org.eclipse.ui.ISaveablePart#doSaveAs()
+ */
+ public void doSaveAs() {
+ }
+ /**
+ * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
+ */
+ public boolean isSaveAsAllowed() {
+ return false;
+ }
+ /**
+ * Implemented by testing if the managed form is dirty.
+ *
+ * @return <code>true</code> if the managed form is dirty,
+ * <code>false</code> otherwise.
+ *
+ * @see org.eclipse.ui.ISaveablePart#isDirty()
+ */
+ public boolean isDirty() {
+ return mform != null ? mform.isDirty() : false;
+ }
+ /**
+ * Preserves the page index.
+ *
+ * @param index
+ * the assigned page index
+ */
+ public void setIndex(int index) {
+ this.index = index;
+ }
+ /**
+ * Returns the saved page index.
+ *
+ * @return the page index
+ */
+ public int getIndex() {
+ return index;
+ }
+ /**
+ * Form pages are not editors.
+ *
+ * @return <code>false</code>
+ */
+ public boolean isEditor() {
+ return false;
+ }
+ /**
+ * Attempts to select and reveal the given object by passing the request to
+ * the managed form.
+ *
+ * @param object
+ * the object to select and reveal in the page if possible.
+ * @return <code>true</code> if the page has been successfully selected
+ * and revealed by one of the managed form parts, <code>false</code>
+ * otherwise.
+ */
+ public boolean selectReveal(Object object) {
+ if (mform != null)
+ return mform.setInput(object);
+ return false;
+ }
+ /**
+ * By default, editor will be allowed to flip the page.
+ * @return <code>true</code>
+ */
+ public boolean canLeaveThePage() {
+ return true;
+ }
+}
--- /dev/null
+package org.argeo.eclipse.ui.forms.editor;
+import org.argeo.eclipse.ui.forms.IManagedForm;
+import org.eclipse.swt.widgets.Control;
+/**
+ * Interface that all GUI pages need to implement in order
+ * to be added to FormEditor part. The interface makes
+ * several assumptions:
+ * <ul>
+ * <li>The form page has a managed form</li>
+ * <li>The form page has a unique id</li>
+ * <li>The form page can be GUI but can also wrap a complete
+ * editor class (in that case, it should return <code>true</code>
+ * from <code>isEditor()</code> method).</li>
+ * <li>The form page is lazy i.e. understands that
+ * its part control will be created at the last possible
+ * moment.</li>.
+ * </ul>
+ * <p>Existing editors can be wrapped by implementing
+ * this interface. In this case, 'isEditor' should return <code>true</code>.
+ * A common editor to wrap in <code>TextEditor</code> that is
+ * often added to show the raw source code of the file open into
+ * the multi-page editor.
+ *
+ * @since 1.0
+ */
+public interface IFormPage {
+ /**
+ * @param editor
+ * the form editor that this page belongs to
+ */
+ void initialize(FormEditor editor);
+ /**
+ * Returns the editor this page belongs to.
+ *
+ * @return the form editor
+ */
+ FormEditor getEditor();
+ /**
+ * Returns the managed form of this page, unless this is a source page.
+ *
+ * @return the managed form or <samp>null </samp> if this is a source page.
+ */
+ IManagedForm getManagedForm();
+ /**
+ * Indicates whether the page has become the active in the editor. Classes
+ * that implement this interface may use this method to commit the page (on
+ * <code>false</code>) or lazily create and/or populate the content on
+ * <code>true</code>.
+ *
+ * @param active
+ * <code>true</code> if page should be visible, <code>false</code>
+ * otherwise.
+ */
+ void setActive(boolean active);
+ /**
+ * Returns <samp>true </samp> if page is currently active, false if not.
+ *
+ * @return <samp>true </samp> for active page.
+ */
+ boolean isActive();
+ /**
+ * Tests if the content of the page is in a state that allows the
+ * editor to flip to another page. Typically, pages that contain
+ * raw source with syntax errors should not allow editors to
+ * leave them until errors are corrected.
+ * @return <code>true</code> if the editor can flip to another page,
+ * <code>false</code> otherwise.
+ */
+ boolean canLeaveThePage();
+ /**
+ * Returns the control associated with this page.
+ *
+ * @return the control of this page if created or <samp>null </samp> if the
+ * page has not been shown yet.
+ */
+ Control getPartControl();
+ /**
+ * Page must have a unique id that can be used to show it without knowing
+ * its relative position in the editor.
+ *
+ * @return the unique page identifier
+ */
+ String getId();
+ /**
+ * Returns the position of the page in the editor.
+ *
+ * @return the zero-based index of the page in the editor.
+ */
+ int getIndex();
+ /**
+ * Sets the position of the page in the editor.
+ *
+ * @param index
+ * the zero-based index of the page in the editor.
+ */
+ void setIndex(int index);
+ /**
+ * Tests whether this page wraps a complete editor that
+ * can be registered on its own, or represents a page
+ * that cannot exist outside the multi-page editor context.
+ *
+ * @return <samp>true </samp> if the page wraps an editor,
+ * <samp>false </samp> if this is a form page.
+ */
+ boolean isEditor();
+ /**
+ * A hint to bring the provided object into focus. If the object is in a
+ * tree or table control, select it. If it is shown on a scrollable page,
+ * ensure that it is visible. If the object is not presented in
+ * the page, <code>false</code> should be returned to allow another
+ * page to try.
+ *
+ * @param object
+ * object to select and reveal
+ * @return <code>true</code> if the request was successful, <code>false</code>
+ * otherwise.
+ */
+ boolean selectReveal(Object object);
+}