diff --git a/plugins/com.minres.scviewer.database.ui.swt/icons/bullet_left.png b/plugins/com.minres.scviewer.database.ui.swt/icons/bullet_left.png new file mode 100644 index 0000000..96b4466 Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/icons/bullet_left.png differ diff --git a/plugins/com.minres.scviewer.database.ui.swt/icons/bullet_right.png b/plugins/com.minres.scviewer.database.ui.swt/icons/bullet_right.png new file mode 100644 index 0000000..e809d37 Binary files /dev/null and b/plugins/com.minres.scviewer.database.ui.swt/icons/bullet_right.png differ diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/HorizontalSpinner.java b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/HorizontalSpinner.java new file mode 100644 index 0000000..521bb8d --- /dev/null +++ b/plugins/com.minres.scviewer.database.ui.swt/src/com/minres/scviewer/database/ui/swt/internal/HorizontalSpinner.java @@ -0,0 +1,1113 @@ +package com.minres.scviewer.database.ui.swt.internal; + +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +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.Menu; +import org.eclipse.swt.widgets.Text; + +/** + * Instances of this class are selectable user interface objects that allow the + * user to enter and modify numeric values. + *

+ *

+ *
Styles:
+ *
READ_ONLY, FLAT
+ *
Events:
+ *
Selection, Modify
+ *
+ *

+ */ +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 + * + * @exception SWTException + * + * + * @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 + * + * @exception SWTException + * + * + * @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 + * + * @exception SWTException + * + * + * @see SelectionListener + * @see #removeSelectionListener + * @see SelectionEvent + */ + public void addSelectionListener(final SelectionListener listener) { + checkWidget(); + } + + /** + * Copies the selected text. + *

+ * The current selection is copied to the clipboard. + *

+ * + * @exception SWTException + * + */ + public void copy() { + checkWidget(); + text.copy(); + } + + /** + * Cuts the selected text. + *

+ * The current selection is first copied to the clipboard and then deleted + * from the widget. + *

+ * + * @exception SWTException + * + */ + public void cut() { + checkWidget(); + text.cut(); + } + + /** + * Returns the number of decimal places used by the receiver. + * + * @return the digits + * + * @exception SWTException + * + */ + 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 + * + */ + public int getIncrement() { + checkWidget(); + return increment; + } + + /** + * Returns the maximum value which the receiver will allow. + * + * @return the maximum + * + * @exception SWTException + * + */ + public int getMaximum() { + checkWidget(); + return maximum; + } + + /** + * Returns the minimum value which the receiver will allow. + * + * @return the minimum + * + * @exception SWTException + * + */ + 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 + * + */ + public int getPageIncrement() { + checkWidget(); + return pageIncrement; + } + + /** + * Returns the selection, which is the receiver's position. + * + * @return the selection + * + * @exception SWTException + * + */ + 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 + * + * + */ + 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 + * + * + * @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 + * + */ + 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 + * + * @exception SWTException + * + * + * @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 + * + * @exception SWTException + * + * + * @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 + * + * @exception SWTException + * + */ + 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 + * + */ + 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 + * + */ + 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 + * + */ + 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 + * + */ + 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 + * + */ + 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 + * + * @exception SWTException + * + * + * @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 + * + */ + 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 + * + */ + @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 + * + * + * @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 + * + * @exception SWTException + * + */ + @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 + * + * @exception SWTException + * + */ + @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 + * + * @exception SWTException + * + */ + @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 + * + */ + @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 + * + * @exception SWTException + * + */ + @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 + * + * @exception SWTException + * + */ + @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 + * + * @exception SWTException + * + */ + @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 + * + */ + @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. + *

