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 }