+ */
+public class HorizontalSpinner extends Composite {
+
+ private enum ALIGNMENT {
+ LEFT, RIGHT, BOTH
+ };
+
+ private final List modifyListeners = new ArrayList();
+
+ private Button leftButton;
+ private Button rightButton;
+ private Text text;
+ private int digits = 0;
+ private int increment = 1;
+ private int maximum = 0;
+ private int minimum = 255;
+ private int pageIncrement = 10;
+ private int storedValue = 0;
+ private ALIGNMENT alignment = ALIGNMENT.BOTH;
+
+ private final char decimalFormatSeparator;
+
+ /**
+ * Constructs a new instance of this class given its parent and a style
+ * value describing its behavior and appearance.
+ *
+ * The style value is either one of the style constants defined in class
+ * SWT which is applicable to instances of this class, or must
+ * be built by bitwise OR'ing together (that is, using the
+ * int "|" operator) two or more of those SWT
+ * style constants. The class description lists the style constants that are
+ * applicable to the class. Style bits are also inherited from superclasses.
+ *
+ *
+ * @param parent a composite control which will be the parent of the new
+ * instance (cannot be null)
+ * @param style the style of control to construct
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_NULL_ARGUMENT - if the parent is null
+ *
+ * @exception SWTException
+ *
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the parent
+ *
ERROR_INVALID_SUBCLASS - if this class is not an
+ * allowed subclass
+ *
+ *
+ * @see SWT#READ_ONLY
+ * @see SWT#FLAT
+ */
+ public HorizontalSpinner(final Composite parent, final int style) {
+ super(parent, style);
+
+ if ((style & SWT.LEFT) == SWT.LEFT) {
+ alignment = ALIGNMENT.LEFT;
+ }
+
+ if ((style & SWT.RIGHT) == SWT.RIGHT) {
+ alignment = ALIGNMENT.RIGHT;
+ }
+
+ final GridLayout gd = new GridLayout(3, false);
+ gd.horizontalSpacing = gd.verticalSpacing = 0;
+ gd.marginWidth = gd.marginHeight = 0;
+ setLayout(gd);
+
+ createContent(style);
+ addTextListeners();
+ addButtonsListener();
+ addModifyListeners();
+
+ decimalFormatSeparator = new DecimalFormatSymbols().getDecimalSeparator();
+ }
+
+ /**
+ * Create the content of the widget
+ *
+ * @param style style of the widget
+ */
+ private void createContent(final int style) {
+ final boolean readOnly = (style & SWT.READ_ONLY) == SWT.READ_ONLY;
+ final boolean flat = (style & SWT.FLAT) == SWT.FLAT;
+ final int buttonStyle = SWT.ARROW | (flat ? SWT.FLAT : SWT.NONE);
+
+ if (alignment == ALIGNMENT.BOTH) {
+ createMinusButton(buttonStyle);
+ createText(readOnly);
+ createPlusButton(buttonStyle);
+ } else if (alignment == ALIGNMENT.LEFT) {
+ createMinusButton(buttonStyle);
+ createPlusButton(buttonStyle);
+ createText(readOnly);
+ } else {
+ createText(readOnly);
+ createMinusButton(buttonStyle);
+ createPlusButton(buttonStyle);
+ }
+ }
+
+ /**
+ * Create minus button
+ *
+ * @param buttonStyle button style
+ */
+ private void createMinusButton(final int buttonStyle) {
+ leftButton = new Button(this, buttonStyle | SWT.LEFT);
+ leftButton.setFont(getFont());
+ leftButton.setBackground(getBackground());
+ leftButton.setCursor(getCursor());
+ leftButton.setEnabled(getEnabled());
+ leftButton.setFont(getFont());
+ leftButton.setForeground(getForeground());
+ leftButton.setLayoutData(new GridData(GridData.FILL, GridData.FILL, false, false));
+ }
+
+ /**
+ * Create the text zone
+ *
+ * @param readOnly if true, the text is read only
+ */
+ private void createText(final boolean readOnly) {
+ text = new Text(this, readOnly ? SWT.READ_ONLY : SWT.NONE);
+ final GridData gd = new GridData(GridData.FILL, GridData.CENTER, true, false);
+ gd.minimumWidth = 40;
+ text.setLayoutData(gd);
+ }
+
+ /**
+ * Create plus button
+ *
+ * @param buttonStyle button style
+ */
+ private void createPlusButton(final int buttonStyle) {
+ rightButton = new Button(this, buttonStyle | SWT.RIGHT);
+ rightButton.setFont(getFont());
+ rightButton.setBackground(getBackground());
+ rightButton.setCursor(getCursor());
+ rightButton.setEnabled(getEnabled());
+ rightButton.setFont(getFont());
+ rightButton.setForeground(getForeground());
+ rightButton.setLayoutData(new GridData(GridData.FILL, GridData.FILL, false, false));
+ }
+
+ /**
+ * Add the text listeners
+ */
+ private void addTextListeners() {
+ text.addListener(SWT.Verify, e -> {
+ if (e.character != 0 && !(Character.isDigit(e.character) || e.character == '-') && e.keyCode != SWT.BS && e.keyCode != SWT.DEL) {
+ e.doit = false;
+ return;
+ }
+ e.doit = verifyEntryAndStoreValue(e.text, e.keyCode);
+ });
+
+ text.addListener(SWT.KeyUp, e -> {
+ if (e.keyCode == SWT.ARROW_UP) {
+ increaseValue(increment);
+ }
+ if (e.keyCode == SWT.ARROW_DOWN) {
+ decreaseValue(increment);
+ }
+ if (e.keyCode == SWT.PAGE_UP) {
+ increaseValue(pageIncrement);
+ }
+ if (e.keyCode == SWT.PAGE_DOWN) {
+ decreaseValue(pageIncrement);
+ }
+
+ });
+
+ text.addListener(SWT.FocusOut, e -> {
+ if (text.getText().trim().equals("")) {
+ setSelection(storedValue);
+ }
+ });
+ }
+
+ /**
+ * Verify the entry and store the value in the field storedValue
+ *
+ * @param entry entry to check
+ * @param keyCode code of the typed key
+ * @return true if the entry if correct, false
+ * otherwise
+ */
+ private boolean verifyEntryAndStoreValue(final String entry, final int keyCode) {
+
+ return true;
+ }
+
+ /**
+ * Add the listener to the buttons
+ */
+ private void addButtonsListener() {
+ leftButton.addListener(SWT.Selection, e -> {
+ decreaseValue(increment);
+ });
+
+ rightButton.addListener(SWT.Selection, e -> {
+ increaseValue(increment);
+ });
+
+ }
+
+ /**
+ * Increase the value stored in this snippet
+ *
+ * @param value value to increase
+ */
+ private void increaseValue(final int value) {
+ setSelection(getSelection() + value);
+
+ }
+
+ /**
+ * Decrease the value stored in this snippet
+ *
+ * @param value value to decrease
+ */
+ private void decreaseValue(final int value) {
+ setSelection(getSelection() - value);
+ }
+
+ /**
+ * Add the modify listeners
+ */
+ private void addModifyListeners() {
+ text.addModifyListener(e -> {
+ for (final ModifyListener m : modifyListeners) {
+ m.modifyText(e);
+ }
+ });
+
+ }
+
+ /**
+ * Adds the listener to the collection of listeners who will be notified
+ * when the receiver's text is modified, by sending it one of the messages
+ * defined in the ModifyListener interface.
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_NULL_ARGUMENT - if the listener is null
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ *
+ * @see ModifyListener
+ * @see #removeModifyListener
+ * @see org.eclipse.swt.widgets.Spinner#addModifyListener(org.eclipse.swt.events.ModifyListener)
+ */
+
+ public void addModifyListener(final ModifyListener listener) {
+ checkWidget();
+ modifyListeners.add(listener);
+ }
+
+ /**
+ * Adds the listener to the collection of listeners who will be notified
+ * when the control is selected by the user, by sending it one of the
+ * messages defined in the SelectionListener interface.
+ *
+ * widgetSelected is not called for texts.
+ * widgetDefaultSelected is typically called when ENTER is
+ * pressed in a single-line text.
+ *
+ *
+ * @param listener the listener which should be notified when the control is
+ * selected by the user
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_NULL_ARGUMENT - if the listener is null
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ * The current selection is first copied to the clipboard and then deleted
+ * from the widget.
+ *
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public void cut() {
+ checkWidget();
+ text.cut();
+ }
+
+ /**
+ * Returns the number of decimal places used by the receiver.
+ *
+ * @return the digits
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public int getDigits() {
+ checkWidget();
+ return digits;
+ }
+
+ /**
+ * Returns the amount that the receiver's value will be modified by when the
+ * up/down arrows are pressed.
+ *
+ * @return the increment
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public int getIncrement() {
+ checkWidget();
+ return increment;
+ }
+
+ /**
+ * Returns the maximum value which the receiver will allow.
+ *
+ * @return the maximum
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public int getMaximum() {
+ checkWidget();
+ return maximum;
+ }
+
+ /**
+ * Returns the minimum value which the receiver will allow.
+ *
+ * @return the minimum
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public int getMinimum() {
+ checkWidget();
+ return minimum;
+ }
+
+ /**
+ * Returns the amount that the receiver's position will be modified by when
+ * the page up/down keys are pressed.
+ *
+ * @return the page increment
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public int getPageIncrement() {
+ checkWidget();
+ return pageIncrement;
+ }
+
+ /**
+ * Returns the selection, which is the receiver's position.
+ *
+ * @return the selection
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public int getSelection() {
+ checkWidget();
+ return storedValue;
+ }
+
+ /**
+ * Returns a string containing a copy of the contents of the receiver's text
+ * field, or an empty string if there are no contents.
+ *
+ * @return the receiver's text
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ *
+ */
+ public String getText() {
+ checkWidget();
+ return text.getText();
+ }
+
+ /**
+ * Returns the maximum number of characters that the receiver's text field
+ * is capable of holding. If this has not been changed by
+ * setTextLimit(), it will be the constant
+ * Spinner.LIMIT.
+ *
+ * @return the text limit
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ *
+ * @see #LIMIT
+ */
+ public int getTextLimit() {
+ checkWidget();
+ return text.getTextLimit();
+ }
+
+ /**
+ * Pastes text from clipboard.
+ *
+ * The selected text is deleted from the widget and new text inserted from
+ * the clipboard.
+ *
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public void paste() {
+ checkWidget();
+ text.paste();
+ }
+
+ /**
+ * Removes the listener from the collection of listeners who will be
+ * notified when the receiver's text is modified.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_NULL_ARGUMENT - if the listener is null
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ *
+ * @see ModifyListener
+ * @see #addModifyListener
+ */
+ public void removeModifyListener(final ModifyListener listener) {
+ checkWidget();
+ modifyListeners.remove(listener);
+ }
+
+ /**
+ * Removes the listener from the collection of listeners who will be
+ * notified when the control is selected by the user.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_NULL_ARGUMENT - if the listener is null
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ *
+ * @see SelectionListener
+ * @see #addSelectionListener
+ */
+ public void removeSelectionListener(final SelectionListener listener) {
+ checkWidget();
+ }
+
+ /**
+ * Sets the number of decimal places used by the receiver.
+ *
+ * The digit setting is used to allow for floating point values in the
+ * receiver. For example, to set the selection to a floating point value of
+ * 1.37 call setDigits() with a value of 2 and setSelection() with a value
+ * of 137. Similarly, if getDigits() has a value of 2 and getSelection()
+ * returns 137 this should be interpreted as 1.37. This applies to all
+ * numeric APIs.
+ *
+ *
+ * @param value the new digits (must be greater than or equal to zero)
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_INVALID_ARGUMENT - if the value is less than
+ * zero
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public void setDigits(final int value) {
+ checkWidget();
+ digits = value;
+ convertSelectionToStringValue();
+ }
+
+ /**
+ * Sets the amount that the receiver's value will be modified by when the
+ * up/down arrows are pressed to the argument, which must be at least one.
+ *
+ * @param value the new increment (must be greater than zero)
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public void setIncrement(final int value) {
+ checkWidget();
+ increment = value;
+ }
+
+ /**
+ * Sets the maximum value that the receiver will allow. This new value will
+ * be ignored if it is less than the receiver's current minimum value. If
+ * the new maximum is applied then the receiver's selection value will be
+ * adjusted if necessary to fall within its new range.
+ *
+ * @param value the new maximum, which must be greater than or equal to the
+ * current minimum
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public void setMaximum(final int value) {
+ checkWidget();
+ maximum = value;
+ }
+
+ /**
+ * Sets the minimum value that the receiver will allow. This new value will
+ * be ignored if it is greater than the receiver's current maximum value. If
+ * the new minimum is applied then the receiver's selection value will be
+ * adjusted if necessary to fall within its new range.
+ *
+ * @param value the new minimum, which must be less than or equal to the
+ * current maximum
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public void setMinimum(final int value) {
+ checkWidget();
+ minimum = value;
+ }
+
+ /**
+ * Sets the amount that the receiver's position will be modified by when the
+ * page up/down keys are pressed to the argument, which must be at least
+ * one.
+ *
+ * @param value the page increment (must be greater than zero)
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public void setPageIncrement(final int value) {
+ checkWidget();
+ pageIncrement = value;
+ }
+
+ /**
+ * Sets the selection, which is the receiver's position, to the
+ * argument. If the argument is not within the range specified by minimum
+ * and maximum, it will be adjusted to fall within this range.
+ *
+ * @param value the new selection (must be zero or greater)
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public void setSelection(int selection) {
+ checkWidget();
+ if (selection < minimum) {
+ selection = minimum;
+ } else if (selection > maximum) {
+ selection = maximum;
+ }
+
+ storedValue = selection;
+ text.setText(convertSelectionToStringValue());
+ }
+
+ /**
+ * Convert the selection into a string
+ *
+ * @return the string representation of the selection
+ */
+ private String convertSelectionToStringValue() {
+ if (getDigits() == 0) {
+ return String.valueOf(storedValue);
+ }
+ final StringBuilder unformatted = new StringBuilder(String.valueOf(storedValue * Math.pow(10, -1 * getDigits())));
+ for (int i = 0; i < digits; i++) {
+ unformatted.append("0");
+ }
+ final int position = unformatted.indexOf(".");
+ final String temp = unformatted.substring(0, position + 1 + digits);
+ return temp.replace('.', decimalFormatSeparator);
+
+ }
+
+ /**
+ * Sets the maximum number of characters that the receiver's text field is
+ * capable of holding to be the argument.
+ *
+ * To reset this value to the default, use
+ * setTextLimit(Spinner.LIMIT). Specifying a limit value larger
+ * than Spinner.LIMIT sets the receiver's limit to
+ * Spinner.LIMIT.
+ *
+ *
+ * @param limit new text limit
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_CANNOT_BE_ZERO - if the limit is zero
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ *
+ * @see #LIMIT
+ */
+ public void setTextLimit(final int limit) {
+ checkWidget();
+ text.setTextLimit(limit);
+ }
+
+ /**
+ * Sets the receiver's selection, minimum value, maximum value, digits,
+ * increment and page increment all at once.
+ *
+ * Note: This is similar to setting the values individually using the
+ * appropriate methods, but may be implemented in a more efficient fashion
+ * on some platforms.
+ *
+ *
+ * @param selection the new selection value
+ * @param minimum the new minimum value
+ * @param maximum the new maximum value
+ * @param digits the new digits value
+ * @param increment the new increment value
+ * @param pageIncrement the new pageIncrement value
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ public void setValues(final int selection, final int minimum, final int maximum, final int digits, final int increment, final int pageIncrement) {
+ setMinimum(minimum);
+ setMaximum(maximum);
+ setDigits(digits);
+ setIncrement(increment);
+ setPageIncrement(pageIncrement);
+ setSelection(selection);
+ }
+
+ /**
+ * Sets the receiver's drag detect state. If the argument is
+ * true, the receiver will detect drag gestures, otherwise
+ * these gestures will be ignored.
+ *
+ * @param dragDetect the new drag detect state
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ @Override
+ public boolean setFocus() {
+ checkWidget();
+ return text.setFocus();
+ }
+
+ /**
+ * Forces the receiver to have the keyboard focus, causing all
+ * keyboard events to be delivered to it.
+ *
+ * @return true if the control got focus, and
+ * false if it was unable to.
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ *
+ * @see #setFocus
+ */
+ @Override
+ public boolean forceFocus() {
+ checkWidget();
+ return text.forceFocus();
+ }
+
+ /**
+ * Sets the receiver's background color to the color specified by the
+ * argument, or to the default system color for the control if the argument
+ * is null.
+ *
+ * Note: This operation is a hint and may be overridden by the platform. For
+ * example, on Windows the background of a Button cannot be changed.
+ *
+ *
+ * @param color the new color (or null)
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_INVALID_ARGUMENT - if the argument has been
+ * disposed
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ @Override
+ public void setBackground(final Color color) {
+ super.setBackground(color);
+ leftButton.setBackground(color);
+ rightButton.setBackground(color);
+ text.setBackground(color);
+ }
+
+ /**
+ * Sets the receiver's background image to the image specified by the
+ * argument, or to the default system color for the control if the argument
+ * is null. The background image is tiled to fill the available space.
+ *
+ * Note: This operation is a hint and may be overridden by the platform. For
+ * example, on Windows the background of a Button cannot be changed.
+ *
+ *
+ * @param image the new image (or null)
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_INVALID_ARGUMENT - if the argument has been
+ * disposed
+ *
ERROR_INVALID_ARGUMENT - if the argument is not a
+ * bitmap
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ @Override
+ public void setBackgroundImage(final Image image) {
+ super.setBackgroundImage(image);
+ leftButton.setBackgroundImage(image);
+ rightButton.setBackgroundImage(image);
+ text.setBackgroundImage(image);
+
+ }
+
+ /**
+ * Sets the receiver's cursor to the cursor specified by the argument, or to
+ * the default cursor for that kind of control if the argument is null.
+ *
+ * When the mouse pointer passes over a control its appearance is changed to
+ * match the control's cursor.
+ *
+ *
+ * @param cursor the new cursor (or null)
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_INVALID_ARGUMENT - if the argument has been
+ * disposed
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ @Override
+ public void setCursor(final Cursor cursor) {
+ super.setCursor(cursor);
+ leftButton.setCursor(cursor);
+ rightButton.setCursor(cursor);
+ text.setCursor(cursor);
+ }
+
+ /**
+ * Enables the receiver if the argument is true, and disables
+ * it otherwise. A disabled control is typically not selectable from the
+ * user interface and draws with an inactive or "grayed" look.
+ *
+ * @param enabled the new enabled state
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ @Override
+ public void setEnabled(final boolean enabled) {
+ super.setEnabled(enabled);
+ leftButton.setEnabled(enabled);
+ rightButton.setEnabled(enabled);
+ text.setEnabled(enabled);
+ }
+
+ /**
+ * Sets the font that the receiver will use to paint textual information to
+ * the font specified by the argument, or to the default font for that kind
+ * of control if the argument is null.
+ *
+ * @param font the new font (or null)
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_INVALID_ARGUMENT - if the argument has been
+ * disposed
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ @Override
+ public void setFont(final Font font) {
+ super.setFont(font);
+ text.setFont(font);
+ }
+
+ /**
+ * Sets the receiver's foreground color to the color specified by the
+ * argument, or to the default system color for the control if the argument
+ * is null.
+ *
+ * Note: This operation is a hint and may be overridden by the platform.
+ *
+ *
+ * @param color the new color (or null)
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_INVALID_ARGUMENT - if the argument has been
+ * disposed
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ @Override
+ public void setForeground(final Color color) {
+ super.setForeground(color);
+ leftButton.setForeground(color);
+ rightButton.setForeground(color);
+ text.setForeground(color);
+ }
+
+ /**
+ * Sets the receiver's pop up menu to the argument. All controls may
+ * optionally have a pop up menu that is displayed when the user requests
+ * one for the control. The sequence of key strokes, button presses and/or
+ * button releases that are used to request a pop up menu is platform
+ * specific.
+ *
+ * Note: Disposing of a control that has a pop up menu will dispose of the
+ * menu. To avoid this behavior, set the menu to null before the control is
+ * disposed.
+ *
+ *
+ * @param menu the new pop up menu
+ *
+ * @exception IllegalArgumentException
+ *
+ *
ERROR_MENU_NOT_POP_UP - the menu is not a pop up menu
+ *
ERROR_INVALID_PARENT - if the menu is not in the same
+ * widget tree
+ *
ERROR_INVALID_ARGUMENT - if the menu has been disposed
+ *
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ @Override
+ public void setMenu(final Menu menu) {
+ super.setMenu(menu);
+ leftButton.setMenu(menu);
+ rightButton.setMenu(menu);
+ text.setMenu(menu);
+ }
+
+ /**
+ * Sets the receiver's tool tip text to the argument, which may be null
+ * indicating that the default tool tip for the control will be shown. For a
+ * control that has a default tool tip, such as the Tree control on Windows,
+ * setting the tool tip text to an empty string replaces the default,
+ * causing no tool tip text to be shown.
+ *
+ * The mnemonic indicator (character '&') is not displayed in a tool
+ * tip. To display a single '&' in the tool tip, the character '&'
+ * can be escaped by doubling it in the string.
+ *
+ *
+ * @param string the new tool tip text (or null)
+ *
+ * @exception SWTException
+ *
+ *
ERROR_WIDGET_DISPOSED - if the receiver has been
+ * disposed
+ *
ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the receiver
+ *
+ */
+ @Override
+ public void setToolTipText(final String tooltipText) {
+ super.setToolTipText(tooltipText);
+ leftButton.setToolTipText(tooltipText);
+ rightButton.setToolTipText(tooltipText);
+ text.setToolTipText(tooltipText);
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/WaveformSlider.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/WaveformSlider.java
new file mode 100644
index 0000000..20c909a
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/WaveformSlider.java
@@ -0,0 +1,95 @@
+package com.minres.scviewer.database.ui.swt.internal;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Slider;
+import org.eclipse.wb.swt.SWTResourceManager;
+
+public class WaveformSlider extends Composite {
+
+ Slider slider;
+
+ Color buttonColor;
+
+ public WaveformSlider(Composite parent, int style) {
+ super(parent, style);
+ GridLayout gridLayout = new GridLayout(3, false);
+ gridLayout.horizontalSpacing = 0;
+ gridLayout.verticalSpacing = 0;
+ gridLayout.marginWidth = 0;
+ gridLayout.marginHeight = 0;
+ setLayout(gridLayout);
+
+ buttonColor = getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
+
+ Button scrlLeft = new Button(this, /*SWT.BORDER |*/ SWT.FLAT | SWT.CENTER);
+ scrlLeft.setFont(SWTResourceManager.getFont("Sans", 5, SWT.NORMAL));
+ GridData gd_scrlLeft = new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1);
+ gd_scrlLeft.heightHint = 16;
+ gd_scrlLeft.widthHint = 16;
+ scrlLeft.setLayoutData(gd_scrlLeft);
+ scrlLeft.addPaintListener(paintEvent -> {
+ GC gc = paintEvent.gc;
+ gc.setBackground(buttonColor);
+ gc.setForeground(buttonColor);
+ int left = paintEvent.x+4;
+ int top = paintEvent.y+5;
+ int width=paintEvent.width-11;
+ int height= paintEvent.height-10;
+ int[] triangle = new int[] {
+ left, top+height/2,
+ left+width, top,
+ left+width, top+height};
+ gc.fillPolygon( triangle );
+ gc.drawPolygon( triangle );
+ });
+ scrlLeft.addSelectionListener(new SelectionAdapter() {
+ public void widgetSelected (SelectionEvent e){
+ slider.setSelection(slider.getSelection()-10);
+ }
+ });
+ scrlLeft.redraw();
+
+ slider = new Slider(this, SWT.NONE);
+ slider.setBackground(SWTResourceManager.getColor(SWT.COLOR_WIDGET_BACKGROUND));
+ GridData gd_canvas = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1);
+ gd_canvas.heightHint = 16;
+ slider.setLayoutData(gd_canvas);
+
+ Button scrlRight = new Button(this, /*SWT.BORDER |*/ SWT.FLAT | SWT.CENTER);
+ scrlRight.setAlignment(SWT.CENTER);
+ GridData gd_scrlRight = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
+ gd_scrlRight.heightHint = 16;
+ gd_scrlRight.widthHint = 16;
+ scrlRight.setLayoutData(gd_scrlRight);
+ scrlRight.addPaintListener(paintEvent -> {
+ GC gc = paintEvent.gc;
+ gc.setBackground(buttonColor);
+ gc.setForeground(buttonColor);
+ int left = paintEvent.x+6;
+ int top = paintEvent.y+5;
+ int width=paintEvent.width-11;
+ int height= paintEvent.height-10;
+ int[] triangle = new int[] {
+ left, top,
+ left+width, top+height/2,
+ left, top+height};
+ gc.fillPolygon( triangle );
+ gc.drawPolygon( triangle );
+ });
+ scrlRight.addSelectionListener(new SelectionAdapter() {
+ public void widgetSelected (SelectionEvent e){
+ slider.setSelection(slider.getSelection()+10);
+ }
+ });
+ redraw();
+ }
+}
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/WaveformView.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/WaveformView.java
index 5742af5..bbd9525 100644
--- a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/WaveformView.java
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/WaveformView.java
@@ -54,6 +54,7 @@ import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
@@ -61,6 +62,7 @@ import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
@@ -69,6 +71,7 @@ import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Slider;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.wb.swt.SWTResourceManager;
@@ -91,6 +94,7 @@ import com.minres.scviewer.database.ui.IWaveformStyleProvider;
import com.minres.scviewer.database.ui.IWaveformView;
import com.minres.scviewer.database.ui.IWaveformZoom;
import com.minres.scviewer.database.ui.TrackEntry;
+import com.minres.scviewer.database.ui.swt.sb.FlatScrollBar;
public class WaveformView implements IWaveformView {
@@ -330,8 +334,22 @@ public class WaveformView implements IWaveformView {
rightSash.setBackground(SWTResourceManager.getColor(SWT.COLOR_GRAY));
Composite valuePane = new Composite(rightSash, SWT.NONE);
- waveformCanvas = new WaveformCanvas(rightSash, SWT.NONE, styleProvider);
-
+
+ Composite waveformPane = new Composite(rightSash, SWT.NONE);
+ GridLayout gl_waveformPane = new GridLayout(1, false);
+ gl_waveformPane.verticalSpacing = 0;
+ gl_waveformPane.marginWidth = 0;
+ gl_waveformPane.marginHeight = 0;
+ waveformPane.setLayout(gl_waveformPane);
+
+ waveformCanvas = new WaveformCanvas(waveformPane, SWT.NONE | SWT.V_SCROLL /*| SWT.H_SCROLL*/, styleProvider);
+ waveformCanvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+
+ Composite timeSliderPane = new WaveformSlider(waveformPane, SWT.NONE);
+ GridData gd_timeSlider = new GridData(SWT.FILL, SWT.BOTTOM, false, false, 1, 1);
+ gd_timeSlider.heightHint = 18;
+ timeSliderPane.setLayoutData(gd_timeSlider);
+
// create the name pane
createTextPane(namePane, "Name");
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/ZoomingScrollbar.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/ZoomingScrollbar.java
new file mode 100644
index 0000000..96445f1
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/ZoomingScrollbar.java
@@ -0,0 +1,108 @@
+package com.minres.scviewer.database.ui.swt.internal;
+
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.wb.swt.ResourceManager;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.widgets.Slider;
+import org.eclipse.wb.swt.SWTResourceManager;
+import org.eclipse.swt.widgets.Scale;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.layout.FillLayout;
+
+public class ZoomingScrollbar extends Composite {
+
+ /**
+ * Create the composite.
+ * @param parent
+ * @param style
+ */
+ public ZoomingScrollbar(Composite parent, int style) {
+ super(parent, SWT.BORDER | SWT.NO_FOCUS);
+ GridLayout gridLayout = new GridLayout(3, false);
+ gridLayout.horizontalSpacing = 0;
+ gridLayout.verticalSpacing = 0;
+ gridLayout.marginWidth = 0;
+ gridLayout.marginHeight = 0;
+ setLayout(gridLayout);
+
+ Button scrlLeft = new Button(this, SWT.BORDER | SWT.FLAT | SWT.CENTER);
+ scrlLeft.setFont(SWTResourceManager.getFont("Sans", 5, SWT.NORMAL));
+ GridData gd_scrlLeft = new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1);
+ gd_scrlLeft.heightHint = 16;
+ gd_scrlLeft.widthHint = 16;
+ scrlLeft.setLayoutData(gd_scrlLeft);
+
+ Slider slider = new Slider(this, SWT.NONE);
+ slider.setBackground(SWTResourceManager.getColor(SWT.COLOR_WIDGET_BACKGROUND));
+ GridData gd_canvas = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1);
+ gd_canvas.heightHint = 16;
+ slider.setLayoutData(gd_canvas);
+
+ Button scrlRight = new Button(this, SWT.BORDER | SWT.FLAT | SWT.CENTER);
+ scrlRight.setAlignment(SWT.CENTER);
+ GridData gd_scrlRight = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
+ gd_scrlRight.heightHint = 16;
+ gd_scrlRight.widthHint = 16;
+ scrlRight.setLayoutData(gd_scrlRight);
+
+ SashForm sashForm = new SashForm(this, SWT.NONE);
+ sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
+
+ Composite composite = new Composite(sashForm, SWT.NONE);
+ composite.setBackground(SWTResourceManager.getColor(SWT.COLOR_MAGENTA));
+ composite.setLayout(null);
+
+ Composite wavformPane = new Composite(sashForm, SWT.BORDER | SWT.NO_FOCUS);
+ wavformPane.setBackground(SWTResourceManager.getColor(SWT.COLOR_BLUE));
+ GridLayout gl_wavformPane = new GridLayout(1, false);
+ gl_wavformPane.verticalSpacing = 0;
+ gl_wavformPane.marginWidth = 0;
+ gl_wavformPane.marginHeight = 0;
+ wavformPane.setLayout(gl_wavformPane);
+
+ Composite waveformCanvas = new Composite(wavformPane, SWT.NONE);
+ waveformCanvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+
+ Composite timeSlider = new Composite(wavformPane, SWT.BORDER | SWT.NO_FOCUS);
+ GridLayout gl_timeSlider = new GridLayout(3, false);
+ gl_timeSlider.marginWidth = 0;
+ gl_timeSlider.verticalSpacing = 0;
+ gl_timeSlider.marginHeight = 0;
+ gl_timeSlider.horizontalSpacing = 0;
+ timeSlider.setLayout(gl_timeSlider);
+ GridData gd_timeSlider = new GridData(SWT.FILL, SWT.BOTTOM, false, false, 1, 1);
+ gd_timeSlider.heightHint = 16;
+ timeSlider.setLayoutData(gd_timeSlider);
+
+ Button buttonLeft = new Button(timeSlider, SWT.BORDER | SWT.FLAT | SWT.CENTER);
+ buttonLeft.setFont(SWTResourceManager.getFont("Sans", 5, SWT.NORMAL));
+ GridData gd_buttonLeft = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1);
+ gd_buttonLeft.widthHint = 10;
+ gd_buttonLeft.heightHint = 16;
+ buttonLeft.setLayoutData(gd_buttonLeft);
+
+ Slider slider2 = new Slider(timeSlider, SWT.NONE);
+ slider2.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true, 1, 1));
+
+ Button buttonRight = new Button(timeSlider, SWT.FLAT | SWT.CENTER);
+ buttonRight.setFont(SWTResourceManager.getFont("Sans", 5, SWT.NORMAL));
+ GridData gd_buttonRight = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1);
+ gd_buttonRight.widthHint = 10;
+ gd_buttonRight.heightHint = 16;
+ buttonRight.setLayoutData(gd_buttonRight);
+ sashForm.setWeights(new int[] {1, 1});
+
+ }
+
+ @Override
+ protected void checkSubclass() {
+ // Disable the check that prevents subclassing of SWT components
+ }
+}
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ActionScheduler.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ActionScheduler.java
new file mode 100644
index 0000000..8207914
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ActionScheduler.java
@@ -0,0 +1,18 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.widgets.Display;
+
+public class ActionScheduler {
+
+ private final Display display;
+ private final Runnable action;
+
+ public ActionScheduler( Display display, Runnable action ) {
+ this.display = display;
+ this.action = action;
+ }
+
+ public void schedule( int delay ) {
+ display.timerExec( delay, action );
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ButtonClick.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ButtonClick.java
new file mode 100644
index 0000000..fe217a5
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ButtonClick.java
@@ -0,0 +1,45 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+
+public class ButtonClick {
+
+ public static final int LEFT_BUTTON = 1;
+
+ private boolean armed;
+
+ public boolean isArmed() {
+ return armed;
+ }
+
+ public void arm( MouseEvent event ) {
+ if( event.button == LEFT_BUTTON ) {
+ armed = true;
+ }
+ }
+
+ public void disarm() {
+ armed = false;
+ }
+
+ public void trigger( MouseEvent event, Runnable action ) {
+ try {
+ doTrigger( event, action );
+ } finally {
+ disarm();
+ }
+ }
+
+ private void doTrigger( MouseEvent event, Runnable action ) {
+ if( armed && inRange( event ) ) {
+ action.run();
+ }
+ }
+
+ private static boolean inRange( MouseEvent event ) {
+ Point size = ( ( Control )event.widget ).getSize();
+ return event.x >= 0 && event.x <= size.x && event.y >= 0 && event.y <= size.y;
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ClickControl.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ClickControl.java
new file mode 100644
index 0000000..959d20e
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ClickControl.java
@@ -0,0 +1,98 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+
+class ClickControl extends ControlAdapter implements ViewComponent, MouseDownActionTimer.TimerAction, MouseListener, MouseTrackListener {
+
+ private final MouseDownActionTimer mouseDownActionTimer;
+ private final ClickAction clickAction;
+ private final ButtonClick buttonClick;
+ private final Label control;
+ private final ImageUpdate imageUpdate;
+
+ public interface ClickAction extends Runnable {
+ void setCoordinates( int x, int y );
+ }
+
+ ClickControl( Composite parent, ClickAction clickAction, int maxExtension ) {
+ this.control = new Label( parent, SWT.NONE );
+ this.imageUpdate = new ImageUpdate( control, maxExtension );
+ this.buttonClick = new ButtonClick();
+ this.mouseDownActionTimer = new MouseDownActionTimer( this, buttonClick, control.getDisplay() );
+ this.clickAction = clickAction;
+ this.control.addMouseTrackListener( this );
+ this.control.addMouseListener( this );
+ this.control.addControlListener( this );
+ }
+
+ @Override
+ public void controlResized( ControlEvent event ) {
+ imageUpdate.update();
+ }
+
+ @Override
+ public Label getControl() {
+ return control;
+ }
+
+ @Override
+ public void mouseDown( MouseEvent event ) {
+ buttonClick.arm( event );
+ clickAction.setCoordinates( event.x, event.y );
+ mouseDownActionTimer.activate();
+ }
+
+ @Override
+ public void mouseUp( MouseEvent event ) {
+ buttonClick.trigger( event, clickAction );
+ }
+
+ @Override
+ public void run() {
+ clickAction.run();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ public void mouseExit( MouseEvent event ) {
+ buttonClick.disarm();
+ }
+
+ void setForeground( Color color ) {
+ imageUpdate.setForeground( color );
+ }
+
+ Color getForeground() {
+ return imageUpdate.getForeground();
+ }
+
+ void setBackground( Color color ) {
+ imageUpdate.setBackground( color );
+ }
+
+ Color getBackground() {
+ return imageUpdate.getBackground();
+ }
+
+ @Override
+ public void mouseEnter( MouseEvent event ) {}
+
+ @Override
+ public void mouseHover( MouseEvent event ) {}
+
+ @Override
+ public void mouseDoubleClick( MouseEvent event ) {}
+}
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ComponentDistribution.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ComponentDistribution.java
new file mode 100644
index 0000000..0111ee1
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ComponentDistribution.java
@@ -0,0 +1,105 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import static java.lang.Math.max;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+class ComponentDistribution {
+
+ private static final int MIN_DRAG_LENGTH = 17;
+
+ final int upFastLength;
+ final int dragStart;
+ final int dragLength;
+ final int downFastStart;
+ final int downFastLength;
+ final int downStart;
+ final int buttonLen;
+
+ ComponentDistribution( int buttonLen, int len, int range, int pos, int thumb ) {
+ int slideLen = slideLen( buttonLen, len );
+ int relDragLen = relDragLen( slideLen, range, thumb );
+ int minDragLength = max( MIN_DRAG_LENGTH, buttonLen );
+ int interval = interval( range, relDragLen, minDragLength );
+ this.dragLength = dragLen( minDragLength, relDragLen );
+ this.upFastLength = upFastLen( minDragLength, interval, pos, slideLen, relDragLen, dragLength );
+ this.downStart = downStart( buttonLen, len );
+ this.downFastStart = downFastStart( buttonLen, upFastLength, dragLength );
+ this.dragStart = dragStart( buttonLen, upFastLength );
+ this.downFastLength = downFastLen( minDragLength, interval, pos, slideLen, relDragLen, dragLength, upFastLength );
+ this.buttonLen = buttonLen;
+ }
+
+ private static int slideLen( int buttonLen, int len ) {
+ return len - buttonLen * 2;
+ }
+
+ private static int relDragLen( int slideLen, int range, int thumb ) {
+ return divide( slideLen * thumb, range );
+ }
+
+ private static int interval( int range, int relDragLen, int minDragLength ) {
+ int result = range;
+ if( useMinDragLen( minDragLength, relDragLen ) ) {
+ result += minDragLength - relDragLen / 2;
+ }
+ return result;
+ }
+
+ private static int dragLen( int buttonLen, int relDragLen ) {
+ return max( relDragLen, buttonLen );
+ }
+
+ private static int upFastLen( int buttonLen, int range, int pos, int slideLen, int relDragLen, int dragLen ) {
+ int result = slideLen * pos / range;
+ if( useMinDragLen( buttonLen, relDragLen ) ) {
+ result -= divide( ( dragLen - relDragLen ) * pos, range );
+ }
+ return result;
+ }
+
+ private static int downStart( int buttonLen, int len ) {
+ return len - buttonLen;
+ }
+
+ private static int downFastStart( int buttonLen, int upFastLength, int dragLength ) {
+ return buttonLen + upFastLength + dragLength;
+ }
+
+ private static int dragStart( int buttonLen, int upFastLen ) {
+ return buttonLen + upFastLen;
+ }
+
+ private static int downFastLen(
+ int buttonLen, int range, int pos, int slideLen, int relDragLen, int dragLen, int upFastLen )
+ {
+ int result = divide( slideLen * ( range - pos ), range ) - dragLen;
+ if( useMinDragLen( buttonLen, relDragLen ) ) {
+ result += divide( ( dragLen - relDragLen ) * pos, range );
+ }
+ return adjustDownFastLen( result, slideLen, dragLen, upFastLen );
+ }
+
+ private static boolean useMinDragLen( int buttonLen, int relDragLen ) {
+ return relDragLen < buttonLen;
+ }
+
+ static int divide( int dividend, int divisor ) {
+ BigDecimal bigDividend = new BigDecimal( dividend );
+ BigDecimal bigDivisor = new BigDecimal( divisor );
+ return bigDividend .divide( bigDivisor, 0, RoundingMode.HALF_EVEN ) .intValue();
+ }
+
+ private static int adjustDownFastLen( int tentative, int slideLen, int dragLen, int upFastLen ) {
+ // TODO [fappel]: Without this there is a flickering of the downFast label of one pixel.
+ // Check whether this can be resolved by better rounding or whatsoever.
+ int result = tentative;
+ if( slideLen < upFastLen + dragLen + result ) {
+ result--;
+ } else if( slideLen > upFastLen + dragLen + result ) {
+ result++;
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/Decrementer.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/Decrementer.java
new file mode 100644
index 0000000..5627882
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/Decrementer.java
@@ -0,0 +1,24 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.SWT;
+
+import com.minres.scviewer.database.ui.swt.sb.ClickControl.ClickAction;
+
+class Decrementer implements ClickAction {
+
+ private final FlatScrollBar scrollBar;
+
+ Decrementer( FlatScrollBar scrollBar ) {
+ this.scrollBar = scrollBar;
+ }
+
+ @Override
+ public void run() {
+ int selection = scrollBar.getSelection() - scrollBar.getIncrement();
+ scrollBar.setSelectionInternal( selection, SWT.ARROW_UP );
+ }
+
+ @Override
+ public void setCoordinates( int x, int y ) {
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/Direction.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/Direction.java
new file mode 100644
index 0000000..f2678ae
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/Direction.java
@@ -0,0 +1,203 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import static com.minres.scviewer.database.ui.swt.sb.FlatScrollBar.BAR_BREADTH;
+import static java.lang.Math.max;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+enum Direction {
+
+ HORIZONTAL( SWT.HORIZONTAL ) {
+
+ @Override
+ protected void layout( FlatScrollBar scrollBar, int buttonLength ) {
+ ComponentDistribution distribution = calculateComponentDistribution( scrollBar, buttonLength );
+ Rectangle[] componentBounds = calculateComponentBounds( distribution, scrollBar );
+ applyComponentBounds( scrollBar, componentBounds );
+ }
+
+ private ComponentDistribution calculateComponentDistribution( FlatScrollBar scrollBar, int buttonLength ) {
+ return calculateComponentDistribution( scrollBar, buttonLength, getControlBounds( scrollBar ).width );
+ }
+
+ private Rectangle[] calculateComponentBounds( ComponentDistribution distribution, FlatScrollBar scrollBar ) {
+ int width = getControlBounds( scrollBar ).width;
+ int height = getControlBounds( scrollBar ).height - FlatScrollBar.BAR_BREADTH + 1;
+ int balance = getRoundingBalance( distribution, scrollBar );
+ return new Rectangle[] {
+ calcButtons( distribution, width, $( 0, CLEARANCE, distribution.buttonLen, height ) ),
+ $( distribution.buttonLen, CLEARANCE, distribution.upFastLength, height ),
+ calcDrag( distribution, $( distribution.dragStart, CLEARANCE, distribution.dragLength + balance, height ) ),
+ $( distribution.downFastStart, CLEARANCE, distribution.downFastLength - balance, height ),
+ calcButtons( distribution, width, $( distribution.downStart, CLEARANCE, distribution.buttonLen, height ) )
+ };
+ }
+
+ private Rectangle calcButtons( ComponentDistribution distribution, int length, Rectangle bounds ) {
+ Rectangle result = bounds;
+ if( length <= distribution.buttonLen* 2 ) {
+ int downStart = calcDownStartForSmallLength( bounds.x, length );
+ result = $( downStart, CLEARANCE, length / 2, bounds.height );
+ }
+ return result;
+ }
+
+ @Override
+ protected void setDefaultSize( Control control ) {
+ Point size = control.getSize();
+ control.setSize( size.x, FlatScrollBar.BAR_BREADTH );
+ }
+
+ @Override
+ protected Point computeSize( Composite composite, int wHint, int hHint, boolean changed ) {
+ int x = wHint == SWT.DEFAULT ? composite.getParent().getClientArea().width : wHint;
+ return new Point( x, FlatScrollBar.BAR_BREADTH );
+ }
+
+ @Override
+ protected void expand( Control control, int maxExpansion ) {
+ Rectangle bounds = control.getBounds();
+ int expand = expand( bounds.height, maxExpansion );
+ control.setBounds( bounds.x, bounds.y - expand, bounds.width, bounds.height + expand );
+ }
+ },
+
+ VERTICAL( SWT.VERTICAL ) {
+
+ @Override
+ protected void layout( FlatScrollBar scrollBar, int buttonLength ) {
+ ComponentDistribution calculation = calculateComponentDistribution( scrollBar, buttonLength );
+ applyComponentBounds( scrollBar, calculateComponentBounds( calculation, scrollBar ) );
+ }
+
+ private ComponentDistribution calculateComponentDistribution( FlatScrollBar scrollBar, int buttonLength ) {
+ return calculateComponentDistribution( scrollBar, buttonLength, getControlBounds( scrollBar ).height );
+ }
+
+ private Rectangle[] calculateComponentBounds( ComponentDistribution distribution, FlatScrollBar scrollBar ) {
+ int width = getControlBounds( scrollBar ).width - FlatScrollBar.BAR_BREADTH + 1;
+ int height = getControlBounds( scrollBar ).height;
+ int balance = getRoundingBalance( distribution, scrollBar );
+ return new Rectangle[] {
+ calculateButtons( distribution, height, $( CLEARANCE, 0, width, distribution.buttonLen ) ),
+ $( CLEARANCE, distribution.buttonLen, width, distribution.upFastLength ),
+ calcDrag( distribution, $( CLEARANCE, distribution.dragStart, width, distribution.dragLength + balance ) ),
+ $( CLEARANCE, distribution.downFastStart, width, distribution.downFastLength - balance ),
+ calculateButtons( distribution, height, $( CLEARANCE, distribution.downStart, width, distribution.buttonLen ) )
+ };
+ }
+
+ private Rectangle calculateButtons( ComponentDistribution distribution, int length, Rectangle bounds ) {
+ Rectangle result = bounds;
+ if( length <= distribution.buttonLen * 2 ) {
+ int downStart = calcDownStartForSmallLength( bounds.y, length );
+ result = $( CLEARANCE, downStart, bounds.width, length / 2 );
+ }
+ return result;
+ }
+
+ @Override
+ protected void setDefaultSize( Control control ) {
+ Point size = control.getSize();
+ control.setSize( FlatScrollBar.BAR_BREADTH, size.y );
+ }
+
+ @Override
+ protected Point computeSize( Composite composite, int wHint, int hHint, boolean changed ) {
+ int y = hHint == SWT.DEFAULT ? composite.getParent().getClientArea().height : hHint;
+ return new Point( FlatScrollBar.BAR_BREADTH, y );
+ }
+
+ @Override
+ protected void expand( Control control, int maxExpansion ) {
+ Rectangle bounds = control.getBounds();
+ int expand = expand( bounds.width, maxExpansion );
+ control.setBounds( bounds.x - expand, bounds.y, bounds.width + expand, bounds.height );
+ }
+ };
+
+ static final Rectangle EMPTY_RECTANGLE = $( 0, 0, 0, 0 );
+ static final int CLEARANCE = BAR_BREADTH - 2;
+
+ private final int value;
+
+ protected abstract void layout( FlatScrollBar scrollBar, int buttonLength );
+ protected abstract void setDefaultSize( Control control );
+ protected abstract Point computeSize( Composite comp, int wHint, int hHint, boolean changed );
+ protected abstract void expand( Control control, int maxExpansion );
+
+ Direction( int value ) {
+ this.value = value;
+ }
+
+ public int value() {
+ return value;
+ }
+
+ private static ComponentDistribution calculateComponentDistribution(
+ FlatScrollBar scrollBar , int buttonLength , int length )
+ {
+ int range = scrollBar.getMaximum() - scrollBar.getMinimum();
+ int position = scrollBar.getSelection() - scrollBar.getMinimum();
+ int thumb = scrollBar.getThumb();
+ return new ComponentDistribution( buttonLength, length, range, position, thumb );
+ }
+
+ private static Rectangle getControlBounds( FlatScrollBar scrollBar ) {
+ return scrollBar.getClientArea();
+ }
+
+ private static void applyComponentBounds( FlatScrollBar scrollBar, Rectangle[] bounds ) {
+ scrollBar.up.getControl().setBounds( bounds[ 0 ] );
+ scrollBar.upFast.getControl().setBounds( bounds[ 1 ] );
+ scrollBar.drag.getControl().setBounds( bounds[ 2 ] );
+ scrollBar.downFast.getControl().setBounds( bounds[ 3 ] );
+ scrollBar.down.getControl().setBounds( bounds[ 4 ] );
+ }
+
+ // TODO [fappel]: There is a 1 pixel rounding problem at the seam of drag/downFast with down.
+ // Seems to work but I would prefer a better solution if possible
+ private static int getRoundingBalance( ComponentDistribution calculation, FlatScrollBar scrollBar ) {
+ int result = 0;
+ int maximumSelection = scrollBar.getMaximum() - scrollBar.getThumb();
+ if( scrollBar.getSelection() == maximumSelection && 0 != calculation.downFastLength ) {
+ result = 1;
+ }
+ return result;
+ }
+
+ private static int expand( int toExpand, int maxExpansion ) {
+ return max( 0, FlatScrollBar.BAR_BREADTH + maxExpansion - max( FlatScrollBar.BAR_BREADTH, toExpand ) );
+ }
+
+ private static Rectangle calcDrag( ComponentDistribution distribution, Rectangle bounds ) {
+ Rectangle result = bounds;
+ if( isUndercutOfDragVisibility( distribution ) ) {
+ result = EMPTY_RECTANGLE;
+ }
+ return result;
+ }
+
+ private static boolean isUndercutOfDragVisibility( ComponentDistribution distribution ) {
+ return distribution.dragLength + distribution.buttonLen >= distribution.downStart;
+ }
+
+ private static int calcDownStartForSmallLength( int position, int length ) {
+ int result = position;
+ if( isDownStartPosition( position ) ) {
+ result = length / 2;
+ }
+ return result;
+ }
+ private static boolean isDownStartPosition( int position ) {
+ return position > 0 || position < 0;
+ }
+
+ private static Rectangle $( int x, int y, int width, int height ) {
+ return new Rectangle( x, y , width , height );
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/DragControl.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/DragControl.java
new file mode 100644
index 0000000..4ca38eb
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/DragControl.java
@@ -0,0 +1,105 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.DragDetectEvent;
+import org.eclipse.swt.events.DragDetectListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+
+class DragControl
+ extends ControlAdapter
+ implements ViewComponent, DragDetectListener, MouseListener, MouseMoveListener
+{
+
+ private final DragDetector dragDetector;
+ private final ImageUpdate imageUpdate;
+ private final DragAction dragAction;
+ private final Label control;
+
+ private Point startingPosition;
+
+ public interface DragAction {
+ void start();
+ void run( int startX, int startY, int currentX, int currentY );
+ void end();
+ }
+
+ DragControl( Composite parent, DragAction dragAction, int maxExpansion ) {
+ this.control = new Label( parent, SWT.NONE );
+ this.imageUpdate = new ImageUpdate( control, maxExpansion );
+ this.dragDetector = new DragDetector( control, 0 );
+ this.dragAction = dragAction;
+ initializeControl();
+ }
+
+ @Override
+ public Label getControl() {
+ return control;
+ }
+
+ @Override
+ public void dragDetected( DragDetectEvent event ) {
+ if( startingPosition != null ) {
+ dragAction.run( startingPosition.x, startingPosition.y, event.x, event.y );
+ }
+ dragDetector.dragHandled();
+ }
+
+ @Override
+ public void mouseDown( MouseEvent event ) {
+ startingPosition = new Point( event.x, event.y );
+ dragAction.start();
+ }
+
+ @Override
+ public void mouseUp( MouseEvent event ) {
+ if( startingPosition != null ) {
+ dragAction.end();
+ }
+ startingPosition = null;
+ }
+
+ @Override
+ public void mouseMove( MouseEvent event ) {
+ dragDetector.mouseMove( event );
+ }
+
+ @Override
+ public void controlResized( ControlEvent event ) {
+ imageUpdate.update();
+ }
+
+ void setForeground( Color color ) {
+ imageUpdate.setForeground( color );
+ }
+
+ Color getForeground() {
+ return imageUpdate.getForeground();
+ }
+
+ Color getBackground() {
+ return imageUpdate.getBackground();
+ }
+
+ void setBackground( Color color ) {
+ imageUpdate.setBackground( color );
+ }
+
+ private void initializeControl( ) {
+ control.addMouseListener( this );
+ control.addMouseMoveListener( this );
+ control.addControlListener( this );
+ control.addDragDetectListener( this );
+ }
+
+ @Override
+ public void mouseDoubleClick( MouseEvent event ) {}
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/DragDetector.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/DragDetector.java
new file mode 100644
index 0000000..b3e33a6
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/DragDetector.java
@@ -0,0 +1,60 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+
+// TODO [fappel]: This is a workaround for a problem described here:
+// http://stackoverflow.com/questions/3908290/mousedown-events-are-not-delivered-until-mouseup-when-a-drag-source-is-present
+// This seems to be related to https://bugs.eclipse.org/bugs/show_bug.cgi?id=328396
+// which is resolved. As it did not work on my setup I adapted the workaround of the last
+// stackoverflow answer.
+
+public class DragDetector {
+
+ int lastMouseX;
+ int lastMouseY;
+ boolean dragEventGenerated;
+
+ private final Control control;
+ private final int sensibility;
+
+ public DragDetector( Control control, int sensibility ) {
+ this.control = control;
+ this.sensibility = sensibility;
+ this.control.setDragDetect( false );
+ }
+
+ public void mouseMove( MouseEvent e ) {
+ if( ( e.stateMask & SWT.BUTTON1 ) > 0 ) {
+ int deltaX = lastMouseX - e.x;
+ int deltaY = lastMouseY - e.y;
+ int dragDistance = deltaX * deltaX + deltaY * deltaY;
+ if( !dragEventGenerated && dragDistance > sensibility ) {
+ dragEventGenerated = true;
+ Event event = createDragEvent( e );
+ control.notifyListeners( SWT.DragDetect, event );
+ }
+ lastMouseX = e.x;
+ lastMouseY = e.y;
+ }
+ }
+
+ public void dragHandled() {
+ dragEventGenerated = false;
+ }
+
+ private Event createDragEvent( MouseEvent e ) {
+ Event event = new Event();
+ event.type = SWT.DragDetect;
+ event.display = control.getDisplay();
+ event.widget = control;
+ event.button = e.button;
+ event.stateMask = e.stateMask;
+ event.time = e.time;
+ event.x = e.x;
+ event.y = e.y;
+ return event;
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/DragShifter.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/DragShifter.java
new file mode 100644
index 0000000..289223d
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/DragShifter.java
@@ -0,0 +1,59 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import static com.minres.scviewer.database.ui.swt.sb.Direction.HORIZONTAL;
+import static com.minres.scviewer.database.ui.swt.sb.ShiftData.calculateSelectionRange;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+
+import com.minres.scviewer.database.ui.swt.sb.DragControl.DragAction;
+
+final class DragShifter implements DragAction {
+
+ private final FlatScrollBar scrollBar;
+ private final int buttonLength;
+
+ public DragShifter( FlatScrollBar scrollBar, int buttonLength ) {
+ this.scrollBar = scrollBar;
+ this.buttonLength = buttonLength;
+ }
+
+ @Override
+ public void start() {
+ scrollBar.notifyListeners( SWT.DRAG );
+ }
+
+ @Override
+ public void run( int startX, int startY, int currentX, int currentY ) {
+ ShiftData shiftData = newShiftData( startX, startY, currentX, currentY );
+ if( shiftData.canShift() ) {
+ int selectionRange = calculateSelectionRange( scrollBar );
+ int selectionDelta = shiftData.calculateSelectionDelta( selectionRange );
+ int selection = scrollBar.getSelection() + selectionDelta;
+ scrollBar.setSelectionInternal( selection, SWT.DRAG );
+ }
+ }
+
+ @Override
+ public void end() {
+ scrollBar.notifyListeners( SWT.NONE );
+ }
+
+ private ShiftData newShiftData( int startX, int startY, int currentX, int currentY ) {
+ ShiftData result;
+ if( scrollBar.direction == HORIZONTAL ) {
+ result = new ShiftData( buttonLength, getScrollBarSize().x, getDragSize().x, currentX - startX );
+ } else {
+ result = new ShiftData( buttonLength, getScrollBarSize().y, getDragSize().y, currentY - startY );
+ }
+ return result;
+ }
+
+ private Point getScrollBarSize() {
+ return scrollBar.getSize();
+ }
+
+ private Point getDragSize() {
+ return scrollBar.drag.getControl().getSize();
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FastDecrementer.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FastDecrementer.java
new file mode 100644
index 0000000..744f522
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FastDecrementer.java
@@ -0,0 +1,49 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Display;
+
+import com.minres.scviewer.database.ui.swt.sb.ClickControl.ClickAction;
+
+class FastDecrementer implements ClickAction {
+
+ private final FlatScrollBar scrollBar;
+
+ private int x;
+ private int y;
+
+ FastDecrementer( FlatScrollBar scrollBar ) {
+ this.scrollBar = scrollBar;
+ }
+
+ @Override
+ public void run() {
+ Rectangle drag = getDragBounds();
+ Point mouse = getMouseLocation();
+ if( mouse.x <= drag.x || mouse.y <= drag.y ) {
+ int selection = scrollBar.getSelection() - scrollBar.getPageIncrement();
+ scrollBar.setSelectionInternal( selection, SWT.PAGE_UP );
+ }
+ }
+
+ @Override
+ public void setCoordinates( int x, int y ) {
+ this.x = x;
+ this.y = y;
+ }
+
+ private Point getMouseLocation() {
+ return getDisplay().map( scrollBar.upFast.getControl(), null, x, y );
+ }
+
+ private Rectangle getDragBounds() {
+ Rectangle dragBounds = scrollBar.drag.getControl().getBounds();
+ return getDisplay().map( scrollBar, null, dragBounds );
+ }
+
+ private Display getDisplay() {
+ return scrollBar.getDisplay();
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FastIncrementer.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FastIncrementer.java
new file mode 100644
index 0000000..5ce4181
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FastIncrementer.java
@@ -0,0 +1,42 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Display;
+
+import com.minres.scviewer.database.ui.swt.sb.ClickControl.ClickAction;
+
+class FastIncrementer implements ClickAction {
+
+ private final FlatScrollBar scrollBar;
+
+ private Point mouse;
+
+ FastIncrementer( FlatScrollBar scrollBar ) {
+ this.scrollBar = scrollBar;
+ }
+
+ @Override
+ public void run() {
+ Rectangle drag = getDragBounds();
+ if( mouse.x > drag.x + drag.width || mouse.y > drag.y + drag.height ) {
+ int selection = scrollBar.getSelection() + scrollBar.getPageIncrement();
+ scrollBar.setSelectionInternal( selection, SWT.PAGE_DOWN );
+ }
+ }
+
+ @Override
+ public void setCoordinates( int x, int y ) {
+ mouse = getMouseLocation( x, y );
+ }
+
+ private Point getMouseLocation(int x, int y) {
+ return Display.getCurrent().map( scrollBar.downFast.getControl(), null, x, y );
+ }
+
+ private Rectangle getDragBounds() {
+ Rectangle dragBounds = scrollBar.drag.getControl().getBounds();
+ return Display.getCurrent().map( scrollBar, null, dragBounds );
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FlatScrollBar.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FlatScrollBar.java
new file mode 100644
index 0000000..802d86a
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FlatScrollBar.java
@@ -0,0 +1,295 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import static com.minres.scviewer.database.ui.swt.sb.UntypedSelectionAdapter.lookup;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Layout;
+import org.eclipse.swt.widgets.Listener;
+
+public class FlatScrollBar extends Composite {
+
+ public static final int BAR_BREADTH = 6;
+
+ static final int DEFAULT_MINIMUM = 0;
+ static final int DEFAULT_MAXIMUM = 100;
+ static final int DEFAULT_INCREMENT = 1;
+ static final int DEFAULT_THUMB = 10;
+ static final int DEFAULT_PAGE_INCREMENT = DEFAULT_THUMB;
+ static final int DEFAULT_SELECTION = 0;
+ static final int DEFAULT_BUTTON_LENGTH = 0;
+ static final int DEFAULT_MAX_EXPANSION = Direction.CLEARANCE + 2;
+
+ final ClickControl up;
+ final ClickControl upFast;
+ final DragControl drag;
+ final ClickControl downFast;
+ final ClickControl down;
+ final Direction direction;
+ final MouseWheelShifter mouseWheelHandler;
+ final Collection listeners;
+
+ private int minimum;
+ private int maximum;
+ private int increment;
+ private int pageIncrement;
+ private int thumb;
+ private int selection;
+ private boolean onDrag;
+ private int buttonLength;
+
+ public FlatScrollBar( final Composite parent, int style ) {
+ this( parent, style, DEFAULT_BUTTON_LENGTH, DEFAULT_MAX_EXPANSION );
+ }
+
+ FlatScrollBar( Composite parent, int style, int buttonLength, int maxExpansion ) {
+ super( parent, SWT.NONE );
+ super.setLayout( new FlatScrollBarLayout( getDirection( style ) ) );
+ this.minimum = DEFAULT_MINIMUM;
+ this.maximum = DEFAULT_MAXIMUM;
+ this.increment = DEFAULT_INCREMENT;
+ this.pageIncrement = DEFAULT_PAGE_INCREMENT;
+ this.thumb = DEFAULT_THUMB;
+ this.selection = DEFAULT_SELECTION;
+ this.buttonLength = buttonLength;
+ this.direction = getDirection( style );
+ this.direction.setDefaultSize( this );
+ this.up = new ClickControl( this, new Decrementer( this ), maxExpansion );
+ this.upFast = new ClickControl( this, new FastDecrementer( this ), maxExpansion );
+ this.drag = new DragControl( this, new DragShifter( this, buttonLength ), maxExpansion );
+ this.downFast = new ClickControl( this, new FastIncrementer( this ), maxExpansion );
+ this.down = new ClickControl( this, new Incrementer( this ), maxExpansion );
+ this.mouseWheelHandler = new MouseWheelShifter( this, parent, buttonLength );
+ this.listeners = new HashSet();
+ addMouseTrackListener( new MouseTracker( this, maxExpansion ) );
+ addControlListener( new ResizeObserver( this ) );
+ setDefaultColorScheme();
+ }
+
+ @Override
+ public void setLayout( Layout layout ) {
+ throw new UnsupportedOperationException( FlatScrollBar.class.getName() + " does not allow to change layout." );
+ };
+
+ @Override
+ public int getStyle() {
+ return direction != null ? super.getStyle() | direction.value() : super.getStyle();
+ };
+
+ Direction getDirection() {
+ return direction;
+ }
+
+ public void setMinimum( int minimum ) {
+ if( this.minimum != minimum && minimum >= 0 && minimum < maximum ) {
+ this.minimum = minimum;
+ adjustThumb();
+ adjustSelection();
+ layout();
+ }
+ }
+
+ public int getMinimum() {
+ return minimum;
+ }
+
+ public void setMaximum( int maximum ) {
+ if( this.maximum != maximum && maximum >= 0 && maximum > minimum ) {
+ this.maximum = maximum;
+ adjustThumb();
+ adjustSelection();
+ layout();
+ }
+ }
+
+ public int getMaximum() {
+ return maximum;
+ }
+
+ public void setThumb( int thumb ) {
+ if( this.thumb != thumb && thumb >= 1 ) {
+ this.thumb = thumb;
+ adjustThumb();
+ adjustSelection();
+ layout();
+ }
+ }
+
+ public int getThumb() {
+ return thumb;
+ }
+
+ public void setIncrement( int increment ) {
+ if( this.increment != increment ) {
+ this.increment = increment;
+ layout();
+ }
+ }
+
+ public int getIncrement() {
+ return increment;
+ }
+
+ public void setPageIncrement( int pageIncrement ) {
+ this.pageIncrement = pageIncrement;
+ }
+
+ public int getPageIncrement() {
+ return pageIncrement;
+ }
+
+ public void setSelection( int selection ) {
+ if( !onDrag ) {
+ updateSelection( selection );
+ }
+ }
+
+ public int getSelection() {
+ return selection;
+ }
+
+ public void addSelectionListener( SelectionListener selectionListener ) {
+ listeners.add( selectionListener );
+ }
+
+ public void removeSelectionListener( SelectionListener selectionListener ) {
+ listeners.remove( selectionListener );
+ }
+
+ @Override
+ public void addListener( int eventType, final Listener listener ) {
+ if( eventType == SWT.Selection ) {
+ addSelectionListener( new UntypedSelectionAdapter( listener ) );
+ } else {
+ super.addListener( eventType, listener );
+ }
+ }
+
+ @Override
+ public void removeListener( int eventType, Listener listener ) {
+ if( eventType == SWT.Selection ) {
+ removeSelectionListener( lookup( listeners, listener ) );
+ } else {
+ super.removeListener( eventType, listener );
+ }
+ }
+
+ @Override
+ public void layout() {
+ direction.layout( this, buttonLength );
+ update();
+ }
+
+ public void setIncrementButtonLength( int length ) {
+ this.buttonLength = length;
+ layout();
+ }
+
+ public int getIncrementButtonLength() {
+ return buttonLength;
+ }
+
+ public void setIncrementColor( Color color ) {
+ up.setForeground( color );
+ down.setForeground( color );
+ }
+
+ public Color getIncrementColor() {
+ return up.getForeground();
+ }
+
+ public void setPageIncrementColor( Color color ) {
+ upFast.setForeground( color );
+ downFast.setForeground( color );
+ }
+
+ public Color getPageIncrementColor() {
+ return upFast.getForeground();
+ }
+
+ public void setThumbColor( Color color ) {
+ drag.setForeground( color );
+ }
+
+ public Color getThumbColor() {
+ return drag.getForeground();
+ }
+
+ @Override
+ public void setBackground( Color color ) {
+ up.setBackground( color );
+ upFast.setBackground( color );
+ drag.setBackground( color );
+ downFast.setBackground( color );
+ down.setBackground( color );
+ super.setBackground( color );
+ }
+
+ protected void setSelectionInternal( int selection, int detail ) {
+ int oldSelection = this.selection;
+ updateSelection( selection );
+ if( oldSelection != this.selection ) {
+ notifyListeners( detail );
+ }
+ }
+
+ private void updateSelection( int selection ) {
+ if( this.selection != selection ) {
+ this.selection = selection;
+ adjustSelection();
+ layout();
+ }
+ }
+
+ public void notifyListeners( int detail ) {
+ updateOnDrag( detail );
+ SelectionEvent selectionEvent = createEvent( detail );
+ for( SelectionListener listener : listeners ) {
+ listener.widgetSelected( selectionEvent );
+ }
+ }
+
+ private void updateOnDrag( int detail ) {
+ onDrag = ( onDrag || ( SWT.DRAG & detail ) > 0 ) && ( SWT.NONE != detail );
+ }
+
+ private SelectionEvent createEvent( int detail ) {
+ Event event = new Event();
+ event.widget = this;
+ event.detail = detail;
+ return new SelectionEvent( event );
+ }
+
+ private void adjustThumb() {
+ if( thumb > maximum - minimum ) {
+ thumb = Math.min( maximum - minimum, thumb );
+ thumb = Math.max( 1, thumb );
+ }
+ }
+
+ private void adjustSelection() {
+ selection = Math.min( selection, maximum - thumb );
+ selection = Math.max( selection, minimum );
+ }
+
+ private static Direction getDirection( int style ) {
+ return ( style & SWT.HORIZONTAL ) > 0 ? Direction.HORIZONTAL : Direction.VERTICAL;
+ }
+
+ private void setDefaultColorScheme() {
+ up.setForeground( Display.getCurrent().getSystemColor( SWT.COLOR_WIDGET_NORMAL_SHADOW ) );
+ upFast.setForeground( Display.getCurrent().getSystemColor( SWT.COLOR_WIDGET_BACKGROUND ) );
+ drag.setForeground( Display.getCurrent().getSystemColor( SWT.COLOR_WIDGET_FOREGROUND ) );
+ downFast.setForeground( Display.getCurrent().getSystemColor( SWT.COLOR_WIDGET_BACKGROUND ) );
+ down.setForeground( Display.getCurrent().getSystemColor( SWT.COLOR_WIDGET_NORMAL_SHADOW ) );
+ setBackground( getBackground() );
+ }
+}
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FlatScrollBarLayout.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FlatScrollBarLayout.java
new file mode 100644
index 0000000..b8106bb
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/FlatScrollBarLayout.java
@@ -0,0 +1,23 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Layout;
+
+class FlatScrollBarLayout extends Layout {
+
+ private final Direction direction;
+
+ public FlatScrollBarLayout( Direction orientation ) {
+ this.direction = orientation;
+ }
+
+ @Override
+ protected void layout( Composite composite, boolean flushCache ) {
+ }
+
+ @Override
+ protected Point computeSize( Composite composite, int wHint, int hHint, boolean flushCache ) {
+ return direction.computeSize( composite, wHint, hHint, flushCache );
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ImageDrawer.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ImageDrawer.java
new file mode 100644
index 0000000..8fb7495
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ImageDrawer.java
@@ -0,0 +1,109 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import static java.lang.Math.min;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Display;
+
+class ImageDrawer {
+
+ static final String IMAGE_DRAWER_IS_DISPOSED = "ImageDrawer is disposed.";
+
+ private final int maxExpansion;
+
+ private Color background;
+ private Color foreground;
+
+ ImageDrawer( int expansion ) {
+ this( expansion, getSystemColor( SWT.COLOR_WIDGET_DARK_SHADOW ), getSystemColor( SWT.COLOR_LIST_BACKGROUND ) );
+ }
+
+ ImageDrawer( int expansion, Color background, Color foreground ) {
+ this.maxExpansion = expansion;
+ this.foreground = defensiveCopy( background );
+ this.background = defensiveCopy( foreground );
+ }
+
+ void setForeground( Color foreground ) {
+ checkDisposed();
+ if( foreground != null ) {
+ this.foreground = prepareColorAttribute( this.foreground, foreground );
+ }
+ }
+
+ Color getForeground() {
+ checkDisposed();
+ return foreground;
+ }
+
+ void setBackground( Color background ) {
+ checkDisposed();
+ if( background != null ) {
+ this.background = prepareColorAttribute( this.background, background );
+ }
+ }
+
+ Color getBackground() {
+ checkDisposed();
+ return background;
+ }
+
+ Image draw( int width, int height ) {
+ checkDisposed();
+ Image result = new Image( getDisplay(), width, height );
+ GC gc = new GC( result );
+ try {
+ draw( gc, width, height );
+ } finally {
+ gc.dispose();
+ }
+ return result;
+ }
+
+ boolean isDisposed() {
+ return background.isDisposed();
+ }
+
+ void dispose() {
+ if( !isDisposed() ) {
+ this.background.dispose();
+ this.foreground.dispose();
+ }
+ }
+
+ private void draw( GC gc, int width, int height ) {
+ gc.setBackground( background );
+ gc.fillRectangle( 0, 0, width, height );
+ gc.setBackground( foreground );
+ gc.setAdvanced( true );
+ gc.setAntialias( SWT.ON );
+ int arc = min( width, height ) == 1 ? 1 : maxExpansion + 2;
+ gc.fillRoundRectangle( 0, 0, width, height, arc, arc );
+ }
+
+ private void checkDisposed() {
+ if( isDisposed() ) {
+ throw new IllegalStateException( IMAGE_DRAWER_IS_DISPOSED );
+ }
+ }
+
+ private static Color getSystemColor( int colorCode ) {
+ return getDisplay().getSystemColor( colorCode );
+ }
+
+ private static Color prepareColorAttribute( Color oldColor, Color newColor ) {
+ oldColor.dispose();
+ return defensiveCopy( newColor );
+ }
+
+ private static Color defensiveCopy( Color background ) {
+ return new Color( getDisplay(), background.getRGB() );
+ }
+
+ private static Display getDisplay() {
+ return Display.getCurrent();
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ImageUpdate.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ImageUpdate.java
new file mode 100644
index 0000000..73719a9
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ImageUpdate.java
@@ -0,0 +1,46 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Label;
+
+class ImageUpdate {
+
+ private final ImageDrawer imageDrawer;
+ private final Label control;
+
+ ImageUpdate( Label control, int maxExpansion ) {
+ this.imageDrawer = new ImageDrawer( maxExpansion );
+ this.control = control;
+ this.control.addListener( SWT.Dispose, evt -> imageDrawer.dispose() );
+ }
+
+ void setForeground( Color color ) {
+ imageDrawer.setForeground( color );
+ }
+
+ Color getForeground() {
+ return imageDrawer.getForeground();
+ }
+
+ void setBackground( Color color ) {
+ imageDrawer.setBackground( color );
+ }
+
+ Color getBackground() {
+ return imageDrawer.getBackground();
+ }
+
+ void update() {
+ if( !control.isDisposed() ) {
+ if( control.getImage() != null ) {
+ control.getImage().dispose();
+ }
+ Point size = control.getSize();
+ if( size.x > 0 && size.y > 0 ) {
+ control.setImage( imageDrawer.draw( size.x, size.y ) );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/Incrementer.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/Incrementer.java
new file mode 100644
index 0000000..351e058
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/Incrementer.java
@@ -0,0 +1,24 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.SWT;
+
+import com.minres.scviewer.database.ui.swt.sb.ClickControl.ClickAction;
+
+class Incrementer implements ClickAction {
+
+ private final FlatScrollBar scrollBar;
+
+ Incrementer( FlatScrollBar scrollBar ) {
+ this.scrollBar = scrollBar;
+ }
+
+ @Override
+ public void run() {
+ int selection = scrollBar.getSelection() + scrollBar.getIncrement();
+ scrollBar.setSelectionInternal( selection, SWT.ARROW_DOWN );
+ }
+
+ @Override
+ public void setCoordinates( int x, int y ) {
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/MouseDownActionTimer.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/MouseDownActionTimer.java
new file mode 100644
index 0000000..1de6295
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/MouseDownActionTimer.java
@@ -0,0 +1,37 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.widgets.Display;
+
+public class MouseDownActionTimer implements Runnable {
+
+ public static final int INITIAL_DELAY = 300;
+ public static final int FAST_DELAY = 50;
+
+ private final ActionScheduler scheduler;
+ private final TimerAction timerAction;
+ private final ButtonClick mouseClick;
+
+ public interface TimerAction extends Runnable {
+ boolean isEnabled();
+ }
+
+ public MouseDownActionTimer( TimerAction timerAction, ButtonClick mouseClick, Display display ) {
+ this.scheduler = new ActionScheduler( display, this );
+ this.timerAction = timerAction;
+ this.mouseClick = mouseClick;
+ }
+
+ public void activate() {
+ if( timerAction.isEnabled() ) {
+ scheduler.schedule( INITIAL_DELAY );
+ }
+ }
+
+ @Override
+ public void run() {
+ if( mouseClick.isArmed() && timerAction.isEnabled() ) {
+ timerAction.run();
+ scheduler.schedule( FAST_DELAY );
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/MouseTracker.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/MouseTracker.java
new file mode 100644
index 0000000..f36e825
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/MouseTracker.java
@@ -0,0 +1,66 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseTrackAdapter;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Display;
+
+class MouseTracker extends MouseTrackAdapter implements Runnable, DisposeListener {
+
+ static final int DELAY = 500;
+
+ private final FlatScrollBar scrollBar;
+ private final int maxExpansion;
+
+ private Rectangle expandedBounds;
+ private Rectangle originBounds;
+ private boolean mouseOver;
+ private boolean disposed;
+
+ MouseTracker( FlatScrollBar scrollBar, int maxExpansion ) {
+ this.scrollBar = scrollBar;
+ this.maxExpansion = maxExpansion;
+ this.scrollBar.addDisposeListener( this );
+ this.scrollBar.up.getControl().addMouseTrackListener( this );
+ this.scrollBar.upFast.getControl().addMouseTrackListener( this );
+ this.scrollBar.drag.getControl().addMouseTrackListener( this );
+ this.scrollBar.downFast.getControl().addMouseTrackListener( this );
+ this.scrollBar.down.getControl().addMouseTrackListener( this );
+ }
+
+ @Override
+ public void mouseEnter( MouseEvent event ) {
+ mouseOver = true;
+ if( !disposed && originBounds == null ) {
+ originBounds = scrollBar.getBounds();
+ scrollBar.getDirection().expand( scrollBar, maxExpansion );
+ expandedBounds = scrollBar.getBounds();
+ }
+ }
+
+ @Override
+ public void mouseExit( MouseEvent event ) {
+ mouseOver = false;
+ if( !disposed ) {
+ Display.getCurrent().timerExec( DELAY, this );
+ }
+ }
+
+ @Override
+ public void run() {
+ if( !disposed && !mouseOver ) {
+ if( scrollBar.getBounds().equals( expandedBounds ) ) {
+ scrollBar.setBounds( originBounds );
+ }
+ originBounds = null;
+ expandedBounds = null;
+ }
+ }
+
+ @Override
+ public void widgetDisposed( DisposeEvent e ) {
+ disposed = true;
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/MouseWheelShifter.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/MouseWheelShifter.java
new file mode 100644
index 0000000..4baa1be
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/MouseWheelShifter.java
@@ -0,0 +1,69 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import static com.minres.scviewer.database.ui.swt.sb.Direction.HORIZONTAL;
+import static com.minres.scviewer.database.ui.swt.sb.ShiftData.calculateSelectionRange;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+public class MouseWheelShifter implements Listener, DisposeListener {
+
+ private final FlatScrollBar scrollBar;
+ private final Composite parent;
+ private final int buttonLength;
+
+ MouseWheelShifter( FlatScrollBar scrollBar, Composite parent, int buttonLength ) {
+ this.scrollBar = scrollBar;
+ this.parent = parent;
+ this.buttonLength = buttonLength;
+ parent.addListener( getListenerType(), this );
+ scrollBar.addDisposeListener( this );
+ }
+
+ @Override
+ public void handleEvent( Event event ) {
+ handleMouseWheelScroll( event );
+ }
+
+ @Override
+ public void widgetDisposed( DisposeEvent e ) {
+ parent.removeListener( getListenerType(), this );
+ }
+
+ private void handleMouseWheelScroll( Event event ) {
+ ShiftData shiftData = newShiftData( event.count );
+ if( shiftData.canShift() ) {
+ int selectionRange = calculateSelectionRange( scrollBar );
+ int selectionDelta = shiftData.calculateSelectionDelta( selectionRange );
+ int selection = scrollBar.getSelection() - selectionDelta;
+ scrollBar.setSelectionInternal( selection, scrollBar.direction.value() );
+ }
+ }
+
+ private ShiftData newShiftData( int delta ) {
+ ShiftData result;
+ if( scrollBar.direction == Direction.HORIZONTAL ) {
+ result = new ShiftData( buttonLength, getScrollBarSize().x, getDragSize().x, delta );
+ } else {
+ result = new ShiftData( buttonLength, getScrollBarSize().y, getDragSize().y, delta );
+ }
+ return result;
+ }
+
+ private Point getScrollBarSize() {
+ return scrollBar.getSize();
+ }
+
+ private Point getDragSize() {
+ return scrollBar.drag.getControl().getSize();
+ }
+
+ private int getListenerType() {
+ return scrollBar.direction == HORIZONTAL ? SWT.MouseHorizontalWheel: SWT.MouseVerticalWheel;
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ResizeObserver.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ResizeObserver.java
new file mode 100644
index 0000000..20cb2b3
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ResizeObserver.java
@@ -0,0 +1,19 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+
+class ResizeObserver extends ControlAdapter {
+
+ private final FlatScrollBar flatScrollBar;
+
+ public ResizeObserver( FlatScrollBar flatScrollBar ) {
+ this.flatScrollBar = flatScrollBar;
+ }
+
+ @Override
+ public void controlResized( ControlEvent event ) {
+ flatScrollBar.layout();
+ flatScrollBar.moveAbove( null );
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ShiftData.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ShiftData.java
new file mode 100644
index 0000000..0bdeccd
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ShiftData.java
@@ -0,0 +1,32 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import static com.minres.scviewer.database.ui.swt.sb.ComponentDistribution.divide;
+
+class ShiftData {
+
+ private final int slidePixels;
+ private final int movedPixels;
+ private final int buttonLength;
+
+ ShiftData( int buttonLength, int scrollBarPixels, int dragPixels, int movedPixels ) {
+ this.buttonLength = buttonLength;
+ this.slidePixels = calculateSlidePixels( scrollBarPixels, dragPixels );
+ this.movedPixels = movedPixels;
+ }
+
+ boolean canShift( ) {
+ return slidePixels > 0;
+ }
+
+ int calculateSelectionDelta( int selectionRange ) {
+ return divide( movedPixels * selectionRange, slidePixels );
+ }
+
+ static int calculateSelectionRange( FlatScrollBar scrollBar ) {
+ return scrollBar.getMaximum() - scrollBar.getMinimum() - scrollBar.getThumb();
+ }
+
+ private int calculateSlidePixels( int scrollBarPixels, int dragPixels ) {
+ return scrollBarPixels - 2 * buttonLength - dragPixels;
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/UntypedSelectionAdapter.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/UntypedSelectionAdapter.java
new file mode 100644
index 0000000..d5fffd8
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/UntypedSelectionAdapter.java
@@ -0,0 +1,43 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import java.util.Collection;
+
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+class UntypedSelectionAdapter extends SelectionAdapter {
+
+ final Listener listener;
+
+ UntypedSelectionAdapter( Listener listener ) {
+ this.listener = listener;
+ }
+
+ @Override
+ public void widgetSelected( SelectionEvent selectionEvent ) {
+ Event event = new Event();
+ event.widget = selectionEvent.widget;
+ event.detail = selectionEvent.detail;
+ listener.handleEvent( event );
+ }
+
+ static SelectionListener lookup( Collection listeners, Listener untypedListener ) {
+ for( SelectionListener listener : listeners ) {
+ if( isAdapterType( listener ) && matches( untypedListener, listener ) ) {
+ return listener;
+ }
+ }
+ return null;
+ }
+
+ private static boolean isAdapterType( SelectionListener listener ) {
+ return listener instanceof UntypedSelectionAdapter;
+ }
+
+ private static boolean matches( Listener untypedListener, SelectionListener listener ) {
+ return ( ( UntypedSelectionAdapter )listener ).listener == untypedListener;
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ViewComponent.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ViewComponent.java
new file mode 100644
index 0000000..af1779c
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/sb/ViewComponent.java
@@ -0,0 +1,7 @@
+package com.minres.scviewer.database.ui.swt.sb;
+
+import org.eclipse.swt.widgets.Control;
+
+public interface ViewComponent {
+ Control getControl();
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/org/eclipse/wb/swt/ResourceManager.java b/plugins/com.minres.scviewer.database.ui.swt/src/org/eclipse/wb/swt/ResourceManager.java
new file mode 100644
index 0000000..8e96dfe
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/org/eclipse/wb/swt/ResourceManager.java
@@ -0,0 +1,438 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc. 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:
+ * Google, Inc. - initial API and implementation
+ * Wim Jongman - 1.8 and higher compliance
+ *******************************************************************************/
+package org.eclipse.wb.swt;
+
+import java.io.File;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.resource.CompositeImageDescriptor;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.ImageDataProvider;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.osgi.framework.Bundle;
+
+/**
+ * Utility class for managing OS resources associated with SWT/JFace controls
+ * such as colors, fonts, images, etc.
+ *
+ * This class is created automatically when you fiddle around with images and
+ * colors in WB. You might want to prevent your application from using this
+ * class and provide your own more effective means of resource caching.
+ *
+ * Even though this class can be used to manage these resources, if they are
+ * here for the duration of the application and not used then you still have an
+ * effective resource leak.
+ *
+ * Application code must explicitly invoke the dispose() method to
+ * release the operating system resources managed by cached objects when those
+ * objects and OS resources are no longer needed.
+ *
+ * This class may be freely distributed as part of any application or plugin.
+ *