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