blob: 9d9e66a476ea9cf87b5f3aa6a4dd454626c3cbf3 [file] [log] [blame]
package org.eclipse.swt.widgets;
/*
* Copyright (c) 2000, 2002 IBM Corp. All rights reserved.
* This file is made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*/
import org.eclipse.swt.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.gtk.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.events.*;
/**
* Instances of this class implement rubber banding rectangles.
*
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>(none)</dd>
* <dt><b>Events:</b></dt>
* <dd>Move</dd>
* </dl>
* <p>
* IMPORTANT: This class is <em>not</em> intended to be subclassed.
* </p>
*/
public class Tracker extends Widget {
Composite parent;
Display display;
boolean tracking, stippled;
Rectangle [] rectangles = new Rectangle [0];
/**
* Constructs a new instance of this class given its parent
* and a style value describing its behavior and appearance.
* <p>
* The style value is either one of the style constants defined in
* class <code>SWT</code> which is applicable to instances of this
* class, or must be built by <em>bitwise OR</em>'ing together
* (that is, using the <code>int</code> "|" operator) two or more
* of those <code>SWT</code> style constants. The class description
* for all SWT widget classes should include a comment which
* describes the style constants which are applicable to the class.
* </p>
*
* @param parent a widget which will be the parent of the new instance (cannot be null)
* @param style the style of widget to construct
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*
* @see SWT
* @see Widget#checkSubclass
* @see Widget#getStyle
*/
public Tracker (Composite parent, int style) {
super (parent, style);
this.parent = parent;
display = parent.getDisplay ();
}
/**
* Constructs a new instance of this class given the display
* to create it on and a style value describing its behavior
* and appearance.
* <p>
* The style value is either one of the style constants defined in
* class <code>SWT</code> which is applicable to instances of this
* class, or must be built by <em>bitwise OR</em>'ing together
* (that is, using the <code>int</code> "|" operator) two or more
* of those <code>SWT</code> style constants. The class description
* for all SWT widget classes should include a comment which
* describes the style constants which are applicable to the class.
* </p><p>
* Note: Currently, null can be passed in for the display argument.
* This has the effect of creating the tracker on the currently active
* display if there is one. If there is no current display, the
* tracker is created on a "default" display. <b>Passing in null as
* the display argument is not considered to be good coding style,
* and may not be supported in a future release of SWT.</b>
* </p>
*
* @param display the display to create the tracker on
* @param style the style of control to construct
*
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*/
public Tracker (Display display, int style) {
if (display == null) display = Display.getCurrent ();
if (display == null) display = Display.getDefault ();
if (!display.isValidThread ()) {
error (SWT.ERROR_THREAD_INVALID_ACCESS);
}
this.style = style;
this.display = display;
}
/*
* === ADD / REMOVE LISTENERS ===
*/
/**
* Adds the listener to the collection of listeners who will
* be notified when the control is moved or resized, by sending
* it one of the messages defined in the <code>ControlListener</code>
* interface.
*
* @param listener the listener which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see ControlListener
* @see #removeControlListener
*/
public void addControlListener(ControlListener listener) {
checkWidget();
if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
TypedListener typedListener = new TypedListener (listener);
addListener (SWT.Move,typedListener);
}
/**
* Removes the listener from the collection of listeners who will
* be notified when the control is moved or resized.
*
* @param listener the listener which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see ControlListener
* @see #addControlListener
*/
public void removeControlListener (ControlListener listener) {
checkWidget();
if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
if (eventTable == null) return;
eventTable.unhook (SWT.Move, listener);
}
/*
* === PUBLIC ACCESSORS ===
*/
public Display getDisplay () {
return display;
}
/**
* Returns the bounds that are being drawn, expressed relative to the parent
* widget. If the parent is a Display then these are screen coordinates.
*
* @return the bounds of the Rectangles being drawn
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public Rectangle [] getRectangles () {
checkWidget();
return rectangles;
}
/**
* Returns <code>true</code> if the rectangles are drawn with a stippled line, <code>false</code> otherwise.
*
* @return the stippled effect of the rectangles
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public boolean getStippled () {
checkWidget();
return stippled;
}
/**
* Specify the rectangles that should be drawn, expressed relative to the parent
* widget. If the parent is a Display then these are screen coordinates.
*
* @param rectangles the bounds of the rectangles to be drawn
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setRectangles (Rectangle [] rectangles) {
checkWidget();
this.rectangles = rectangles;
}
/**
* Change the appearance of the line used to draw the rectangles.
*
* @param stippled <code>true</code> if rectangle should appear stippled
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setStippled (boolean stippled) {
checkWidget();
this.stippled = stippled;
}
/*
* === PUBLIC FUNCTIONALITY ===
*/
/**
* Stop displaying the tracker rectangles.
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void close () {
checkWidget();
tracking = false;
}
/**
* Start displaying the Tracker rectangles.
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public boolean open () {
checkWidget();
int xWindow = calculateWindow();
boolean cancelled=false;
tracking = true;
drawRectangles ();
int[] newX = new int[1];
int[] newY = new int[1];
int[] oldX = new int[1];
int[] oldY = new int[1];
OS.gdk_window_get_pointer(xWindow, oldX,oldY, 0);
while (tracking) {
if (parent != null && parent.isDisposed ()) break;
int eventType = waitEvent();
switch (eventType) {
case OS.GDK_BUTTON_RELEASE:
case OS.GDK_MOTION_NOTIFY:
OS.gdk_window_get_pointer(xWindow, newX,newY, 0);
if (oldX [0] != newX [0] || oldY [0] != newY [0]) {
drawRectangles ();
for (int i=0; i<rectangles.length; i++) {
rectangles [i].x += newX [0] - oldX [0];
rectangles [i].y += newY [0] - oldY [0];
}
Event event = new Event();
event.x = newX[0];
event.y = newY[0];
sendEvent (SWT.Move,event);
drawRectangles ();
oldX [0] = newX [0]; oldY [0] = newY [0];
}
tracking = (eventType != OS.GDK_BUTTON_RELEASE);
break;
case OS.GDK_KEY_PRESS:
// error(SWT.ERROR_NOT_IMPLEMENTED);
/*
XKeyEvent keyEvent = new XKeyEvent ();
OS.memmove (keyEvent, xEvent, XKeyEvent.sizeof);
if (keyEvent.keycode != 0) {
int [] keysym = new int [1];
OS.XLookupString (keyEvent, null, 0, keysym, null);
keysym [0] &= 0xFFFF;
tracking = keysym [0] != OS.XK_Escape && keysym [0] != OS.XK_Cancel;
cancelled = !tracking;
}*/
break;
} // switch
} // while
drawRectangles(); // clean up our mess
tracking = false;
return !cancelled;
}
private void drawRectangles () {
int xWindow = calculateWindow();
if (parent != null) {
if (parent.isDisposed ()) return;
parent.getShell ().update ();
} else {
display.update ();
}
int gc = OS.gdk_gc_new(xWindow);
if (gc==0) error(SWT.ERROR_UNSPECIFIED);
/* White foreground */
int colormap = OS.gdk_colormap_get_system();
GdkColor color = new GdkColor();
OS.gdk_color_white(colormap, color);
OS.gdk_gc_set_foreground(gc, color);
/* Draw on top of inferior widgets */
OS.gdk_gc_set_subwindow(gc, OS.GDK_INCLUDE_INFERIORS);
/* XOR */
OS.gdk_gc_set_function(gc, OS.GDK_XOR);
for (int i=0; i<rectangles.length; i++) {
Rectangle rect = rectangles [i];
OS.gdk_draw_rectangle(xWindow, gc, 0, rect.x, rect.y, rect.width, rect.height);
}
OS.gdk_gc_destroy(gc);
}
/*
* Wait for an event to show up.
* Return the event's type as a GdkEventType.
*/
private int waitEvent() {
int[] eventType = new int[1];
int eventPtr;
while (true) {
eventPtr = OS.gdk_event_get();
if (eventPtr != 0) {
// hack, must implement memmove properly
// GdkEvent event = new GdkEvent(eventPtr);
OS.memmove(eventType, eventPtr, 4);
OS.gdk_event_free(eventPtr);
return eventType[0];
}
else {
try { Thread.sleep(50); } catch (Exception ex) {}
}
}
}
/*
* Figure which GdkWindow we'll draw on.
* That's normally the root X window, or the parent's GdkWindow if we have a parent.
*/
private int calculateWindow() {
int answer;
if (parent == null) {
answer = OS.GDK_ROOT_PARENT();
} else {
answer = parent._gdkWindow();
}
if (answer==0) error(SWT.ERROR_UNSPECIFIED);
return answer;
}
public void setCursor (Cursor value) {
}
}