diff --git a/README.md b/README.md
index 589cc20..e330fb0 100644
--- a/README.md
+++ b/README.md
@@ -30,25 +30,43 @@ Legend:
* Mouse Scroll wheel: MScrl
* Context any means Name List, Value List or Waveform
-| Input | Modifier | Context | Action |
-|-----------|----------|----------|-----------------------------------|
-| LMB klick | | any | select |
-| LMB klick | Shift | Waveform | move selected marker to position |
-| LMB klick | Control | Waveform | move cursor to position |
-| LMB drag | | Waveform | zoom to range |
-| MMB klick | | Waveform | move selected marker to position |
-| MScrl | | any | scroll window up/down |
-| MScrl | Shift | any | scroll window left/right |
-| Key left | | Waveform | scroll window to the left (slow) |
-| Key right | | Waveform | scroll window to the right (slow) |
-| Key left | Shift | Waveform | scroll window to the left (fast) |
-| Key right | Shift | Waveform | scroll window to the right (fast) |
-| Key up | | Waveform | move selection up |
-| Key down | | Waveform | move selection down |
-| Key up | Control | Waveform | move selected track up |
-| Key down | Control | Waveform | move selected track down |
-| Key + | Control | Waveform | zoom in |
-| Key - | Control | Waveform | zoom out |
-| Key Pos1 | | Waveform | jump to selected marker |
-| Key End | | Waveform | jump to cursor |
-| Key Del | | any | delete selected entries |
+| Input | Modifier | Context | Action |
+|------------|----------|----------|-----------------------------------|
+| LMB click | | any | select |
+| LMB click | Shift | Waveform | move selected marker to position |
+| LMB click | Control | Waveform | move cursor to position |
+| LMB drag | | Waveform | zoom to range |
+| MMB click | | Waveform | move selected marker to position |
+| MScrl | | any | scroll window up/down |
+| MScrl | Shift | any | scroll window left/right |
+| MScrl | Control | Waveform | zoom in/out |
+| Key left | | Waveform | scroll window to the left (slow) |
+| Key right | | Waveform | scroll window to the right (slow) |
+| Key left | Shift | Waveform | scroll window to the left (fast) |
+| Key right | Shift | Waveform | scroll window to the right (fast) |
+| Key up | | Waveform | move selection up |
+| Key down | | Waveform | move selection down |
+| Key up | Control | Waveform | move selected track up |
+| Key down | Control | Waveform | move selected track down |
+| Key + | Control | Waveform | zoom in |
+| Key - | Control | Waveform | zoom out |
+| Key Pos1 | | Waveform | jump to selected marker |
+| Key End | | Waveform | jump to cursor |
+| Key Del | | any | delete selected entries |
+| LMB click | | ZoomBar | increment/decrement 1 page |
+| LMB drag | | ZoomBar | drag both markers (pan) |
+| LMB drag | Control | ZoomBar | drag one marker (zoom) |
+| MMB drag | | ZoomBar | drag one marker (zoom) |
+| xMB dclick | | ZoomBar | pan to position |
+| MScrl | | ZoomBar | scroll window left/right |
+| MScrl | Shift | ZoomBar | scroll window left/right double speed |
+| MScrl | Control | ZoomBar | zoom in/out |
+| Key left | | ZoomBar | scroll window to the left (slow) |
+| Key right | | ZoomBar | scroll window to the right (slow) |
+| Key up | | ZoomBar | scroll window to the left (slow) |
+| Key down | | ZoomBar | scroll window to the right (slow) |
+| Key PgUp | | ZoomBar | scroll window to the left (fast) |
+| Key PgDown | | ZoomBar | scroll window to the right (fast) |
+| Key Pos1 | | ZoomBar | scroll to begin |
+| Key End | | ZoomBar | scroll to end |
+
diff --git a/doc/com.minres.scviewer.doc/.gitignore b/doc/com.minres.scviewer.doc/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/doc/com.minres.scviewer.doc/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/doc/com.minres.scviewer.doc/.project b/doc/com.minres.scviewer.doc/.project
new file mode 100644
index 0000000..f250ef1
--- /dev/null
+++ b/doc/com.minres.scviewer.doc/.project
@@ -0,0 +1,11 @@
+
+
+ *
+ * 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.
+ *
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
+ * SelectionListener
interface.
+ *
+ * widgetSelected
is not called for texts.
+ * widgetDefaultSelected
is typically called when ENTER is
+ * pressed in a single-line text.
+ *
+ * The current selection is copied to the clipboard. + *
+ * + * @exception SWTException + *+ * The current selection is first copied to the clipboard and then deleted + * from the widget. + *
+ * + * @exception SWTException + *setTextLimit()
, it will be the constant
+ * Spinner.LIMIT
.
+ *
+ * @return the text limit
+ *
+ * @exception SWTException
+ * + * The selected text is deleted from the widget and new text inserted from + * the clipboard. + *
+ * + * @exception SWTException + *+ * 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 + *
+ * 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
.
+ *
+ * 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 + *true
, the receiver will detect drag gestures, otherwise
+ * these gestures will be ignored.
+ *
+ * @param dragDetect the new drag detect state
+ *
+ * @exception SWTException
+ * true
if the control got focus, and
+ * false
if it was unable to.
+ *
+ * @exception SWTException
+ * + * 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 + *+ * 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 + *+ * 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 + *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
+ * + * Note: This operation is a hint and may be overridden by the platform. + *
+ * + * @param color the new color (or null) + * + * @exception IllegalArgumentException + *+ * 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 + *+ * 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 + *SelectionListener
interface.
+ *
+ * widgetSelected
is called when the user changes the receiver's
+ * value. widgetDefaultSelected
is not called.
+ *
SelectionListener
to a given Control
+ *
+ * @param control control on which the selection listener is added
+ * @param listener listener to add
+ */
+ public static void addSelectionListener(final Control control, final SelectionListener listener) {
+ if (listener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ TypedListener typedListener = new TypedListener(listener);
+ control.addListener(SWT.Selection, typedListener);
+ }
+
+ /**
+ * Remove a SelectionListener
of a given Control
+ *
+ * @param control control on which the selection listener is removed
+ * @param listener listener to remove
+ */
+ public static void removeSelectionListener(final Control control, final SelectionListener listener) {
+ if (listener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ final Listener[] listeners = control.getListeners(SWT.Selection);
+ for (Listener l : listeners) {
+ if (l instanceof TypedListener) {
+ TypedListener typedListener = (TypedListener) l;
+ if (typedListener.getEventListener() == listener) {
+ ReflectionUtils.callMethod(control, "removeListener", SWT.Selection, ((TypedListener) l).getEventListener());
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Fire the selection listeners of a given control
+ *
+ * @param control the control that fires the event
+ * @param sourceEvent mouse event
+ * @return true if the selection could be changed, false otherwise
+ */
+ public static boolean fireSelectionListeners(final Control control, final Event sourceEvent) {
+ for (final Listener listener : control.getListeners(SWT.Selection)) {
+ final Event event = new Event();
+
+ event.button = sourceEvent==null?1:sourceEvent.button;
+ event.display = control.getDisplay();
+ event.item = null;
+ event.widget = control;
+ event.data = sourceEvent == null ? null : sourceEvent.data;
+ event.time = sourceEvent == null ? 0 : sourceEvent.time;
+ event.x = sourceEvent == null ? 0 : sourceEvent.x;
+ event.y = sourceEvent == null ? 0 : sourceEvent.y;
+ event.type = SWT.Selection;
+
+ listener.handleEvent(event);
+ if (!event.doit) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/ZoomBar.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/ZoomBar.java
new file mode 100644
index 0000000..593a5a2
--- /dev/null
+++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/ZoomBar.java
@@ -0,0 +1,127 @@
+package com.minres.scviewer.database.ui.swt.internal.slider;
+
+import java.text.Format;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.wb.swt.SWTResourceManager;
+
+public class ZoomBar extends Composite {
+
+ static public interface IProvider {
+ ZoomBar getScrollBar();
+ }
+
+ final RangeSlider timeSlider;
+ final ImageButton leftButton;
+ final ImageButton rightButton;
+ /**
+ * Create the composite.
+ * @param parent
+ * @param style
+ */
+ public ZoomBar(Composite parent, int style) {
+ super(parent, SWT.NO_FOCUS);
+ GridLayout gridLayout = new GridLayout(3, false);
+ gridLayout.horizontalSpacing = 0;
+ gridLayout.verticalSpacing = 0;
+ gridLayout.marginWidth = 0;
+ gridLayout.marginHeight = 0;
+ setLayout(gridLayout);
+
+ leftButton = new ImageButton(this, SWT.NONE);
+ GridData gd_leftButton = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
+ gd_leftButton.widthHint=14;
+ gd_leftButton.heightHint=18;
+ leftButton.setLayoutData(gd_leftButton);
+ leftButton.setImage(new Image[] {
+ SWTResourceManager.getImage(this.getClass(), "arrow_left.png"),
+ SWTResourceManager.getImage(this.getClass(), "arrow_left_hover.png"),
+ SWTResourceManager.getImage(this.getClass(), "arrow_left_pressed.png")});
+ leftButton.setAutoFire(true);
+
+ timeSlider = new RangeSlider(this, SWT.ON|SWT.HIGH|SWT.SMOOTH|SWT.CONTROL);
+ timeSlider.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+
+ rightButton = new ImageButton(this, SWT.NONE);
+ GridData gd_rightButton = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
+ gd_rightButton.widthHint=18;
+ gd_rightButton.heightHint=18;
+ rightButton.setLayoutData(gd_rightButton);
+ rightButton.setImage(new Image[] {
+ SWTResourceManager.getImage(this.getClass(), "arrow_right.png"),
+ SWTResourceManager.getImage(this.getClass(), "arrow_right_hover.png"),
+ SWTResourceManager.getImage(this.getClass(), "arrow_right_pressed.png")});
+ rightButton.setAutoFire(true);
+
+ leftButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ int[] value = timeSlider.getSelection();
+ int incr=timeSlider.getIncrement();
+ int lower = Math.max(timeSlider.getMinimum(), value[0]-incr);
+ timeSlider.setValues(lower, lower + (value[1]-value[0]), true);
+ }
+ });
+ rightButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ int[] value = timeSlider.getSelection();
+ int incr=timeSlider.getIncrement();
+ int upper = Math.min(timeSlider.getMaximum(), value[1]+incr);
+ timeSlider.setValues(upper - (value[1]-value[0]), upper, true);
+ }
+ });
+
+ }
+ @Override
+ public void setEnabled (boolean enabled) {
+ timeSlider.setEnabled(enabled);
+ leftButton.setEnabled(enabled);
+ rightButton.setEnabled(enabled);
+ super.setEnabled(enabled);
+ redraw();
+ }
+ public void setButtonsEnabled (boolean enabled) {
+ leftButton.setEnabled(enabled);
+ rightButton.setEnabled(enabled);
+ redraw();
+ }
+ public void setToolTipFormatter(Format formatter){
+ timeSlider.setToolTipFormatter(formatter);
+ }
+ public void setToolTipText(String string) {
+ timeSlider.setToolTipText(string);
+ }
+ public void setSelection(int sel) {
+ timeSlider.setLowerValue(sel);
+ }
+ public void setSelection(int[] sel) {
+ assert(sel.length==2);
+ timeSlider.setValues(sel[0], sel[1]);
+ }
+ public int[] getSelection() {
+ return timeSlider.getSelection();
+ }
+ public void addSelectionListener(SelectionListener selectionListener) {
+ timeSlider.addSelectionListener(selectionListener);
+ }
+ public void setMinimum(int value) {
+ timeSlider.setMinimum(value);
+ }
+ public void setMaximum(int value) {
+ timeSlider.setMaximum(value);
+ }
+ public int getMaximum() {
+ return timeSlider.getMaximum();
+ }
+ public int getMinimum() {
+ return timeSlider.getMinimum();
+ }
+}
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_left.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_left.png
new file mode 100644
index 0000000..61e7214
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_left.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_left_hover.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_left_hover.png
new file mode 100644
index 0000000..61156c3
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_left_hover.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_left_pressed.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_left_pressed.png
new file mode 100644
index 0000000..61e7214
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_left_pressed.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_right.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_right.png
new file mode 100644
index 0000000..c0c0b9a
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_right.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_right_hover.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_right_hover.png
new file mode 100644
index 0000000..890472f
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_right_hover.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_right_pressed.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_right_pressed.png
new file mode 100644
index 0000000..b37d47d
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/arrow_right_pressed.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_l.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_l.png
new file mode 100644
index 0000000..ce0e740
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_l.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_l_hover.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_l_hover.png
new file mode 100644
index 0000000..452b8b3
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_l_hover.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_l_pressed.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_l_pressed.png
new file mode 100644
index 0000000..3c41142
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_l_pressed.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_r.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_r.png
new file mode 100644
index 0000000..49f7b14
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_r.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_r_hover.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_r_hover.png
new file mode 100644
index 0000000..8b3696e
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_r_hover.png differ
diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_r_pressed.png b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_r_pressed.png
new file mode 100644
index 0000000..e602b56
Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/slider/marker_r_pressed.png differ
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
- * !!! IMPORTANT !!! 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
+ * !!! IMPORTANT !!! 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 (e.g. on application shutdown)
*
* This class may be freely distributed as part of any application or plugin. *
- *
* @author scheglov_ke
* @author Dan Rubel
*/
@@ -49,54 +46,57 @@ public class SWTResourceManager {
// Color
//
////////////////////////////////////////////////////////////////////////////
- private static Map'/'
+ * @param clazz
+ * the {@link Class} relative to which to find the image
+ * @param path
+ * the path to the image file, if starts with '/'
* @return the {@link Image} stored in the file at the specified path
*/
public static Image getImage(Class> clazz, String path) {
String key = clazz.getName() + '|' + path;
- Image image = imageMap.get(key);
+ Image image = m_imageMap.get(key);
if (image == null) {
try {
image = getImage(clazz.getResourceAsStream(path));
- imageMap.put(key, image);
+ m_imageMap.put(key, image);
} catch (Exception e) {
image = getMissingImage();
- imageMap.put(key, image);
+ m_imageMap.put(key, image);
}
}
return image;
}
-
private static final int MISSING_IMAGE_SIZE = 10;
-
/**
- * @return the small {@link Image} that can be used as placeholder for missing
- * image.
+ * @return the small {@link Image} that can be used as placeholder for missing image.
*/
private static Image getMissingImage() {
Image image = new Image(Display.getCurrent(), MISSING_IMAGE_SIZE, MISSING_IMAGE_SIZE);
@@ -185,7 +182,6 @@ public class SWTResourceManager {
//
return image;
}
-
/**
* Style constant for placing decorator image in top left corner of base image.
*/
@@ -195,13 +191,11 @@ public class SWTResourceManager {
*/
public static final int TOP_RIGHT = 2;
/**
- * Style constant for placing decorator image in bottom left corner of base
- * image.
+ * Style constant for placing decorator image in bottom left corner of base image.
*/
public static final int BOTTOM_LEFT = 3;
/**
- * Style constant for placing decorator image in bottom right corner of base
- * image.
+ * Style constant for placing decorator image in bottom right corner of base image.
*/
public static final int BOTTOM_RIGHT = 4;
/**
@@ -212,77 +206,83 @@ public class SWTResourceManager {
* Maps images to decorated images.
*/
@SuppressWarnings("unchecked")
- private static Map