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