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