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 }