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