1 /******************************************************************************* 2 * Copyright (c) 2000, 2008 IBM Corporation and others. 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * IBM Corporation - initial API and implementation 10 * Port to the D programming language: 11 * Frank Benoit <benoit@tionex.de> 12 *******************************************************************************/ 13 module org.eclipse.swt.widgets.Tracker; 14 15 import java.lang.all; 16 17 18 19 import org.eclipse.swt.SWT; 20 import org.eclipse.swt.SWTException; 21 import org.eclipse.swt.events.ControlListener; 22 import org.eclipse.swt.events.KeyListener; 23 import org.eclipse.swt.graphics.Cursor; 24 import org.eclipse.swt.graphics.Point; 25 import org.eclipse.swt.graphics.Rectangle; 26 import org.eclipse.swt.internal.gtk.OS; 27 import org.eclipse.swt.widgets.Widget; 28 import org.eclipse.swt.widgets.Composite; 29 import org.eclipse.swt.widgets.Display; 30 import org.eclipse.swt.widgets.TypedListener; 31 import org.eclipse.swt.widgets.Event; 32 33 import java.lang.Thread; 34 35 /** 36 * Instances of this class implement rubber banding rectangles that are 37 * drawn onto a parent <code>Composite</code> or <code>Display</code>. 38 * These rectangles can be specified to respond to mouse and key events 39 * by either moving or resizing themselves accordingly. Trackers are 40 * typically used to represent window geometries in a lightweight manner. 41 * 42 * <dl> 43 * <dt><b>Styles:</b></dt> 44 * <dd>LEFT, RIGHT, UP, DOWN, RESIZE</dd> 45 * <dt><b>Events:</b></dt> 46 * <dd>Move, Resize</dd> 47 * </dl> 48 * <p> 49 * Note: Rectangle move behavior is assumed unless RESIZE is specified. 50 * </p><p> 51 * IMPORTANT: This class is <em>not</em> intended to be subclassed. 52 * </p> 53 * 54 * @see <a href="http://www.eclipse.org/swt/snippets/#tracker">Tracker snippets</a> 55 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> 56 */ 57 public class Tracker : Widget { 58 Composite parent; 59 Cursor cursor; 60 GdkCursor* lastCursor; 61 GdkDrawable* window; 62 bool tracking, cancelled, grabbed, stippled; 63 Rectangle [] rectangles, proportions; 64 Rectangle bounds; 65 int cursorOrientation = SWT.NONE; 66 int oldX, oldY; 67 68 const static int STEPSIZE_SMALL = 1; 69 const static int STEPSIZE_LARGE = 9; 70 71 /** 72 * Constructs a new instance of this class given its parent 73 * and a style value describing its behavior and appearance. 74 * <p> 75 * The style value is either one of the style constants defined in 76 * class <code>SWT</code> which is applicable to instances of this 77 * class, or must be built by <em>bitwise OR</em>'ing together 78 * (that is, using the <code>int</code> "|" operator) two or more 79 * of those <code>SWT</code> style constants. The class description 80 * lists the style constants that are applicable to the class. 81 * Style bits are also inherited from superclasses. 82 * </p> 83 * 84 * @param parent a widget which will be the parent of the new instance (cannot be null) 85 * @param style the style of widget to construct 86 * 87 * @exception IllegalArgumentException <ul> 88 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> 89 * </ul> 90 * @exception SWTException <ul> 91 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> 92 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li> 93 * </ul> 94 * 95 * @see SWT#LEFT 96 * @see SWT#RIGHT 97 * @see SWT#UP 98 * @see SWT#DOWN 99 * @see SWT#RESIZE 100 * @see Widget#checkSubclass 101 * @see Widget#getStyle 102 */ 103 public this (Composite parent, int style) { 104 super (parent, checkStyle(style)); 105 this.parent = parent; 106 } 107 108 /** 109 * Constructs a new instance of this class given the display 110 * to create it on and a style value describing its behavior 111 * and appearance. 112 * <p> 113 * The style value is either one of the style constants defined in 114 * class <code>SWT</code> which is applicable to instances of this 115 * class, or must be built by <em>bitwise OR</em>'ing together 116 * (that is, using the <code>int</code> "|" operator) two or more 117 * of those <code>SWT</code> style constants. The class description 118 * lists the style constants that are applicable to the class. 119 * Style bits are also inherited from superclasses. 120 * </p><p> 121 * Note: Currently, null can be passed in for the display argument. 122 * This has the effect of creating the tracker on the currently active 123 * display if there is one. If there is no current display, the 124 * tracker is created on a "default" display. <b>Passing in null as 125 * the display argument is not considered to be good coding style, 126 * and may not be supported in a future release of SWT.</b> 127 * </p> 128 * 129 * @param display the display to create the tracker on 130 * @param style the style of control to construct 131 * 132 * @exception SWTException <ul> 133 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> 134 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li> 135 * </ul> 136 * 137 * @see SWT#LEFT 138 * @see SWT#RIGHT 139 * @see SWT#UP 140 * @see SWT#DOWN 141 */ 142 public this (Display display, int style) { 143 if (display is null) display = Display.getCurrent (); 144 if (display is null) display = Display.getDefault (); 145 if (!display.isValidThread ()) { 146 error (SWT.ERROR_THREAD_INVALID_ACCESS); 147 } 148 this.style = checkStyle (style); 149 this.display = display; 150 } 151 152 /** 153 * Adds the listener to the collection of listeners who will 154 * be notified when the control is moved or resized, by sending 155 * it one of the messages defined in the <code>ControlListener</code> 156 * interface. 157 * 158 * @param listener the listener which should be notified 159 * 160 * @exception IllegalArgumentException <ul> 161 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> 162 * </ul> 163 * @exception SWTException <ul> 164 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 165 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 166 * </ul> 167 * 168 * @see ControlListener 169 * @see #removeControlListener 170 */ 171 public void addControlListener(ControlListener listener) { 172 checkWidget(); 173 if (listener is null) error (SWT.ERROR_NULL_ARGUMENT); 174 TypedListener typedListener = new TypedListener (listener); 175 addListener (SWT.Resize, typedListener); 176 addListener (SWT.Move, typedListener); 177 } 178 179 /** 180 * Adds the listener to the collection of listeners who will 181 * be notified when keys are pressed and released on the system keyboard, by sending 182 * it one of the messages defined in the <code>KeyListener</code> 183 * interface. 184 * 185 * @param listener the listener which should be notified 186 * 187 * @exception IllegalArgumentException <ul> 188 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> 189 * </ul> 190 * @exception SWTException <ul> 191 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 192 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 193 * </ul> 194 * 195 * @see KeyListener 196 * @see #removeKeyListener 197 */ 198 public void addKeyListener(KeyListener listener) { 199 checkWidget(); 200 if (listener is null) error (SWT.ERROR_NULL_ARGUMENT); 201 TypedListener typedListener = new TypedListener (listener); 202 addListener(SWT.KeyUp,typedListener); 203 addListener(SWT.KeyDown,typedListener); 204 } 205 206 Point adjustMoveCursor () { 207 if (bounds is null) return null; 208 int newX = bounds.x + bounds.width / 2; 209 int newY = bounds.y; 210 211 Point point = display.map (parent, null, newX, newY); 212 display.setCursorLocation (point); 213 214 /* 215 * The call to XWarpPointer does not always place the pointer on the 216 * exact location that is specified, so do a query (below) to get the 217 * actual location of the pointer after it has been moved. 218 */ 219 int actualX, actualY, state; 220 OS.gdk_window_get_pointer (cast(GdkWindow*)window, &actualX, &actualY, &state); 221 return new Point (actualX, actualY); 222 } 223 224 Point adjustResizeCursor () { 225 if (bounds is null) return null; 226 int newX, newY; 227 228 if ((cursorOrientation & SWT.LEFT) !is 0) { 229 newX = bounds.x; 230 } else if ((cursorOrientation & SWT.RIGHT) !is 0) { 231 newX = bounds.x + bounds.width; 232 } else { 233 newX = bounds.x + bounds.width / 2; 234 } 235 236 if ((cursorOrientation & SWT.UP) !is 0) { 237 newY = bounds.y; 238 } else if ((cursorOrientation & SWT.DOWN) !is 0) { 239 newY = bounds.y + bounds.height; 240 } else { 241 newY = bounds.y + bounds.height / 2; 242 } 243 244 Point point = display.map (parent, null, newX, newY); 245 display.setCursorLocation (point); 246 247 /* 248 * The call to XWarpPointer does not always place the pointer on the 249 * exact location that is specified, so do a query (below) to get the 250 * actual location of the pointer after it has been moved. 251 */ 252 int actualX, actualY, state; 253 OS.gdk_window_get_pointer (window, &actualX, &actualY, &state); 254 return new Point (actualX, actualY ); 255 } 256 257 258 /** 259 * Stops displaying the tracker rectangles. Note that this is not considered 260 * to be a cancelation by the user. 261 * 262 * @exception SWTException <ul> 263 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 264 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 265 * </ul> 266 */ 267 public void close () { 268 checkWidget(); 269 tracking = false; 270 } 271 272 static int checkStyle (int style) { 273 if ((style & (SWT.LEFT | SWT.RIGHT | SWT.UP | SWT.DOWN)) is 0) { 274 style |= SWT.LEFT | SWT.RIGHT | SWT.UP | SWT.DOWN; 275 } 276 return style; 277 } 278 279 Rectangle computeBounds () { 280 if (rectangles.length is 0) return null; 281 int xMin = rectangles [0].x; 282 int yMin = rectangles [0].y; 283 int xMax = rectangles [0].x + rectangles [0].width; 284 int yMax = rectangles [0].y + rectangles [0].height; 285 286 for (int i = 1; i < rectangles.length; i++) { 287 if (rectangles [i].x < xMin) xMin = rectangles [i].x; 288 if (rectangles [i].y < yMin) yMin = rectangles [i].y; 289 int rectRight = rectangles [i].x + rectangles [i].width; 290 if (rectRight > xMax) xMax = rectRight; 291 int rectBottom = rectangles [i].y + rectangles [i].height; 292 if (rectBottom > yMax) yMax = rectBottom; 293 } 294 295 return new Rectangle (xMin, yMin, xMax - xMin, yMax - yMin); 296 } 297 298 Rectangle [] computeProportions (Rectangle [] rects) { 299 Rectangle [] result = new Rectangle [rects.length]; 300 bounds = computeBounds (); 301 if (bounds !is null) { 302 for (int i = 0; i < rects.length; i++) { 303 int x = 0, y = 0, width = 0, height = 0; 304 if (bounds.width !is 0) { 305 x = (rects [i].x - bounds.x) * 100 / bounds.width; 306 width = rects [i].width * 100 / bounds.width; 307 } else { 308 width = 100; 309 } 310 if (bounds.height !is 0) { 311 y = (rects [i].y - bounds.y) * 100 / bounds.height; 312 height = rects [i].height * 100 / bounds.height; 313 } else { 314 height = 100; 315 } 316 result [i] = new Rectangle (x, y, width, height); 317 } 318 } 319 return result; 320 } 321 322 void drawRectangles (Rectangle [] rects) { 323 auto window = OS.GDK_ROOT_PARENT (); 324 if (parent !is null) { 325 window = OS.GTK_WIDGET_WINDOW (parent.paintHandle()); 326 } 327 if (window is null) return; 328 auto gc = OS.gdk_gc_new (window); 329 if (gc is null) return; 330 auto colormap = OS.gdk_colormap_get_system (); 331 GdkColor color; 332 OS.gdk_color_white (colormap, &color); 333 OS.gdk_gc_set_foreground (gc, &color); 334 OS.gdk_gc_set_subwindow (gc, OS.GDK_INCLUDE_INFERIORS); 335 OS.gdk_gc_set_function (gc, OS.GDK_XOR); 336 for (int i=0; i<rects.length; i++) { 337 Rectangle rect = rects [i]; 338 int x = rect.x; 339 if (parent !is null && (parent.style & SWT.MIRRORED) !is 0) x = parent.getClientWidth () - rect.width - x; 340 OS.gdk_draw_rectangle (window, gc, 0, x, rect.y, rect.width, rect.height); 341 } 342 OS.g_object_unref (gc); 343 } 344 345 /** 346 * Returns the bounds that are being drawn, expressed relative to the parent 347 * widget. If the parent is a <code>Display</code> then these are screen 348 * coordinates. 349 * 350 * @return the bounds of the Rectangles being drawn 351 * 352 * @exception SWTException <ul> 353 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 354 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 355 * </ul> 356 */ 357 public Rectangle [] getRectangles () { 358 checkWidget(); 359 Rectangle [] result = new Rectangle [rectangles.length]; 360 for (int i = 0; i < rectangles.length; i++) { 361 Rectangle current = rectangles [i]; 362 result [i] = new Rectangle (current.x, current.y, current.width, current.height); 363 } 364 return result; 365 } 366 367 /** 368 * Returns <code>true</code> if the rectangles are drawn with a stippled line, <code>false</code> otherwise. 369 * 370 * @return the stippled effect of the rectangles 371 * 372 * @exception SWTException <ul> 373 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 374 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 375 * </ul> 376 */ 377 public bool getStippled () { 378 checkWidget(); 379 return stippled; 380 } 381 382 bool grab () { 383 auto cursor = this.cursor !is null ? this.cursor.handle : null; 384 int result = OS.gdk_pointer_grab (window, false, OS.GDK_POINTER_MOTION_MASK | OS.GDK_BUTTON_RELEASE_MASK, window, cursor, OS.GDK_CURRENT_TIME); 385 return result is OS.GDK_GRAB_SUCCESS; 386 } 387 388 override int gtk_button_release_event (GtkWidget* widget, GdkEventButton* event) { 389 return gtk_mouse (OS.GDK_BUTTON_RELEASE, widget, event); 390 } 391 392 override int gtk_key_press_event (GtkWidget* widget, GdkEventKey* keyEvent) { 393 auto result = super.gtk_key_press_event (widget, keyEvent); 394 if (result !is 0) return result; 395 int stepSize = ((keyEvent.state & OS.GDK_CONTROL_MASK) !is 0) ? STEPSIZE_SMALL : STEPSIZE_LARGE; 396 int xChange = 0, yChange = 0; 397 switch (keyEvent.keyval) { 398 case OS.GDK_Escape: 399 cancelled = true; 400 goto case OS.GDK_Return; 401 case OS.GDK_Return: 402 tracking = false; 403 break; 404 case OS.GDK_Left: 405 xChange = -stepSize; 406 break; 407 case OS.GDK_Right: 408 xChange = stepSize; 409 break; 410 case OS.GDK_Up: 411 yChange = -stepSize; 412 break; 413 case OS.GDK_Down: 414 yChange = stepSize; 415 break; 416 default: 417 } 418 if (xChange !is 0 || yChange !is 0) { 419 Rectangle [] oldRectangles = rectangles; 420 Rectangle [] rectsToErase = new Rectangle [rectangles.length]; 421 for (int i = 0; i < rectangles.length; i++) { 422 Rectangle current = rectangles [i]; 423 rectsToErase [i] = new Rectangle (current.x, current.y, current.width, current.height); 424 } 425 Event event = new Event (); 426 event.x = oldX + xChange; 427 event.y = oldY + yChange; 428 if (parent !is null && (parent.style & SWT.MIRRORED) !is 0) { 429 event.x = parent.getClientWidth () - event.width - event.x; 430 } 431 if ((style & SWT.RESIZE) !is 0) { 432 resizeRectangles (xChange, yChange); 433 sendEvent (SWT.Resize, event); 434 /* 435 * It is possible (but unlikely) that application 436 * code could have disposed the widget in the resize 437 * event. If this happens return false to indicate 438 * that the tracking has failed. 439 */ 440 if (isDisposed ()) { 441 cancelled = true; 442 return 1; 443 } 444 bool draw = false; 445 /* 446 * It is possible that application code could have 447 * changed the rectangles in the resize event. If this 448 * happens then only redraw the tracker if the rectangle 449 * values have changed. 450 */ 451 if (rectangles !is oldRectangles) { 452 ptrdiff_t length = rectangles.length; 453 if (length !is rectsToErase.length) { 454 draw = true; 455 } else { 456 for (int i = 0; i < length; i++) { 457 if (rectangles [i] !=/*eq*/ rectsToErase [i]) { 458 draw = true; 459 break; 460 } 461 } 462 } 463 } else { 464 draw = true; 465 } 466 if (draw) { 467 drawRectangles (rectsToErase); 468 update (); 469 drawRectangles (rectangles); 470 } 471 Point cursorPos = adjustResizeCursor (); 472 if (cursorPos !is null) { 473 oldX = cursorPos.x; 474 oldY = cursorPos.y; 475 } 476 } else { 477 moveRectangles (xChange, yChange); 478 sendEvent (SWT.Move, event); 479 /* 480 * It is possible (but unlikely) that application 481 * code could have disposed the widget in the move 482 * event. If this happens return false to indicate 483 * that the tracking has failed. 484 */ 485 if (isDisposed ()) { 486 cancelled = true; 487 return 1; 488 } 489 bool draw = false; 490 /* 491 * It is possible that application code could have 492 * changed the rectangles in the move event. If this 493 * happens then only redraw the tracker if the rectangle 494 * values have changed. 495 */ 496 if (rectangles !is oldRectangles) { 497 ptrdiff_t length = rectangles.length; 498 if (length !is rectsToErase.length) { 499 draw = true; 500 } else { 501 for (int i = 0; i < length; i++) { 502 if (rectangles [i] !=/*eq*/ rectsToErase [i]) { 503 draw = true; 504 break; 505 } 506 } 507 } 508 } else { 509 draw = true; 510 } 511 if (draw) { 512 drawRectangles (rectsToErase); 513 update (); 514 drawRectangles (rectangles); 515 } 516 Point cursorPos = adjustMoveCursor (); 517 if (cursorPos !is null) { 518 oldX = cursorPos.x; 519 oldY = cursorPos.y; 520 } 521 } 522 } 523 return result; 524 } 525 526 override int gtk_motion_notify_event (GtkWidget* widget, GdkEventMotion* eventPtr) { 527 auto cursor = this.cursor !is null ? this.cursor.handle : null; 528 if (cursor !is lastCursor) { 529 ungrab (); 530 grabbed = grab (); 531 lastCursor = cursor; 532 } 533 return gtk_mouse (OS.GDK_MOTION_NOTIFY, widget, eventPtr); 534 } 535 536 private int gtk_mouse (int eventType, GtkWidget* widget, void* eventPtr) { 537 int newX, newY; 538 OS.gdk_window_get_pointer (window, &newX, &newY, null); 539 if (oldX !is newX || oldY !is newY ) { 540 Rectangle [] oldRectangles = rectangles; 541 Rectangle [] rectsToErase = new Rectangle [rectangles.length]; 542 for (int i = 0; i < rectangles.length; i++) { 543 Rectangle current = rectangles [i]; 544 rectsToErase [i] = new Rectangle (current.x, current.y, current.width, current.height); 545 } 546 Event event = new Event (); 547 if (parent is null) { 548 event.x = newX ; 549 event.y = newY ; 550 } else { 551 Point screenCoord = display.map (parent, null, newX , newY ); 552 event.x = screenCoord.x; 553 event.y = screenCoord.y; 554 } 555 if ((style & SWT.RESIZE) !is 0) { 556 resizeRectangles (newX - oldX, newY - oldY); 557 sendEvent (SWT.Resize, event); 558 /* 559 * It is possible (but unlikely), that application 560 * code could have disposed the widget in the resize 561 * event. If this happens, return false to indicate 562 * that the tracking has failed. 563 */ 564 if (isDisposed ()) { 565 cancelled = true; 566 return 1; 567 } 568 bool draw = false; 569 /* 570 * It is possible that application code could have 571 * changed the rectangles in the resize event. If this 572 * happens then only redraw the tracker if the rectangle 573 * values have changed. 574 */ 575 if (rectangles !is oldRectangles) { 576 ptrdiff_t length = rectangles.length; 577 if (length !is rectsToErase.length) { 578 draw = true; 579 } else { 580 for (int i = 0; i < length; i++) { 581 if (rectangles [i] !=/*eq*/ rectsToErase [i]) { 582 draw = true; 583 break; 584 } 585 } 586 } 587 } else { 588 draw = true; 589 } 590 if (draw) { 591 drawRectangles (rectsToErase); 592 update (); 593 drawRectangles (rectangles); 594 } 595 Point cursorPos = adjustResizeCursor (); 596 if (cursorPos !is null) { 597 newX = cursorPos.x; 598 newY = cursorPos.y; 599 } 600 } else { 601 moveRectangles (newX - oldX, newY - oldY); 602 sendEvent (SWT.Move, event); 603 /* 604 * It is possible (but unlikely), that application 605 * code could have disposed the widget in the move 606 * event. If this happens, return false to indicate 607 * that the tracking has failed. 608 */ 609 if (isDisposed ()) { 610 cancelled = true; 611 return 1; 612 } 613 bool draw = false; 614 /* 615 * It is possible that application code could have 616 * changed the rectangles in the move event. If this 617 * happens then only redraw the tracker if the rectangle 618 * values have changed. 619 */ 620 if (rectangles !is oldRectangles) { 621 ptrdiff_t length = rectangles.length; 622 if (length !is rectsToErase.length) { 623 draw = true; 624 } else { 625 for (int i = 0; i < length; i++) { 626 if (rectangles [i] !=/*eq*/ rectsToErase [i]) { 627 draw = true; 628 break; 629 } 630 } 631 } 632 } else { 633 draw = true; 634 } 635 if (draw) { 636 drawRectangles (rectsToErase); 637 update (); 638 drawRectangles (rectangles); 639 } 640 } 641 oldX = newX ; 642 oldY = newY ; 643 } 644 tracking = eventType !is OS.GDK_BUTTON_RELEASE; 645 return 0; 646 } 647 648 void moveRectangles (int xChange, int yChange) { 649 if (bounds is null) return; 650 if (xChange < 0 && ((style & SWT.LEFT) is 0)) xChange = 0; 651 if (xChange > 0 && ((style & SWT.RIGHT) is 0)) xChange = 0; 652 if (yChange < 0 && ((style & SWT.UP) is 0)) yChange = 0; 653 if (yChange > 0 && ((style & SWT.DOWN) is 0)) yChange = 0; 654 if (xChange is 0 && yChange is 0) return; 655 if (parent !is null && (parent.style & SWT.MIRRORED) !is 0) xChange *= -1; 656 bounds.x += xChange; bounds.y += yChange; 657 for (int i = 0; i < rectangles.length; i++) { 658 rectangles [i].x += xChange; 659 rectangles [i].y += yChange; 660 } 661 } 662 663 /** 664 * Displays the Tracker rectangles for manipulation by the user. Returns when 665 * the user has either finished manipulating the rectangles or has cancelled the 666 * Tracker. 667 * 668 * @return <code>true</code> if the user did not cancel the Tracker, <code>false</code> otherwise 669 * 670 * @exception SWTException <ul> 671 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 672 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 673 * </ul> 674 */ 675 public bool open () { 676 checkWidget(); 677 window = OS.GDK_ROOT_PARENT (); 678 if (parent !is null) { 679 window = OS.GTK_WIDGET_WINDOW (parent.paintHandle()); 680 } 681 if (window is null) return false; 682 cancelled = false; 683 tracking = true; 684 update (); 685 drawRectangles (rectangles); 686 int oldX, oldY, state; 687 OS.gdk_window_get_pointer (window, &oldX, &oldY, &state); 688 689 /* 690 * if exactly one of UP/DOWN is specified as a style then set the cursor 691 * orientation accordingly (the same is done for LEFT/RIGHT styles below) 692 */ 693 int vStyle = style & (SWT.UP | SWT.DOWN); 694 if (vStyle is SWT.UP || vStyle is SWT.DOWN) { 695 cursorOrientation |= vStyle; 696 } 697 int hStyle = style & (SWT.LEFT | SWT.RIGHT); 698 if (hStyle is SWT.LEFT || hStyle is SWT.RIGHT) { 699 cursorOrientation |= hStyle; 700 } 701 702 int mask = OS.GDK_BUTTON1_MASK | OS.GDK_BUTTON2_MASK | OS.GDK_BUTTON3_MASK; 703 bool mouseDown = (state & mask) !is 0; 704 if (!mouseDown) { 705 Point cursorPos = null; 706 if ((style & SWT.RESIZE) !is 0) { 707 cursorPos = adjustResizeCursor (); 708 } else { 709 cursorPos = adjustMoveCursor (); 710 } 711 if (cursorPos !is null) { 712 oldX = cursorPos.x; 713 oldY = cursorPos.y; 714 } 715 } 716 this.oldX = oldX ; 717 this.oldY = oldY ; 718 719 grabbed = grab (); 720 lastCursor = this.cursor !is null ? this.cursor.handle : null; 721 722 /* Tracker behaves like a Dialog with its own OS event loop. */ 723 GdkEvent* gdkEvent; 724 while (tracking) { 725 if (parent !is null && parent.isDisposed ()) break; 726 GdkEvent* eventPtr; 727 while (true) { 728 eventPtr = OS.gdk_event_get (); 729 if (eventPtr !is null) { 730 break; 731 } else { 732 try { Thread.sleep(50); } catch (Exception ex) {} 733 } 734 } 735 gdkEvent = eventPtr; 736 auto widget = OS.gtk_get_event_widget (eventPtr); 737 switch (gdkEvent.type) { 738 case OS.GDK_MOTION_NOTIFY: gtk_motion_notify_event (widget, cast(GdkEventMotion*)eventPtr); break; 739 case OS.GDK_BUTTON_RELEASE: gtk_button_release_event (widget, cast(GdkEventButton*)eventPtr); break; 740 case OS.GDK_KEY_PRESS: gtk_key_press_event (widget, cast(GdkEventKey*)eventPtr); break; 741 case OS.GDK_KEY_RELEASE: gtk_key_release_event (widget, cast(GdkEventKey*)eventPtr); break; 742 case OS.GDK_BUTTON_PRESS: 743 case OS.GDK_2BUTTON_PRESS: 744 case OS.GDK_3BUTTON_PRESS: 745 case OS.GDK_ENTER_NOTIFY: 746 case OS.GDK_LEAVE_NOTIFY: 747 /* Do not dispatch these */ 748 break; 749 case OS.GDK_EXPOSE: 750 update (); 751 drawRectangles (rectangles); 752 OS.gtk_main_do_event (eventPtr); 753 drawRectangles (rectangles); 754 break; 755 default: 756 OS.gtk_main_do_event (eventPtr); 757 } 758 OS.gdk_event_free (eventPtr); 759 } 760 if (!isDisposed ()) { 761 update (); 762 drawRectangles (rectangles); 763 } 764 ungrab (); 765 window = null; 766 return !cancelled; 767 } 768 769 override void releaseWidget () { 770 super.releaseWidget (); 771 parent = null; 772 rectangles = proportions = null; 773 bounds = null; 774 } 775 776 /** 777 * Removes the listener from the collection of listeners who will 778 * be notified when the control is moved or resized. 779 * 780 * @param listener the listener which should no longer be notified 781 * 782 * @exception IllegalArgumentException <ul> 783 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> 784 * </ul> 785 * @exception SWTException <ul> 786 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 787 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 788 * </ul> 789 * 790 * @see ControlListener 791 * @see #addControlListener 792 */ 793 public void removeControlListener (ControlListener listener) { 794 checkWidget(); 795 if (listener is null) error (SWT.ERROR_NULL_ARGUMENT); 796 if (eventTable is null) return; 797 eventTable.unhook (SWT.Resize, listener); 798 eventTable.unhook (SWT.Move, listener); 799 } 800 801 /** 802 * Removes the listener from the collection of listeners who will 803 * be notified when keys are pressed and released on the system keyboard. 804 * 805 * @param listener the listener which should no longer be notified 806 * 807 * @exception IllegalArgumentException <ul> 808 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> 809 * </ul> 810 * @exception SWTException <ul> 811 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 812 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 813 * </ul> 814 * 815 * @see KeyListener 816 * @see #addKeyListener 817 */ 818 public void removeKeyListener(KeyListener listener) { 819 checkWidget(); 820 if (listener is null) error (SWT.ERROR_NULL_ARGUMENT); 821 if (eventTable is null) return; 822 eventTable.unhook (SWT.KeyUp, listener); 823 eventTable.unhook (SWT.KeyDown, listener); 824 } 825 826 void resizeRectangles (int xChange, int yChange) { 827 if (bounds is null) return; 828 if (parent !is null && (parent.style & SWT.MIRRORED) !is 0) xChange *= -1; 829 /* 830 * If the cursor orientation has not been set in the orientation of 831 * this change then try to set it here. 832 */ 833 if (xChange < 0 && ((style & SWT.LEFT) !is 0) && ((cursorOrientation & SWT.RIGHT) is 0)) { 834 cursorOrientation |= SWT.LEFT; 835 } 836 if (xChange > 0 && ((style & SWT.RIGHT) !is 0) && ((cursorOrientation & SWT.LEFT) is 0)) { 837 cursorOrientation |= SWT.RIGHT; 838 } 839 if (yChange < 0 && ((style & SWT.UP) !is 0) && ((cursorOrientation & SWT.DOWN) is 0)) { 840 cursorOrientation |= SWT.UP; 841 } 842 if (yChange > 0 && ((style & SWT.DOWN) !is 0) && ((cursorOrientation & SWT.UP) is 0)) { 843 cursorOrientation |= SWT.DOWN; 844 } 845 846 /* 847 * If the bounds will flip about the x or y axis then apply the adjustment 848 * up to the axis (ie.- where bounds width/height becomes 0), change the 849 * cursor's orientation accordingly, and flip each Rectangle's origin (only 850 * necessary for > 1 Rectangles) 851 */ 852 if ((cursorOrientation & SWT.LEFT) !is 0) { 853 if (xChange > bounds.width) { 854 if ((style & SWT.RIGHT) is 0) return; 855 cursorOrientation |= SWT.RIGHT; 856 cursorOrientation &= ~SWT.LEFT; 857 bounds.x += bounds.width; 858 xChange -= bounds.width; 859 bounds.width = 0; 860 if (proportions.length > 1) { 861 for (int i = 0; i < proportions.length; i++) { 862 Rectangle proportion = proportions [i]; 863 proportion.x = 100 - proportion.x - proportion.width; 864 } 865 } 866 } 867 } else if ((cursorOrientation & SWT.RIGHT) !is 0) { 868 if (bounds.width < -xChange) { 869 if ((style & SWT.LEFT) is 0) return; 870 cursorOrientation |= SWT.LEFT; 871 cursorOrientation &= ~SWT.RIGHT; 872 xChange += bounds.width; 873 bounds.width = 0; 874 if (proportions.length > 1) { 875 for (int i = 0; i < proportions.length; i++) { 876 Rectangle proportion = proportions [i]; 877 proportion.x = 100 - proportion.x - proportion.width; 878 } 879 } 880 } 881 } 882 if ((cursorOrientation & SWT.UP) !is 0) { 883 if (yChange > bounds.height) { 884 if ((style & SWT.DOWN) is 0) return; 885 cursorOrientation |= SWT.DOWN; 886 cursorOrientation &= ~SWT.UP; 887 bounds.y += bounds.height; 888 yChange -= bounds.height; 889 bounds.height = 0; 890 if (proportions.length > 1) { 891 for (int i = 0; i < proportions.length; i++) { 892 Rectangle proportion = proportions [i]; 893 proportion.y = 100 - proportion.y - proportion.height; 894 } 895 } 896 } 897 } else if ((cursorOrientation & SWT.DOWN) !is 0) { 898 if (bounds.height < -yChange) { 899 if ((style & SWT.UP) is 0) return; 900 cursorOrientation |= SWT.UP; 901 cursorOrientation &= ~SWT.DOWN; 902 yChange += bounds.height; 903 bounds.height = 0; 904 if (proportions.length > 1) { 905 for (int i = 0; i < proportions.length; i++) { 906 Rectangle proportion = proportions [i]; 907 proportion.y = 100 - proportion.y - proportion.height; 908 } 909 } 910 } 911 } 912 913 // apply the bounds adjustment 914 if ((cursorOrientation & SWT.LEFT) !is 0) { 915 bounds.x += xChange; 916 bounds.width -= xChange; 917 } else if ((cursorOrientation & SWT.RIGHT) !is 0) { 918 bounds.width += xChange; 919 } 920 if ((cursorOrientation & SWT.UP) !is 0) { 921 bounds.y += yChange; 922 bounds.height -= yChange; 923 } else if ((cursorOrientation & SWT.DOWN) !is 0) { 924 bounds.height += yChange; 925 } 926 927 Rectangle [] newRects = new Rectangle [rectangles.length]; 928 for (int i = 0; i < rectangles.length; i++) { 929 Rectangle proportion = proportions[i]; 930 newRects[i] = new Rectangle ( 931 proportion.x * bounds.width / 100 + bounds.x, 932 proportion.y * bounds.height / 100 + bounds.y, 933 proportion.width * bounds.width / 100, 934 proportion.height * bounds.height / 100); 935 } 936 rectangles = newRects; 937 } 938 939 /** 940 * Sets the <code>Cursor</code> of the Tracker. If this cursor is <code>null</code> 941 * then the cursor reverts to the default. 942 * 943 * @param newCursor the new <code>Cursor</code> to display 944 * 945 * @exception SWTException <ul> 946 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 947 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 948 * </ul> 949 */ 950 public void setCursor (Cursor value) { 951 checkWidget (); 952 cursor = value; 953 } 954 955 /** 956 * Specifies the rectangles that should be drawn, expressed relative to the parent 957 * widget. If the parent is a Display then these are screen coordinates. 958 * 959 * @param rectangles the bounds of the rectangles to be drawn 960 * 961 * @exception IllegalArgumentException <ul> 962 * <li>ERROR_NULL_ARGUMENT - if the set of rectangles contains a null rectangle</li> 963 * </ul> 964 * @exception SWTException <ul> 965 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 966 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 967 * </ul> 968 */ 969 public void setRectangles (Rectangle [] rectangles) { 970 checkWidget(); 971 // SWT extension: allow null for zero length string 972 //if (rectangles is null) error (SWT.ERROR_NULL_ARGUMENT); 973 ptrdiff_t length = rectangles.length; 974 this.rectangles = new Rectangle [length]; 975 for (int i = 0; i < length; i++) { 976 Rectangle current = rectangles [i]; 977 if (current is null) error (SWT.ERROR_NULL_ARGUMENT); 978 this.rectangles [i] = new Rectangle (current.x, current.y, current.width, current.height); 979 } 980 proportions = computeProportions (rectangles); 981 } 982 983 /** 984 * Changes the appearance of the line used to draw the rectangles. 985 * 986 * @param stippled <code>true</code> if rectangle should appear stippled 987 * 988 * @exception SWTException <ul> 989 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> 990 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> 991 * </ul> 992 */ 993 public void setStippled (bool stippled) { 994 checkWidget(); 995 this.stippled = stippled; 996 } 997 998 void ungrab () { 999 if (grabbed) OS.gdk_pointer_ungrab (OS.GDK_CURRENT_TIME); 1000 } 1001 1002 void update () { 1003 if (parent !is null) { 1004 if (parent.isDisposed ()) return; 1005 parent.getShell ().update (); 1006 } else { 1007 display.update (); 1008 } 1009 } 1010 1011 }