Mercurial > projects > dwt-addons
comparison dwtx/draw2d/Clickable.d @ 98:95307ad235d9
Added Draw2d code, still work in progress
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sun, 03 Aug 2008 00:52:14 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
96:b492ba44e44d | 98:95307ad235d9 |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2000, 2005 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 dwtx.draw2d.Clickable; | |
14 | |
15 import dwt.dwthelper.utils; | |
16 | |
17 import dwtx.dwtxhelper.Collection; | |
18 | |
19 import dwtx.draw2d.geometry.Rectangle; | |
20 import dwtx.draw2d.Figure; | |
21 import dwtx.draw2d.ActionListener; | |
22 import dwtx.draw2d.ActionEvent; | |
23 import dwtx.draw2d.ChangeListener; | |
24 import dwtx.draw2d.ClickableEventHandler; | |
25 import dwtx.draw2d.IFigure; | |
26 import dwtx.draw2d.ButtonModel; | |
27 import dwtx.draw2d.ChangeEvent; | |
28 import dwtx.draw2d.Graphics; | |
29 import dwtx.draw2d.ToggleModel; | |
30 import dwtx.draw2d.StackLayout; | |
31 import dwtx.draw2d.ColorConstants; | |
32 import dwtx.draw2d.ButtonBorder; | |
33 | |
34 | |
35 /** | |
36 * A Clickable responds to mouse clicks in some way (determined by a ClickBehavior) and | |
37 * fires action events. No visual appearance of feedback is offered. Depends on a model | |
38 * holder and an event handler which understands the model and updates the model | |
39 * accordingly. {@link ButtonModel} is used by default. Any figure can be set as contents | |
40 * to a Clickable. Clickable->EventHandler->Model->ModelObserver->Listeners of actions. | |
41 */ | |
42 public class Clickable | |
43 : Figure | |
44 { | |
45 | |
46 private static const int | |
47 ROLLOVER_ENABLED_FLAG = Figure.MAX_FLAG << 1, | |
48 STYLE_BUTTON_FLAG = Figure.MAX_FLAG << 2, | |
49 STYLE_TOGGLE_FLAG = Figure.MAX_FLAG << 3; | |
50 | |
51 /** | |
52 * The highest reserved flag used by this class | |
53 */ | |
54 protected static int | |
55 MAX_FLAG = STYLE_TOGGLE_FLAG; | |
56 | |
57 /** | |
58 * Style constant that defines a push button. The button will be pressed when the mouse is | |
59 * pressed on top of it. The button will be released when the mouse is released or is move | |
60 * off of the button. | |
61 * | |
62 */ | |
63 public static const int STYLE_BUTTON = STYLE_BUTTON_FLAG; | |
64 | |
65 /** | |
66 * Style constant that defines a toggle button. The button will toggle between 2 states | |
67 * when the mouse is clicked on the button. | |
68 */ | |
69 public static const int STYLE_TOGGLE = STYLE_TOGGLE_FLAG; | |
70 | |
71 /** | |
72 * An action is performed every time the mouse is released. | |
73 */ | |
74 public static const int DEFAULT_FIRING = 0; | |
75 | |
76 /** | |
77 * Firing starts as soon as the mouse is pressed on this Clickable, and keeps firing at | |
78 * prefixed intervals until the mouse is released. | |
79 */ | |
80 public static const int REPEAT_FIRING = 1; | |
81 | |
82 /** | |
83 * Observes the model for action and state changes. | |
84 * | |
85 * @see ActionListener | |
86 * @see ChangeListener | |
87 */ | |
88 /+static+/ interface ModelObserver | |
89 : ActionListener, ChangeListener | |
90 { } | |
91 | |
92 private ClickableEventHandler eventHandler; | |
93 | |
94 private ButtonModel model; | |
95 | |
96 private ModelObserver modelObserver; | |
97 | |
98 private void instanceInit(){ | |
99 init(); | |
100 setRequestFocusEnabled(true); | |
101 setFocusTraversable(true); | |
102 } | |
103 | |
104 /** | |
105 * Constructs a Clickable with no contents. | |
106 */ | |
107 public this() { | |
108 instanceInit(); | |
109 } | |
110 | |
111 /** | |
112 * Constructs a Clickable whose contents are provided as input. The content figure | |
113 * occupies the entire region of the Clickable. | |
114 * | |
115 * @param contents The content figure | |
116 */ | |
117 public this(IFigure contents) { | |
118 this(contents, 0); | |
119 } | |
120 | |
121 /** | |
122 * Constructs a Clickable whose contents are provided as input. The content figure | |
123 * occupies the entire region of the Clickable. Sets the style to the given <i>style</i> | |
124 * (either {@link #STYLE_BUTTON} or {@link #STYLE_TOGGLE}). | |
125 * | |
126 * @param contents The content figure | |
127 * @param style The button style | |
128 */ | |
129 public this(IFigure contents, int style) { | |
130 instanceInit(); | |
131 setContents(contents); | |
132 setStyle(style); | |
133 } | |
134 | |
135 /** | |
136 * Adds the given listener to the list of action listeners of this Figure. Listener is | |
137 * called whenever an action is performed. | |
138 * | |
139 * @param listener The ActionListener to be added | |
140 * @since 2.0 | |
141 */ | |
142 public void addActionListener(ActionListener listener) { | |
143 addListener(ActionListener.classinfo, cast(Object)listener); | |
144 } | |
145 | |
146 /** | |
147 * Adds the given listener to the list of state change listeners of this figure. A | |
148 * ChangeListener is informed if there is any state change in the model requiring action | |
149 * by the listener. | |
150 * | |
151 * @param listener The ChangeListener to be added | |
152 * @since 2.0 | |
153 */ | |
154 public void addChangeListener(ChangeListener listener) { | |
155 addListener(ChangeListener.classinfo, cast(Object)listener); | |
156 } | |
157 | |
158 /** | |
159 * Returns a newly created ButtonModel as the default model to be used by this Clickable | |
160 * based on the button style. | |
161 * | |
162 * @return The model to be used by default | |
163 * @since 2.0 | |
164 */ | |
165 protected ButtonModel createDefaultModel() { | |
166 if (isStyle(STYLE_TOGGLE)) | |
167 return new ToggleModel(); | |
168 else | |
169 return new ButtonModel(); | |
170 } | |
171 | |
172 /** | |
173 * Returns a newly created event handler for this Clickable and its model. | |
174 * | |
175 * @return The event handler | |
176 * @since 2.0 | |
177 */ | |
178 protected ClickableEventHandler createEventHandler() { | |
179 return new ClickableEventHandler(); | |
180 } | |
181 | |
182 /** | |
183 * Returns a newly created model observer which listens to the model, and fires any action | |
184 * or state changes. A ModelObserver holds both an action listener and a state change | |
185 * listener. | |
186 * | |
187 * @return The newly created model observer | |
188 * @since 2.0 | |
189 */ | |
190 protected ModelObserver createModelObserver() { | |
191 return new class() ModelObserver { | |
192 public void actionPerformed(ActionEvent action) { | |
193 fireActionPerformed(); | |
194 } | |
195 public void handleStateChanged(ChangeEvent change) { | |
196 fireStateChanged(change); | |
197 } | |
198 }; | |
199 } | |
200 | |
201 /** | |
202 * Fires an action performed event. | |
203 * | |
204 * @since 2.0 | |
205 */ | |
206 public void doClick() { | |
207 fireActionPerformed(); | |
208 } | |
209 | |
210 /** | |
211 * Called when there has been an action performed by this Clickable, which is determined | |
212 * by the model. Notifies all ActionListener type listeners of an action performed. | |
213 * | |
214 * @since 2.0 | |
215 */ | |
216 protected void fireActionPerformed() { | |
217 ActionEvent action = new ActionEvent(this); | |
218 Iterator listeners = getListeners(ActionListener.classinfo); | |
219 while (listeners.hasNext()) | |
220 (cast(ActionListener)listeners.next()) //Leave newline for debug stepping | |
221 .actionPerformed(action); | |
222 } | |
223 | |
224 /** | |
225 * Called when there has been a change of state in the model of this clickable. Notifies | |
226 * all ChangeListener type listeners of the state change. | |
227 * | |
228 * @param modelChange The ChangeEvent | |
229 * @since 2.0 | |
230 */ | |
231 protected void fireStateChanged(ChangeEvent modelChange) { | |
232 ChangeEvent change = new ChangeEvent(this, modelChange.getPropertyName()); | |
233 Iterator listeners = getListeners(ChangeListener.classinfo); | |
234 while (listeners.hasNext()) | |
235 (cast(ChangeListener)listeners.next()) //Leave newline for debug stepping | |
236 .handleStateChanged(change); | |
237 } | |
238 | |
239 /** | |
240 * Returns the behavior model used by this Clickable. | |
241 * | |
242 * @return The model used by this Clickable | |
243 * @since 2.0 | |
244 */ | |
245 public ButtonModel getModel() { | |
246 return model; | |
247 } | |
248 | |
249 /** | |
250 * Adds the given ClickableEventHandler to this clickable. A ClickableEventHandler | |
251 * should be a MouseListener, MouseMotionListener, ChangeListener, KeyListener, and | |
252 * FocusListener. | |
253 * | |
254 * @param handler The new event handler | |
255 * @since 2.0 | |
256 */ | |
257 protected void hookEventHandler(ClickableEventHandler handler) { | |
258 if (handler is null) | |
259 return; | |
260 addMouseListener(handler); | |
261 addMouseMotionListener(handler); | |
262 addChangeListener(handler); | |
263 addKeyListener(handler); | |
264 addFocusListener(handler); | |
265 } | |
266 | |
267 /** | |
268 * Initializes this Clickable by setting a default model and adding a clickable event | |
269 * handler for that model. | |
270 * | |
271 * @since 2.0 | |
272 */ | |
273 protected void init() { | |
274 setModel(createDefaultModel()); | |
275 setEventHandler(createEventHandler()); | |
276 } | |
277 | |
278 /** | |
279 * Returns <code>true</code> if rollover feedback is enabled. | |
280 * | |
281 * @return <code>true</code> rollover feedback is enabled | |
282 * @since 2.0 | |
283 */ | |
284 public bool isRolloverEnabled() { | |
285 return (flags & ROLLOVER_ENABLED_FLAG) !is 0; | |
286 } | |
287 | |
288 /** | |
289 * Returns <code>true</code> if this Clickable is in a selected state. The model is the | |
290 * one which holds all this state based information. | |
291 * | |
292 * @return <code>true</code> if this Clickable is in a selected state | |
293 * @since 2.0 | |
294 */ | |
295 public bool isSelected() { | |
296 return getModel().isSelected(); | |
297 } | |
298 | |
299 /** | |
300 * Returns <code>true</code> if this Clickable's style is the same as the passed style. | |
301 * | |
302 * @param style The style to be checked | |
303 * @return <code>true</code> if this Clickable's style is the same as the passed style | |
304 * @since 2.0 | |
305 */ | |
306 public bool isStyle(int style) { | |
307 return ((style & flags) is style); | |
308 } | |
309 | |
310 /** | |
311 * If this Clickable has focus, this method paints a focus rectangle. | |
312 * | |
313 * @param graphics Graphics handle for painting | |
314 */ | |
315 protected void paintBorder(Graphics graphics) { | |
316 super.paintBorder(graphics); | |
317 if (hasFocus()) { | |
318 graphics.setForegroundColor(ColorConstants.black); | |
319 graphics.setBackgroundColor(ColorConstants.white); | |
320 | |
321 Rectangle area = getClientArea(); | |
322 if (isStyle(STYLE_BUTTON)) | |
323 graphics.drawFocus(area.x, area.y, area.width, area.height); | |
324 else | |
325 graphics.drawFocus(area.x, area.y, area.width - 1, area.height - 1); | |
326 } | |
327 } | |
328 | |
329 /** | |
330 * Paints the area of this figure excluded by the borders. Induces a (1,1) pixel shift in | |
331 * the painting if the mouse is armed, giving it the pressed appearance. | |
332 * | |
333 * @param graphics Graphics handle for painting | |
334 * @since 2.0 | |
335 */ | |
336 protected void paintClientArea(Graphics graphics) { | |
337 if (isStyle(STYLE_BUTTON) && (getModel().isArmed() || getModel().isSelected())) { | |
338 graphics.translate(1, 1); | |
339 graphics.pushState(); | |
340 super.paintClientArea(graphics); | |
341 graphics.popState(); | |
342 graphics.translate(-1, -1); | |
343 } else | |
344 super.paintClientArea(graphics); | |
345 } | |
346 | |
347 /** | |
348 * Removes the given listener from the list of ActionListener's of this Clickable. | |
349 * | |
350 * @param listener Listener to be removed from this figure | |
351 * @since 2.0 | |
352 */ | |
353 public void removeActionListener(ActionListener listener) { | |
354 removeListener(ActionListener.classinfo, cast(Object)listener); | |
355 } | |
356 | |
357 /** | |
358 * Removes the given listener from the list of ChangeListener's of this clickable. | |
359 * | |
360 * @param listener Listener to be removed from this figure | |
361 * @since 2.0 | |
362 */ | |
363 public void removeChangeListener(ChangeListener listener) { | |
364 removeListener(ChangeListener.classinfo, cast(Object)listener); | |
365 } | |
366 | |
367 /** | |
368 * Sets the Figure which is the contents of this Clickable. This Figure occupies the | |
369 * entire clickable region. | |
370 * | |
371 * @param contents Contents of the clickable | |
372 * @since 2.0 | |
373 */ | |
374 protected void setContents(IFigure contents) { | |
375 setLayoutManager(new StackLayout()); | |
376 if (getChildren().size() > 0) | |
377 remove(cast(IFigure)getChildren().get(0)); | |
378 add(contents); | |
379 } | |
380 | |
381 /** | |
382 * @see dwtx.draw2d.IFigure#setEnabled(bool) | |
383 */ | |
384 public void setEnabled(bool value) { | |
385 if (isEnabled() is value) | |
386 return; | |
387 super.setEnabled(value); | |
388 getModel().setEnabled(value); | |
389 setChildrenEnabled(value); | |
390 } | |
391 | |
392 /** | |
393 * Sets the event handler which interacts with the model to determine the behavior of this | |
394 * Clickable. | |
395 * | |
396 * @param h Event handler for this clickable | |
397 * @since 2.0 | |
398 */ | |
399 public void setEventHandler(ClickableEventHandler h) { | |
400 if (eventHandler !is null) | |
401 unhookEventHandler(eventHandler); | |
402 eventHandler = h; | |
403 if (eventHandler !is null) | |
404 hookEventHandler(eventHandler); | |
405 } | |
406 | |
407 /** | |
408 * Determines how this clickable is to fire notifications to its listeners. In the default | |
409 * firing method ({@link #DEFAULT_FIRING}), an action is performed every time the mouse | |
410 * is released. In the repeat firing method ({@link #REPEAT_FIRING}), firing starts as | |
411 * soon as it is pressed on this clickable, and keeps firing at prefixed intervals until | |
412 * the mouse is released. | |
413 * | |
414 * @param type Type of firing | |
415 * @since 2.0 | |
416 */ | |
417 public void setFiringMethod(int type) { | |
418 getModel().setFiringBehavior(type); | |
419 } | |
420 | |
421 /** | |
422 * Sets the model to be used by this clickable for its state and behavior determination. | |
423 * This clickable removes any observers from the previous model before adding new ones to | |
424 * the new model. | |
425 * | |
426 * @param model The new model of this Clickable | |
427 * @since 2.0 | |
428 */ | |
429 public void setModel(ButtonModel model) { | |
430 if (this.model !is null) { | |
431 this.model.removeChangeListener(modelObserver); | |
432 this.model.removeActionListener(modelObserver); | |
433 modelObserver = null; | |
434 } | |
435 this.model = model; | |
436 if (model !is null) { | |
437 modelObserver = createModelObserver(); | |
438 model.addActionListener(modelObserver); | |
439 model.addChangeListener(modelObserver); | |
440 } | |
441 } | |
442 | |
443 /** | |
444 * Enables or disables rollover feedback of this figure. Generally used in conjunction | |
445 * with the model to determine if feedback is to be shown. | |
446 * | |
447 * @param value The rollover state to be set | |
448 * @since 2.0 | |
449 */ | |
450 public void setRolloverEnabled(bool value) { | |
451 if (isRolloverEnabled() is value) | |
452 return; | |
453 setFlag(ROLLOVER_ENABLED_FLAG, value); | |
454 repaint(); | |
455 } | |
456 | |
457 /** | |
458 * Sets the selected state of this Clickable. Since the model is responsible for all state | |
459 * based information, it is informed of the state change. Extending classes can choose | |
460 * selection information, if they do not represent any selection. | |
461 * | |
462 * @param value New selected state of this clickable. | |
463 * @see #isSelected() | |
464 * @since 2.0 | |
465 */ | |
466 public void setSelected(bool value) { | |
467 getModel().setSelected(value); | |
468 } | |
469 | |
470 /** | |
471 * Sets this Clickable's style to the passed value, either {@link #STYLE_BUTTON} or | |
472 * {@link #STYLE_TOGGLE}. | |
473 * | |
474 * @param style The button style | |
475 * @since 2.0 | |
476 */ | |
477 public void setStyle(int style) { | |
478 if ((style & STYLE_BUTTON) !is 0) { | |
479 setFlag(STYLE_BUTTON_FLAG, true); | |
480 if (!(null !is cast(ButtonBorder)getBorder() )) | |
481 setBorder(new ButtonBorder()); | |
482 setOpaque(true); | |
483 } else { | |
484 setFlag(STYLE_BUTTON_FLAG, false); | |
485 setOpaque(false); | |
486 } | |
487 | |
488 if ((style & STYLE_TOGGLE) !is 0) { | |
489 setFlag(STYLE_TOGGLE_FLAG, true); | |
490 setModel(createDefaultModel()); | |
491 } | |
492 } | |
493 | |
494 /** | |
495 * Removes the given ClickableEventHandler containing listeners from this Clickable. | |
496 * | |
497 * @param handler The event handler to be removed | |
498 * @since 2.0 | |
499 */ | |
500 protected void unhookEventHandler(ClickableEventHandler handler) { | |
501 if (handler is null) | |
502 return; | |
503 removeMouseListener(handler); | |
504 removeMouseMotionListener(handler); | |
505 removeChangeListener(handler); | |
506 } | |
507 | |
508 } |