1 /*******************************************************************************
2 * Copyright (c) 2000, 2013 IBM Corporation and others.
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.widgets;
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.events.*;
19 import org.eclipse.swt.graphics.*;
20 import org.eclipse.swt.internal.*;
21 import org.eclipse.swt.internal.win32.*;
24 * Instances of this class implement rubber banding rectangles that are
25 * drawn onto a parent <code>Composite</code> or <code>Display</code>.
26 * These rectangles can be specified to respond to mouse and key events
27 * by either moving or resizing themselves accordingly. Trackers are
28 * typically used to represent window geometries in a lightweight manner.
31 * <dt><b>Styles:</b></dt>
32 * <dd>LEFT, RIGHT, UP, DOWN, RESIZE</dd>
33 * <dt><b>Events:</b></dt>
34 * <dd>Move, Resize</dd>
37 * Note: Rectangle move behavior is assumed unless RESIZE is specified.
39 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
42 * @see <a href="http://www.eclipse.org/swt/snippets/#tracker">Tracker snippets</a>
43 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
44 * @noextend This class is not intended to be subclassed by clients.
46 public class Tracker extends Widget {
48 boolean tracking, cancelled, stippled;
49 Rectangle [] rectangles = new Rectangle [0], proportions = rectangles;
53 int cursorOrientation = SWT.NONE;
54 boolean inEvent = false;
56 long hwndTransparent, hwndOpaque, oldTransparentProc, oldOpaqueProc;
60 * The following values mirror step sizes on Windows
62 final static int STEPSIZE_SMALL = 1;
63 final static int STEPSIZE_LARGE = 9;
66 * Constructs a new instance of this class given its parent
67 * and a style value describing its behavior and appearance.
69 * The style value is either one of the style constants defined in
70 * class <code>SWT</code> which is applicable to instances of this
71 * class, or must be built by <em>bitwise OR</em>'ing together
72 * (that is, using the <code>int</code> "|" operator) two or more
73 * of those <code>SWT</code> style constants. The class description
74 * lists the style constants that are applicable to the class.
75 * Style bits are also inherited from superclasses.
78 * @param parent a widget which will be the parent of the new instance (cannot be null)
79 * @param style the style of widget to construct
81 * @exception IllegalArgumentException <ul>
82 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
84 * @exception SWTException <ul>
85 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
86 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
94 * @see Widget#checkSubclass
95 * @see Widget#getStyle
97 public Tracker (Composite parent, int style) {
98 super (parent, checkStyle (style));
103 * Constructs a new instance of this class given the display
104 * to create it on and a style value describing its behavior
107 * The style value is either one of the style constants defined in
108 * class <code>SWT</code> which is applicable to instances of this
109 * class, or must be built by <em>bitwise OR</em>'ing together
110 * (that is, using the <code>int</code> "|" operator) two or more
111 * of those <code>SWT</code> style constants. The class description
112 * lists the style constants that are applicable to the class.
113 * Style bits are also inherited from superclasses.
115 * Note: Currently, null can be passed in for the display argument.
116 * This has the effect of creating the tracker on the currently active
117 * display if there is one. If there is no current display, the
118 * tracker is created on a "default" display. <b>Passing in null as
119 * the display argument is not considered to be good coding style,
120 * and may not be supported in a future release of SWT.</b>
123 * @param display the display to create the tracker on
124 * @param style the style of control to construct
126 * @exception SWTException <ul>
127 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
128 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
137 public Tracker (Display display, int style) {
138 if (display == null) display = Display.getCurrent ();
139 if (display == null) display = Display.getDefault ();
140 if (!display.isValidThread ()) {
141 error (SWT.ERROR_THREAD_INVALID_ACCESS);
143 this.style = checkStyle (style);
144 this.display = display;
149 * Adds the listener to the collection of listeners who will
150 * be notified when the control is moved or resized, by sending
151 * it one of the messages defined in the <code>ControlListener</code>
154 * @param listener the listener which should be notified
156 * @exception IllegalArgumentException <ul>
157 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
159 * @exception SWTException <ul>
160 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
161 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
164 * @see ControlListener
165 * @see #removeControlListener
167 public void addControlListener (ControlListener listener) {
169 if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
170 TypedListener typedListener = new TypedListener (listener);
171 addListener (SWT.Resize, typedListener);
172 addListener (SWT.Move, typedListener);
176 * Adds the listener to the collection of listeners who will
177 * be notified when keys are pressed and released on the system keyboard, by sending
178 * it one of the messages defined in the <code>KeyListener</code>
181 * @param listener the listener which should be notified
183 * @exception IllegalArgumentException <ul>
184 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
186 * @exception SWTException <ul>
187 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
188 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
192 * @see #removeKeyListener
194 public void addKeyListener (KeyListener listener) {
196 if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
197 TypedListener typedListener = new TypedListener (listener);
198 addListener (SWT.KeyUp,typedListener);
199 addListener (SWT.KeyDown,typedListener);
202 Point adjustMoveCursor () {
203 if (bounds == null) return null;
204 int newX = bounds.x + bounds.width / 2;
206 POINT pt = new POINT ();
207 pt.x = newX; pt.y = newY;
209 * Convert to screen coordinates iff needed
211 if (parent != null) {
212 OS.ClientToScreen (parent.handle, pt);
214 OS.SetCursorPos (pt.x, pt.y);
215 return new Point (pt.x, pt.y);
218 Point adjustResizeCursor () {
219 if (bounds == null) return null;
222 if ((cursorOrientation & SWT.LEFT) != 0) {
224 } else if ((cursorOrientation & SWT.RIGHT) != 0) {
225 newX = bounds.x + bounds.width;
227 newX = bounds.x + bounds.width / 2;
230 if ((cursorOrientation & SWT.UP) != 0) {
232 } else if ((cursorOrientation & SWT.DOWN) != 0) {
233 newY = bounds.y + bounds.height;
235 newY = bounds.y + bounds.height / 2;
238 POINT pt = new POINT ();
239 pt.x = newX; pt.y = newY;
241 * Convert to screen coordinates iff needed
243 if (parent != null) {
244 OS.ClientToScreen (parent.handle, pt);
246 OS.SetCursorPos (pt.x, pt.y);
249 * If the client has not provided a custom cursor then determine
250 * the appropriate resize cursor.
252 if (clientCursor == null) {
254 switch (cursorOrientation) {
256 newCursor = OS.LoadCursor (0, OS.IDC_SIZENS);
259 newCursor = OS.LoadCursor (0, OS.IDC_SIZENS);
262 newCursor = OS.LoadCursor (0, OS.IDC_SIZEWE);
265 newCursor = OS.LoadCursor (0, OS.IDC_SIZEWE);
267 case SWT.LEFT | SWT.UP:
268 newCursor = OS.LoadCursor (0, OS.IDC_SIZENWSE);
270 case SWT.RIGHT | SWT.DOWN:
271 newCursor = OS.LoadCursor (0, OS.IDC_SIZENWSE);
273 case SWT.LEFT | SWT.DOWN:
274 newCursor = OS.LoadCursor (0, OS.IDC_SIZENESW);
276 case SWT.RIGHT | SWT.UP:
277 newCursor = OS.LoadCursor (0, OS.IDC_SIZENESW);
280 newCursor = OS.LoadCursor (0, OS.IDC_SIZEALL);
283 OS.SetCursor (newCursor);
284 if (resizeCursor != 0) {
285 OS.DestroyCursor (resizeCursor);
287 resizeCursor = newCursor;
290 return new Point (pt.x, pt.y);
293 static int checkStyle (int style) {
294 if ((style & (SWT.LEFT | SWT.RIGHT | SWT.UP | SWT.DOWN)) == 0) {
295 style |= SWT.LEFT | SWT.RIGHT | SWT.UP | SWT.DOWN;
301 * Stops displaying the tracker rectangles. Note that this is not considered
302 * to be a cancelation by the user.
304 * @exception SWTException <ul>
305 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
306 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
309 public void close () {
314 Rectangle computeBounds () {
315 if (rectangles.length == 0) return null;
316 int xMin = rectangles [0].x;
317 int yMin = rectangles [0].y;
318 int xMax = rectangles [0].x + rectangles [0].width;
319 int yMax = rectangles [0].y + rectangles [0].height;
321 for (int i = 1; i < rectangles.length; i++) {
322 if (rectangles [i].x < xMin) xMin = rectangles [i].x;
323 if (rectangles [i].y < yMin) yMin = rectangles [i].y;
324 int rectRight = rectangles [i].x + rectangles [i].width;
325 if (rectRight > xMax) xMax = rectRight;
326 int rectBottom = rectangles [i].y + rectangles [i].height;
327 if (rectBottom > yMax) yMax = rectBottom;
330 return new Rectangle (xMin, yMin, xMax - xMin, yMax - yMin);
333 Rectangle [] computeProportions (Rectangle [] rects) {
334 Rectangle [] result = new Rectangle [rects.length];
335 bounds = computeBounds ();
336 if (bounds != null) {
337 for (int i = 0; i < rects.length; i++) {
338 int x = 0, y = 0, width = 0, height = 0;
339 if (bounds.width != 0) {
340 x = (rects [i].x - bounds.x) * 100 / bounds.width;
341 width = rects [i].width * 100 / bounds.width;
345 if (bounds.height != 0) {
346 y = (rects [i].y - bounds.y) * 100 / bounds.height;
347 height = rects [i].height * 100 / bounds.height;
351 result [i] = new Rectangle (x, y, width, height);
358 * Draw the rectangles displayed by the tracker.
360 void drawRectangles (Rectangle [] rects, boolean stippled) {
361 if (hwndOpaque != 0) {
362 RECT rect1 = new RECT();
363 int bandWidth = stippled ? 3 : 1;
364 for (int i = 0; i < rects.length; i++) {
365 Rectangle rect = rects[i];
366 rect1.left = rect.x - bandWidth;
367 rect1.top = rect.y - bandWidth;
368 rect1.right = rect.x + rect.width + bandWidth * 2;
369 rect1.bottom = rect.y + rect.height + bandWidth * 2;
370 OS.MapWindowPoints (0, hwndOpaque, rect1, 2);
371 OS.RedrawWindow (hwndOpaque, rect1, 0, OS.RDW_INVALIDATE);
376 long hwndTrack = parent == null ? OS.GetDesktopWindow () : parent.handle;
377 long hDC = OS.GetDCEx (hwndTrack, 0, OS.DCX_CACHE);
378 long hBitmap = 0, hBrush = 0, oldBrush = 0;
381 byte [] bits = {-86, 0, 85, 0, -86, 0, 85, 0, -86, 0, 85, 0, -86, 0, 85, 0};
382 hBitmap = OS.CreateBitmap (8, 8, 1, 1, bits);
383 hBrush = OS.CreatePatternBrush (hBitmap);
384 oldBrush = OS.SelectObject (hDC, hBrush);
386 for (int i=0; i<rects.length; i++) {
387 Rectangle rect = rects [i];
388 OS.PatBlt (hDC, rect.x, rect.y, rect.width, bandWidth, OS.PATINVERT);
389 OS.PatBlt (hDC, rect.x, rect.y + bandWidth, bandWidth, rect.height - (bandWidth * 2), OS.PATINVERT);
390 OS.PatBlt (hDC, rect.x + rect.width - bandWidth, rect.y + bandWidth, bandWidth, rect.height - (bandWidth * 2), OS.PATINVERT);
391 OS.PatBlt (hDC, rect.x, rect.y + rect.height - bandWidth, rect.width, bandWidth, OS.PATINVERT);
394 OS.SelectObject (hDC, oldBrush);
395 OS.DeleteObject (hBrush);
396 OS.DeleteObject (hBitmap);
398 OS.ReleaseDC (hwndTrack, hDC);
402 * Returns the bounds that are being drawn, expressed relative to the parent
403 * widget. If the parent is a <code>Display</code> then these are screen
406 * @return the bounds of the Rectangles being drawn
408 * @exception SWTException <ul>
409 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
410 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
413 public Rectangle [] getRectangles () {
415 Rectangle [] result = getRectanglesInPixels();
416 for (int i = 0; i < result.length; i++) {
417 result[i] = DPIUtil.autoScaleDown(result[i]);
422 Rectangle [] getRectanglesInPixels () {
423 Rectangle [] result = new Rectangle [rectangles.length];
424 for (int i = 0; i < rectangles.length; i++) {
425 Rectangle current = rectangles [i];
426 result [i] = new Rectangle (current.x, current.y, current.width, current.height);
432 * Returns <code>true</code> if the rectangles are drawn with a stippled line, <code>false</code> otherwise.
434 * @return the stippled effect of the rectangles
436 * @exception SWTException <ul>
437 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
438 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
441 public boolean getStippled () {
446 void moveRectangles (int xChange, int yChange) {
447 if (bounds == null) return;
448 if (xChange < 0 && ((style & SWT.LEFT) == 0)) xChange = 0;
449 if (xChange > 0 && ((style & SWT.RIGHT) == 0)) xChange = 0;
450 if (yChange < 0 && ((style & SWT.UP) == 0)) yChange = 0;
451 if (yChange > 0 && ((style & SWT.DOWN) == 0)) yChange = 0;
452 if (xChange == 0 && yChange == 0) return;
453 bounds.x += xChange; bounds.y += yChange;
454 for (int i = 0; i < rectangles.length; i++) {
455 rectangles [i].x += xChange;
456 rectangles [i].y += yChange;
461 * Displays the Tracker rectangles for manipulation by the user. Returns when
462 * the user has either finished manipulating the rectangles or has cancelled the
465 * @return <code>true</code> if the user did not cancel the Tracker, <code>false</code> otherwise
467 * @exception SWTException <ul>
468 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
469 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
472 public boolean open () {
478 * If exactly one of UP/DOWN is specified as a style then set the cursor
479 * orientation accordingly (the same is done for LEFT/RIGHT styles below).
481 int vStyle = style & (SWT.UP | SWT.DOWN);
482 if (vStyle == SWT.UP || vStyle == SWT.DOWN) {
483 cursorOrientation |= vStyle;
485 int hStyle = style & (SWT.LEFT | SWT.RIGHT);
486 if (hStyle == SWT.LEFT || hStyle == SWT.RIGHT) {
487 cursorOrientation |= hStyle;
490 Callback newProc = null;
491 boolean mouseDown = OS.GetKeyState(OS.VK_LBUTTON) < 0;
493 * Bug in Vista. Drawing directly to the screen with XOR does not
494 * perform well. The fix is to draw on layered window instead.
496 * Note that one window (almost opaque) is used for catching all events and a
497 * second window is used for drawing the rectangles.
499 if (parent == null) {
500 Rectangle bounds = display.getBoundsInPixels();
501 hwndTransparent = OS.CreateWindowEx (
502 OS.WS_EX_LAYERED | OS.WS_EX_NOACTIVATE | OS.WS_EX_TOOLWINDOW,
507 bounds.width, bounds.height,
510 OS.GetModuleHandle (null),
512 OS.SetLayeredWindowAttributes (hwndTransparent, 0, (byte)0x01, OS.LWA_ALPHA);
513 hwndOpaque = OS.CreateWindowEx (
514 OS.WS_EX_LAYERED | OS.WS_EX_NOACTIVATE | OS.WS_EX_TOOLWINDOW,
519 bounds.width, bounds.height,
522 OS.GetModuleHandle (null),
524 OS.SetLayeredWindowAttributes (hwndOpaque, 0xFFFFFF, (byte)0, OS.LWA_COLORKEY | OS.LWA_ALPHA);
526 newProc = new Callback (this, "transparentProc", 4); //$NON-NLS-1$
527 long newProcAddress = newProc.getAddress ();
528 if (newProcAddress == 0) error (SWT.ERROR_NO_MORE_CALLBACKS);
529 oldTransparentProc = OS.GetWindowLongPtr (hwndTransparent, OS.GWLP_WNDPROC);
530 OS.SetWindowLongPtr (hwndTransparent, OS.GWLP_WNDPROC, newProcAddress);
531 oldOpaqueProc = OS.GetWindowLongPtr (hwndOpaque, OS.GWLP_WNDPROC);
532 OS.SetWindowLongPtr (hwndOpaque, OS.GWLP_WNDPROC, newProcAddress);
533 OS.ShowWindow (hwndTransparent, OS.SW_SHOWNOACTIVATE);
534 OS.ShowWindow (hwndOpaque, OS.SW_SHOWNOACTIVATE);
537 * If this tracker is being created without a mouse drag then
538 * we need to create a transparent window that fills the screen
539 * in order to get all mouse/keyboard events that occur
540 * outside of our visible windows (ie.- over the desktop).
543 Rectangle bounds = display.getBoundsInPixels();
544 hwndTransparent = OS.CreateWindowEx (
545 OS.WS_EX_TRANSPARENT,
550 bounds.width, bounds.height,
553 OS.GetModuleHandle (null),
555 newProc = new Callback (this, "transparentProc", 4); //$NON-NLS-1$
556 long newProcAddress = newProc.getAddress ();
557 if (newProcAddress == 0) error (SWT.ERROR_NO_MORE_CALLBACKS);
558 oldTransparentProc = OS.GetWindowLongPtr (hwndTransparent, OS.GWLP_WNDPROC);
559 OS.SetWindowLongPtr (hwndTransparent, OS.GWLP_WNDPROC, newProcAddress);
560 OS.ShowWindow (hwndTransparent, OS.SW_SHOWNOACTIVATE);
565 drawRectangles (rectangles, stippled);
566 Point cursorPos = null;
568 POINT pt = new POINT ();
569 OS.GetCursorPos (pt);
570 cursorPos = new Point (pt.x, pt.y);
572 if ((style & SWT.RESIZE) != 0) {
573 cursorPos = adjustResizeCursor ();
575 cursorPos = adjustMoveCursor ();
578 if (cursorPos != null) {
583 Display display = this.display;
585 /* Tracker behaves like a Dialog with its own OS event loop. */
586 MSG msg = new MSG ();
587 while (tracking && !cancelled) {
588 if (parent != null && parent.isDisposed ()) break;
590 display.runDeferredLayouts ();
591 display.sendPreExternalEventDispatchEvent ();
592 OS.GetMessage (msg, 0, 0, 0);
593 display.sendPostExternalEventDispatchEvent ();
594 OS.TranslateMessage (msg);
595 switch (msg.message) {
596 case OS.WM_LBUTTONUP:
597 case OS.WM_MOUSEMOVE:
598 wmMouse (msg.message, msg.wParam, msg.lParam);
600 case OS.WM_IME_CHAR: wmIMEChar (msg.hwnd, msg.wParam, msg.lParam); break;
601 case OS.WM_CHAR: wmChar (msg.hwnd, msg.wParam, msg.lParam); break;
602 case OS.WM_KEYDOWN: wmKeyDown (msg.hwnd, msg.wParam, msg.lParam); break;
603 case OS.WM_KEYUP: wmKeyUp (msg.hwnd, msg.wParam, msg.lParam); break;
604 case OS.WM_SYSCHAR: wmSysChar (msg.hwnd, msg.wParam, msg.lParam); break;
605 case OS.WM_SYSKEYDOWN: wmSysKeyDown (msg.hwnd, msg.wParam, msg.lParam); break;
606 case OS.WM_SYSKEYUP: wmSysKeyUp (msg.hwnd, msg.wParam, msg.lParam); break;
608 if (OS.WM_KEYFIRST <= msg.message && msg.message <= OS.WM_KEYLAST) continue;
609 if (OS.WM_MOUSEFIRST <= msg.message && msg.message <= OS.WM_MOUSELAST) continue;
610 if (hwndOpaque == 0) {
611 if (msg.message == OS.WM_PAINT) {
613 drawRectangles (rectangles, stippled);
616 OS.DispatchMessage (msg);
617 if (hwndOpaque == 0) {
618 if (msg.message == OS.WM_PAINT) {
619 drawRectangles (rectangles, stippled);
622 display.runAsyncMessages (false);
624 if (mouseDown) OS.ReleaseCapture ();
627 drawRectangles (rectangles, stippled);
631 * Cleanup: If a transparent window was created in order to capture events then
632 * destroy it and its callback object now.
634 if (hwndTransparent != 0) {
635 OS.DestroyWindow (hwndTransparent);
639 if (newProc != null) {
641 oldTransparentProc = oldOpaqueProc = 0;
644 * Cleanup: If this tracker was resizing then the last cursor that it created
645 * needs to be destroyed.
647 if (resizeCursor != 0) {
648 OS.DestroyCursor (resizeCursor);
657 void releaseWidget () {
658 super.releaseWidget ();
660 rectangles = proportions = null;
665 * Removes the listener from the collection of listeners who will
666 * be notified when the control is moved or resized.
668 * @param listener the listener which should no longer be notified
670 * @exception IllegalArgumentException <ul>
671 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
673 * @exception SWTException <ul>
674 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
675 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
678 * @see ControlListener
679 * @see #addControlListener
681 public void removeControlListener (ControlListener listener) {
683 if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
684 if (eventTable == null) return;
685 eventTable.unhook (SWT.Resize, listener);
686 eventTable.unhook (SWT.Move, listener);
690 * Removes the listener from the collection of listeners who will
691 * be notified when keys are pressed and released on the system keyboard.
693 * @param listener the listener which should no longer be notified
695 * @exception IllegalArgumentException <ul>
696 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
698 * @exception SWTException <ul>
699 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
700 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
704 * @see #addKeyListener
706 public void removeKeyListener(KeyListener listener) {
708 if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
709 if (eventTable == null) return;
710 eventTable.unhook (SWT.KeyUp, listener);
711 eventTable.unhook (SWT.KeyDown, listener);
714 void resizeRectangles (int xChange, int yChange) {
715 if (bounds == null) return;
717 * If the cursor orientation has not been set in the orientation of
718 * this change then try to set it here.
720 if (xChange < 0 && ((style & SWT.LEFT) != 0) && ((cursorOrientation & SWT.RIGHT) == 0)) {
721 cursorOrientation |= SWT.LEFT;
723 if (xChange > 0 && ((style & SWT.RIGHT) != 0) && ((cursorOrientation & SWT.LEFT) == 0)) {
724 cursorOrientation |= SWT.RIGHT;
726 if (yChange < 0 && ((style & SWT.UP) != 0) && ((cursorOrientation & SWT.DOWN) == 0)) {
727 cursorOrientation |= SWT.UP;
729 if (yChange > 0 && ((style & SWT.DOWN) != 0) && ((cursorOrientation & SWT.UP) == 0)) {
730 cursorOrientation |= SWT.DOWN;
734 * If the bounds will flip about the x or y axis then apply the adjustment
735 * up to the axis (ie.- where bounds width/height becomes 0), change the
736 * cursor's orientation accordingly, and flip each Rectangle's origin (only
737 * necessary for > 1 Rectangles)
739 if ((cursorOrientation & SWT.LEFT) != 0) {
740 if (xChange > bounds.width) {
741 if ((style & SWT.RIGHT) == 0) return;
742 cursorOrientation |= SWT.RIGHT;
743 cursorOrientation &= ~SWT.LEFT;
744 bounds.x += bounds.width;
745 xChange -= bounds.width;
747 if (proportions.length > 1) {
748 for (int i = 0; i < proportions.length; i++) {
749 Rectangle proportion = proportions [i];
750 proportion.x = 100 - proportion.x - proportion.width;
754 } else if ((cursorOrientation & SWT.RIGHT) != 0) {
755 if (bounds.width < -xChange) {
756 if ((style & SWT.LEFT) == 0) return;
757 cursorOrientation |= SWT.LEFT;
758 cursorOrientation &= ~SWT.RIGHT;
759 xChange += bounds.width;
761 if (proportions.length > 1) {
762 for (int i = 0; i < proportions.length; i++) {
763 Rectangle proportion = proportions [i];
764 proportion.x = 100 - proportion.x - proportion.width;
769 if ((cursorOrientation & SWT.UP) != 0) {
770 if (yChange > bounds.height) {
771 if ((style & SWT.DOWN) == 0) return;
772 cursorOrientation |= SWT.DOWN;
773 cursorOrientation &= ~SWT.UP;
774 bounds.y += bounds.height;
775 yChange -= bounds.height;
777 if (proportions.length > 1) {
778 for (int i = 0; i < proportions.length; i++) {
779 Rectangle proportion = proportions [i];
780 proportion.y = 100 - proportion.y - proportion.height;
784 } else if ((cursorOrientation & SWT.DOWN) != 0) {
785 if (bounds.height < -yChange) {
786 if ((style & SWT.UP) == 0) return;
787 cursorOrientation |= SWT.UP;
788 cursorOrientation &= ~SWT.DOWN;
789 yChange += bounds.height;
791 if (proportions.length > 1) {
792 for (int i = 0; i < proportions.length; i++) {
793 Rectangle proportion = proportions [i];
794 proportion.y = 100 - proportion.y - proportion.height;
800 // apply the bounds adjustment
801 if ((cursorOrientation & SWT.LEFT) != 0) {
803 bounds.width -= xChange;
804 } else if ((cursorOrientation & SWT.RIGHT) != 0) {
805 bounds.width += xChange;
807 if ((cursorOrientation & SWT.UP) != 0) {
809 bounds.height -= yChange;
810 } else if ((cursorOrientation & SWT.DOWN) != 0) {
811 bounds.height += yChange;
814 Rectangle [] newRects = new Rectangle [rectangles.length];
815 for (int i = 0; i < rectangles.length; i++) {
816 Rectangle proportion = proportions[i];
817 newRects[i] = new Rectangle (
818 proportion.x * bounds.width / 100 + bounds.x,
819 proportion.y * bounds.height / 100 + bounds.y,
820 proportion.width * bounds.width / 100,
821 proportion.height * bounds.height / 100);
823 rectangles = newRects;
827 * Sets the <code>Cursor</code> of the Tracker. If this cursor is <code>null</code>
828 * then the cursor reverts to the default.
830 * @param newCursor the new <code>Cursor</code> to display
832 * @exception SWTException <ul>
833 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
834 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
837 public void setCursor(Cursor newCursor) {
839 clientCursor = newCursor;
840 if (newCursor != null) {
841 if (inEvent) OS.SetCursor (clientCursor.handle);
846 * Specifies the rectangles that should be drawn, expressed relative to the parent
847 * widget. If the parent is a Display then these are screen coordinates.
849 * @param rectangles the bounds of the rectangles to be drawn
851 * @exception IllegalArgumentException <ul>
852 * <li>ERROR_NULL_ARGUMENT - if the set of rectangles is null or contains a null rectangle</li>
854 * @exception SWTException <ul>
855 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
856 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
859 public void setRectangles (Rectangle [] rectangles) {
861 if (rectangles == null) error (SWT.ERROR_NULL_ARGUMENT);
862 Rectangle [] rectanglesInPixels = new Rectangle [rectangles.length];
863 for (int i = 0; i < rectangles.length; i++) {
864 rectanglesInPixels [i] = DPIUtil.autoScaleUp (rectangles [i]);
866 setRectanglesInPixels (rectanglesInPixels);
869 void setRectanglesInPixels (Rectangle [] rectangles) {
870 this.rectangles = new Rectangle [rectangles.length];
871 for (int i = 0; i < rectangles.length; i++) {
872 Rectangle current = rectangles [i];
873 if (current == null) error (SWT.ERROR_NULL_ARGUMENT);
874 this.rectangles [i] = new Rectangle (current.x, current.y, current.width, current.height);
876 proportions = computeProportions (rectangles);
880 * Changes the appearance of the line used to draw the rectangles.
882 * @param stippled <code>true</code> if rectangle should appear stippled
884 * @exception SWTException <ul>
885 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
886 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
889 public void setStippled (boolean stippled) {
891 this.stippled = stippled;
894 long transparentProc (long hwnd, long msg, long wParam, long lParam) {
897 * We typically do not want to answer that the transparent window is
898 * transparent to hits since doing so negates the effect of having it
899 * to grab events. However, clients of the tracker should not be aware
900 * of this transparent window. Therefore if there is a hit query
901 * performed as a result of client code then answer that the transparent
902 * window is transparent to hits so that its existence will not impact
905 case OS.WM_NCHITTEST:
906 if (inEvent) return OS.HTTRANSPARENT;
908 case OS.WM_SETCURSOR:
909 if (clientCursor != null) {
910 OS.SetCursor (clientCursor.handle);
913 if (resizeCursor != 0) {
914 OS.SetCursor (resizeCursor);
919 if (hwndOpaque == hwnd) {
920 PAINTSTRUCT ps = new PAINTSTRUCT();
921 long hDC = OS.BeginPaint (hwnd, ps);
922 long hBitmap = 0, hBrush = 0, oldBrush = 0;
923 long transparentBrush = OS.CreateSolidBrush(0xFFFFFF);
924 oldBrush = OS.SelectObject (hDC, transparentBrush);
925 OS.PatBlt (hDC, ps.left, ps.top, ps.right - ps.left, ps.bottom - ps.top, OS.PATCOPY);
926 OS.SelectObject (hDC, oldBrush);
927 OS.DeleteObject (transparentBrush);
931 byte [] bits = {-86, 0, 85, 0, -86, 0, 85, 0, -86, 0, 85, 0, -86, 0, 85, 0};
932 hBitmap = OS.CreateBitmap (8, 8, 1, 1, bits);
933 hBrush = OS.CreatePatternBrush (hBitmap);
934 oldBrush = OS.SelectObject (hDC, hBrush);
935 OS.SetBkColor (hDC, 0xF0F0F0);
937 oldBrush = OS.SelectObject (hDC, OS.GetStockObject(OS.BLACK_BRUSH));
939 Rectangle[] rects = this.rectangles;
940 RECT rect1 = new RECT ();
941 for (int i=0; i<rects.length; i++) {
942 Rectangle rect = rects [i];
945 rect1.right = rect.x + rect.width;
946 rect1.bottom = rect.y + rect.height;
947 OS.MapWindowPoints (0, hwndOpaque, rect1, 2);
948 int width = rect1.right - rect1.left;
949 int height = rect1.bottom - rect1.top;
950 OS.PatBlt (hDC, rect1.left, rect1.top, width, bandWidth, OS.PATCOPY);
951 OS.PatBlt (hDC, rect1.left, rect1.top + bandWidth, bandWidth, height - (bandWidth * 2), OS.PATCOPY);
952 OS.PatBlt (hDC, rect1.right - bandWidth, rect1.top + bandWidth, bandWidth, height - (bandWidth * 2), OS.PATCOPY);
953 OS.PatBlt (hDC, rect1.left, rect1.bottom - bandWidth, width, bandWidth, OS.PATCOPY);
955 OS.SelectObject (hDC, oldBrush);
957 OS.DeleteObject (hBrush);
958 OS.DeleteObject (hBitmap);
960 OS.EndPaint (hwnd, ps);
962 OS.SetLayeredWindowAttributes (hwndOpaque, 0xFFFFFF, (byte)0xFF, OS.LWA_COLORKEY | OS.LWA_ALPHA);
968 return OS.CallWindowProc (hwnd == hwndTransparent ? oldTransparentProc : oldOpaqueProc, hwnd, (int)msg, wParam, lParam);
972 if (hwndOpaque != 0) return;
973 if (parent != null) {
974 if (parent.isDisposed ()) return;
975 Shell shell = parent.getShell ();
983 LRESULT wmKeyDown (long hwnd, long wParam, long lParam) {
984 LRESULT result = super.wmKeyDown (hwnd, wParam, lParam);
985 if (result != null) return result;
986 boolean isMirrored = parent != null && (parent.style & SWT.MIRRORED) != 0;
987 int stepSize = OS.GetKeyState (OS.VK_CONTROL) < 0 ? STEPSIZE_SMALL : STEPSIZE_LARGE;
988 int xChange = 0, yChange = 0;
989 switch ((int)wParam) {
998 xChange = isMirrored ? stepSize : -stepSize;
1001 xChange = isMirrored ? -stepSize : stepSize;
1004 yChange = -stepSize;
1010 if (xChange != 0 || yChange != 0) {
1011 Rectangle [] oldRectangles = rectangles;
1012 boolean oldStippled = stippled;
1013 Rectangle [] rectsToErase = new Rectangle [rectangles.length];
1014 for (int i = 0; i < rectangles.length; i++) {
1015 Rectangle current = rectangles [i];
1016 rectsToErase [i] = new Rectangle (current.x, current.y, current.width, current.height);
1018 Event event = new Event ();
1019 event.setLocationInPixels(oldX + xChange, oldY + yChange);
1021 if ((style & SWT.RESIZE) != 0) {
1022 resizeRectangles (xChange, yChange);
1024 sendEvent (SWT.Resize, event);
1027 * It is possible (but unlikely) that application
1028 * code could have disposed the widget in the resize
1029 * event. If this happens return false to indicate
1030 * that the tracking has failed.
1032 if (isDisposed ()) {
1036 boolean draw = false;
1038 * It is possible that application code could have
1039 * changed the rectangles in the resize event. If this
1040 * happens then only redraw the tracker if the rectangle
1041 * values have changed.
1043 if (rectangles != oldRectangles) {
1044 int length = rectangles.length;
1045 if (length != rectsToErase.length) {
1048 for (int i = 0; i < length; i++) {
1049 if (!rectangles [i].equals (rectsToErase [i])) {
1059 drawRectangles (rectsToErase, oldStippled);
1061 drawRectangles (rectangles, stippled);
1063 cursorPos = adjustResizeCursor ();
1065 moveRectangles (xChange, yChange);
1067 sendEvent (SWT.Move, event);
1070 * It is possible (but unlikely) that application
1071 * code could have disposed the widget in the move
1072 * event. If this happens return false to indicate
1073 * that the tracking has failed.
1075 if (isDisposed ()) {
1079 boolean draw = false;
1081 * It is possible that application code could have
1082 * changed the rectangles in the move event. If this
1083 * happens then only redraw the tracker if the rectangle
1084 * values have changed.
1086 if (rectangles != oldRectangles) {
1087 int length = rectangles.length;
1088 if (length != rectsToErase.length) {
1091 for (int i = 0; i < length; i++) {
1092 if (!rectangles [i].equals (rectsToErase [i])) {
1102 drawRectangles (rectsToErase, oldStippled);
1104 drawRectangles (rectangles, stippled);
1106 cursorPos = adjustMoveCursor ();
1108 if (cursorPos != null) {
1117 LRESULT wmSysKeyDown (long hwnd, long wParam, long lParam) {
1118 LRESULT result = super.wmSysKeyDown (hwnd, wParam, lParam);
1119 if (result != null) return result;
1125 LRESULT wmMouse (int message, long wParam, long lParam) {
1126 boolean isMirrored = parent != null && (parent.style & SWT.MIRRORED) != 0;
1127 int newPos = OS.GetMessagePos ();
1128 int newX = OS.GET_X_LPARAM (newPos);
1129 int newY = OS.GET_Y_LPARAM (newPos);
1130 if (newX != oldX || newY != oldY) {
1131 Rectangle [] oldRectangles = rectangles;
1132 boolean oldStippled = stippled;
1133 Rectangle [] rectsToErase = new Rectangle [rectangles.length];
1134 for (int i = 0; i < rectangles.length; i++) {
1135 Rectangle current = rectangles [i];
1136 rectsToErase [i] = new Rectangle (current.x, current.y, current.width, current.height);
1138 Event event = new Event ();
1139 event.setLocationInPixels(newX, newY);
1140 if ((style & SWT.RESIZE) != 0) {
1142 resizeRectangles (oldX - newX, newY - oldY);
1144 resizeRectangles (newX - oldX, newY - oldY);
1147 sendEvent (SWT.Resize, event);
1150 * It is possible (but unlikely), that application
1151 * code could have disposed the widget in the resize
1152 * event. If this happens, return false to indicate
1153 * that the tracking has failed.
1155 if (isDisposed ()) {
1159 boolean draw = false;
1161 * It is possible that application code could have
1162 * changed the rectangles in the resize event. If this
1163 * happens then only redraw the tracker if the rectangle
1164 * values have changed.
1166 if (rectangles != oldRectangles) {
1167 int length = rectangles.length;
1168 if (length != rectsToErase.length) {
1171 for (int i = 0; i < length; i++) {
1172 if (!rectangles [i].equals (rectsToErase [i])) {
1183 drawRectangles (rectsToErase, oldStippled);
1185 drawRectangles (rectangles, stippled);
1187 Point cursorPos = adjustResizeCursor ();
1188 if (cursorPos != null) {
1194 moveRectangles (oldX - newX, newY - oldY);
1196 moveRectangles (newX - oldX, newY - oldY);
1199 sendEvent (SWT.Move, event);
1202 * It is possible (but unlikely), that application
1203 * code could have disposed the widget in the move
1204 * event. If this happens, return false to indicate
1205 * that the tracking has failed.
1207 if (isDisposed ()) {
1211 boolean draw = false;
1213 * It is possible that application code could have
1214 * changed the rectangles in the move event. If this
1215 * happens then only redraw the tracker if the rectangle
1216 * values have changed.
1218 if (rectangles != oldRectangles) {
1219 int length = rectangles.length;
1220 if (length != rectsToErase.length) {
1223 for (int i = 0; i < length; i++) {
1224 if (!rectangles [i].equals (rectsToErase [i])) {
1234 drawRectangles (rectsToErase, oldStippled);
1236 drawRectangles (rectangles, stippled);
1242 tracking = message != OS.WM_LBUTTONUP;