/** * Copyright (C) 2007 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 63.529,40 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package org.gudy.azureus2.ui.swt.shells; import java.util.Iterator; import java.util.LinkedHashMap; import org.eclipse.swt.SWT; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.*; import org.gudy.azureus2.core3.util.*; import org.gudy.azureus2.ui.swt.Utils; /** * Cheap ugly slider shell * * @author TuxPaper * @created Jul 5, 2007 * */ public class SpeedScaleShell { private static final boolean MOUSE_ONLY_UP_EXITS = true; private static final int OPTION_HEIGHT = 15; private static final int TEXT_HEIGHT = 32; private static final int SCALER_HEIGHT = 20; private int HEIGHT = TEXT_HEIGHT + SCALER_HEIGHT; private static final int WIDTH = 120; private static final int PADDING_X0 = 10; private static final int PADDING_X1 = 10; private static final int WIDTH_NO_PADDING = WIDTH - PADDING_X0 - PADDING_X1; private static final int TYPED_TEXT_ALPHA = 80; private static final long CLOSE_DELAY = 600; private int value; private boolean cancelled; private int minValue; private int maxValue; private int maxTextValue; private int pageIncrement; private int bigPageIncrement; private Shell shell; private LinkedHashMap mapOptions = new LinkedHashMap(); private String sValue = ""; private Composite composite; private boolean menuChosen; protected boolean lastMoveHadMouseDown; private boolean assumeInitiallyDown; private TimerEventPerformer cursorBlinkPerformer = null; private TimerEvent cursorBlinkEvent = null; public static void main(String[] args) { SpeedScaleShell speedScaleWidget = new SpeedScaleShell() { public String getStringValue() { return getValue() + "b/s"; } }; speedScaleWidget.setMaxValue(10000); speedScaleWidget.setMaxTextValue(15000); speedScaleWidget.addOption("AutoSpeed", -1); speedScaleWidget.addOption("Preset: 10b/s", 10); speedScaleWidget.addOption("Preset: 20b/s", 20); speedScaleWidget.addOption("Preset: 1b/s", 1); speedScaleWidget.addOption("Preset: 1000b/s", 1000); speedScaleWidget.addOption("Preset: A really long preset", 2000); System.out.println("returns " + speedScaleWidget.open(1000, Constants.isWindows) + " w/" + speedScaleWidget.getValue()); } public SpeedScaleShell() { minValue = 0; maxValue = -1; maxTextValue = -1; pageIncrement = 10; bigPageIncrement = 100; cancelled = true; menuChosen = false; } /** * Borks with 0 or -1 maxValue * * @param startValue * @param assumeInitiallyDown * @return * * @since 3.0.1.7 */ public boolean open(final int startValue, boolean _assumeInitiallyDown) { value = startValue; this.assumeInitiallyDown = _assumeInitiallyDown; cancelled = true; shell = new Shell(Utils.findAnyShell(), SWT.DOUBLE_BUFFERED | SWT.ON_TOP); shell.setLayout(new FillLayout()); final Display display = shell.getDisplay(); composite = new Composite(shell, SWT.DOUBLE_BUFFERED); final Point firstMousePos = display.getCursorLocation(); composite.addTraverseListener(new TraverseListener() { public void keyTraversed(TraverseEvent e) { if (e.detail == SWT.TRAVERSE_ESCAPE) { setCancelled(true); shell.dispose(); } else if (e.detail == SWT.TRAVERSE_ARROW_NEXT) { setValue(value + 1); } else if (e.detail == SWT.TRAVERSE_ARROW_PREVIOUS) { setValue(value - 1); } else if (e.detail == SWT.TRAVERSE_PAGE_NEXT) { setValue(value + bigPageIncrement); } else if (e.detail == SWT.TRAVERSE_PAGE_PREVIOUS) { setValue(value - bigPageIncrement); } else if (e.detail == SWT.TRAVERSE_RETURN) { setCancelled(false); shell.dispose(); } } }); composite.addKeyListener(new KeyListener() { public void keyReleased(KeyEvent e) { } public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.PAGE_DOWN && e.stateMask == 0) { setValue(value + pageIncrement); } else if (e.keyCode == SWT.PAGE_UP && e.stateMask == 0) { setValue(value - pageIncrement); } else if (e.keyCode == SWT.HOME) { setValue(minValue); } else if (e.keyCode == SWT.END) { if (maxValue != -1) { setValue(maxValue); } } } }); composite.addMouseMoveListener(new MouseMoveListener() { public void mouseMove(MouseEvent e) { lastMoveHadMouseDown = false; boolean hasButtonDown = (e.stateMask & SWT.BUTTON_MASK) > 0 || assumeInitiallyDown; if (hasButtonDown) { if (e.y > HEIGHT - SCALER_HEIGHT) { lastMoveHadMouseDown = true; setValue(getValueFromMousePos(e.x)); } composite.redraw(); } else { composite.redraw(); } } }); composite.addMouseTrackListener(new MouseTrackListener() { boolean mouseIsOut = false; private boolean exitCancelled = false; public void mouseHover(MouseEvent e) { } public void mouseExit(MouseEvent e) { mouseIsOut = true; SimpleTimer.addEvent("close scaler", SystemTime.getOffsetTime(CLOSE_DELAY), new TimerEventPerformer() { public void perform(TimerEvent event) { Utils.execSWTThread(new AERunnable() { public void runSupport() { if (!exitCancelled) { shell.dispose(); } else { exitCancelled = false; } } }); } }); } public void mouseEnter(MouseEvent e) { if (mouseIsOut) { exitCancelled = true; } mouseIsOut = false; } }); composite.addMouseListener(new MouseListener() { boolean bMouseDown = false; public void mouseUp(MouseEvent e) { if (assumeInitiallyDown) { assumeInitiallyDown = false; } if (MOUSE_ONLY_UP_EXITS) { if (lastMoveHadMouseDown) { Point mousePos = display.getCursorLocation(); if (mousePos.equals(firstMousePos)) { lastMoveHadMouseDown = false; return; } } bMouseDown = true; } if (bMouseDown) { if (e.y > HEIGHT - SCALER_HEIGHT) { setValue(getValueFromMousePos(e.x)); setCancelled(false); if (lastMoveHadMouseDown) { shell.dispose(); } } else if (e.y > TEXT_HEIGHT) { int idx = (e.y - TEXT_HEIGHT) / OPTION_HEIGHT; Iterator iterator = mapOptions.keySet().iterator(); int newValue; do { newValue = ((Integer) iterator.next()).intValue(); idx--; } while (idx >= 0); value = newValue; // ignore min/max setCancelled(false); setMenuChosen(true); shell.dispose(); } } } public void mouseDown(MouseEvent e) { if (e.count > 1) { lastMoveHadMouseDown = true; return; } Point mousePos = display.getCursorLocation(); if (e.y > HEIGHT - SCALER_HEIGHT) { bMouseDown = true; setValue(getValueFromMousePos(e.x)); } } public void mouseDoubleClick(MouseEvent e) { } }); composite.addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { int x = WIDTH_NO_PADDING * value / maxValue; if (x < 0) { x = 0; } else if (x > WIDTH_NO_PADDING) { x = WIDTH_NO_PADDING; } int startX = WIDTH_NO_PADDING * startValue / maxValue; if (startX < 0) { startX = 0; } else if (startX > WIDTH_NO_PADDING) { startX = WIDTH_NO_PADDING; } int baseLinePos = getBaselinePos(); try { e.gc.setAdvanced(true); e.gc.setAntialias(SWT.ON); } catch (Exception ex) { // aw } e.gc.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW)); // left e.gc.drawLine(PADDING_X0, baseLinePos - 6, PADDING_X0, baseLinePos + 6); // right e.gc.drawLine(PADDING_X0 + WIDTH_NO_PADDING, baseLinePos - 6, PADDING_X0 + WIDTH_NO_PADDING, baseLinePos + 6); // baseline e.gc.drawLine(PADDING_X0, baseLinePos, PADDING_X0 + WIDTH_NO_PADDING, baseLinePos); e.gc.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND)); e.gc.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND)); // start value marker e.gc.drawLine(PADDING_X0 + startX, baseLinePos - 5, PADDING_X0 + startX, baseLinePos + 5); // current value marker e.gc.fillRoundRectangle(PADDING_X0 + x - 2, baseLinePos - 5, 5, 10, 10, 10); // Current Value Text e.gc.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); e.gc.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); e.gc.fillRectangle(0, 0, WIDTH, TEXT_HEIGHT); GCStringPrinter.printString(e.gc, _getStringValue(), new Rectangle(0, 0, WIDTH, HEIGHT), true, false, SWT.CENTER | SWT.TOP | SWT.WRAP); e.gc.drawLine(0, TEXT_HEIGHT - 1, WIDTH, TEXT_HEIGHT - 1); // options list int y = TEXT_HEIGHT; Point mousePos = composite.toControl(display.getCursorLocation()); for (Iterator iter = mapOptions.keySet().iterator(); iter.hasNext();) { Integer value = (Integer) iter.next(); String text = (String) mapOptions.get(value); Rectangle area = new Rectangle(0, y, WIDTH, OPTION_HEIGHT); Color bg; if (area.contains(mousePos)) { bg = display.getSystemColor(SWT.COLOR_LIST_SELECTION); e.gc.setBackground(bg); e.gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT)); e.gc.fillRectangle(area); } else { bg = display.getSystemColor(SWT.COLOR_LIST_BACKGROUND); e.gc.setBackground(bg); e.gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); } int ovalSize = OPTION_HEIGHT - 6; if (getValue() == value.intValue()) { Color saveColor = e.gc.getBackground(); e.gc.setBackground(e.gc.getForeground()); e.gc.fillOval(4, y + 5, ovalSize - 3, ovalSize - 3); e.gc.setBackground(saveColor); } if (Constants.isLinux) { // Hack: on linux, drawing oval seems to draw a line from last pos // to start of oval.. drawing a point (anywhere) seems to clear the // path Color saveColor = e.gc.getForeground(); e.gc.setForeground(bg); e.gc.drawPoint(2, y + 3); e.gc.setForeground(saveColor); } e.gc.drawOval(2, y + 3, ovalSize, ovalSize); GCStringPrinter.printString(e.gc, text, new Rectangle(OPTION_HEIGHT, y, WIDTH - OPTION_HEIGHT, OPTION_HEIGHT), true, false, SWT.LEFT); y += OPTION_HEIGHT; } // typed value if (sValue.length() > 0) { Point extent = e.gc.textExtent(sValue); if (extent.x > WIDTH - 10) { extent.x = WIDTH - 10; } Rectangle rect = new Rectangle(WIDTH - 8 - extent.x, 14, extent.x + 5, extent.y + 4 + 14 > TEXT_HEIGHT ? TEXT_HEIGHT - 15 : extent.y + 4); e.gc.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); e.gc.fillRectangle(rect); try { e.gc.setAlpha(TYPED_TEXT_ALPHA); } catch (Exception ex) { } e.gc.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND)); e.gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND)); //e.gc.drawRectangle(rect); GCStringPrinter.printString(e.gc, sValue, new Rectangle(rect.x + 2, rect.y + 2, WIDTH - 5, OPTION_HEIGHT), true, false, SWT.LEFT | SWT.BOTTOM); } } }); // blinking cursor so people know they can type final AERunnable cursorBlinkRunnable = new AERunnable() { boolean on = false; public void runSupport() { if (composite.isDisposed()) { return; } on = !on; GC gc = new GC(composite); try { gc.setLineWidth(2); if (!on) { gc.setForeground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); } else { try { gc.setAlpha(TYPED_TEXT_ALPHA); } catch (Exception e) { } } int y = 15; gc.drawLine(WIDTH - 5, y + 1, WIDTH - 5, y + OPTION_HEIGHT); } finally { gc.dispose(); } if (cursorBlinkPerformer != null) { cursorBlinkEvent = SimpleTimer.addEvent("BlinkingCursor", SystemTime.getOffsetTime(500), cursorBlinkPerformer); } } }; cursorBlinkPerformer = new TimerEventPerformer() { public void perform(final TimerEvent event) { Utils.execSWTThread(cursorBlinkRunnable); } }; cursorBlinkEvent = SimpleTimer.addEvent("BlinkingCursor", SystemTime.getOffsetTime(500), cursorBlinkPerformer); composite.addKeyListener(new KeyListener() { public void keyReleased(KeyEvent e) { } public void keyPressed(KeyEvent e) { if (Character.isDigit(e.character)) { sValue += e.character; } else if (e.keyCode == SWT.BS && sValue.length() > 0) { sValue = sValue.substring(0, sValue.length() - 1); } else { return; } try { int newValue = Integer.parseInt(sValue); if (maxTextValue == -1) { setValue(newValue); } else { if (minValue > 0 && newValue < minValue) { newValue = minValue; } if (newValue > maxTextValue) { newValue = maxTextValue; } value = newValue; composite.redraw(); } } catch (Exception ex) { setValue(startValue); } } }); Point location = display.getCursorLocation(); location.y -= getBaselinePos(); int x = (int) (WIDTH_NO_PADDING * (value > maxValue ? 1 : (double) value / maxValue)); location.x -= PADDING_X0 + x; Rectangle bounds = new Rectangle(location.x, location.y, WIDTH, HEIGHT); Monitor mouseMonitor = shell.getMonitor(); Monitor[] monitors = display.getMonitors(); for (int i = 0; i < monitors.length; i++) { Monitor monitor = monitors[i]; if (monitor.getBounds().contains(location)) { mouseMonitor = monitor; break; } } Rectangle monitorBounds = mouseMonitor.getBounds(); Rectangle intersection = monitorBounds.intersection(bounds); if (intersection.width != bounds.width) { bounds.x = monitorBounds.x + monitorBounds.width - WIDTH; bounds.width = WIDTH; } if (intersection.height != bounds.height) { bounds.y = monitorBounds.y + monitorBounds.height - HEIGHT; bounds.height = HEIGHT; } shell.setBounds(bounds); if (!bounds.contains(firstMousePos)) { // should never happen, which means it probably will, so handle it badly shell.setLocation(firstMousePos.x - (bounds.width / 2), firstMousePos.y - bounds.height + 2); } shell.open(); // must be after, for OSX composite.setFocus(); try { while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } } catch (Throwable t) { Debug.out(t); } if (cursorBlinkEvent != null) { cursorBlinkEvent.cancel(); cursorBlinkEvent = null; } return !cancelled; } /** * @param x * @return * * @since 3.0.1.7 */ protected int getValueFromMousePos(int x) { int x0 = x + 1; if (x < PADDING_X0) { x0 = PADDING_X0; } else if (x > PADDING_X0 + WIDTH_NO_PADDING) { x0 = PADDING_X0 + WIDTH_NO_PADDING; } return (x0 - PADDING_X0) * maxValue / WIDTH_NO_PADDING; } public int getValue() { return value; } public boolean isCancelled() { return cancelled; } public void setCancelled(boolean cancelled) { this.cancelled = cancelled; } public int getMinValue() { return minValue; } public void setMinValue(int minValue) { this.minValue = minValue; } public int getMaxValue() { return maxValue; } public void setMaxValue(int maxValue) { this.maxValue = maxValue; } public void setValue(int value) { //System.out.println("sv " + value + ";" + Debug.getCompressedStackTrace()); if (value > maxValue) { value = maxValue; } else if (value < minValue) { value = minValue; } this.value = value; if (composite != null && !composite.isDisposed()) { composite.redraw(); } } public String _getStringValue() { String name = (String) mapOptions.get(new Integer(value)); return getStringValue(value, name); } public String getStringValue(int value, String sValue) { if (sValue != null) { return sValue; } return "" + value; } private int getBaselinePos() { return HEIGHT - (SCALER_HEIGHT / 2); } public void addOption(String id, int value) { mapOptions.put(new Integer(value), id); HEIGHT += OPTION_HEIGHT; } public int getMaxTextValue() { return maxTextValue; } public void setMaxTextValue(int maxTextValue) { this.maxTextValue = maxTextValue; } public boolean wasMenuChosen() { return menuChosen; } public void setMenuChosen(boolean menuChosen) { this.menuChosen = menuChosen; } }