75
|
1 /*******************************************************************************
|
|
2 * Copyright (c) 2000, 2007 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 * Kai Nacke - Fix for Bug 202382
|
|
11 * Port to the D programming language:
|
|
12 * Frank Benoit <benoit@tionex.de>
|
|
13 *******************************************************************************/
|
|
14 module dwtx.ui.forms.widgets.ExpandableComposite;
|
|
15
|
|
16 import dwtx.ui.forms.widgets.ToggleHyperlink;
|
|
17 import dwtx.ui.forms.widgets.ILayoutExtension;
|
|
18 import dwtx.ui.forms.widgets.SizeCache;
|
|
19 import dwtx.ui.forms.widgets.Twistie;
|
|
20 import dwtx.ui.forms.widgets.TreeNode;
|
|
21 import dwtx.ui.forms.widgets.Hyperlink;
|
|
22
|
|
23 import dwt.DWT;
|
|
24 import dwt.events.FocusEvent;
|
|
25 import dwt.events.FocusListener;
|
|
26 import dwt.events.KeyAdapter;
|
|
27 import dwt.events.KeyEvent;
|
|
28 import dwt.events.PaintEvent;
|
|
29 import dwt.events.PaintListener;
|
|
30 import dwt.events.TraverseEvent;
|
|
31 import dwt.events.TraverseListener;
|
|
32 import dwt.graphics.Color;
|
|
33 import dwt.graphics.Font;
|
|
34 import dwt.graphics.FontMetrics;
|
|
35 import dwt.graphics.GC;
|
|
36 import dwt.graphics.Point;
|
|
37 import dwt.graphics.Rectangle;
|
|
38 import dwt.widgets.Canvas;
|
|
39 import dwt.widgets.Composite;
|
|
40 import dwt.widgets.Control;
|
|
41 import dwt.widgets.Event;
|
|
42 import dwt.widgets.Label;
|
|
43 import dwt.widgets.Layout;
|
|
44 import dwt.widgets.Listener;
|
|
45 import dwt.widgets.Menu;
|
|
46 import dwtx.core.runtime.Assert;
|
|
47 import dwtx.core.runtime.ListenerList;
|
|
48 import dwtx.ui.forms.events.ExpansionEvent;
|
|
49 import dwtx.ui.forms.events.HyperlinkAdapter;
|
|
50 import dwtx.ui.forms.events.HyperlinkEvent;
|
|
51 import dwtx.ui.forms.events.IExpansionListener;
|
|
52 import dwtx.ui.internal.forms.widgets.FormUtil;
|
|
53 import dwtx.ui.internal.forms.widgets.FormsResources;
|
|
54
|
|
55 import dwt.dwthelper.utils;
|
|
56
|
|
57 /**
|
|
58 * This composite is capable of expanding or collapsing a single client that is
|
|
59 * its direct child. The composite renders an expansion toggle affordance
|
|
60 * (according to the chosen style), and a title that also acts as a hyperlink
|
|
61 * (can be selected and is traversable). The client is layed out below the title
|
|
62 * when expanded, or hidden when collapsed.
|
|
63 * <p>
|
|
64 * The widget can be instantiated as-is, or subclassed to modify some aspects of
|
|
65 * it. *
|
|
66 * <p>
|
|
67 * Since 3.1, left/right arrow keys can be used to control the expansion state.
|
|
68 * If several expandable composites are created in the same parent, up/down
|
|
69 * arrow keys can be used to traverse between them. Expandable text accepts
|
|
70 * mnemonics and mnemonic activation will toggle the expansion state.
|
|
71 *
|
|
72 * <p>
|
|
73 * While expandable composite recognize that different styles can be used to
|
|
74 * render the title bar, and even defines the constants for these styles (<code>TITLE_BAR</code>
|
|
75 * and <code>SHORT_TITLE_BAR</code> the actual painting is done in the
|
|
76 * subclasses.
|
|
77 *
|
|
78 * @see Section
|
|
79 * @since 3.0
|
|
80 */
|
|
81 public class ExpandableComposite : Canvas {
|
|
82 /**
|
|
83 * If this style is used, a twistie will be used to render the expansion
|
|
84 * toggle.
|
|
85 */
|
|
86 public static const int TWISTIE = 1 << 1;
|
|
87
|
|
88 /**
|
|
89 * If this style is used, a tree node with either + or - signs will be used
|
|
90 * to render the expansion toggle.
|
|
91 */
|
|
92 public static const int TREE_NODE = 1 << 2;
|
|
93
|
|
94 /**
|
|
95 * If this style is used, the title text will be rendered as a hyperlink
|
|
96 * that can individually accept focus. Otherwise, it will still act like a
|
|
97 * hyperlink, but only the toggle control will accept focus.
|
|
98 */
|
|
99 public static const int FOCUS_TITLE = 1 << 3;
|
|
100
|
|
101 /**
|
|
102 * If this style is used, the client origin will be vertically aligned with
|
|
103 * the title text. Otherwise, it will start at x = 0.
|
|
104 */
|
|
105 public static const int CLIENT_INDENT = 1 << 4;
|
|
106
|
|
107 /**
|
|
108 * If this style is used, computed size of the composite will take the
|
|
109 * client width into consideration only in the expanded state. Otherwise,
|
|
110 * client width will always be taken into acount.
|
|
111 */
|
|
112 public static const int COMPACT = 1 << 5;
|
|
113
|
|
114 /**
|
|
115 * If this style is used, the control will be created in the expanded state.
|
|
116 * This state can later be changed programmatically or by the user if
|
|
117 * TWISTIE or TREE_NODE style is used.
|
|
118 */
|
|
119 public static const int EXPANDED = 1 << 6;
|
|
120
|
|
121 /**
|
|
122 * If this style is used, title bar decoration will be painted behind the
|
|
123 * text.
|
|
124 */
|
|
125 public static const int TITLE_BAR = 1 << 8;
|
|
126
|
|
127 /**
|
|
128 * If this style is used, a short version of the title bar decoration will
|
|
129 * be painted behind the text. This style is useful when a more descrete
|
|
130 * option is needed for the title bar.
|
|
131 *
|
|
132 * @since 3.1
|
|
133 */
|
|
134 public static const int SHORT_TITLE_BAR = 1 << 9;
|
|
135
|
|
136 /**
|
|
137 * If this style is used, title will not be rendered.
|
|
138 */
|
|
139 public static const int NO_TITLE = 1 << 12;
|
|
140
|
|
141 /**
|
|
142 * By default, text client is right-aligned. If this style is used, it will
|
|
143 * be positioned after the text control and vertically centered with it.
|
|
144 */
|
|
145 public static const int LEFT_TEXT_CLIENT_ALIGNMENT = 1 << 13;
|
|
146
|
|
147 /**
|
|
148 * Width of the margin that will be added around the control (default is 0).
|
|
149 */
|
|
150 public int marginWidth = 0;
|
|
151
|
|
152 /**
|
|
153 * Height of the margin that will be added around the control (default is
|
|
154 * 0).
|
|
155 */
|
|
156 public int marginHeight = 0;
|
|
157
|
|
158 /**
|
|
159 * Vertical spacing between the title area and the composite client control
|
|
160 * (default is 3).
|
|
161 */
|
|
162 public int clientVerticalSpacing = 3;
|
|
163
|
|
164 /**
|
|
165 * Vertical spacing between the title area and the description control
|
|
166 * (default is 0). The description control is normally placed at the new
|
|
167 * line as defined in the font used to render it. This value will be added
|
|
168 * to it.
|
|
169 *
|
|
170 * @since 3.3
|
|
171 */
|
|
172 public int descriptionVerticalSpacing = 0;
|
|
173
|
|
174 /**
|
|
175 * Horizontal margin around the inside of the title bar area when TITLE_BAR
|
|
176 * or SHORT_TITLE_BAR style is used. This variable is not used otherwise.
|
|
177 *
|
|
178 * @since 3.3
|
|
179 */
|
|
180 public int titleBarTextMarginWidth = 6;
|
|
181
|
|
182 /**
|
|
183 * The toggle widget used to expand the composite.
|
|
184 */
|
|
185 protected ToggleHyperlink toggle;
|
|
186 package ToggleHyperlink toggle_package(){
|
|
187 return toggle;
|
|
188 }
|
|
189 package ToggleHyperlink toggle_package(ToggleHyperlink t){
|
|
190 return (toggle = t);
|
|
191 }
|
|
192
|
|
193 /**
|
|
194 * The text label for the title.
|
|
195 */
|
|
196 protected Control textLabel;
|
|
197 package Control textLabel_package(){
|
|
198 return textLabel;
|
|
199 }
|
|
200 package Control textLabel_package(Control t){
|
|
201 return (textLabel = t);
|
|
202 }
|
|
203
|
|
204 /**
|
|
205 * @deprecated this variable was left as protected by mistake. It will be
|
|
206 * turned into static and hidden in the future versions. Do not
|
|
207 * use them and do not change its value.
|
|
208 */
|
|
209 protected int VGAP = 3;
|
|
210 /**
|
|
211 * @deprecated this variable was left as protected by mistake. It will be
|
|
212 * turned into static and hidden in the future versions. Do not
|
|
213 * use it and do not change its value.
|
|
214 */
|
|
215 protected int GAP = 4;
|
|
216
|
|
217 static const int IGAP = 4;
|
|
218 static const int IVGAP = 3;
|
|
219
|
|
220 private static Point NULL_SIZE_;
|
|
221 private static Point NULL_SIZE(){
|
|
222 if( NULL_SIZE_ is null ){
|
|
223 synchronized(ExpandableComposite.classinfo ){
|
|
224 if( NULL_SIZE_ is null ){
|
|
225 NULL_SIZE_ = new Point(0, 0);
|
|
226 }
|
|
227 }
|
|
228 }
|
|
229 return NULL_SIZE_;
|
|
230 }
|
|
231
|
|
232 private static const int VSPACE = 3;
|
|
233
|
|
234 private static const int SEPARATOR_HEIGHT = 2;
|
|
235
|
|
236 private int expansionStyle = TWISTIE | FOCUS_TITLE | EXPANDED;
|
|
237
|
|
238 private bool expanded;
|
|
239
|
|
240 private Control textClient;
|
|
241
|
|
242 private Control client;
|
|
243
|
|
244 private ListenerList listeners;
|
|
245
|
|
246 private Color titleBarForeground;
|
|
247
|
|
248 private class ExpandableLayout : Layout, ILayoutExtension {
|
|
249
|
|
250 private SizeCache toggleCache;
|
|
251
|
|
252 private SizeCache textClientCache;
|
|
253
|
|
254 private SizeCache textLabelCache;
|
|
255
|
|
256 private SizeCache descriptionCache;
|
|
257
|
|
258 private SizeCache clientCache;
|
|
259
|
|
260 this(){
|
|
261 toggleCache = new SizeCache();
|
|
262 textClientCache = new SizeCache();
|
|
263 textLabelCache = new SizeCache();
|
|
264 descriptionCache = new SizeCache();
|
|
265 clientCache = new SizeCache();
|
|
266 }
|
|
267 private void initCache(bool shouldFlush) {
|
|
268 toggleCache.setControl(toggle);
|
|
269 textClientCache.setControl(textClient);
|
|
270 textLabelCache.setControl(textLabel);
|
|
271 descriptionCache.setControl(getDescriptionControl());
|
|
272 clientCache.setControl(client);
|
|
273
|
|
274 if (shouldFlush) {
|
|
275 toggleCache.flush();
|
|
276 textClientCache.flush();
|
|
277 textLabelCache.flush();
|
|
278 descriptionCache.flush();
|
|
279 clientCache.flush();
|
|
280 }
|
|
281 }
|
|
282
|
|
283 protected void layout(Composite parent, bool changed) {
|
|
284 initCache(changed);
|
|
285
|
|
286 Rectangle clientArea = parent.getClientArea();
|
|
287 int thmargin = 0;
|
|
288 int tvmargin = 0;
|
|
289
|
|
290 if (hasTitleBar()) {
|
|
291 thmargin = titleBarTextMarginWidth;
|
|
292 tvmargin = IVGAP;
|
|
293 }
|
|
294 int x = marginWidth + thmargin;
|
|
295 int y = marginHeight + tvmargin;
|
|
296 Point tsize = NULL_SIZE;
|
|
297 Point tcsize = NULL_SIZE;
|
|
298 if (toggle !is null)
|
|
299 tsize = toggleCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
|
|
300 int twidth = clientArea.width - marginWidth - marginWidth
|
|
301 - thmargin - thmargin;
|
|
302 if (tsize.x > 0)
|
|
303 twidth -= tsize.x + IGAP;
|
|
304 if (textClient !is null) {
|
|
305 tcsize = textClientCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
|
|
306 }
|
|
307 Point size = NULL_SIZE;
|
|
308 if (textLabel !is null) {
|
|
309 if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) {
|
|
310 size = textLabelCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
|
|
311 if (twidth < size.x + IGAP + tcsize.x) {
|
|
312 twidth -= IGAP;
|
|
313 if (null !is cast(Label)textLabel )
|
|
314 size = FormUtil.computeWrapSize(new GC(textLabel), (cast(Label)textLabel).getText(), cast(int) Math.round(twidth*(size.x/cast(float)(size.x+tcsize.x))));
|
|
315 else
|
|
316 size = textLabelCache.computeSize(cast(int) Math.round(twidth*(size.x/cast(float)(size.x+tcsize.x))), DWT.DEFAULT);
|
|
317 tcsize = textClientCache.computeSize(twidth-size.x, DWT.DEFAULT);
|
|
318 }
|
|
319 }
|
|
320 else {
|
|
321 if (tcsize.x > 0)
|
|
322 twidth -= tcsize.x + IGAP;
|
|
323 size = textLabelCache.computeSize(twidth, DWT.DEFAULT);
|
|
324 }
|
|
325 }
|
|
326 if (null !is cast(Label)textLabel ) {
|
|
327 Point defSize = textLabelCache.computeSize(DWT.DEFAULT,
|
|
328 DWT.DEFAULT);
|
|
329 if (defSize.y is size.y) {
|
|
330 // One line - pick the smaller of the two widths
|
|
331 size.x = Math.min(defSize.x, size.x);
|
|
332 }
|
|
333 }
|
|
334 if (toggle !is null) {
|
|
335 GC gc = new GC(this.outer);
|
|
336 gc.setFont(getFont());
|
|
337 FontMetrics fm = gc.getFontMetrics();
|
|
338 int textHeight = fm.getHeight();
|
|
339 gc.dispose();
|
|
340 if (textClient !is null
|
|
341 && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 0) {
|
|
342 textHeight = Math.max(textHeight, tcsize.y);
|
|
343 }
|
|
344 int ty = textHeight / 2 - tsize.y / 2 + 1;
|
|
345 ty = Math.max(ty, 0);
|
|
346 ty += marginHeight + tvmargin;
|
|
347 toggle.setLocation(x, ty);
|
|
348 toggle.setSize(tsize);
|
|
349 x += tsize.x + IGAP;
|
|
350 }
|
|
351 if (textLabel !is null) {
|
|
352 int ty = y;
|
|
353 if (textClient !is null
|
|
354 && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 0) {
|
|
355 if (size.y < tcsize.y)
|
|
356 ty = tcsize.y / 2 - size.y / 2 + marginHeight
|
|
357 + tvmargin;
|
|
358 }
|
|
359 textLabelCache.setBounds(x, ty, size.x, size.y);
|
|
360 }
|
|
361 if (textClient !is null) {
|
|
362 int tcx;
|
|
363 if ((expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) !is 0) {
|
|
364 tcx = x + size.x + GAP;
|
|
365 } else {
|
|
366 tcx = clientArea.width - tcsize.x - marginWidth - thmargin;
|
|
367 }
|
|
368 textClientCache.setBounds(tcx, y, tcsize.x, tcsize.y);
|
|
369 }
|
|
370 int tbarHeight = 0;
|
|
371 if (size.y > 0)
|
|
372 tbarHeight = size.y;
|
|
373 if (tcsize.y > 0)
|
|
374 tbarHeight = Math.max(tbarHeight, tcsize.y);
|
|
375 y += tbarHeight;
|
|
376 if (hasTitleBar())
|
|
377 y += tvmargin;
|
|
378 if (getSeparatorControl() !is null) {
|
|
379 y += VSPACE;
|
|
380 getSeparatorControl().setBounds(marginWidth, y,
|
|
381 clientArea.width - marginWidth - marginWidth,
|
|
382 SEPARATOR_HEIGHT);
|
|
383 y += SEPARATOR_HEIGHT;
|
|
384 if (expanded)
|
|
385 y += VSPACE;
|
|
386 }
|
|
387 if (expanded) {
|
|
388 int areaWidth = clientArea.width - marginWidth - marginWidth
|
|
389 - thmargin - thmargin;
|
|
390 int cx = marginWidth + thmargin;
|
|
391 if ((expansionStyle & CLIENT_INDENT) !is 0) {
|
|
392 cx = x;
|
|
393 areaWidth -= x;
|
|
394 }
|
|
395 if (client !is null) {
|
|
396 Point dsize = null;
|
|
397 Control desc = getDescriptionControl();
|
|
398 if (desc !is null) {
|
|
399 dsize = descriptionCache.computeSize(areaWidth,
|
|
400 DWT.DEFAULT);
|
|
401 y += descriptionVerticalSpacing;
|
|
402 descriptionCache.setBounds(cx, y, areaWidth, dsize.y);
|
|
403 y += dsize.y + clientVerticalSpacing;
|
|
404 } else {
|
|
405 y += clientVerticalSpacing;
|
|
406 if (getSeparatorControl() !is null)
|
|
407 y -= VSPACE;
|
|
408 }
|
|
409 int cwidth = areaWidth;
|
|
410 int cheight = clientArea.height - marginHeight
|
|
411 - marginHeight - y;
|
|
412 clientCache.setBounds(cx, y, cwidth, cheight);
|
|
413 }
|
|
414 }
|
|
415 }
|
|
416
|
|
417 protected Point computeSize(Composite parent, int wHint, int hHint,
|
|
418 bool changed) {
|
|
419 initCache(changed);
|
|
420
|
|
421 int width = 0, height = 0;
|
|
422 Point tsize = NULL_SIZE;
|
|
423 int twidth = 0;
|
|
424 if (toggle !is null) {
|
|
425 tsize = toggleCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
|
|
426 twidth = tsize.x + IGAP;
|
|
427 }
|
|
428 int thmargin = 0;
|
|
429 int tvmargin = 0;
|
|
430
|
|
431 if (hasTitleBar()) {
|
|
432 thmargin = titleBarTextMarginWidth;
|
|
433 tvmargin = IVGAP;
|
|
434 }
|
|
435 int innerwHint = wHint;
|
|
436 if (innerwHint !is DWT.DEFAULT)
|
|
437 innerwHint -= twidth + marginWidth + marginWidth + thmargin
|
|
438 + thmargin;
|
|
439
|
|
440 int innertHint = innerwHint;
|
|
441
|
|
442 Point tcsize = NULL_SIZE;
|
|
443 if (textClient !is null) {
|
|
444 tcsize = textClientCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
|
|
445 }
|
|
446 Point size = NULL_SIZE;
|
|
447
|
|
448 if (textLabel !is null) {
|
|
449 if (tcsize.x > 0 && FormUtil.isWrapControl(textClient)) {
|
|
450 size = textLabelCache.computeSize(DWT.DEFAULT, DWT.DEFAULT);
|
|
451 if (innertHint !is DWT.DEFAULT && innertHint < size.x + IGAP + tcsize.x) {
|
|
452 innertHint -= IGAP;
|
|
453 if (null !is cast(Label)textLabel )
|
|
454 size = FormUtil.computeWrapSize(new GC(textLabel), (cast(Label)textLabel).getText(), cast(int) Math.round(innertHint*(size.x/cast(float)(size.x+tcsize.x))));
|
|
455 else
|
|
456 size = textLabelCache.computeSize(cast(int) Math.round(innertHint*(size.x/cast(float)(size.x+tcsize.x))), DWT.DEFAULT);
|
|
457 tcsize = textClientCache.computeSize(innertHint-size.x, DWT.DEFAULT);
|
|
458 }
|
|
459 } else {
|
|
460 if (innertHint !is DWT.DEFAULT && tcsize.x > 0)
|
|
461 innertHint -= IGAP + tcsize.x;
|
|
462 size = textLabelCache.computeSize(innertHint, DWT.DEFAULT);
|
|
463 }
|
|
464 }
|
|
465 if (null !is cast(Label)textLabel ) {
|
|
466 Point defSize = textLabelCache.computeSize(DWT.DEFAULT,
|
|
467 DWT.DEFAULT);
|
|
468 if (defSize.y is size.y) {
|
|
469 // One line - pick the smaller of the two widths
|
|
470 size.x = Math.min(defSize.x, size.x);
|
|
471 }
|
|
472 }
|
|
473 if (size.x > 0)
|
|
474 width = size.x;
|
|
475 if (tcsize.x > 0)
|
|
476 width += IGAP + tcsize.x;
|
|
477 if (toggle !is null)
|
|
478 width += twidth;
|
|
479 height = tcsize.y > 0 ? Math.max(tcsize.y, size.y) : size.y;
|
|
480 if (getSeparatorControl() !is null) {
|
|
481 height += VSPACE + SEPARATOR_HEIGHT;
|
|
482 if (expanded && client !is null)
|
|
483 height += VSPACE;
|
|
484 }
|
|
485 // if (hasTitleBar())
|
|
486 // height += VSPACE;
|
|
487 if ((expanded || (expansionStyle & COMPACT) is 0) && client !is null) {
|
|
488 int cwHint = wHint;
|
|
489 int clientIndent = 0;
|
|
490 if ((expansionStyle & CLIENT_INDENT) !is 0)
|
|
491 clientIndent = twidth;
|
|
492
|
|
493 if (cwHint !is DWT.DEFAULT) {
|
|
494 cwHint -= marginWidth + marginWidth + thmargin + thmargin;
|
|
495 if ((expansionStyle & CLIENT_INDENT) !is 0)
|
|
496 if (tcsize.x > 0)
|
|
497 cwHint -= twidth;
|
|
498 }
|
|
499 Point dsize = null;
|
|
500 Point csize = clientCache.computeSize(FormUtil.getWidthHint(
|
|
501 cwHint, client), DWT.DEFAULT);
|
|
502 if (getDescriptionControl() !is null) {
|
|
503 int dwHint = cwHint;
|
|
504 if (dwHint is DWT.DEFAULT) {
|
|
505 dwHint = csize.x;
|
|
506 if ((expansionStyle & CLIENT_INDENT) !is 0)
|
|
507 dwHint -= twidth;
|
|
508 }
|
|
509 dsize = descriptionCache.computeSize(dwHint, DWT.DEFAULT);
|
|
510 }
|
|
511 if (dsize !is null) {
|
|
512 width = Math.max(width, dsize.x + clientIndent);
|
|
513 if (expanded)
|
|
514 height += descriptionVerticalSpacing + dsize.y
|
|
515 + clientVerticalSpacing;
|
|
516 } else {
|
|
517 height += clientVerticalSpacing;
|
|
518 if (getSeparatorControl() !is null)
|
|
519 height -= VSPACE;
|
|
520 }
|
|
521 width = Math.max(width, csize.x + clientIndent);
|
|
522 if (expanded)
|
|
523 height += csize.y;
|
|
524 }
|
|
525 if (toggle !is null)
|
|
526 height = height - size.y + Math.max(size.y, tsize.y);
|
|
527
|
|
528 Point result = new Point(width + marginWidth + marginWidth
|
|
529 + thmargin + thmargin, height + marginHeight + marginHeight
|
|
530 + tvmargin + tvmargin);
|
|
531 return result;
|
|
532 }
|
|
533
|
|
534 public int computeMinimumWidth(Composite parent, bool changed) {
|
|
535 return computeSize(parent, 0, DWT.DEFAULT, changed).x;
|
|
536 }
|
|
537
|
|
538 /*
|
|
539 * (non-Javadoc)
|
|
540 *
|
|
541 * @see dwtx.ui.forms.parts.ILayoutExtension#computeMinimumWidth(dwt.widgets.Composite,
|
|
542 * bool)
|
|
543 */
|
|
544 public int computeMaximumWidth(Composite parent, bool changed) {
|
|
545 return computeSize(parent, DWT.DEFAULT, DWT.DEFAULT, changed).x;
|
|
546 }
|
|
547 }
|
|
548
|
|
549 /**
|
|
550 * Creates an expandable composite using a TWISTIE toggle.
|
|
551 *
|
|
552 * @param parent
|
|
553 * the parent composite
|
|
554 * @param style
|
|
555 * DWT style bits
|
|
556 */
|
|
557 public this(Composite parent, int style) {
|
|
558 this(parent, style, TWISTIE);
|
|
559 }
|
|
560
|
|
561 /**
|
|
562 * Creates the expandable composite in the provided parent.
|
|
563 *
|
|
564 * @param parent
|
|
565 * the parent
|
|
566 * @param style
|
|
567 * the control style (as expected by DWT subclass)
|
|
568 * @param expansionStyle
|
|
569 * the style of the expansion widget (TREE_NODE, TWISTIE,
|
|
570 * CLIENT_INDENT, COMPACT, FOCUS_TITLE,
|
|
571 * LEFT_TEXT_CLIENT_ALIGNMENT, NO_TITLE)
|
|
572 */
|
|
573 public this(Composite parent, int style, int expansionStyle) {
|
|
574 listeners = new ListenerList();
|
|
575 super(parent, style);
|
|
576 this.expansionStyle = expansionStyle;
|
|
577 if ((expansionStyle & TITLE_BAR) !is 0)
|
|
578 setBackgroundMode(DWT.INHERIT_DEFAULT);
|
|
579 super.setLayout(new ExpandableLayout());
|
|
580 if (hasTitleBar()) {
|
|
581 this.addPaintListener(new class PaintListener {
|
|
582 public void paintControl(PaintEvent e) {
|
|
583 onPaint(e);
|
|
584 }
|
|
585 });
|
|
586 }
|
|
587 if ((expansionStyle & TWISTIE) !is 0)
|
|
588 toggle = new Twistie(this, DWT.NULL);
|
|
589 else if ((expansionStyle & TREE_NODE) !is 0)
|
|
590 toggle = new TreeNode(this, DWT.NULL);
|
|
591 else
|
|
592 expanded = true;
|
|
593 if ((expansionStyle & EXPANDED) !is 0)
|
|
594 expanded = true;
|
|
595 if (toggle !is null) {
|
|
596 toggle.setExpanded(expanded);
|
|
597 toggle.addHyperlinkListener(new class HyperlinkAdapter {
|
|
598 public void linkActivated(HyperlinkEvent e) {
|
|
599 toggleState();
|
|
600 }
|
|
601 });
|
|
602 toggle.addPaintListener(new class PaintListener {
|
|
603 public void paintControl(PaintEvent e) {
|
|
604 if (null !is cast(Label)textLabel && !isFixedStyle())
|
|
605 textLabel.setForeground(toggle.hover_package ? toggle
|
|
606 .getHoverDecorationColor()
|
|
607 : getTitleBarForeground());
|
|
608 }
|
|
609 });
|
|
610 toggle.addKeyListener(new class KeyAdapter {
|
|
611 public void keyPressed(KeyEvent e) {
|
|
612 if (e.keyCode is DWT.ARROW_UP) {
|
|
613 verticalMove(false);
|
|
614 e.doit = false;
|
|
615 } else if (e.keyCode is DWT.ARROW_DOWN) {
|
|
616 verticalMove(true);
|
|
617 e.doit = false;
|
|
618 }
|
|
619 }
|
|
620 });
|
|
621 if ((getExpansionStyle()&FOCUS_TITLE) is 0) {
|
|
622 toggle.paintFocus=false;
|
|
623 toggle.addFocusListener(new class FocusListener {
|
|
624 public void focusGained(FocusEvent e) {
|
|
625 textLabel.redraw();
|
|
626 }
|
|
627
|
|
628 public void focusLost(FocusEvent e) {
|
|
629 textLabel.redraw();
|
|
630 }
|
|
631 });
|
|
632 }
|
|
633 }
|
|
634 if ((expansionStyle & FOCUS_TITLE) !is 0) {
|
|
635 Hyperlink link = new Hyperlink(this, DWT.WRAP);
|
|
636 link.addHyperlinkListener(new class HyperlinkAdapter {
|
|
637 public void linkActivated(HyperlinkEvent e) {
|
|
638 programmaticToggleState();
|
|
639 }
|
|
640 });
|
|
641 textLabel = link;
|
|
642 } else if ((expansionStyle & NO_TITLE) is 0) {
|
|
643 final Label label = new Label(this, DWT.WRAP);
|
|
644 if (!isFixedStyle()) {
|
|
645 label.setCursor(FormsResources.getHandCursor());
|
|
646 Listener listener = dgListener( (Event e, Label label) {
|
|
647 switch (e.type) {
|
|
648 case DWT.MouseDown:
|
|
649 if (toggle !is null)
|
|
650 toggle.setFocus();
|
|
651 break;
|
|
652 case DWT.MouseUp:
|
|
653 label.setCursor(FormsResources.getBusyCursor());
|
|
654 programmaticToggleState();
|
|
655 label.setCursor(FormsResources.getHandCursor());
|
|
656 break;
|
|
657 case DWT.MouseEnter:
|
|
658 if (toggle !is null) {
|
|
659 label.setForeground(toggle
|
|
660 .getHoverDecorationColor());
|
|
661 toggle.hover_package = true;
|
|
662 toggle.redraw();
|
|
663 }
|
|
664 break;
|
|
665 case DWT.MouseExit:
|
|
666 if (toggle !is null) {
|
|
667 label.setForeground(getTitleBarForeground());
|
|
668 toggle.hover_package = false;
|
|
669 toggle.redraw();
|
|
670 }
|
|
671 break;
|
|
672 case DWT.Paint:
|
|
673 if (toggle !is null) {
|
|
674 paintTitleFocus(e.gc);
|
|
675 }
|
|
676 break;
|
|
677 }
|
|
678 }, label );
|
|
679 label.addListener(DWT.MouseDown, listener);
|
|
680 label.addListener(DWT.MouseUp, listener);
|
|
681 label.addListener(DWT.MouseEnter, listener);
|
|
682 label.addListener(DWT.MouseExit, listener);
|
|
683 label.addListener(DWT.Paint, listener);
|
|
684 }
|
|
685 textLabel = label;
|
|
686 }
|
|
687 if (textLabel !is null) {
|
|
688 textLabel.setMenu(getMenu());
|
|
689 textLabel.addTraverseListener(new class TraverseListener {
|
|
690 public void keyTraversed(TraverseEvent e) {
|
|
691 if (e.detail is DWT.TRAVERSE_MNEMONIC) {
|
|
692 // steal the mnemonic
|
|
693 if (!isVisible() || !isEnabled())
|
|
694 return;
|
|
695 if (FormUtil.mnemonicMatch(getText(), e.character)) {
|
|
696 e.doit = false;
|
|
697 if (!isFixedStyle()) {
|
|
698 programmaticToggleState();
|
|
699 }
|
|
700 setFocus();
|
|
701 }
|
|
702 }
|
|
703 }
|
|
704 });
|
|
705 }
|
|
706 }
|
|
707
|
|
708 /* (non-Javadoc)
|
|
709 * @see dwt.widgets.Control#forceFocus()
|
|
710 */
|
|
711 public bool forceFocus() {
|
|
712 return false;
|
|
713 }
|
|
714
|
|
715 /**
|
|
716 * Overrides 'super' to pass the menu to the text label.
|
|
717 *
|
|
718 * @param menu
|
|
719 * the menu from the parent to attach to this control.
|
|
720 */
|
|
721
|
|
722 public void setMenu(Menu menu) {
|
|
723 if (textLabel !is null)
|
|
724 textLabel.setMenu(menu);
|
|
725 super.setMenu(menu);
|
|
726 }
|
|
727
|
|
728 /**
|
|
729 * Prevents assignment of the layout manager - expandable composite uses its
|
|
730 * own layout.
|
|
731 */
|
|
732 public final void setLayout(Layout layout) {
|
|
733 }
|
|
734
|
|
735 /**
|
|
736 * Sets the background of all the custom controls in the expandable.
|
|
737 */
|
|
738 public void setBackground(Color bg) {
|
|
739 super.setBackground(bg);
|
|
740 if ((getExpansionStyle() & TITLE_BAR) is 0) {
|
|
741 if (textLabel !is null)
|
|
742 textLabel.setBackground(bg);
|
|
743 if (toggle !is null)
|
|
744 toggle.setBackground(bg);
|
|
745 }
|
|
746 }
|
|
747
|
|
748 /**
|
|
749 * Sets the foreground of all the custom controls in the expandable.
|
|
750 */
|
|
751 public void setForeground(Color fg) {
|
|
752 super.setForeground(fg);
|
|
753 if (textLabel !is null)
|
|
754 textLabel.setForeground(fg);
|
|
755 if (toggle !is null)
|
|
756 toggle.setForeground(fg);
|
|
757 }
|
|
758
|
|
759 /**
|
|
760 * Sets the color of the toggle control.
|
|
761 *
|
|
762 * @param c
|
|
763 * the color object
|
|
764 */
|
|
765 public void setToggleColor(Color c) {
|
|
766 if (toggle !is null)
|
|
767 toggle.setDecorationColor(c);
|
|
768 }
|
|
769
|
|
770 /**
|
|
771 * Sets the active color of the toggle control (when the mouse enters the
|
|
772 * toggle area).
|
|
773 *
|
|
774 * @param c
|
|
775 * the active color object
|
|
776 */
|
|
777 public void setActiveToggleColor(Color c) {
|
|
778 if (toggle !is null)
|
|
779 toggle.setHoverDecorationColor(c);
|
|
780 }
|
|
781
|
|
782 /**
|
|
783 * Sets the fonts of all the custom controls in the expandable.
|
|
784 */
|
|
785 public void setFont(Font font) {
|
|
786 super.setFont(font);
|
|
787 if (textLabel !is null)
|
|
788 textLabel.setFont(font);
|
|
789 if (toggle !is null)
|
|
790 toggle.setFont(font);
|
|
791 }
|
|
792
|
|
793 /*
|
|
794 * (non-Javadoc)
|
|
795 *
|
|
796 * @see dwt.widgets.Control#setEnabled(bool)
|
|
797 */
|
|
798
|
|
799 public void setEnabled(bool enabled) {
|
|
800 if (textLabel !is null)
|
|
801 textLabel.setEnabled(enabled);
|
|
802 if (toggle !is null)
|
|
803 toggle.setEnabled(enabled);
|
|
804 super.setEnabled(enabled);
|
|
805 }
|
|
806
|
|
807 /**
|
|
808 * Sets the client of this expandable composite. The client must not be
|
|
809 * <samp>null </samp> and must be a direct child of this container.
|
|
810 *
|
|
811 * @param client
|
|
812 * the client that will be expanded or collapsed
|
|
813 */
|
|
814 public void setClient(Control client) {
|
|
815 Assert.isTrue(client !is null && client.getParent().opEquals(this));
|
|
816 this.client = client;
|
|
817 }
|
|
818
|
|
819 /**
|
|
820 * Returns the current expandable client.
|
|
821 *
|
|
822 * @return the client control
|
|
823 */
|
|
824 public Control getClient() {
|
|
825 return client;
|
|
826 }
|
|
827
|
|
828 /**
|
|
829 * Sets the title of the expandable composite. The title will act as a
|
|
830 * hyperlink and activating it will toggle the client between expanded and
|
|
831 * collapsed state.
|
|
832 *
|
|
833 * @param title
|
|
834 * the new title string
|
|
835 * @see #getText()
|
|
836 */
|
|
837 public void setText(String title) {
|
|
838 if (null !is cast(Label)textLabel )
|
|
839 (cast(Label) textLabel).setText(title);
|
|
840 else if (null !is cast(Hyperlink)textLabel )
|
|
841 (cast(Hyperlink) textLabel).setText(title);
|
|
842 }
|
|
843
|
|
844 /**
|
|
845 * Returns the title string.
|
|
846 *
|
|
847 * @return the title string
|
|
848 * @see #setText(String)
|
|
849 */
|
|
850 public String getText() {
|
|
851 if (null !is cast(Label)textLabel )
|
|
852 return (cast(Label) textLabel).getText();
|
|
853 else if (null !is cast(Hyperlink)textLabel )
|
|
854 return (cast(Hyperlink) textLabel).getText();
|
|
855 else
|
|
856 return ""; //$NON-NLS-1$
|
|
857 }
|
|
858
|
|
859 /**
|
|
860 * Tests the expanded state of the composite.
|
|
861 *
|
|
862 * @return <samp>true </samp> if expanded, <samp>false </samp> if collapsed.
|
|
863 */
|
|
864 public bool isExpanded() {
|
|
865 return expanded;
|
|
866 }
|
|
867
|
|
868 /**
|
|
869 * Returns the bitwise-ORed style bits for the expansion control.
|
|
870 *
|
|
871 * @return the bitwise-ORed style bits for the expansion control
|
|
872 */
|
|
873 public int getExpansionStyle() {
|
|
874 return expansionStyle;
|
|
875 }
|
|
876
|
|
877 /**
|
|
878 * Programmatically changes expanded state.
|
|
879 *
|
|
880 * @param expanded
|
|
881 * the new expanded state
|
|
882 */
|
|
883 public void setExpanded(bool expanded) {
|
|
884 internalSetExpanded(expanded);
|
|
885 if (toggle !is null)
|
|
886 toggle.setExpanded(expanded);
|
|
887 }
|
|
888
|
|
889 /**
|
|
890 * Performs the expansion state change for the expandable control.
|
|
891 *
|
|
892 * @param expanded
|
|
893 * the expansion state
|
|
894 */
|
|
895 protected void internalSetExpanded(bool expanded) {
|
|
896 if (this.expanded !is expanded) {
|
|
897 this.expanded = expanded;
|
|
898 if (getDescriptionControl() !is null)
|
|
899 getDescriptionControl().setVisible(expanded);
|
|
900 if (client !is null)
|
|
901 client.setVisible(expanded);
|
|
902 layout();
|
|
903 }
|
|
904 }
|
|
905
|
|
906 /**
|
|
907 * Adds the listener that will be notified when the expansion state changes.
|
|
908 *
|
|
909 * @param listener
|
|
910 * the listener to add
|
|
911 */
|
|
912 public void addExpansionListener(IExpansionListener listener) {
|
|
913 listeners.add(cast(Object)listener);
|
|
914 }
|
|
915
|
|
916 /**
|
|
917 * Removes the expansion listener.
|
|
918 *
|
|
919 * @param listener
|
|
920 * the listner to remove
|
|
921 */
|
|
922 public void removeExpansionListener(IExpansionListener listener) {
|
|
923 listeners.remove(cast(Object)listener);
|
|
924 }
|
|
925
|
|
926 /**
|
|
927 * If TITLE_BAR or SHORT_TITLE_BAR style is used, title bar decoration will
|
|
928 * be painted behind the text in this method. The default implementation
|
|
929 * does nothing - subclasses are responsible for rendering the title area.
|
|
930 *
|
|
931 * @param e
|
|
932 * the paint event
|
|
933 */
|
|
934 protected void onPaint(PaintEvent e) {
|
|
935 }
|
|
936
|
|
937 /**
|
|
938 * Returns description control that will be placed under the title if
|
|
939 * present.
|
|
940 *
|
|
941 * @return the description control or <samp>null </samp> if not used.
|
|
942 */
|
|
943 protected Control getDescriptionControl() {
|
|
944 return null;
|
|
945 }
|
|
946
|
|
947 /**
|
|
948 * Returns the separator control that will be placed between the title and
|
|
949 * the description if present.
|
|
950 *
|
|
951 * @return the separator control or <samp>null </samp> if not used.
|
|
952 */
|
|
953 protected Control getSeparatorControl() {
|
|
954 return null;
|
|
955 }
|
|
956
|
|
957 /**
|
|
958 * Computes the size of the expandable composite.
|
|
959 *
|
|
960 * @see dwt.widgets.Composite#computeSize
|
|
961 */
|
|
962 public Point computeSize(int wHint, int hHint, bool changed) {
|
|
963 checkWidget();
|
|
964 Point size;
|
|
965 ExpandableLayout layout = cast(ExpandableLayout) getLayout();
|
|
966 if (wHint is DWT.DEFAULT || hHint is DWT.DEFAULT) {
|
|
967 size = layout.computeSize(this, wHint, hHint, changed);
|
|
968 } else {
|
|
969 size = new Point(wHint, hHint);
|
|
970 }
|
|
971 Rectangle trim = computeTrim(0, 0, size.x, size.y);
|
|
972 return new Point(trim.width, trim.height);
|
|
973 }
|
|
974
|
|
975 /**
|
|
976 * Returns <samp>true </samp> if the composite is fixed i.e. cannot be
|
|
977 * expanded or collapsed. Fixed control will still contain the title,
|
|
978 * separator and description (if present) as well as the client, but will be
|
|
979 * in the permanent expanded state and the toggle affordance will not be
|
|
980 * shown.
|
|
981 *
|
|
982 * @return <samp>true </samp> if the control is fixed in the expanded state,
|
|
983 * <samp>false </samp> if it can be collapsed.
|
|
984 */
|
|
985 protected bool isFixedStyle() {
|
|
986 return (expansionStyle & TWISTIE) is 0
|
|
987 && (expansionStyle & TREE_NODE) is 0;
|
|
988 }
|
|
989
|
|
990 /**
|
|
991 * Returns the text client control.
|
|
992 *
|
|
993 * @return Returns the text client control if specified, or
|
|
994 * <code>null</code> if not.
|
|
995 */
|
|
996 public Control getTextClient() {
|
|
997 return textClient;
|
|
998 }
|
|
999
|
|
1000 /**
|
|
1001 * Sets the text client control. Text client is a control that is a child of
|
|
1002 * the expandable composite and is placed to the right of the text. It can
|
|
1003 * be used to place small image hyperlinks. If more than one control is
|
|
1004 * needed, use Composite to hold them. Care should be taken that the height
|
|
1005 * of the control is comparable to the height of the text.
|
|
1006 *
|
|
1007 * @param textClient
|
|
1008 * the textClient to set or <code>null</code> if not needed any
|
|
1009 * more.
|
|
1010 */
|
|
1011 public void setTextClient(Control textClient) {
|
|
1012 if (this.textClient !is null)
|
|
1013 this.textClient.dispose();
|
|
1014 this.textClient = textClient;
|
|
1015 }
|
|
1016
|
|
1017 /**
|
|
1018 * Returns the difference in height between the text and the text client (if
|
|
1019 * set). This difference can cause vertical alignment problems when two
|
|
1020 * expandable composites are placed side by side, one with and one without
|
|
1021 * the text client. Use this method obtain the value to add to either
|
|
1022 * <code>descriptionVerticalSpacing</code> (if you have description) or
|
|
1023 * <code>clientVerticalSpacing</code> to correct the alignment of the
|
|
1024 * expandable without the text client.
|
|
1025 *
|
|
1026 * @return the difference in height between the text and the text client or
|
|
1027 * 0 if no corrective action is needed.
|
|
1028 * @since 3.3
|
|
1029 */
|
|
1030 public int getTextClientHeightDifference() {
|
|
1031 if (textClient is null || textLabel is null)
|
|
1032 return 0;
|
|
1033 int theight = textLabel.computeSize(DWT.DEFAULT, DWT.DEFAULT).y;
|
|
1034 int tcheight = textClient.computeSize(DWT.DEFAULT, DWT.DEFAULT).y;
|
|
1035 return Math.max(tcheight - theight, 0);
|
|
1036 }
|
|
1037
|
|
1038 /**
|
|
1039 * Tests if this expandable composite renders a title bar around the text.
|
|
1040 *
|
|
1041 * @return <code>true</code> for <code>TITLE_BAR</code> or
|
|
1042 * <code>SHORT_TITLE_BAR</code> styles, <code>false</code>
|
|
1043 * otherwise.
|
|
1044 */
|
|
1045 protected bool hasTitleBar() {
|
|
1046 return (getExpansionStyle() & TITLE_BAR) !is 0
|
|
1047 || (getExpansionStyle() & SHORT_TITLE_BAR) !is 0;
|
|
1048 }
|
|
1049
|
|
1050 /**
|
|
1051 * Sets the color of the title bar foreground when TITLE_BAR style is used.
|
|
1052 *
|
|
1053 * @param color
|
|
1054 * the title bar foreground
|
|
1055 */
|
|
1056 public void setTitleBarForeground(Color color) {
|
|
1057 titleBarForeground = color;
|
|
1058 textLabel.setForeground(color);
|
|
1059 }
|
|
1060
|
|
1061 /**
|
|
1062 * Returns the title bar foreground when TITLE_BAR style is used.
|
|
1063 *
|
|
1064 * @return the title bar foreground
|
|
1065 */
|
|
1066 public Color getTitleBarForeground() {
|
|
1067 return titleBarForeground;
|
|
1068 }
|
|
1069
|
|
1070 // end of APIs
|
|
1071
|
|
1072 private void toggleState() {
|
|
1073 bool newState = !isExpanded();
|
|
1074 fireExpanding(newState, true);
|
|
1075 internalSetExpanded(newState);
|
|
1076 fireExpanding(newState, false);
|
|
1077 if (newState)
|
|
1078 FormUtil.ensureVisible(this);
|
|
1079 }
|
|
1080
|
|
1081 private void fireExpanding(bool state, bool before) {
|
|
1082 int size = listeners.size();
|
|
1083 if (size is 0)
|
|
1084 return;
|
|
1085 ExpansionEvent e = new ExpansionEvent(this, state);
|
|
1086 Object [] listenerList = listeners.getListeners();
|
|
1087 for (int i = 0; i < size; i++) {
|
|
1088 IExpansionListener listener = cast(IExpansionListener) listenerList[i];
|
|
1089 if (before)
|
|
1090 listener.expansionStateChanging(e);
|
|
1091 else
|
|
1092 listener.expansionStateChanged(e);
|
|
1093 }
|
|
1094 }
|
|
1095
|
|
1096 private void verticalMove(bool down) {
|
|
1097 Composite parent = getParent();
|
|
1098 Control[] children = parent.getChildren();
|
|
1099 for (int i = 0; i < children.length; i++) {
|
|
1100 Control child = children[i];
|
|
1101 if (child is this) {
|
|
1102 ExpandableComposite sibling = getSibling(children, i, down);
|
|
1103 if (sibling !is null && sibling.toggle !is null) {
|
|
1104 sibling.setFocus();
|
|
1105 }
|
|
1106 break;
|
|
1107 }
|
|
1108 }
|
|
1109 }
|
|
1110
|
|
1111 private ExpandableComposite getSibling(Control[] children, int index,
|
|
1112 bool down) {
|
|
1113 int loc = down ? index + 1 : index - 1;
|
|
1114 while (loc >= 0 && loc < children.length) {
|
|
1115 Control c = children[loc];
|
|
1116 if (null !is cast(ExpandableComposite)c && c.isVisible())
|
|
1117 return cast(ExpandableComposite) c;
|
|
1118 loc = down ? loc + 1 : loc - 1;
|
|
1119 }
|
|
1120 return null;
|
|
1121 }
|
|
1122
|
|
1123 private void programmaticToggleState() {
|
|
1124 if (toggle !is null)
|
|
1125 toggle.setExpanded(!toggle.isExpanded());
|
|
1126 toggleState();
|
|
1127 }
|
|
1128
|
|
1129 private void paintTitleFocus(GC gc) {
|
|
1130 Point size = textLabel.getSize();
|
|
1131 gc.setBackground(textLabel.getBackground());
|
|
1132 gc.setForeground(textLabel.getForeground());
|
|
1133 if (toggle.isFocusControl())
|
|
1134 gc.drawFocus(0, 0, size.x, size.y);
|
|
1135 }
|
|
1136 }
|