Bug 575578: [UI-SWT] Add ExpandableComposite including styling for
dark theme
Change-Id: I20abda6d1743b9add088b204fbae998c28d2ee1c
diff --git a/ecommons/org.eclipse.statet.ecommons.uimisc/META-INF/MANIFEST.MF b/ecommons/org.eclipse.statet.ecommons.uimisc/META-INF/MANIFEST.MF
index fb076ff..2082894 100644
--- a/ecommons/org.eclipse.statet.ecommons.uimisc/META-INF/MANIFEST.MF
+++ b/ecommons/org.eclipse.statet.ecommons.uimisc/META-INF/MANIFEST.MF
@@ -12,6 +12,7 @@
Require-Bundle: org.eclipse.statet.ecommons.runtime.core;bundle-version="[4.5.0,4.6.0)",
org.eclipse.core.runtime;bundle-version="3.19.0",
org.eclipse.e4.core.contexts;bundle-version="1.7.0";resolution:=optional,
+ org.eclipse.statet.ecommons.preferences.core;bundle-version="[4.5.0,4.6.0)",
org.eclipse.ui,
org.eclipse.e4.ui.services,
org.eclipse.jface.text;resolution:=optional,
@@ -22,10 +23,15 @@
org.eclipse.core.variables;resolution:=optional,
org.eclipse.core.resources;resolution:=optional,
org.eclipse.core.filesystem;resolution:=optional,
- org.eclipse.statet.ecommons.preferences.core;bundle-version="[4.5.0,4.6.0)"
+ org.eclipse.ui.forms;resolution:=optional
Import-Package: com.ibm.icu.lang;version="67.1.0",
com.ibm.icu.text;version="67.1.0",
org.eclipse.debug.ui;resolution:=optional,
+ org.eclipse.e4.ui.css.core.dom;resolution:=optional,
+ org.eclipse.e4.ui.css.core.engine;resolution:=optional,
+ org.eclipse.e4.ui.css.swt.dom;resolution:=optional,
+ org.eclipse.e4.ui.css.swt.properties;resolution:=optional,
+ org.eclipse.e4.ui.css.swt.theme;resolution:=optional,
org.eclipse.statet.ecommons.collections;version="4.5.0",
org.eclipse.statet.ecommons.commands.core;version="4.5.0",
org.eclipse.statet.ecommons.databinding,
@@ -39,7 +45,6 @@
org.eclipse.statet.ecommons.variables.core,
org.eclipse.statet.jcommons.collections;version="4.5.0",
org.eclipse.statet.jcommons.lang;version="4.5.0",
- org.eclipse.e4.ui.css.swt.theme;resolution:=optional,
org.eclipse.ui.dialogs;resolution:=optional,
org.eclipse.ui.model;resolution:=optional,
org.eclipse.ui.views.contentoutline;resolution:=optional,
@@ -53,6 +58,7 @@
org.eclipse.statet.ecommons.ui.dialogs,
org.eclipse.statet.ecommons.ui.mpbv,
org.eclipse.statet.ecommons.ui.swt,
+ org.eclipse.statet.ecommons.ui.swt.expandable,
org.eclipse.statet.ecommons.ui.util,
org.eclipse.statet.ecommons.ui.viewers,
org.eclipse.statet.ecommons.ui.viewers.breadcrumb,
diff --git a/ecommons/org.eclipse.statet.ecommons.uimisc/css/e4_dark.css b/ecommons/org.eclipse.statet.ecommons.uimisc/css/e4_dark.css
new file mode 100644
index 0000000..a7921b2
--- /dev/null
+++ b/ecommons/org.eclipse.statet.ecommons.uimisc/css/e4_dark.css
@@ -0,0 +1,19 @@
+/*
+ #=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+.MPart ExpandableComposite Table {
+ background-color: inherit;
+ color: inherit;
+}
diff --git a/ecommons/org.eclipse.statet.ecommons.uimisc/plugin.xml b/ecommons/org.eclipse.statet.ecommons.uimisc/plugin.xml
index 28b541b..16c161c 100644
--- a/ecommons/org.eclipse.statet.ecommons.uimisc/plugin.xml
+++ b/ecommons/org.eclipse.statet.ecommons.uimisc/plugin.xml
@@ -229,4 +229,27 @@
</command>
</extension>
+ <extension
+ point="org.eclipse.e4.ui.css.core.elementProvider">
+ <provider
+ class="org.eclipse.statet.internal.ecommons.ui.swt.css.dom.ECommonsSwtElementProvider">
+ <widget
+ class="org.eclipse.statet.ecommons.ui.swt.expandable.ExpandableComposite"/>
+ </provider>
+ </extension>
+ <extension
+ point="org.eclipse.e4.ui.css.core.propertyHandler">
+ <handler
+ adapter="org.eclipse.statet.internal.ecommons.ui.swt.css.dom.ExpandableCompositeElement"
+ handler="org.eclipse.statet.internal.ecommons.ui.swt.css.properties.ExpandableCompositeHandler">
+ <property-name
+ name="swt-titlebar-color"/>
+ </handler>
+ </extension>
+ <extension
+ point="org.eclipse.e4.ui.css.swt.theme">
+ <stylesheet
+ uri="css/e4_dark.css"/>
+ </extension>
+
</plugin>
diff --git a/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/ecommons/ui/swt/expandable/ExpandableComposite.java b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/ecommons/ui/swt/expandable/ExpandableComposite.java
new file mode 100644
index 0000000..af1f424
--- /dev/null
+++ b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/ecommons/ui/swt/expandable/ExpandableComposite.java
@@ -0,0 +1,1318 @@
+/*=============================================================================#
+ # Copyright (c) 2000, 2021 IBM Corporation and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0
+ #
+ # Contributors:
+ # IBM Corporation - org.eclipse.ui.forms: initial API and implementation
+ # Kai Nacke - org.eclipse.ui.forms: Bug 202382
+ # Bryan Hunt - org.eclipse.ui.forms: Bug 245457
+ # Didier Villevalois - org.eclipse.ui.forms: Bug 178534
+ # Alena Laskavaia - org.eclipse.ui.forms: Bug 481604
+ # Ralf Petter <ralf.petter@gmail.com> - org.eclipse.ui.forms: Bug 183675
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+// org.eclipse.ui.forms.widgets.ExpandableComposite
+// a2318aea7aeb731a6b1cadf0e85dd1e1287f4dd1
+// removed textClient
+// without changed layout for wrapping controls (2016-2017)
+
+package org.eclipse.statet.ecommons.ui.swt.expandable;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.action.LegacyActionTools;
+import org.eclipse.osgi.service.environment.Constants;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.FontMetrics;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+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.Layout;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.forms.events.ExpansionEvent;
+import org.eclipse.ui.forms.events.HyperlinkAdapter;
+import org.eclipse.ui.forms.events.HyperlinkEvent;
+import org.eclipse.ui.forms.events.IExpansionListener;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Hyperlink;
+import org.eclipse.ui.forms.widgets.ILayoutExtension;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.forms.widgets.SharedScrolledComposite;
+import org.eclipse.ui.forms.widgets.SizeCache;
+import org.eclipse.ui.forms.widgets.Twistie;
+
+import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet;
+import org.eclipse.statet.jcommons.collections.ImIdentityList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+
+/**
+ * This composite is capable of expanding or collapsing a single client that is
+ * its direct child. The composite renders an expansion toggle affordance
+ * (according to the chosen style), and a title that also acts as a hyperlink
+ * (can be selected and is traversable). The client is laid out below the title
+ * when expanded, or hidden when collapsed.
+ * <p>
+ * The widget can be instantiated as-is, or subclassed to modify some aspects of
+ * it. *
+ * <p>
+ * Since 3.1, left/right arrow keys can be used to control the expansion state.
+ * If several expandable composites are created in the same parent, up/down
+ * arrow keys can be used to traverse between them. Expandable text accepts
+ * mnemonics and mnemonic activation will toggle the expansion state.
+ *
+ * <p>
+ * While expandable composite recognize that different styles can be used to
+ * render the title bar, and even defines the constants for these styles
+ * (<code>TITLE_BAR</code> and <code>SHORT_TITLE_BAR</code>) the actual painting
+ * is done in the subclasses.
+ *
+ * @see Section
+ */
+@NonNullByDefault
+public class ExpandableComposite extends Canvas {
+
+
+ private static class Toggle extends Twistie {
+
+ public Toggle(final Composite parent, final int style) {
+ super(parent, style);
+ }
+
+ public void setHover(final boolean on) {
+ this.hover= on;
+ }
+
+ public boolean getHover() {
+ return this.hover;
+ }
+
+ @Override
+ protected void paint(final PaintEvent e) {
+ // don't paint focus
+ final GC gc= e.gc;
+ final Rectangle clientArea= getClientArea();
+ if (clientArea.width == 0 || clientArea.height == 0) {
+ return;
+ }
+ paintHyperlink(gc);
+ }
+
+ }
+
+
+ /**
+ * If this style is used, a twistie will be used to render the expansion
+ * toggle.
+ */
+ public static final int TWISTIE= 1 << 1;
+
+ /**
+ * If this style is used, the title text will be rendered as a hyperlink
+ * that can individually accept focus. Otherwise, it will still act like a
+ * hyperlink, but only the toggle control will accept focus.
+ */
+ public static final int FOCUS_TITLE= 1 << 3;
+
+ /**
+ * If this style is used, the client origin will be vertically aligned with
+ * the title text. Otherwise, it will start at x= 0.
+ */
+ public static final int CLIENT_INDENT= 1 << 4;
+
+ /**
+ * If this style is used, computed size of the composite will take the
+ * client width into consideration only in the expanded state. Otherwise,
+ * client width will always be taken into account.
+ */
+ public static final int COMPACT= 1 << 5;
+
+ /**
+ * If this style is used, the control will be created in the expanded state.
+ * This state can later be changed programmatically or by the user if
+ * TWISTIE or TREE_NODE style is used.
+ */
+ public static final int EXPANDED= 1 << 6;
+
+ /**
+ * If this style is used, title bar decoration will be painted behind the
+ * text.
+ */
+ public static final int TITLE_BAR= 1 << 8;
+
+ /**
+ * If this style is used, a short version of the title bar decoration will
+ * be painted behind the text. This style is useful when a more discrete
+ * option is needed for the title bar.
+ *
+ * @since 3.1
+ */
+ public static final int SHORT_TITLE_BAR= 1 << 9;
+
+ /**
+ * By default, text client is right-aligned. If this style is used, it will
+ * be positioned after the text control and vertically centered with it.
+ */
+ public static final int LEFT_TEXT_CLIENT_ALIGNMENT= 1 << 13;
+
+ /**
+ * By default, a focus box is painted around the title when it receives focus.
+ * If this style is used, the focus box will not be painted. This style does
+ * not apply when FOCUS_TITLE is used.
+ * @since 3.5
+ */
+ public static final int NO_TITLE_FOCUS_BOX= 1 << 14;
+
+ public static final int IMAGE= 1 << 30;
+
+
+ /**
+ * The toggle widget used to expand the composite.
+ */
+ protected @Nullable Toggle toggle;
+
+ /**
+ * The text label for the title.
+ */
+ protected @Nullable Control textLabel;
+
+ private static final int IGAP= 4;
+ private static final int IVGAP= 3;
+
+ private static final Point NULL_SIZE= new Point(0, 0);
+
+ private static final int VSPACE= 3;
+
+ private static final int SEPARATOR_HEIGHT= 2;
+
+ private int expansionStyle= TWISTIE | FOCUS_TITLE | EXPANDED;
+
+ private boolean expanded;
+
+ private @Nullable Label imageLabel;
+
+ private Control client;
+
+ private final CopyOnWriteIdentityListSet<IExpansionListener> listeners= new CopyOnWriteIdentityListSet<>();
+
+ private @Nullable Color titleBarForeground;
+
+ private final Rectangle titleHeaderRegion= new Rectangle(0, 0, 0, 0);
+
+
+ private class ExpandableLayout extends Layout implements ILayoutExtension {
+
+ /**
+ * Width of the margin that will be added around the control (default is 0).
+ */
+ private int marginWidth= 0;
+
+ /**
+ * Height of the margin that will be added around the control (default is
+ * 0).
+ */
+ private int marginHeight= 0;
+
+ /**
+ * Horizontal margin around the inside of the title bar area when TITLE_BAR
+ * or SHORT_TITLE_BAR style is used. This variable is not used otherwise.
+ */
+ private int titleBarTextMarginWidth= 6;
+
+ /**
+ * Vertical spacing between the title area and the description control
+ * (default is 0). The description control is normally placed at the new
+ * line as defined in the font used to render it. This value will be added
+ * to it.
+ */
+ public int descriptionVerticalSpacing= 0;
+
+ /**
+ * Vertical spacing between the title area and the composite client control
+ * (default is 3).
+ */
+ public int clientVerticalSpacing= 3;
+
+
+ private final SizeCache toggleCache= new SizeCache();
+
+ private final SizeCache textClientCache= new SizeCache();
+
+ private final SizeCache textLabelCache= new SizeCache();
+
+ private final SizeCache descriptionCache= new SizeCache();
+
+ private final SizeCache clientCache= new SizeCache();
+
+
+ private void initCache(final boolean shouldFlush) {
+ this.toggleCache.setControl(ExpandableComposite.this.toggle);
+ this.textLabelCache.setControl(ExpandableComposite.this.textLabel);
+ this.descriptionCache.setControl(getDescriptionControl());
+ this.clientCache.setControl(ExpandableComposite.this.client);
+
+ if (shouldFlush) {
+ this.toggleCache.flush();
+ this.textClientCache.flush();
+ this.textLabelCache.flush();
+ this.descriptionCache.flush();
+ this.clientCache.flush();
+ }
+ }
+
+ @Override
+ protected void layout(final Composite parent, final boolean changed) {
+ initCache(changed);
+
+ final var toggle= ExpandableComposite.this.toggle;
+ final var imageLabel= ExpandableComposite.this.imageLabel;
+ final var textLabel= ExpandableComposite.this.textLabel;
+
+ final Rectangle clientArea= parent.getClientArea();
+ int thmargin= 0;
+ int tvmargin= 0;
+
+ if (hasTitleBar()) {
+ thmargin= this.titleBarTextMarginWidth;
+ tvmargin= IVGAP;
+ }
+ int x= this.marginWidth + thmargin;
+ int y= this.marginHeight + tvmargin;
+ Point toggleSize= NULL_SIZE;
+ if (toggle != null) {
+ toggleSize= this.toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ }
+ Point imageSize= NULL_SIZE;
+ if (imageLabel != null) {
+ imageSize= imageLabel.computeSize(16, 16);
+ }
+ int toggleWidth= clientArea.width - this.marginWidth - this.marginWidth - thmargin - thmargin;
+ if (toggleSize.x > 0) {
+ toggleWidth-= toggleSize.x + IGAP;
+ }
+ if (imageSize.x > 0) {
+ toggleWidth-= imageSize.x;
+ }
+
+ Point size= NULL_SIZE;
+ if (textLabel != null) {
+ size= this.textLabelCache.computeSize(toggleWidth, SWT.DEFAULT);
+
+ if (textLabel instanceof Label) {
+ final Point defSize= this.textLabelCache.computeSize(SWT.DEFAULT,
+ SWT.DEFAULT);
+ if (defSize.y == size.y) {
+ // One line - pick the smaller of the two widths
+ size.x= Math.min(defSize.x, size.x);
+ }
+ }
+ }
+ int theaderHeight;
+ { final GC gc= new GC(ExpandableComposite.this);
+ gc.setFont(getFont());
+ final FontMetrics fm= gc.getFontMetrics();
+ theaderHeight= fm.getHeight();
+ gc.dispose();
+
+ if (imageSize.y > theaderHeight) {
+ theaderHeight= imageSize.y;
+ }
+ }
+ if (toggle != null) {
+ int ty= theaderHeight / 2 - toggleSize.y / 2 + 1;
+ ty= Math.max(ty, 0);
+ ty+= this.marginHeight + tvmargin;
+ toggle.setLocation(x, ty);
+ toggle.setSize(toggleSize);
+ x+= toggleSize.x + IGAP;
+ }
+ if (imageLabel != null) {
+ final int iy= theaderHeight / 2 - imageSize.y / 2;
+ imageLabel.setBounds(x, iy, imageSize.x, imageSize.y);
+ }
+ if (textLabel != null) {
+ int ty= theaderHeight / 2 - size.y / 2;
+ int tx= x;
+ if (imageLabel != null) {
+ tx+= imageSize.x + IGAP;
+ }
+ if (Constants.WS_GTK.equals(Platform.getWS())) {
+ size.x+= 1; // See Bug 342610
+ }
+
+ this.textLabelCache.setBounds(tx, ty, size.x, size.y);
+ }
+ int tbarHeight= theaderHeight;
+ if (size.y > tbarHeight) {
+ tbarHeight= size.y;
+ }
+ y+= tbarHeight;
+ if (hasTitleBar()) {
+ y+= tvmargin;
+ }
+ final Control separatorControl= getSeparatorControl();
+ if (separatorControl != null) {
+ y+= VSPACE;
+ separatorControl.setBounds(this.marginWidth, y,
+ clientArea.width - this.marginWidth - this.marginWidth,
+ SEPARATOR_HEIGHT);
+ y+= SEPARATOR_HEIGHT;
+ }
+ if (ExpandableComposite.this.expanded
+ && ExpandableComposite.this.client != null) {
+ int areaWidth= clientArea.width - this.marginWidth - thmargin;
+ int cx= this.marginWidth + thmargin;
+ if ((ExpandableComposite.this.expansionStyle & CLIENT_INDENT) != 0) {
+ cx= x;
+ }
+ areaWidth-= cx;
+ if (getDescriptionControl() != null) {
+ if (ExpandableComposite.this.expanded) {
+ y+= VSPACE;
+ }
+ final Point dsize= this.descriptionCache.computeSize(areaWidth, SWT.DEFAULT);
+ y+= this.descriptionVerticalSpacing;
+ this.descriptionCache.setBounds(cx, y, areaWidth, dsize.y);
+ y+= dsize.y + this.clientVerticalSpacing;
+ }
+ y+= this.clientVerticalSpacing;
+ final int cwidth= areaWidth;
+ final int cheight= clientArea.height - this.marginHeight -
+ this.marginHeight - y;
+ this.clientCache.setBounds(cx, y, cwidth, cheight);
+ }
+
+ ExpandableComposite.this.titleHeaderRegion.x= this.marginWidth;
+ ExpandableComposite.this.titleHeaderRegion.y= this.marginHeight;
+ ExpandableComposite.this.titleHeaderRegion.width= clientArea.width - this.marginWidth - this.marginWidth;
+ ExpandableComposite.this.titleHeaderRegion.height= theaderHeight;
+ }
+
+ @Override
+ protected Point computeSize(final Composite parent, final int wHint, final int hHint,
+ final boolean changed) {
+ initCache(changed);
+
+ final var toggle= ExpandableComposite.this.toggle;
+ final var imageLabel= ExpandableComposite.this.imageLabel;
+ final var textLabel= ExpandableComposite.this.textLabel;
+
+ int width= 0, height= 0;
+ Point toggleSize= NULL_SIZE;
+ int toggleWidth= 0;
+ if (toggle != null) {
+ toggleSize= this.toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ toggleWidth= toggleSize.x + IGAP;
+ }
+ int thmargin= 0;
+ int tvmargin= 0;
+
+ Point imageSize= NULL_SIZE;
+ if (imageLabel != null) {
+ imageSize= imageLabel.computeSize(16, 16);
+ }
+
+ if (hasTitleBar()) {
+ thmargin= this.titleBarTextMarginWidth;
+ tvmargin= IVGAP;
+ }
+ int innerwHint= wHint;
+ if (innerwHint != SWT.DEFAULT) {
+ innerwHint-= toggleWidth + this.marginWidth + this.marginWidth + thmargin + thmargin;
+ if (imageSize.x > 0) {
+ innerwHint-= imageSize.x + IGAP;
+ }
+ }
+
+ int innertHint= innerwHint;
+
+ Point size= NULL_SIZE;
+ if (textLabel != null) {
+ size= this.textLabelCache.computeSize(innertHint, SWT.DEFAULT);
+
+ if (textLabel instanceof Label) {
+ final Point defSize= this.textLabelCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ if (defSize.y == size.y) {
+ // One line - pick the smaller of the two widths
+ size.x= Math.min(defSize.x, size.x);
+ }
+ }
+ }
+
+ if (toggleWidth > 0) {
+ width+= toggleWidth;
+ }
+ if (imageSize.x > 0) {
+ width+= imageSize.x + IGAP;
+ }
+ if (size.x > 0) {
+ width+= size.x;
+ }
+ if (toggleSize.y > height) {
+ height= toggleSize.y;
+ }
+ if (imageSize.y > height) {
+ height= imageSize.y;
+ }
+ if (size.y > height) {
+ height= size.y;
+ }
+ final Control separatorControl= getSeparatorControl();
+ if (separatorControl != null) {
+ height+= VSPACE + SEPARATOR_HEIGHT;
+ }
+
+ if (wHint < width && size.x > 50) {
+ width-= size.x - 50;
+ }
+
+ // if (hasTitleBar())
+ // height+= VSPACE;
+ if ((ExpandableComposite.this.expanded || (ExpandableComposite.this.expansionStyle & COMPACT) == 0)
+ && ExpandableComposite.this.client != null) {
+ int cwHint= wHint;
+ int clientIndent= 0;
+ if ((ExpandableComposite.this.expansionStyle & CLIENT_INDENT) != 0) {
+ clientIndent= toggleWidth;
+ }
+
+ if (cwHint != SWT.DEFAULT) {
+ cwHint-= this.marginWidth + this.marginWidth + thmargin + thmargin;
+ }
+ final Point csize= this.clientCache.computeSize(cwHint, SWT.DEFAULT);
+ if (getDescriptionControl() != null) {
+ int dwHint= cwHint;
+ if (dwHint == SWT.DEFAULT) {
+ dwHint= csize.x;
+ if ((ExpandableComposite.this.expansionStyle & CLIENT_INDENT) != 0) {
+ dwHint-= toggleWidth;
+ }
+ }
+ final Point dsize= this.descriptionCache.computeSize(dwHint, SWT.DEFAULT);
+ width= Math.max(width, dsize.x + clientIndent);
+ if (ExpandableComposite.this.expanded) {
+ if (separatorControl != null) {
+ height+= VSPACE;
+ }
+ height+= this.descriptionVerticalSpacing + dsize.y;
+ }
+ }
+ width= Math.max(width, csize.x + clientIndent);
+ if (ExpandableComposite.this.expanded) {
+ height+= this.clientVerticalSpacing;
+ height+= csize.y;
+ }
+ }
+
+ final Point result= new Point(width + this.marginWidth + this.marginWidth
+ + thmargin + thmargin, height + this.marginHeight + this.marginHeight
+ + tvmargin + tvmargin);
+ return result;
+ }
+
+ @Override
+ public int computeMinimumWidth(final Composite parent, final boolean changed) {
+ return computeSize(parent, 0, SWT.DEFAULT, changed).x;
+ }
+
+ @Override
+ public int computeMaximumWidth(final Composite parent, final boolean changed) {
+ return computeSize(parent, SWT.DEFAULT, SWT.DEFAULT, changed).x;
+ }
+
+ }
+
+
+ /**
+ * Creates an expandable composite using a TWISTIE toggle.
+ *
+ * @param parent
+ * the parent composite
+ * @param style
+ * SWT style bits
+ */
+ public ExpandableComposite(final Composite parent, final int style) {
+ this(parent, style, TWISTIE);
+ }
+
+ /**
+ * Creates the expandable composite in the provided parent.
+ *
+ * @param parent
+ * the parent
+ * @param style
+ * the control style (as expected by SWT subclass)
+ * @param expansionStyle
+ * the style of the expansion widget (TREE_NODE, TWISTIE,
+ * CLIENT_INDENT, COMPACT, FOCUS_TITLE,
+ * LEFT_TEXT_CLIENT_ALIGNMENT, NO_TITLE)
+ */
+ public ExpandableComposite(final Composite parent, final int style, final int expansionStyle) {
+ super(parent, style);
+ this.expansionStyle= expansionStyle;
+ if ((expansionStyle & TITLE_BAR) != 0) {
+ setBackgroundMode(SWT.INHERIT_DEFAULT);
+ }
+ super.setLayout(new ExpandableLayout());
+ if (hasTitleBar()) {
+ addPaintListener(this::onPaint);
+ }
+
+ final Toggle toggle;
+ final Label imageLabel;
+ final Control textLabel;
+ if ((expansionStyle & TWISTIE) != 0) {
+ toggle= new Toggle(this, SWT.NULL);
+ }
+ else {
+ toggle= null;
+ this.expanded= true;
+ }
+ if ((expansionStyle & EXPANDED) != 0) {
+ this.expanded= true;
+ }
+ this.toggle= toggle;
+ if (toggle != null) {
+ toggle.setExpanded(this.expanded);
+ toggle.addHyperlinkListener(new HyperlinkAdapter() {
+ @Override
+ public void linkActivated(final HyperlinkEvent e) {
+ toggleState();
+ }
+ });
+ toggle.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(final KeyEvent e) {
+ if (e.keyCode == SWT.ARROW_UP) {
+ verticalMove(false);
+ e.doit= false;
+ }
+ else if (e.keyCode == SWT.ARROW_DOWN) {
+ verticalMove(true);
+ e.doit= false;
+ }
+ }
+ });
+ if ((getExpansionStyle() & FOCUS_TITLE) == 0) {
+ toggle.addFocusListener(new FocusListener() {
+ @Override
+ public void focusGained(final FocusEvent e) {
+ final var textLabel= ExpandableComposite.this.textLabel;
+ if (textLabel != null) {
+ textLabel.redraw();
+ }
+ }
+ @Override
+ public void focusLost(final FocusEvent e) {
+ final var textLabel= ExpandableComposite.this.textLabel;
+ if (textLabel != null) {
+ textLabel.redraw();
+ }
+ }
+ });
+ }
+ }
+ setToggleHoverColor(computeToggleHoverColor());
+
+ if ((expansionStyle & IMAGE) != 0) {
+ imageLabel= new Label(this, SWT.NONE);
+ }
+ else {
+ imageLabel= null;
+ }
+ this.imageLabel= imageLabel;
+
+ if ((expansionStyle & FOCUS_TITLE) != 0) {
+ final Hyperlink link= new Hyperlink(this, SWT.NONE);
+ link.addHyperlinkListener(new HyperlinkAdapter() {
+ @Override
+ public void linkActivated(final HyperlinkEvent e) {
+ programmaticToggleState();
+ }
+ });
+ textLabel= link;
+ }
+ else {
+ final Label label= new Label(this, SWT.NONE);
+ if (!isFixedStyle()) {
+ final Listener listener= new Listener() {
+ @Override
+ public void handleEvent(final Event e) {
+ switch (e.type) {
+ case SWT.Paint:
+ if (ExpandableComposite.this.toggle != null
+ && (getExpansionStyle() & NO_TITLE_FOCUS_BOX) == 0) {
+ paintTitleFocus(e.gc);
+ }
+ break;
+ case SWT.Resize:
+ updateLabelTrim();
+ break;
+ }
+ }
+ };
+ label.addListener(SWT.Paint, listener);
+ label.addListener(SWT.Resize, listener);
+ }
+ textLabel= label;
+ }
+ this.textLabel= textLabel;
+ if (textLabel != null) {
+ textLabel.setMenu(getMenu());
+ textLabel.addTraverseListener(new TraverseListener() {
+ @Override
+ public void keyTraversed(final TraverseEvent e) {
+ if (e.detail == SWT.TRAVERSE_MNEMONIC) {
+ // steal the mnemonic
+ if (!isVisible() || !isEnabled()) {
+ return;
+ }
+ if (e.character != 0
+ && e.character == LegacyActionTools.extractMnemonic(getText())) {
+ e.doit= false;
+ if (!isFixedStyle()) {
+ programmaticToggleState();
+ }
+ setFocus();
+ }
+ }
+ }
+ });
+ }
+ { // Complete Title header
+ final Listener listener= new Listener() {
+ @Override
+ public void handleEvent(final Event event) {
+ int x= event.x;
+ int y= event.y;
+ if (event.widget != ExpandableComposite.this) {
+ final Point p= ((Control)event.widget).getLocation();
+ x+= p.x;
+ y+= p.y;
+ }
+ switch (event.type) {
+ case SWT.MouseDown:
+ if (ExpandableComposite.this.titleHeaderRegion.contains(x, y)) {
+ final var toggle= ExpandableComposite.this.toggle;
+ if (toggle != null) {
+ toggle.setFocus();
+ }
+ }
+ break;
+ case SWT.MouseUp:
+ if (ExpandableComposite.this.titleHeaderRegion.contains(x, y)
+ && event.button == 1) {
+// textLabel.setCursor(FormsResources.getBusyCursor());
+ programmaticToggleState();
+// textLabel.setCursor(null);
+ }
+ break;
+ case SWT.MouseEnter:
+ case SWT.MouseMove:
+ case SWT.MouseExit:
+ if (ExpandableComposite.this.titleHeaderRegion.contains(x, y)) {
+ final var toggle= ExpandableComposite.this.toggle;
+ if (toggle != null && !toggle.getHover()) {
+ toggle.setHover(true);
+ toggle.redraw();
+ }
+ }
+ else {
+ final var toggle= ExpandableComposite.this.toggle;
+ if (toggle != null && toggle.getHover()) {
+ toggle.setHover(false);
+ toggle.redraw();
+ }
+ }
+ break;
+// case SWT.Paint:
+// if (toggle != null && (getExpansionStyle() & NO_TITLE_FOCUS_BOX) == 0) {
+// paintTitleFocus(event.gc);
+// }
+// break;
+ }
+ }
+ };
+ addListener(SWT.MouseDown, listener);
+ addListener(SWT.MouseUp, listener);
+ addListener(SWT.MouseEnter, listener);
+ addListener(SWT.MouseMove, listener);
+ addListener(SWT.MouseExit, listener);
+ if (imageLabel != null) {
+ imageLabel.addListener(SWT.MouseDown, listener);
+ imageLabel.addListener(SWT.MouseUp, listener);
+ imageLabel.addListener(SWT.MouseEnter, listener);
+ imageLabel.addListener(SWT.MouseExit, listener);
+ }
+ if (textLabel != null) {
+ textLabel.addListener(SWT.MouseDown, listener);
+ textLabel.addListener(SWT.MouseUp, listener);
+ textLabel.addListener(SWT.MouseEnter, listener);
+ textLabel.addListener(SWT.MouseExit, listener);
+ }
+ }
+ }
+
+
+ @Override
+ public boolean forceFocus() {
+ return false;
+ }
+
+ /**
+ * Overrides 'super' to pass the menu to the text label.
+ *
+ * @param menu
+ * the menu from the parent to attach to this control.
+ */
+ @Override
+ public void setMenu(final @Nullable Menu menu) {
+ final var toggle= this.toggle;
+ final var imageLabel= this.imageLabel;
+ final var textLabel= this.textLabel;
+ if (toggle != null) {
+ toggle.setMenu(menu);
+ }
+ if (imageLabel != null) {
+ imageLabel.setMenu(menu);
+ }
+ if (textLabel != null) {
+ textLabel.setMenu(menu);
+ }
+
+ super.setMenu(menu);
+ }
+
+ /**
+ * Prevents assignment of the layout manager - expandable composite uses its
+ * own layout.
+ */
+ @Override
+ public final void setLayout(final @Nullable Layout layout) {
+ }
+
+ /**
+ * Sets the background of all the custom controls in the expandable.
+ */
+ @Override
+ public void setBackground(final @Nullable Color color) {
+ super.setBackground(color);
+
+ if ((getExpansionStyle() & TITLE_BAR) == 0) {
+ final var toggle= this.toggle;
+ final var imageLabel= this.imageLabel;
+ final var textLabel= this.textLabel;
+ if (toggle != null) {
+ toggle.setBackground(color);
+ }
+ if (imageLabel != null) {
+ imageLabel.setBackground(color);
+ }
+ if (textLabel != null) {
+ textLabel.setBackground(color);
+ }
+ }
+ }
+
+ protected Color computeToggleHoverColor() {
+ final Display display= getDisplay();
+ return display.getSystemColor(SWT.COLOR_LIST_SELECTION);
+ }
+
+ /**
+ * Sets the foreground of all the custom controls in the expandable.
+ */
+ @Override
+ public void setForeground(final @Nullable Color color) {
+ super.setForeground(color);
+
+ final var toggle= this.toggle;
+ final var textLabel= this.textLabel;
+
+ if (toggle != null) {
+ toggle.setForeground(color);
+ }
+ if (textLabel != null) {
+ textLabel.setForeground(color);
+ }
+ }
+
+ /**
+ * Sets the color of the toggle control.
+ *
+ * @param color
+ * the color object
+ */
+ public void setToggleColor(final @Nullable Color color) {
+ final var toggle= this.toggle;
+ if (toggle != null) {
+ toggle.setDecorationColor(color);
+ }
+ }
+
+ /**
+ * Sets the active color of the toggle control (when the mouse enters the
+ * toggle area).
+ *
+ * @param color
+ * the active color object
+ */
+ public void setToggleHoverColor(final @Nullable Color color) {
+ final var toggle= this.toggle;
+ if (toggle != null) {
+ toggle.setHoverDecorationColor(color);
+ }
+ }
+
+ /**
+ * Sets the fonts of all the custom controls in the expandable.
+ */
+ @Override
+ public void setFont(final @Nullable Font font) {
+ super.setFont(font);
+
+ final var toggle= this.toggle;
+ final var textLabel= this.textLabel;
+ if (toggle != null) {
+ toggle.setFont(font);
+ }
+ if (textLabel != null) {
+ textLabel.setFont(font);
+ }
+ }
+
+ @Override
+ public void setEnabled(final boolean enabled) {
+ final var toggle= this.toggle;
+ final var textLabel= this.textLabel;
+ if (toggle != null) {
+ toggle.setEnabled(enabled);
+ }
+ if (textLabel != null) {
+ textLabel.setEnabled(enabled);
+ }
+
+ super.setEnabled(enabled);
+ }
+
+ /**
+ * Sets the client of this expandable composite. The client must not be
+ * <samp>null </samp> and must be a direct child of this container.
+ *
+ * @param client
+ * the client that will be expanded or collapsed
+ */
+ public void setClient(final Control client) {
+ Assert.isTrue(client != null && client.getParent() == this);
+ this.client= client;
+ }
+
+ /**
+ * Returns the current expandable client.
+ *
+ * @return the client control
+ */
+ public Control getClient() {
+ return this.client;
+ }
+
+ public void setImage(final @Nullable Image image) {
+ final var imageLabel= this.imageLabel;
+ if (imageLabel != null) {
+ imageLabel.setImage(image);
+ }
+ }
+
+ public @Nullable Image getImage() {
+ final var imageLabel= this.imageLabel;
+ if (imageLabel != null) {
+ return imageLabel.getImage();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the title of the expandable composite. The title will act as a
+ * hyperlink and activating it will toggle the client between expanded and
+ * collapsed state.
+ *
+ * @param title
+ * the new title string
+ * @see #getText()
+ */
+ public void setText(final String title) {
+ final var textLabel= this.textLabel;
+ if (textLabel instanceof Label) {
+ ((Label)textLabel).setText(title);
+ updateLabelTrim();
+ }
+ else if (textLabel instanceof Hyperlink) {
+ ((Hyperlink)textLabel).setText(title);
+ }
+ else {
+ return;
+ }
+ layout();
+ }
+
+ /**
+ * Returns the title string.
+ *
+ * @return the title string
+ * @see #setText(String)
+ */
+ public String getText() {
+ final var textLabel= this.textLabel;
+ if (textLabel instanceof Label) {
+ return ((Label)textLabel).getText();
+ }
+ else if (textLabel instanceof Hyperlink) {
+ return ((Hyperlink)textLabel).getText();
+ }
+ else {
+ return ""; //$NON-NLS-1$
+ }
+ }
+
+ @Override
+ public void setToolTipText(final @Nullable String string) {
+ super.setToolTipText(string);
+
+ // Also set on label, otherwise it's just on the background without text.
+ final var toggle= this.toggle;
+ final var imageLabel= this.imageLabel;
+ final var textLabel= this.textLabel;
+ if (toggle != null) {
+ toggle.setToolTipText(string);
+ }
+ if (imageLabel != null) {
+ imageLabel.setToolTipText(string);
+ }
+ if (textLabel != null) {
+ textLabel.setToolTipText(string);
+ }
+ }
+
+ /**
+ * Tests the expanded state of the composite.
+ *
+ * @return <samp>true </samp> if expanded, <samp>false </samp> if collapsed.
+ */
+ public boolean isExpanded() {
+ return this.expanded;
+ }
+
+ /**
+ * Returns the bitwise-ORed style bits for the expansion control.
+ *
+ * @return the bitwise-ORed style bits for the expansion control
+ */
+ public int getExpansionStyle() {
+ return this.expansionStyle;
+ }
+
+ /**
+ * Programmatically changes expanded state.
+ *
+ * @param expanded
+ * the new expanded state
+ */
+ public void setExpanded(final boolean expanded) {
+ internalSetExpanded(expanded);
+
+ final var toggle= this.toggle;
+ if (toggle != null) {
+ toggle.setExpanded(expanded);
+ }
+ }
+
+ /**
+ * Performs the expansion state change for the expandable control.
+ *
+ * @param expanded
+ * the expansion state
+ */
+ protected void internalSetExpanded(final boolean expanded) {
+ if (this.expanded != expanded) {
+ this.expanded= expanded;
+
+ final var descriptionControl= getDescriptionControl();
+ if (descriptionControl != null) {
+ descriptionControl.setVisible(expanded);
+ }
+ if (this.client != null) {
+ this.client.setVisible(expanded);
+ }
+ reflow();
+ }
+ }
+
+ /**
+ * Adds the listener that will be notified when the expansion state changes.
+ *
+ * @param listener
+ * the listener to add
+ */
+ public void addExpansionListener(final IExpansionListener listener) {
+ this.listeners.add(listener);
+ }
+
+ /**
+ * Removes the expansion listener.
+ *
+ * @param listener
+ * the listener to remove
+ */
+ public void removeExpansionListener(final IExpansionListener listener) {
+ this.listeners.remove(listener);
+ }
+
+ /**
+ * If TITLE_BAR or SHORT_TITLE_BAR style is used, title bar decoration will
+ * be painted behind the text in this method. The default implementation
+ * does nothing - subclasses are responsible for rendering the title area.
+ *
+ * @param e
+ * the paint event
+ */
+ protected void onPaint(final PaintEvent e) {
+ }
+
+ /**
+ * Returns description control that will be placed under the title if
+ * present.
+ *
+ * @return the description control or <samp>null </samp> if not used.
+ */
+ protected @Nullable Control getDescriptionControl() {
+ return null;
+ }
+
+ /**
+ * Returns the separator control that will be placed between the title and
+ * the description if present.
+ *
+ * @return the separator control or <samp>null </samp> if not used.
+ */
+ protected @Nullable Control getSeparatorControl() {
+ return null;
+ }
+
+ /**
+ * Computes the size of the expandable composite.
+ *
+ * @see org.eclipse.swt.widgets.Composite#computeSize
+ */
+ @Override
+ public Point computeSize(final int wHint, final int hHint, final boolean changed) {
+ checkWidget();
+ final Point size;
+ final var layout= (ExpandableLayout)getLayout();
+ if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
+ size= layout.computeSize(this, wHint, hHint, changed);
+ } else {
+ size= new Point(wHint, hHint);
+ }
+ final Rectangle trim= computeTrim(0, 0, size.x, size.y);
+ return new Point(trim.width, trim.height);
+ }
+
+ /**
+ * Returns <samp>true </samp> if the composite is fixed i.e. cannot be
+ * expanded or collapsed. Fixed control will still contain the title,
+ * separator and description (if present) as well as the client, but will be
+ * in the permanent expanded state and the toggle affordance will not be
+ * shown.
+ *
+ * @return <samp>true </samp> if the control is fixed in the expanded state,
+ * <samp>false </samp> if it can be collapsed.
+ */
+ protected boolean isFixedStyle() {
+ return ((this.expansionStyle & TWISTIE) == 0);
+ }
+
+ /**
+ * Tests if this expandable composite renders a title bar around the text.
+ *
+ * @return <code>true</code> for <code>TITLE_BAR</code> or
+ * <code>SHORT_TITLE_BAR</code> styles, <code>false</code>
+ * otherwise.
+ */
+ protected boolean hasTitleBar() {
+ return ((getExpansionStyle() & TITLE_BAR) != 0
+ || (getExpansionStyle() & SHORT_TITLE_BAR) != 0 );
+ }
+
+ /**
+ * Sets the color of the title bar foreground when TITLE_BAR style is used.
+ *
+ * @param color
+ * the title bar foreground
+ */
+ public void setTitleBarForeground(final @Nullable Color color) {
+ this.titleBarForeground= color;
+
+ final var textLabel= this.textLabel;
+ if (textLabel != null) {
+ textLabel.setForeground(color);
+ }
+ }
+
+ /**
+ * Returns the title bar foreground when TITLE_BAR style is used.
+ *
+ * @return the title bar foreground
+ */
+ public @Nullable Color getTitleBarForeground() {
+ return this.titleBarForeground;
+ }
+
+ // end of APIs
+
+ private void toggleState() {
+ final boolean newState= !isExpanded();
+ fireExpanding(newState, true);
+ internalSetExpanded(newState);
+ fireExpanding(newState, false);
+ if (newState) {
+ FormToolkit.ensureVisible(this);
+ }
+ }
+
+ private void fireExpanding(final boolean state, final boolean before) {
+ final ImIdentityList<IExpansionListener> listenerList= this.listeners.toList();
+ if (listenerList.isEmpty()) {
+ return;
+ }
+ final ExpansionEvent e= new ExpansionEvent(this, state);
+ for (final IExpansionListener listener : listenerList) {
+ if (before) {
+ listener.expansionStateChanging(e);
+ }
+ else {
+ listener.expansionStateChanged(e);
+ }
+ }
+ }
+
+ private void verticalMove(final boolean down) {
+ final Composite parent= nonNullAssert(getParent());
+ final Control[] children= parent.getChildren();
+ for (int i= 0; i < children.length; i++) {
+ final Control child= children[i];
+ if (child == this) {
+ final ExpandableComposite sibling= getSibling(children, i, down);
+ if (sibling != null && sibling.toggle != null) {
+ sibling.setFocus();
+ }
+ break;
+ }
+ }
+ }
+
+ private @Nullable ExpandableComposite getSibling(final Control[] children, final int index,
+ final boolean down) {
+ int loc= (down) ? index + 1 : index - 1;
+ while (loc >= 0 && loc < children.length) {
+ final Control c= children[loc];
+ if (c instanceof ExpandableComposite && c.isVisible()) {
+ return (ExpandableComposite)c;
+ }
+ loc= (down) ? loc + 1 : loc - 1;
+ }
+ return null;
+ }
+
+ private void programmaticToggleState() {
+ final var toggle= this.toggle;
+ if (toggle != null) {
+ toggle.setExpanded(!toggle.isExpanded());
+ }
+ toggleState();
+ }
+
+ private void paintTitleFocus(final GC gc) {
+ final var toggle= this.toggle;
+ final var textLabel= this.textLabel;
+ if (textLabel != null) {
+ final Point size= textLabel.getSize();
+ gc.setBackground(textLabel.getBackground());
+ gc.setForeground(textLabel.getForeground());
+ if (toggle != null && toggle.isFocusControl()) {
+ gc.drawFocus(0, 0, size.x, size.y);
+ }
+ }
+ }
+
+ void reflow() {
+ Composite c= this;
+ while (c != null) {
+ c.setRedraw(false);
+ c= c.getParent();
+ if (c instanceof SharedScrolledComposite || c instanceof Shell) {
+ break;
+ }
+ }
+ try {
+ c= this;
+ while (c != null) {
+ c.requestLayout();
+ c= c.getParent();
+ if (c instanceof SharedScrolledComposite) {
+ ((SharedScrolledComposite)c).reflow(true);
+ break;
+ }
+ if (c instanceof Shell) {
+ break;
+ }
+ }
+ }
+ finally {
+ c= this;
+ while (c != null) {
+ c.setRedraw(true);
+ c= c.getParent();
+ if (c instanceof SharedScrolledComposite || c instanceof Shell) {
+ break;
+ }
+ }
+ }
+ }
+
+ private void updateLabelTrim() {
+// if (textLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).x > textLabel.getSize().x) {
+// textLabel.setToolTipText(((Label)textLabel).getText());
+// }
+// else {
+// textLabel.setToolTipText(null);
+// }
+ }
+
+}
diff --git a/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/ecommons/ui/swt/expandable/ExpandableRowsList.java b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/ecommons/ui/swt/expandable/ExpandableRowsList.java
new file mode 100644
index 0000000..2aca247
--- /dev/null
+++ b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/ecommons/ui/swt/expandable/ExpandableRowsList.java
@@ -0,0 +1,135 @@
+/*=============================================================================#
+ # Copyright (c) 2012, 2021 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ecommons.ui.swt.expandable;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.forms.FormColors;
+import org.eclipse.ui.forms.events.ExpansionAdapter;
+import org.eclipse.ui.forms.events.ExpansionEvent;
+import org.eclipse.ui.forms.events.IExpansionListener;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.SharedScrolledComposite;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+
+@NonNullByDefault
+public class ExpandableRowsList extends SharedScrolledComposite {
+
+
+ private static @Nullable FormToolkit gDialogsFormToolkit;
+
+ private static FormToolkit getViewFormToolkit() {
+ FormToolkit toolkit= gDialogsFormToolkit;
+ if (toolkit == null) {
+ final FormColors colors= new FormColors(Display.getCurrent());
+ colors.setBackground(null);
+ colors.setForeground(null);
+ toolkit= new FormToolkit(colors);
+ gDialogsFormToolkit= toolkit;
+ }
+ return toolkit;
+ }
+
+
+ private final FormToolkit toolkit;
+
+ private @Nullable IExpansionListener expansionListener;
+
+ private int delayReflowCounter;
+
+
+ public ExpandableRowsList(final Composite parent) {
+ this(parent, SWT.H_SCROLL | SWT.V_SCROLL);
+ }
+
+ public ExpandableRowsList(final Composite parent, final int style) {
+ super(parent, style);
+
+ if ((style & SWT.H_SCROLL) == 0) {
+ setExpandHorizontal(true);
+ }
+
+ setFont(parent.getFont());
+
+ this.toolkit= getViewFormToolkit();
+
+ setExpandHorizontal(true);
+ setExpandVertical(true);
+
+ final Composite body= new Composite(this, SWT.NONE);
+ setContent(body);
+
+ setFont(parent.getFont());
+ setBackgroundMode(SWT.INHERIT_FORCE);
+ setBackground(getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
+ setForeground(getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND));
+ }
+
+
+ @Override
+ @SuppressWarnings("null")
+ public Composite getContent() {
+ return (Composite)super.getContent();
+ }
+
+ @Override
+ public void setDelayedReflow(final boolean delayedReflow) {
+ if (delayedReflow) {
+ this.delayReflowCounter++;
+ }
+ else {
+ this.delayReflowCounter--;
+ }
+ super.setDelayedReflow(this.delayReflowCounter > 0);
+ }
+
+ public void adaptChild(final Control childControl) {
+ if (childControl instanceof ExpandableComposite) {
+ final var expandableComposite= (ExpandableComposite)childControl;
+ this.toolkit.adapt(expandableComposite, true, false);
+ var expansionListener= this.expansionListener;
+ if (expansionListener == null) {
+ expansionListener= new ExpansionAdapter() {
+ @Override
+ public void expansionStateChanged(final ExpansionEvent e) {
+ expandedStateChanged();
+ }
+ };
+ this.expansionListener= expansionListener;
+ }
+ expandableComposite.addExpansionListener(expansionListener);
+ }
+ else if (childControl instanceof Composite) {
+ final var composite= (Composite)childControl;
+ this.toolkit.adapt(composite, false, false);
+ for (final Control child : composite.getChildren()) {
+ this.toolkit.adapt(child, true, false);
+ }
+ }
+ else {
+ this.toolkit.adapt(childControl, true, false);
+ }
+ }
+
+ protected void expandedStateChanged() {
+ reflow(true);
+ }
+
+}
diff --git a/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/internal/ecommons/ui/swt/css/dom/ECommonsSwtElementProvider.java b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/internal/ecommons/ui/swt/css/dom/ECommonsSwtElementProvider.java
new file mode 100644
index 0000000..a3528b5
--- /dev/null
+++ b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/internal/ecommons/ui/swt/css/dom/ECommonsSwtElementProvider.java
@@ -0,0 +1,42 @@
+/*=============================================================================#
+ # Copyright (c) 2017 Stephan Wahlbrink and others.
+ # All rights reserved. This program and the accompanying materials
+ # are made available under the terms of the Eclipse Public License v1.0
+ # which accompanies this distribution, and is available at
+ # http://www.eclipse.org/legal/epl-v10.html
+ #
+ # Contributors:
+ # Stephan Wahlbrink <stephan.wahlbrink@walware.de> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.internal.ecommons.ui.swt.css.dom;
+
+import org.eclipse.e4.ui.css.core.dom.IElementProvider;
+import org.eclipse.e4.ui.css.core.engine.CSSEngine;
+
+import org.w3c.dom.Element;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ecommons.ui.swt.expandable.ExpandableComposite;
+
+
+@NonNullByDefault
+@SuppressWarnings("restriction")
+public class ECommonsSwtElementProvider implements IElementProvider {
+
+
+ public ECommonsSwtElementProvider() {
+ }
+
+
+ @Override
+ public @Nullable Element getElement(final Object element, final CSSEngine engine) {
+ if (element instanceof ExpandableComposite) {
+ return new ExpandableCompositeElement((ExpandableComposite)element, engine);
+ }
+ return null;
+ }
+
+}
diff --git a/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/internal/ecommons/ui/swt/css/dom/ExpandableCompositeElement.java b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/internal/ecommons/ui/swt/css/dom/ExpandableCompositeElement.java
new file mode 100644
index 0000000..b3514d0
--- /dev/null
+++ b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/internal/ecommons/ui/swt/css/dom/ExpandableCompositeElement.java
@@ -0,0 +1,52 @@
+/*=============================================================================#
+ # Copyright (c) 2017 Stephan Wahlbrink and others.
+ # All rights reserved. This program and the accompanying materials
+ # are made available under the terms of the Eclipse Public License v1.0
+ # which accompanies this distribution, and is available at
+ # http://www.eclipse.org/legal/epl-v10.html
+ #
+ # Contributors:
+ # Stephan Wahlbrink <stephan.wahlbrink@walware.de> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.internal.ecommons.ui.swt.css.dom;
+
+import org.eclipse.e4.ui.css.core.engine.CSSEngine;
+import org.eclipse.e4.ui.css.swt.dom.CompositeElement;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+import org.eclipse.statet.ecommons.ui.swt.expandable.ExpandableComposite;
+
+
+@NonNullByDefault
+@SuppressWarnings("restriction")
+public class ExpandableCompositeElement extends CompositeElement {
+
+
+ public ExpandableCompositeElement(final ExpandableComposite composite, final CSSEngine engine) {
+ super(composite, engine);
+ this.hasMouseHover= true;
+ }
+
+
+ @Override
+ protected String computeLocalName() {
+ return "ExpandableComposite"; //$NON-NLS-1$
+ }
+
+ @Override
+ public ExpandableComposite getComposite() {
+ return (ExpandableComposite)getNativeWidget();
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+ final var composite= getComposite();
+ composite.setTitleBarForeground(null);
+ composite.setToggleColor(null);
+ composite.setToggleHoverColor(null);
+ }
+
+}
diff --git a/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/internal/ecommons/ui/swt/css/properties/ExpandableCompositeHandler.java b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/internal/ecommons/ui/swt/css/properties/ExpandableCompositeHandler.java
new file mode 100644
index 0000000..7d749d0
--- /dev/null
+++ b/ecommons/org.eclipse.statet.ecommons.uimisc/src/org/eclipse/statet/internal/ecommons/ui/swt/css/properties/ExpandableCompositeHandler.java
@@ -0,0 +1,77 @@
+/*=============================================================================#
+ # Copyright (c) 2017, 2018 Stephan Wahlbrink and others.
+ #
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ #
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ #
+ # Contributors:
+ # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.internal.ecommons.ui.swt.css.properties;
+
+import org.eclipse.e4.ui.css.core.engine.CSSEngine;
+import org.eclipse.e4.ui.css.swt.properties.AbstractCSSPropertySWTHandler;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.widgets.Control;
+
+import org.w3c.dom.css.CSSValue;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ecommons.ui.swt.expandable.ExpandableComposite;
+
+
+@NonNullByDefault
+@SuppressWarnings("restriction")
+public class ExpandableCompositeHandler extends AbstractCSSPropertySWTHandler {
+
+
+ private static final String TITLE_BAR_FOREGROUND= "swt-titlebar-color"; //$NON-NLS-1$
+
+
+ @Override
+ protected @Nullable String retrieveCSSProperty(final Control control, final String property,
+ final @Nullable String pseudo, final CSSEngine engine) throws Exception {
+ return null;
+ }
+
+ @Override
+ protected void applyCSSProperty(final Control control, final String property,
+ final CSSValue value,
+ final @Nullable String pseudo, final CSSEngine engine) throws Exception {
+ if (!(control instanceof ExpandableComposite)
+ || property == null
+ || value.getCssValueType() != CSSValue.CSS_PRIMITIVE_VALUE) {
+ return;
+ }
+ final var expandableComposite= (ExpandableComposite)control;
+
+ switch (property.toLowerCase()) {
+ case TITLE_BAR_FOREGROUND:
+ if (pseudo == null) {
+ expandableComposite.setTitleBarForeground(
+ (Color)engine.convert(value, Color.class, control.getDisplay()) );
+ }
+ return;
+// case CSSPropertyFormHandler.TB_TOGGLE:
+// if (pseudo == null) {
+// expandableComposite.setToggleColor(
+// (Color)engine.convert(value, Color.class, control.getDisplay()) );
+// }
+// else if (pseudo.equalsIgnoreCase("hover")) { //$NON-NLS-1$
+// expandableComposite.setToggleHoverColor(
+// (Color)engine.convert(value, Color.class, control.getDisplay()) );
+// }
+// return;
+ default:
+ return;
+ }
+ }
+
+}