1 
2 module dfl.toolbar;
3 
4 private import dfl.base, dfl.control, dfl.drawing, dfl.application,
5         dfl.event, dfl.collections;
6 private import dfl.internal.winapi, dfl.internal.dlib;
7 
8 version(DFL_NO_IMAGELIST) {
9 }
10 else {
11    private import dfl.imagelist;
12 }
13 
14 version(DFL_NO_MENUS)
15 version = DFL_TOOLBAR_NO_MENU;
16 
17 version(DFL_TOOLBAR_NO_MENU) {
18 }
19 else {
20    private import dfl.menu;
21 }
22 
23 
24 
25 enum ToolBarButtonStyle: ubyte {
26    PUSH_BUTTON = TBSTYLE_BUTTON, ///
27    TOGGLE_BUTTON = TBSTYLE_CHECK, /// ditto
28    SEPARATOR = TBSTYLE_SEP, /// ditto
29    //DROP_DOWN_BUTTON = TBSTYLE_DROPDOWN, /// ditto
30    DROP_DOWN_BUTTON = TBSTYLE_DROPDOWN | BTNS_WHOLEDROPDOWN, /// ditto
31 }
32 
33 
34 
35 class ToolBarButton {
36 
37    this() {
38       Application.ppin(cast(void*)this);
39    }
40 
41 
42    this(Dstring text) {
43       this();
44 
45       this.text = text;
46    }
47 
48 
49    version(DFL_NO_IMAGELIST) {
50    }
51    else {
52 
53       final @property void imageIndex(int index) { // setter
54          this._imgidx = index;
55 
56          //if(tbar && tbar.created)
57          // tbar.updateItem(this);
58       }
59 
60       /// ditto
61       final @property int imageIndex() { // getter
62          return _imgidx;
63       }
64    }
65 
66 
67 
68    @property void text(Dstring newText) { // setter
69       _text = newText;
70 
71       //if(tbar && tbar.created)
72       //
73    }
74 
75    /// ditto
76    @property Dstring text() { // getter
77       return _text;
78    }
79 
80 
81 
82    final @property void style(ToolBarButtonStyle st) { // setter
83       this._style = st;
84 
85       //if(tbar && tbar.created)
86       //
87    }
88 
89    /// ditto
90    final @property ToolBarButtonStyle style() { // getter
91       return _style;
92    }
93 
94 
95    override Dstring toString() {
96       return text;
97    }
98 
99 
100    override Dequ opEquals(Object o) {
101       return text == getObjectString(o);
102    }
103 
104 
105    Dequ opEquals(Dstring val) {
106       return text == val;
107    }
108 
109 
110    override int opCmp(Object o) {
111       return stringICmp(text, getObjectString(o));
112    }
113 
114 
115    int opCmp(Dstring val) {
116       return stringICmp(text, val);
117    }
118 
119 
120 
121    final @property void tag(Object o) { // setter
122       _tag = o;
123    }
124 
125    /// ditto
126    final @property Object tag() { // getter
127       return _tag;
128    }
129 
130 
131    version(DFL_TOOLBAR_NO_MENU) {
132    }
133    else {
134 
135       final @property void dropDownMenu(ContextMenu cmenu) { // setter
136          _cmenu = cmenu;
137       }
138 
139       /// ditto
140       final @property ContextMenu dropDownMenu() { // getter
141          return _cmenu;
142       }
143    }
144 
145 
146 
147    final @property ToolBar parent() { // getter
148       return tbar;
149    }
150 
151 
152 
153    final @property Rect rectangle() { // getter
154       //if(!tbar || !tbar.created)
155       if(!visible) {
156          return Rect(0, 0, 0, 0);   // ?
157       }
158       assert(tbar !is null);
159       RECT rect;
160       //assert(-1 != tbar.buttons.indexOf(this));
161       tbar.prevwproc(TB_GETITEMRECT, tbar.buttons.indexOf(this), cast(LPARAM)&rect); // Fails if item is hidden.
162       return Rect(&rect); // Should return all 0`s if TB_GETITEMRECT failed.
163    }
164 
165 
166 
167    final @property void visible(bool byes) { // setter
168       if(byes) {
169          _state &= ~TBSTATE_HIDDEN;
170       } else {
171          _state |= TBSTATE_HIDDEN;
172       }
173 
174       if(tbar && tbar.created) {
175          tbar.prevwproc(TB_SETSTATE, _id, MAKELPARAM(_state, 0));
176       }
177    }
178 
179    /// ditto
180    final @property bool visible() { // getter
181       if(!tbar || !tbar.created) {
182          return false;
183       }
184       return true; // To-do: get actual hidden state.
185    }
186 
187 
188 
189    final @property void enabled(bool byes) { // setter
190       if(byes) {
191          _state |= TBSTATE_ENABLED;
192       } else {
193          _state &= ~TBSTATE_ENABLED;
194       }
195 
196       if(tbar && tbar.created) {
197          tbar.prevwproc(TB_SETSTATE, _id, MAKELPARAM(_state, 0));
198       }
199    }
200 
201    /// ditto
202    final @property bool enabled() { // getter
203       if(_state & TBSTATE_ENABLED) {
204          return true;
205       }
206       return false;
207    }
208 
209 
210 
211    final @property void pushed(bool byes) { // setter
212       if(byes) {
213          _state = (_state & ~TBSTATE_INDETERMINATE) | TBSTATE_CHECKED;
214       } else {
215          _state &= ~TBSTATE_CHECKED;
216       }
217 
218       if(tbar && tbar.created) {
219          tbar.prevwproc(TB_SETSTATE, _id, MAKELPARAM(_state, 0));
220       }
221    }
222 
223    /// ditto
224    final @property bool pushed() { // getter
225       if(TBSTATE_CHECKED == (_state & TBSTATE_CHECKED)) {
226          return true;
227       }
228       return false;
229    }
230 
231 
232 
233    final @property void partialPush(bool byes) { // setter
234       if(byes) {
235          _state = (_state & ~TBSTATE_CHECKED) | TBSTATE_INDETERMINATE;
236       } else {
237          _state &= ~TBSTATE_INDETERMINATE;
238       }
239 
240       if(tbar && tbar.created) {
241          tbar.prevwproc(TB_SETSTATE, _id, MAKELPARAM(_state, 0));
242       }
243    }
244 
245    /// ditto
246    final @property bool partialPush() { // getter
247       if(TBSTATE_INDETERMINATE == (_state & TBSTATE_INDETERMINATE)) {
248          return true;
249       }
250       return false;
251    }
252 
253 
254  private:
255    ToolBar tbar;
256    int _id = 0;
257    Dstring _text;
258    Object _tag;
259    ToolBarButtonStyle _style = ToolBarButtonStyle.PUSH_BUTTON;
260    BYTE _state = TBSTATE_ENABLED;
261    version(DFL_TOOLBAR_NO_MENU) {
262    }
263    else {
264       ContextMenu _cmenu;
265    }
266    version(DFL_NO_IMAGELIST) {
267    }
268    else {
269       int _imgidx = -1;
270    }
271 }
272 
273 
274 
275 class ToolBarButtonClickEventArgs: EventArgs {
276    this(ToolBarButton tbbtn) {
277       _btn = tbbtn;
278    }
279 
280 
281 
282    final @property ToolBarButton button() { // getter
283       return _btn;
284    }
285 
286 
287  private:
288 
289    ToolBarButton _btn;
290 }
291 
292 
293 
294 class ToolBar: ControlSuperClass { // docmain
295    class ToolBarButtonCollection {
296       protected this() {
297       }
298 
299 
300     private:
301 
302       ToolBarButton[] _buttons;
303 
304 
305       void _adding(size_t idx, ToolBarButton val) {
306          if(val.tbar) {
307             throw new DflException("ToolBarButton already belongs to a ToolBar");
308          }
309       }
310 
311 
312       void _added(size_t idx, ToolBarButton val) {
313          val.tbar = tbar;
314          val._id = tbar._allocTbbID();
315 
316          if(created) {
317             _ins(idx, val);
318          }
319       }
320 
321 
322       void _removed(size_t idx, ToolBarButton val) {
323          if(size_t.max == idx) { // Clear all.
324          } else {
325             if(created) {
326                prevwproc(TB_DELETEBUTTON, idx, 0);
327             }
328             val.tbar = null;
329          }
330       }
331 
332 
333     public:
334 
335       mixin ListWrapArray!(ToolBarButton, _buttons,
336                            _adding, _added,
337                            _blankListCallback!(ToolBarButton), _removed,
338                            true, false, false,
339                            true); // CLEAR_EACH
340    }
341 
342 
343    private @property ToolBar tbar() {
344       return this;
345    }
346 
347 
348    this() {
349       _initToolbar();
350 
351       _tbuttons = new ToolBarButtonCollection();
352 
353       dock = DockStyle.TOP;
354 
355       //wexstyle |= WS_EX_CLIENTEDGE;
356       wclassStyle = toolbarClassStyle;
357    }
358 
359 
360 
361    final @property ToolBarButtonCollection buttons() { // getter
362       return _tbuttons;
363    }
364 
365 
366    // buttonSize...
367 
368 
369 
370    final @property Size imageSize() { // getter
371       version(DFL_NO_IMAGELIST) {
372       }
373       else {
374          if(_imglist) {
375             return _imglist.imageSize;
376          }
377       }
378       return Size(16, 16); // ?
379    }
380 
381 
382    version(DFL_NO_IMAGELIST) {
383    }
384    else {
385 
386       final @property void imageList(ImageList imglist) { // setter
387          if(isHandleCreated) {
388             prevwproc(TB_SETIMAGELIST, 0, cast(WPARAM)imglist.handle);
389          }
390 
391          _imglist = imglist;
392       }
393 
394       /// ditto
395       final @property ImageList imageList() { // getter
396          return _imglist;
397       }
398    }
399 
400 
401 
402    Event!(ToolBar, ToolBarButtonClickEventArgs) buttonClick;
403 
404 
405 
406    protected void onButtonClick(ToolBarButtonClickEventArgs ea) {
407       buttonClick(this, ea);
408    }
409 
410 
411    protected override void onReflectedMessage(ref Message m) {
412       switch(m.msg) {
413          case WM_NOTIFY: {
414                auto nmh = cast(LPNMHDR)m.lParam;
415                switch(nmh.code) {
416                   case NM_CLICK: {
417                         auto nmm = cast(LPNMMOUSE)nmh;
418                         if(nmm.dwItemData) {
419                            auto tbb = cast(ToolBarButton)cast(void*)nmm.dwItemData;
420                            scope ToolBarButtonClickEventArgs bcea = new ToolBarButtonClickEventArgs(tbb);
421                            onButtonClick(bcea);
422                         }
423                      }
424                      break;
425 
426                   case TBN_DROPDOWN:
427                      version(DFL_TOOLBAR_NO_MENU) { // This condition might be removed later.
428                      }
429                      else { // Ditto.
430                         auto nmtb = cast(LPNMTOOLBARA)nmh; // NMTOOLBARA/NMTOOLBARW doesn't matter here; string fields not used.
431                         auto tbb = buttomFromID(nmtb.iItem);
432                         if(tbb) {
433                            version(DFL_TOOLBAR_NO_MENU) { // Keep this here in case the other condition is removed.
434                            }
435                            else { // Ditto.
436                               if(tbb._cmenu) {
437                                  auto brect = tbb.rectangle;
438                                  tbb._cmenu.show(this, pointToScreen(Point(brect.x, brect.bottom)));
439                                  // Note: showing a menu also triggers a click!
440                               }
441                            }
442                         }
443                      }
444                      m.result = TBDDRET_DEFAULT;
445                      return;
446 
447                   default:
448                }
449             }
450             break;
451 
452          default:
453             super.onReflectedMessage(m);
454       }
455    }
456 
457 
458    protected override @property Size defaultSize() { // getter
459       return Size(100, 16);
460    }
461 
462 
463    protected override void createParams(ref CreateParams cp) {
464       super.createParams(cp);
465 
466       cp.className = TOOLBAR_CLASSNAME;
467    }
468 
469 
470 
471    // Used internally
472    /+package+/ final ToolBarButton buttomFromID(int id) { // package
473       foreach(tbb; _tbuttons._buttons) {
474          if(id == tbb._id) {
475             return tbb;
476          }
477       }
478       return null;
479    }
480 
481 
482    package int _lastTbbID = 0;
483 
484    package final int _allocTbbID() {
485       for(int j = 0; j != 250; j++) {
486          _lastTbbID++;
487          if(_lastTbbID >= short.max) {
488             _lastTbbID = 1;
489          }
490 
491          if(!buttomFromID(_lastTbbID)) {
492             return _lastTbbID;
493          }
494       }
495       return 0;
496    }
497 
498 
499 
500    protected override void onHandleCreated(EventArgs ea) {
501       super.onHandleCreated(ea);
502 
503       static assert(TBBUTTON.sizeof == 20);
504       prevwproc(TB_BUTTONSTRUCTSIZE, TBBUTTON.sizeof, 0);
505 
506       //prevwproc(TB_SETPADDING, 0, MAKELPARAM(0, 0));
507 
508       version(DFL_NO_IMAGELIST) {
509       }
510       else {
511          if(_imglist) {
512             prevwproc(TB_SETIMAGELIST, 0, cast(WPARAM)_imglist.handle);
513          }
514       }
515 
516       foreach(idx, tbb; _tbuttons._buttons) {
517          _ins(idx, tbb);
518       }
519 
520       //prevwproc(TB_AUTOSIZE, 0, 0);
521    }
522 
523 
524    protected override void prevWndProc(ref Message msg) {
525       //msg.result = CallWindowProcA(toolbarPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
526       msg.result = dfl.internal.utf.callWindowProc(toolbarPrevWndProc, msg.hWnd, msg.msg, msg.wParam, msg.lParam);
527    }
528 
529 
530  private:
531 
532    ToolBarButtonCollection _tbuttons;
533 
534    version(DFL_NO_IMAGELIST) {
535    }
536    else {
537       ImageList _imglist;
538    }
539 
540 
541    void _ins(size_t idx, ToolBarButton tbb) {
542       // To change: TB_SETBUTTONINFO
543 
544       TBBUTTON xtb;
545       version(DFL_NO_IMAGELIST) {
546          xtb.iBitmap = -1;
547       }
548       else {
549          xtb.iBitmap = tbb._imgidx;
550       }
551       xtb.idCommand = tbb._id;
552       xtb.dwData = cast(DWORD)cast(void*)tbb;
553       xtb.fsState = tbb._state;
554       xtb.fsStyle = TBSTYLE_AUTOSIZE | tbb._style; // TBSTYLE_AUTOSIZE factors in the text's width instead of default button size.
555       LRESULT lresult;
556       // MSDN says iString can be either an int offset or pointer to a string buffer.
557       if(dfl.internal.utf.useUnicode) {
558          if(tbb._text.length) {
559             xtb.iString = cast(typeof(xtb.iString))dfl.internal.utf.toUnicodez(tbb._text);
560          }
561          //prevwproc(TB_ADDBUTTONSW, 1, cast(LPARAM)&xtb);
562          lresult = prevwproc(TB_INSERTBUTTONW, idx, cast(LPARAM)&xtb);
563       } else {
564          if(tbb._text.length) {
565             xtb.iString = cast(typeof(xtb.iString))dfl.internal.utf.toAnsiz(tbb._text);
566          }
567          //prevwproc(TB_ADDBUTTONSA, 1, cast(LPARAM)&xtb);
568          lresult = prevwproc(TB_INSERTBUTTONA, idx, cast(LPARAM)&xtb);
569       }
570       //if(!lresult)
571       // throw new DflException("Unable to add ToolBarButton");
572    }
573 
574 
575  package:
576  final:
577    LRESULT prevwproc(UINT msg, WPARAM wparam, LPARAM lparam) {
578       //return CallWindowProcA(toolbarPrevWndProc, hwnd, msg, wparam, lparam);
579       return dfl.internal.utf.callWindowProc(toolbarPrevWndProc, hwnd, msg, wparam, lparam);
580    }
581 }
582 
583 
584 private {
585    enum TOOLBAR_CLASSNAME = "DFL_ToolBar";
586 
587    WNDPROC toolbarPrevWndProc;
588 
589    LONG toolbarClassStyle;
590 
591    void _initToolbar() {
592       if(!toolbarPrevWndProc) {
593          _initCommonControls(ICC_BAR_CLASSES);
594 
595          dfl.internal.utf.WndClass info;
596          toolbarPrevWndProc = superClass(HINSTANCE.init, "ToolbarWindow32", TOOLBAR_CLASSNAME, info);
597          if(!toolbarPrevWndProc) {
598             _unableToInit(TOOLBAR_CLASSNAME);
599          }
600          toolbarClassStyle = info.wc.style;
601       }
602    }
603 }
604