+ * + * @author scheglov_ke + * @author Dan Rubel + * @author Wim Jongman + */ +public class ResourceManager extends SWTResourceManager { + + /** + * The map where we store our images. + */ + private static Map m_descriptorImageMap = new HashMap(); + + /** + * Returns an {@link ImageDescriptor} stored in the file at the specified path + * relative to the specified class. + * + * @param clazz the {@link Class} relative to which to find the image + * descriptor. + * @param path the path to the image file. + * @return the {@link ImageDescriptor} stored in the file at the specified path. + */ + public static ImageDescriptor getImageDescriptor(Class clazz, String path) { + return ImageDescriptor.createFromFile(clazz, path); + } + + /** + * Returns an {@link ImageDescriptor} stored in the file at the specified path. + * + * @param path the path to the image file. + * @return the {@link ImageDescriptor} stored in the file at the specified path. + */ + public static ImageDescriptor getImageDescriptor(String path) { + try { + return ImageDescriptor.createFromURL(new File(path).toURI().toURL()); + } catch (MalformedURLException e) { + return null; + } + } + + /** + * Returns an {@link Image} based on the specified {@link ImageDescriptor}. + * + * @param descriptor the {@link ImageDescriptor} for the {@link Image}. + * @return the {@link Image} based on the specified {@link ImageDescriptor}. + */ + public static Image getImage(ImageDescriptor descriptor) { + if (descriptor == null) { + return null; + } + Image image = m_descriptorImageMap.get(descriptor); + if (image == null) { + image = descriptor.createImage(); + m_descriptorImageMap.put(descriptor, image); + } + return image; + } + + /** + * Maps images to decorated images. + */ + @SuppressWarnings("unchecked") + private static Map>[] m_decoratedImageMap = new Map[LAST_CORNER_KEY]; + + /** + * Returns an {@link Image} composed of a base image decorated by another image. + * + * @param baseImage the base {@link Image} that should be decorated. + * @param decorator the {@link Image} to decorate the base image. + * @return {@link Image} The resulting decorated image. + */ + public static Image decorateImage(Image baseImage, Image decorator) { + return decorateImage(baseImage, decorator, BOTTOM_RIGHT); + } + + /** + * Returns an {@link Image} composed of a base image decorated by another image. + * + * @param baseImage + * the base {@link Image} that should be decorated. + * @param decorator + * the {@link Image} to decorate the base image. + * @param corner + * the corner to place decorator image. + * @return the resulting decorated {@link Image}. + */ + public static Image decorateImage(final Image baseImage, final Image decorator, final int corner) { + if (corner <= 0 || corner >= LAST_CORNER_KEY) { + throw new IllegalArgumentException("Wrong decorate corner"); + } + Map> cornerDecoratedImageMap = m_decoratedImageMap[corner]; + if (cornerDecoratedImageMap == null) { + cornerDecoratedImageMap = new HashMap>(); + m_decoratedImageMap[corner] = cornerDecoratedImageMap; + } + Map decoratedMap = cornerDecoratedImageMap.get(baseImage); + if (decoratedMap == null) { + decoratedMap = new HashMap(); + cornerDecoratedImageMap.put(baseImage, decoratedMap); + } + // + Image result = decoratedMap.get(decorator); + if (result == null) { + final Rectangle bib = baseImage.getBounds(); + final Rectangle dib = decorator.getBounds(); + final Point baseImageSize = new Point(bib.width, bib.height); + CompositeImageDescriptor compositImageDesc = new CompositeImageDescriptor() { + @Override + protected void drawCompositeImage(int width, int height) { + drawImage(createCachedImageDataProvider(baseImage), 0, 0); + if (corner == TOP_LEFT) { + drawImage(getUnzoomedImageDataProvider(decorator.getImageData()) , 0, 0); + } else if (corner == TOP_RIGHT) { + drawImage(getUnzoomedImageDataProvider(decorator.getImageData()), bib.width - dib.width, 0); + } else if (corner == BOTTOM_LEFT) { + drawImage(getUnzoomedImageDataProvider(decorator.getImageData()), 0, bib.height - dib.height); + } else if (corner == BOTTOM_RIGHT) { + drawImage(getUnzoomedImageDataProvider(decorator.getImageData()), bib.width - dib.width, bib.height - dib.height); + } + } + @Override + protected Point getSize() { + return baseImageSize; + } + }; + // + result = compositImageDesc.createImage(); + decoratedMap.put(decorator, result); + } + return result; + } + + private static ImageDataProvider getUnzoomedImageDataProvider(ImageData imageData) { + return zoom -> zoom == 100 ? imageData : null; + } + + + /** + * Dispose all of the cached images. + */ + public static void disposeImages() { + SWTResourceManager.disposeImages(); + // dispose ImageDescriptor images + { + for (Iterator I = m_descriptorImageMap.values().iterator(); I.hasNext();) { + I.next().dispose(); + } + m_descriptorImageMap.clear(); + } + // dispose decorated images + for (int i = 0; i < m_decoratedImageMap.length; i++) { + Map> cornerDecoratedImageMap = m_decoratedImageMap[i]; + if (cornerDecoratedImageMap != null) { + for (Map decoratedMap : cornerDecoratedImageMap.values()) { + for (Image image : decoratedMap.values()) { + image.dispose(); + } + decoratedMap.clear(); + } + cornerDecoratedImageMap.clear(); + } + } + // dispose plugin images + { + for (Iterator I = m_URLImageMap.values().iterator(); I.hasNext();) { + I.next().dispose(); + } + m_URLImageMap.clear(); + } + } + + //////////////////////////////////////////////////////////////////////////// + // + // Plugin images support + // + //////////////////////////////////////////////////////////////////////////// + /** + * Maps URL to images. + */ + private static Map m_URLImageMap = new HashMap(); + + /** + * Provider for plugin resources, used by WindowBuilder at design time. + */ + public interface PluginResourceProvider { + URL getEntry(String symbolicName, String path); + } + + /** + * Instance of {@link PluginResourceProvider}, used by WindowBuilder at design + * time. + */ + private static PluginResourceProvider m_designTimePluginResourceProvider = null; + + /** + * Returns an {@link Image} based on a plugin and file path. + * + * @param plugin the plugin {@link Object} containing the image + * @param name the path to the image within the plugin + * @return the {@link Image} stored in the file at the specified path + * + * @deprecated Use {@link #getPluginImage(String, String)} instead. + */ + @Deprecated + public static Image getPluginImage(Object plugin, String name) { + try { + URL url = getPluginImageURL(plugin, name); + if (url != null) { + return getPluginImageFromUrl(url); + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link Image} based on a {@link Bundle} and resource entry path. + * + * @param symbolicName the symbolic name of the {@link Bundle}. + * @param path the path of the resource entry. + * @return the {@link Image} stored in the file at the specified path. + */ + public static Image getPluginImage(String symbolicName, String path) { + try { + URL url = getPluginImageURL(symbolicName, path); + if (url != null) { + return getPluginImageFromUrl(url); + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link Image} based on given {@link URL}. + */ + private static Image getPluginImageFromUrl(URL url) { + try { + try { + String key = url.toExternalForm(); + Image image = m_URLImageMap.get(key); + if (image == null) { + InputStream stream = url.openStream(); + try { + image = getImage(stream); + m_URLImageMap.put(key, image); + } finally { + stream.close(); + } + } + return image; + } catch (Throwable e) { + // Ignore any exceptions + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link ImageDescriptor} based on a plugin and file path. + * + * @param plugin the plugin {@link Object} containing the image. + * @param name the path to th eimage within the plugin. + * @return the {@link ImageDescriptor} stored in the file at the specified path. + * + * @deprecated Use {@link #getPluginImageDescriptor(String, String)} instead. + */ + @Deprecated + public static ImageDescriptor getPluginImageDescriptor(Object plugin, String name) { + try { + try { + URL url = getPluginImageURL(plugin, name); + return ImageDescriptor.createFromURL(url); + } catch (Throwable e) { + // Ignore any exceptions + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link ImageDescriptor} based on a {@link Bundle} and resource + * entry path. + * + * @param symbolicName the symbolic name of the {@link Bundle}. + * @param path the path of the resource entry. + * @return the {@link ImageDescriptor} based on a {@link Bundle} and resource + * entry path. + */ + public static ImageDescriptor getPluginImageDescriptor(String symbolicName, String path) { + try { + URL url = getPluginImageURL(symbolicName, path); + if (url != null) { + return ImageDescriptor.createFromURL(url); + } + } catch (Throwable e) { + // Ignore any exceptions + } + return null; + } + + /** + * Returns an {@link URL} based on a {@link Bundle} and resource entry path. + */ + private static URL getPluginImageURL(String symbolicName, String path) { + // try runtime plugins + { + Bundle bundle = Platform.getBundle(symbolicName); + if (bundle != null) { + return bundle.getEntry(path); + } + } + // try design time provider + if (m_designTimePluginResourceProvider != null) { + return m_designTimePluginResourceProvider.getEntry(symbolicName, path); + } + // no such resource + return null; + } + + /** + * Returns an {@link URL} based on a plugin and file path. + * + * @param plugin the plugin {@link Object} containing the file path. + * @param name the file path. + * @return the {@link URL} representing the file at the specified path. + * @throws Exception + */ + private static URL getPluginImageURL(Object plugin, String name) throws Exception { + // try to work with 'plugin' as with OSGI BundleContext + try { + Class BundleClass = Class.forName("org.osgi.framework.Bundle"); //$NON-NLS-1$ + Class BundleContextClass = Class.forName("org.osgi.framework.BundleContext"); //$NON-NLS-1$ + if (BundleContextClass.isAssignableFrom(plugin.getClass())) { + Method getBundleMethod = BundleContextClass.getMethod("getBundle", new Class[0]); //$NON-NLS-1$ + Object bundle = getBundleMethod.invoke(plugin, new Object[0]); + // + Class PathClass = Class.forName("org.eclipse.core.runtime.Path"); //$NON-NLS-1$ + Constructor pathConstructor = PathClass.getConstructor(new Class[] { String.class }); + Object path = pathConstructor.newInstance(new Object[] { name }); + // + Class IPathClass = Class.forName("org.eclipse.core.runtime.IPath"); //$NON-NLS-1$ + Class PlatformClass = Class.forName("org.eclipse.core.runtime.Platform"); //$NON-NLS-1$ + Method findMethod = PlatformClass.getMethod("find", new Class[] { BundleClass, IPathClass }); //$NON-NLS-1$ + return (URL) findMethod.invoke(null, new Object[] { bundle, path }); + } + } catch (Throwable e) { + // Ignore any exceptions + } + // else work with 'plugin' as with usual Eclipse plugin + { + Class PluginClass = Class.forName("org.eclipse.core.runtime.Plugin"); //$NON-NLS-1$ + if (PluginClass.isAssignableFrom(plugin.getClass())) { + // + Class PathClass = Class.forName("org.eclipse.core.runtime.Path"); //$NON-NLS-1$ + Constructor pathConstructor = PathClass.getConstructor(new Class[] { String.class }); + Object path = pathConstructor.newInstance(new Object[] { name }); + // + Class IPathClass = Class.forName("org.eclipse.core.runtime.IPath"); //$NON-NLS-1$ + Method findMethod = PluginClass.getMethod("find", new Class[] { IPathClass }); //$NON-NLS-1$ + return (URL) findMethod.invoke(plugin, new Object[] { path }); + } + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // + // General + // + //////////////////////////////////////////////////////////////////////////// + /** + * Dispose of cached objects and their underlying OS resources. This should only + * be called when the cached objects are no longer needed (e.g. on application + * shutdown). + */ + public static void dispose() { + disposeColors(); + disposeFonts(); + disposeImages(); + } +} \ No newline at end of file diff --git a/plugins/com.minres.scviewer.database.ui.swt/src/org/eclipse/wb/swt/SWTResourceManager.java b/plugins/com.minres.scviewer.database.ui.swt/src/org/eclipse/wb/swt/SWTResourceManager.java index 0024a03..d8a2858 100644 --- a/plugins/com.minres.scviewer.database.ui.swt/src/org/eclipse/wb/swt/SWTResourceManager.java +++ b/plugins/com.minres.scviewer.database.ui.swt/src/org/eclipse/wb/swt/SWTResourceManager.java @@ -29,17 +29,14 @@ import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; /** - * Utility class for managing OS resources associated with SWT controls such as - * colors, fonts, images, etc. + * Utility class for managing OS resources associated with SWT controls such as colors, fonts, images, etc. *

- * !!! 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 colorMap = new HashMap<>(); - - private SWTResourceManager() {} - + private static Map m_colorMap = new HashMap(); /** * Returns the system {@link Color} matching the specific ID. * - * @param systemColorID the ID value for the color + * @param systemColorID + * the ID value for the color * @return the system {@link Color} matching the specific ID */ public static Color getColor(int systemColorID) { Display display = Display.getCurrent(); return display.getSystemColor(systemColorID); } - /** * Returns a {@link Color} given its red, green and blue component values. * - * @param r the red component of the color - * @param g the green component of the color - * @param b the blue component of the color - * @return the {@link Color} matching the given red, green and blue component - * values + * @param r + * the red component of the color + * @param g + * the green component of the color + * @param b + * the blue component of the color + * @return the {@link Color} matching the given red, green and blue component values */ public static Color getColor(int r, int g, int b) { return getColor(new RGB(r, g, b)); } - /** * Returns a {@link Color} given its RGB value. * - * @param rgb the {@link RGB} value of the color + * @param rgb + * the {@link RGB} value of the color * @return the {@link Color} matching the RGB value */ public static Color getColor(RGB rgb) { - return colorMap.computeIfAbsent(rgb, k -> new Color(Display.getCurrent(), rgb)); + Color color = m_colorMap.get(rgb); + if (color == null) { + Display display = Display.getCurrent(); + color = new Color(display, rgb); + m_colorMap.put(rgb, color); + } + return color; } - /** * Dispose of all the cached {@link Color}'s. */ public static void disposeColors() { - for (Color color : colorMap.values()) { + for (Color color : m_colorMap.values()) { color.dispose(); } - colorMap.clear(); + m_colorMap.clear(); } - //////////////////////////////////////////////////////////////////////////// // // Image @@ -105,12 +105,12 @@ public class SWTResourceManager { /** * Maps image paths to images. */ - private static Map imageMap = new HashMap<>(); - + private static Map m_imageMap = new HashMap(); /** * Returns an {@link Image} encoded by the specified {@link InputStream}. * - * @param stream the {@link InputStream} encoding the image data + * @param stream + * the {@link InputStream} encoding the image data * @return the {@link Image} encoded by the specified input stream */ protected static Image getImage(InputStream stream) throws IOException { @@ -125,55 +125,52 @@ public class SWTResourceManager { stream.close(); } } - /** * Returns an {@link Image} stored in the file at the specified path. * - * @param path the path to the image file + * @param path + * the path to the image file * @return the {@link Image} stored in the file at the specified path */ public static Image getImage(String path) { - Image image = imageMap.get(path); + Image image = m_imageMap.get(path); if (image == null) { try { image = getImage(new FileInputStream(path)); - imageMap.put(path, image); + m_imageMap.put(path, image); } catch (Exception e) { image = getMissingImage(); - imageMap.put(path, image); + m_imageMap.put(path, image); } } return image; } - /** - * Returns an {@link Image} stored in the file at the specified path relative to - * the specified class. + * Returns an {@link Image} stored in the file at the specified path relative to the specified class. * - * @param clazz the {@link Class} relative to which to find the image - * @param path the path to the image file, if starts with '/' + * @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>[] decoratedImageMap = new Map[LAST_CORNER_KEY]; - + private static Map>[] m_decoratedImageMap = new Map[LAST_CORNER_KEY]; /** * Returns an {@link Image} composed of a base image decorated by another image. * - * @param baseImage the base {@link Image} that should be decorated - * @param decorator the {@link Image} to decorate the base image + * @param baseImage + * the base {@link Image} that should be decorated + * @param decorator + * the {@link Image} to decorate the base image * @return {@link Image} The resulting decorated image */ public static Image decorateImage(Image baseImage, Image decorator) { return decorateImage(baseImage, decorator, BOTTOM_RIGHT); } - /** * Returns an {@link Image} composed of a base image decorated by another image. * - * @param baseImage the base {@link Image} that should be decorated - * @param decorator the {@link Image} to decorate the base image - * @param corner the corner to place decorator image + * @param baseImage + * the base {@link Image} that should be decorated + * @param decorator + * the {@link Image} to decorate the base image + * @param corner + * the corner to place decorator image * @return the resulting decorated {@link Image} */ public static Image decorateImage(final Image baseImage, final Image decorator, final int corner) { if (corner <= 0 || corner >= LAST_CORNER_KEY) { throw new IllegalArgumentException("Wrong decorate corner"); } - Map> cornerDecoratedImageMap = decoratedImageMap[corner]; + Map> cornerDecoratedImageMap = m_decoratedImageMap[corner]; if (cornerDecoratedImageMap == null) { - cornerDecoratedImageMap = new HashMap<>(); - decoratedImageMap[corner] = cornerDecoratedImageMap; + cornerDecoratedImageMap = new HashMap>(); + m_decoratedImageMap[corner] = cornerDecoratedImageMap; } - Map decoratedMap = cornerDecoratedImageMap.computeIfAbsent(baseImage, - k -> new HashMap()); - return decoratedMap.computeIfAbsent(decorator, k -> { + Map decoratedMap = cornerDecoratedImageMap.get(baseImage); + if (decoratedMap == null) { + decoratedMap = new HashMap(); + cornerDecoratedImageMap.put(baseImage, decoratedMap); + } + // + Image result = decoratedMap.get(decorator); + if (result == null) { Rectangle bib = baseImage.getBounds(); Rectangle dib = decorator.getBounds(); - Image result = new Image(Display.getCurrent(), bib.width, bib.height); + // + result = new Image(Display.getCurrent(), bib.width, bib.height); + // GC gc = new GC(result); gc.drawImage(baseImage, 0, 0); - switch (corner) { - case TOP_LEFT: + if (corner == TOP_LEFT) { gc.drawImage(decorator, 0, 0); - break; - case TOP_RIGHT: + } else if (corner == TOP_RIGHT) { gc.drawImage(decorator, bib.width - dib.width, 0); - break; - case BOTTOM_LEFT: + } else if (corner == BOTTOM_LEFT) { gc.drawImage(decorator, 0, bib.height - dib.height); - break; - case BOTTOM_RIGHT: + } else if (corner == BOTTOM_RIGHT) { gc.drawImage(decorator, bib.width - dib.width, bib.height - dib.height); - break; - default: - // do nothing } gc.dispose(); - return result; - }); + // + decoratedMap.put(decorator, result); + } + return result; } - /** * Dispose all of the cached {@link Image}'s. */ public static void disposeImages() { // dispose loaded images - for (Image image : imageMap.values()) { - image.dispose(); + { + for (Image image : m_imageMap.values()) { + image.dispose(); + } + m_imageMap.clear(); } - imageMap.clear(); // dispose decorated images - for (int i = 0; i < decoratedImageMap.length; i++) { - Map> cornerDecoratedImageMap = decoratedImageMap[i]; + for (int i = 0; i < m_decoratedImageMap.length; i++) { + Map> cornerDecoratedImageMap = m_decoratedImageMap[i]; if (cornerDecoratedImageMap != null) { for (Map decoratedMap : cornerDecoratedImageMap.values()) { for (Image image : decoratedMap.values()) { @@ -294,7 +294,6 @@ public class SWTResourceManager { } } } - //////////////////////////////////////////////////////////////////////////// // // Font @@ -303,39 +302,45 @@ public class SWTResourceManager { /** * Maps font names to fonts. */ - private static Map fontMap = new HashMap<>(); + private static Map m_fontMap = new HashMap(); /** * Maps fonts to their bold versions. */ - private static Map fontToBoldFontMap = new HashMap<>(); - + private static Map m_fontToBoldFontMap = new HashMap(); /** * Returns a {@link Font} based on its name, height and style. * - * @param name the name of the font - * @param height the height of the font - * @param style the style of the font + * @param name + * the name of the font + * @param height + * the height of the font + * @param style + * the style of the font * @return {@link Font} The font matching the name, height and style */ public static Font getFont(String name, int height, int style) { return getFont(name, height, style, false, false); } - /** - * Returns a {@link Font} based on its name, height and style. Windows-specific - * strikeout and underline flags are also supported. + * Returns a {@link Font} based on its name, height and style. Windows-specific strikeout and underline + * flags are also supported. * - * @param name the name of the font - * @param size the size of the font - * @param style the style of the font - * @param strikeout the strikeout flag (warning: Windows only) - * @param underline the underline flag (warning: Windows only) - * @return {@link Font} The font matching the name, height, style, strikeout and - * underline + * @param name + * the name of the font + * @param size + * the size of the font + * @param style + * the style of the font + * @param strikeout + * the strikeout flag (warning: Windows only) + * @param underline + * the underline flag (warning: Windows only) + * @return {@link Font} The font matching the name, height, style, strikeout and underline */ public static Font getFont(String name, int size, int style, boolean strikeout, boolean underline) { String fontName = name + '|' + size + '|' + style + '|' + strikeout + '|' + underline; - return fontMap.computeIfAbsent(fontName, k -> { + Font font = m_fontMap.get(fontName); + if (font == null) { FontData fontData = new FontData(name, size, style); if (strikeout || underline) { try { @@ -349,45 +354,47 @@ public class SWTResourceManager { logFontClass.getField("lfUnderline").set(logFont, Byte.valueOf((byte) 1)); //$NON-NLS-1$ } } - } catch (Exception e) { + } catch (Throwable e) { + System.err.println("Unable to set underline or strikeout" + " (probably on a non-Windows platform). " + e); //$NON-NLS-1$ //$NON-NLS-2$ } } - return new Font(Display.getCurrent(), fontData); - - }); - + font = new Font(Display.getCurrent(), fontData); + m_fontMap.put(fontName, font); + } + return font; } - /** * Returns a bold version of the given {@link Font}. * - * @param baseFont the {@link Font} for which a bold version is desired + * @param baseFont + * the {@link Font} for which a bold version is desired * @return the bold version of the given {@link Font} */ public static Font getBoldFont(Font baseFont) { - return fontToBoldFontMap.computeIfAbsent(baseFont, k -> { - FontData[] fontDatas = baseFont.getFontData(); + Font font = m_fontToBoldFontMap.get(baseFont); + if (font == null) { + FontData fontDatas[] = baseFont.getFontData(); FontData data = fontDatas[0]; - return new Font(Display.getCurrent(), data.getName(), data.getHeight(), SWT.BOLD); - }); + font = new Font(Display.getCurrent(), data.getName(), data.getHeight(), SWT.BOLD); + m_fontToBoldFontMap.put(baseFont, font); + } + return font; } - /** * Dispose all of the cached {@link Font}'s. */ public static void disposeFonts() { // clear fonts - for (Font font : fontMap.values()) { + for (Font font : m_fontMap.values()) { font.dispose(); } - fontMap.clear(); + m_fontMap.clear(); // clear bold fonts - for (Font font : fontToBoldFontMap.values()) { + for (Font font : m_fontToBoldFontMap.values()) { font.dispose(); } - fontToBoldFontMap.clear(); + m_fontToBoldFontMap.clear(); } - //////////////////////////////////////////////////////////////////////////// // // Cursor @@ -396,38 +403,40 @@ public class SWTResourceManager { /** * Maps IDs to cursors. */ - private static Map idToCursorMap = new HashMap<>(); - + private static Map m_idToCursorMap = new HashMap(); /** * Returns the system cursor matching the specific ID. * - * @param id int The ID value for the cursor + * @param id + * int The ID value for the cursor * @return Cursor The system cursor matching the specific ID */ public static Cursor getCursor(int id) { Integer key = Integer.valueOf(id); - return idToCursorMap.computeIfAbsent(key, k -> new Cursor(Display.getDefault(), id)); + Cursor cursor = m_idToCursorMap.get(key); + if (cursor == null) { + cursor = new Cursor(Display.getDefault(), id); + m_idToCursorMap.put(key, cursor); + } + return cursor; } - /** * Dispose all of the cached cursors. */ public static void disposeCursors() { - for (Cursor cursor : idToCursorMap.values()) { + for (Cursor cursor : m_idToCursorMap.values()) { cursor.dispose(); } - idToCursorMap.clear(); + m_idToCursorMap.clear(); } - //////////////////////////////////////////////////////////////////////////// // // General // //////////////////////////////////////////////////////////////////////////// /** - * Dispose of cached objects and their underlying OS resources. This should only - * be called when the cached objects are no longer needed (e.g. on application - * shutdown). + * Dispose of cached objects and their underlying OS resources. This should only be called when the cached + * objects are no longer needed (e.g. on application shutdown). */ public static void dispose() { disposeColors(); diff --git a/products/com.minres.scviewer.e4.product/scviewer.product b/products/com.minres.scviewer.e4.product/scviewer.product index 3e2be35..fb43cfd 100644 --- a/products/com.minres.scviewer.e4.product/scviewer.product +++ b/products/com.minres.scviewer.e4.product/scviewer.product @@ -80,7 +80,6 @@ -