]> git.saurik.com Git - wxWidgets.git/blob - src/msw/tbar95.cpp
added wrapper for <commctrl.h> as well
[wxWidgets.git] / src / msw / tbar95.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: msw/tbar95.cpp
3 // Purpose: wxToolBar
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 04/01/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "tbar95.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #ifndef WX_PRECOMP
32 #include "wx/frame.h"
33 #include "wx/log.h"
34 #include "wx/intl.h"
35 #include "wx/dynarray.h"
36 #include "wx/settings.h"
37 #include "wx/bitmap.h"
38 #include "wx/dcmemory.h"
39 #include "wx/control.h"
40 #endif
41
42 #if wxUSE_TOOLBAR && defined(__WIN95__) && wxUSE_TOOLBAR_NATIVE
43
44 #include "wx/toolbar.h"
45
46 #if !defined(__GNUWIN32__) && !defined(__SALFORDC__)
47 #include "malloc.h"
48 #endif
49
50 #include "wx/msw/private.h"
51
52 // include <commctrl.h> "properly"
53 #include "wx/msw/wrapcctl.h"
54
55 #include "wx/app.h" // for GetComCtl32Version
56
57 // ----------------------------------------------------------------------------
58 // conditional compilation
59 // ----------------------------------------------------------------------------
60
61 // wxWindows previously always considered that toolbar buttons have light grey
62 // (0xc0c0c0) background and so ignored any bitmap masks - however, this
63 // doesn't work with XPMs which then appear to have black background. To make
64 // this work, we must respect the bitmap masks - which we do now. This should
65 // be ok in any case, but to restore 100% compatible with the old version
66 // behaviour, you can set this to 0.
67 #define USE_BITMAP_MASKS 1
68
69 // ----------------------------------------------------------------------------
70 // constants
71 // ----------------------------------------------------------------------------
72
73 // these standard constants are not always defined in compilers headers
74
75 // Styles
76 #ifndef TBSTYLE_FLAT
77 #define TBSTYLE_LIST 0x1000
78 #define TBSTYLE_FLAT 0x0800
79 #endif
80
81 #ifndef TBSTYLE_TRANSPARENT
82 #define TBSTYLE_TRANSPARENT 0x8000
83 #endif
84
85 #ifndef TBSTYLE_TOOLTIPS
86 #define TBSTYLE_TOOLTIPS 0x0100
87 #endif
88
89 // Messages
90 #ifndef TB_GETSTYLE
91 #define TB_SETSTYLE (WM_USER + 56)
92 #define TB_GETSTYLE (WM_USER + 57)
93 #endif
94
95 #ifndef TB_HITTEST
96 #define TB_HITTEST (WM_USER + 69)
97 #endif
98
99 // these values correspond to those used by comctl32.dll
100 #define DEFAULTBITMAPX 16
101 #define DEFAULTBITMAPY 15
102 #define DEFAULTBUTTONX 24
103 #define DEFAULTBUTTONY 24
104 #define DEFAULTBARHEIGHT 27
105
106 // ----------------------------------------------------------------------------
107 // wxWin macros
108 // ----------------------------------------------------------------------------
109
110 IMPLEMENT_DYNAMIC_CLASS(wxToolBar, wxControl)
111
112 BEGIN_EVENT_TABLE(wxToolBar, wxToolBarBase)
113 EVT_MOUSE_EVENTS(wxToolBar::OnMouseEvent)
114 EVT_SYS_COLOUR_CHANGED(wxToolBar::OnSysColourChanged)
115 END_EVENT_TABLE()
116
117 // ----------------------------------------------------------------------------
118 // private classes
119 // ----------------------------------------------------------------------------
120
121 class wxToolBarTool : public wxToolBarToolBase
122 {
123 public:
124 wxToolBarTool(wxToolBar *tbar,
125 int id,
126 const wxString& label,
127 const wxBitmap& bmpNormal,
128 const wxBitmap& bmpDisabled,
129 wxItemKind kind,
130 wxObject *clientData,
131 const wxString& shortHelp,
132 const wxString& longHelp)
133 : wxToolBarToolBase(tbar, id, label, bmpNormal, bmpDisabled, kind,
134 clientData, shortHelp, longHelp)
135 {
136 m_nSepCount = 0;
137 }
138
139 wxToolBarTool(wxToolBar *tbar, wxControl *control)
140 : wxToolBarToolBase(tbar, control)
141 {
142 m_nSepCount = 1;
143 }
144
145 virtual void SetLabel(const wxString& label)
146 {
147 if ( label == m_label )
148 return;
149
150 wxToolBarToolBase::SetLabel(label);
151
152 // we need to update the label shown in the toolbar because it has a
153 // pointer to the internal buffer of the old label
154 //
155 // TODO: use TB_SETBUTTONINFO
156 }
157
158 // set/get the number of separators which we use to cover the space used by
159 // a control in the toolbar
160 void SetSeparatorsCount(size_t count) { m_nSepCount = count; }
161 size_t GetSeparatorsCount() const { return m_nSepCount; }
162
163 private:
164 size_t m_nSepCount;
165
166 DECLARE_NO_COPY_CLASS(wxToolBarTool)
167 };
168
169
170 // ============================================================================
171 // implementation
172 // ============================================================================
173
174 // ----------------------------------------------------------------------------
175 // wxToolBarTool
176 // ----------------------------------------------------------------------------
177
178 wxToolBarToolBase *wxToolBar::CreateTool(int id,
179 const wxString& label,
180 const wxBitmap& bmpNormal,
181 const wxBitmap& bmpDisabled,
182 wxItemKind kind,
183 wxObject *clientData,
184 const wxString& shortHelp,
185 const wxString& longHelp)
186 {
187 return new wxToolBarTool(this, id, label, bmpNormal, bmpDisabled, kind,
188 clientData, shortHelp, longHelp);
189 }
190
191 wxToolBarToolBase *wxToolBar::CreateTool(wxControl *control)
192 {
193 return new wxToolBarTool(this, control);
194 }
195
196 // ----------------------------------------------------------------------------
197 // wxToolBar construction
198 // ----------------------------------------------------------------------------
199
200 void wxToolBar::Init()
201 {
202 m_hBitmap = 0;
203
204 m_nButtons = 0;
205
206 m_defaultWidth = DEFAULTBITMAPX;
207 m_defaultHeight = DEFAULTBITMAPY;
208
209 m_pInTool = 0;
210 }
211
212 bool wxToolBar::Create(wxWindow *parent,
213 wxWindowID id,
214 const wxPoint& pos,
215 const wxSize& size,
216 long style,
217 const wxString& name)
218 {
219 // common initialisation
220 if ( !CreateControl(parent, id, pos, size, style, wxDefaultValidator, name) )
221 return FALSE;
222
223 // MSW-specific initialisation
224 if ( !MSWCreateToolbar(pos, size) )
225 return FALSE;
226
227 // set up the colors and fonts
228 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_MENUBAR));
229 SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
230
231 return TRUE;
232 }
233
234 bool wxToolBar::MSWCreateToolbar(const wxPoint& pos, const wxSize& size)
235 {
236 if ( !MSWCreateControl(TOOLBARCLASSNAME, wxEmptyString, pos, size) )
237 return FALSE;
238
239 // toolbar-specific post initialisation
240 ::SendMessage(GetHwnd(), TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
241
242 return TRUE;
243 }
244
245 void wxToolBar::Recreate()
246 {
247 const HWND hwndOld = GetHwnd();
248 if ( !hwndOld )
249 {
250 // we haven't been created yet, no need to recreate
251 return;
252 }
253
254 // get the position and size before unsubclassing the old toolbar
255 const wxPoint pos = GetPosition();
256 const wxSize size = GetSize();
257
258 UnsubclassWin();
259
260 if ( !MSWCreateToolbar(pos, size) )
261 {
262 // what can we do?
263 wxFAIL_MSG( _T("recreating the toolbar failed") );
264
265 return;
266 }
267
268 // reparent all our children under the new toolbar
269 for ( wxWindowList::compatibility_iterator node = m_children.GetFirst();
270 node;
271 node = node->GetNext() )
272 {
273 wxWindow *win = node->GetData();
274 if ( !win->IsTopLevel() )
275 ::SetParent(GetHwndOf(win), GetHwnd());
276 }
277
278 // only destroy the old toolbar now -- after all the children had been
279 // reparented
280 ::DestroyWindow(hwndOld);
281
282 // it is for the old bitmap control and can't be used with the new one
283 if ( m_hBitmap )
284 {
285 ::DeleteObject((HBITMAP) m_hBitmap);
286 m_hBitmap = 0;
287 }
288
289 Realize();
290 UpdateSize();
291 }
292
293 wxToolBar::~wxToolBar()
294 {
295 // we must refresh the frame size when the toolbar is deleted but the frame
296 // is not - otherwise toolbar leaves a hole in the place it used to occupy
297 wxFrame *frame = wxDynamicCast(GetParent(), wxFrame);
298 if ( frame && !frame->IsBeingDeleted() )
299 {
300 frame->SendSizeEvent();
301 }
302
303 if ( m_hBitmap )
304 {
305 ::DeleteObject((HBITMAP) m_hBitmap);
306 }
307 }
308
309 wxSize wxToolBar::DoGetBestSize() const
310 {
311 wxSize sizeBest = GetToolSize();
312 sizeBest.x *= GetToolsCount();
313
314 // reverse horz and vertical components if necessary
315 return HasFlag(wxTB_VERTICAL) ? wxSize(sizeBest.y, sizeBest.x) : sizeBest;
316 }
317
318 WXDWORD wxToolBar::MSWGetStyle(long style, WXDWORD *exstyle) const
319 {
320 // toolbars never have border, giving one to them results in broken
321 // appearance
322 WXDWORD msStyle = wxControl::MSWGetStyle
323 (
324 (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
325 );
326
327 // always include this one, it never hurts and setting it later only if we
328 // do have tooltips wouldn't work
329 msStyle |= TBSTYLE_TOOLTIPS;
330
331 if ( style & (wxTB_FLAT | wxTB_HORZ_LAYOUT) )
332 {
333 // static as it doesn't change during the program lifetime
334 static int s_verComCtl = wxTheApp->GetComCtl32Version();
335
336 // comctl32.dll 4.00 doesn't support the flat toolbars and using this
337 // style with 6.00 (part of Windows XP) leads to the toolbar with
338 // incorrect background colour - and not using it still results in the
339 // correct (flat) toolbar, so don't use it there
340 if ( s_verComCtl > 400 && s_verComCtl < 600 )
341 {
342 msStyle |= TBSTYLE_FLAT | TBSTYLE_TRANSPARENT;
343 }
344
345 if ( s_verComCtl >= 470 && style & wxTB_HORZ_LAYOUT )
346 {
347 msStyle |= TBSTYLE_LIST;
348 }
349 }
350
351 if ( style & wxTB_NODIVIDER )
352 msStyle |= CCS_NODIVIDER;
353
354 if ( style & wxTB_NOALIGN )
355 msStyle |= CCS_NOPARENTALIGN;
356
357 if ( style & wxTB_VERTICAL )
358 msStyle |= CCS_VERT;
359
360 return msStyle;
361 }
362
363 // ----------------------------------------------------------------------------
364 // adding/removing tools
365 // ----------------------------------------------------------------------------
366
367 bool wxToolBar::DoInsertTool(size_t WXUNUSED(pos), wxToolBarToolBase *tool)
368 {
369 // nothing special to do here - we really create the toolbar buttons in
370 // Realize() later
371 tool->Attach(this);
372
373 return TRUE;
374 }
375
376 bool wxToolBar::DoDeleteTool(size_t pos, wxToolBarToolBase *tool)
377 {
378 // the main difficulty we have here is with the controls in the toolbars:
379 // as we (sometimes) use several separators to cover up the space used by
380 // them, the indices are not the same for us and the toolbar
381
382 // first determine the position of the first button to delete: it may be
383 // different from pos if we use several separators to cover the space used
384 // by a control
385 wxToolBarToolsList::compatibility_iterator node;
386 for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
387 {
388 wxToolBarToolBase *tool2 = node->GetData();
389 if ( tool2 == tool )
390 {
391 // let node point to the next node in the list
392 node = node->GetNext();
393
394 break;
395 }
396
397 if ( tool2->IsControl() )
398 {
399 pos += ((wxToolBarTool *)tool2)->GetSeparatorsCount() - 1;
400 }
401 }
402
403 // now determine the number of buttons to delete and the area taken by them
404 size_t nButtonsToDelete = 1;
405
406 // get the size of the button we're going to delete
407 RECT r;
408 if ( !::SendMessage(GetHwnd(), TB_GETITEMRECT, pos, (LPARAM)&r) )
409 {
410 wxLogLastError(_T("TB_GETITEMRECT"));
411 }
412
413 int width = r.right - r.left;
414
415 if ( tool->IsControl() )
416 {
417 nButtonsToDelete = ((wxToolBarTool *)tool)->GetSeparatorsCount();
418
419 width *= nButtonsToDelete;
420 }
421
422 // do delete all buttons
423 m_nButtons -= nButtonsToDelete;
424 while ( nButtonsToDelete-- > 0 )
425 {
426 if ( !::SendMessage(GetHwnd(), TB_DELETEBUTTON, pos, 0) )
427 {
428 wxLogLastError(wxT("TB_DELETEBUTTON"));
429
430 return FALSE;
431 }
432 }
433
434 tool->Detach();
435
436 // and finally reposition all the controls after this button (the toolbar
437 // takes care of all normal items)
438 for ( /* node -> first after deleted */ ; node; node = node->GetNext() )
439 {
440 wxToolBarToolBase *tool2 = node->GetData();
441 if ( tool2->IsControl() )
442 {
443 int x;
444 wxControl *control = tool2->GetControl();
445 control->GetPosition(&x, NULL);
446 control->Move(x - width, -1);
447 }
448 }
449
450 return TRUE;
451 }
452
453 bool wxToolBar::Realize()
454 {
455 const size_t nTools = GetToolsCount();
456 if ( nTools == 0 )
457 {
458 // nothing to do
459 return TRUE;
460 }
461
462 const bool isVertical = HasFlag(wxTB_VERTICAL);
463
464 // delete all old buttons, if any
465 for ( size_t pos = 0; pos < m_nButtons; pos++ )
466 {
467 if ( !::SendMessage(GetHwnd(), TB_DELETEBUTTON, 0, 0) )
468 {
469 wxLogDebug(wxT("TB_DELETEBUTTON failed"));
470 }
471 }
472
473 // First, add the bitmap: we use one bitmap for all toolbar buttons
474 // ----------------------------------------------------------------
475
476 wxToolBarToolsList::compatibility_iterator node;
477 int bitmapId = 0;
478
479 wxSize sizeBmp;
480 if ( HasFlag(wxTB_NOICONS) )
481 {
482 // no icons, don't leave space for them
483 sizeBmp.x =
484 sizeBmp.y = 0;
485 }
486 else // do show icons
487 {
488 // if we already have a bitmap, we'll replace the existing one --
489 // otherwise we'll install a new one
490 HBITMAP oldToolBarBitmap = (HBITMAP)m_hBitmap;
491
492 sizeBmp.x = m_defaultWidth;
493 sizeBmp.y = m_defaultHeight;
494
495 const wxCoord totalBitmapWidth = m_defaultWidth * nTools,
496 totalBitmapHeight = m_defaultHeight;
497
498 // Create a bitmap and copy all the tool bitmaps to it
499 #if USE_BITMAP_MASKS
500 wxMemoryDC dcAllButtons;
501 wxBitmap bitmap(totalBitmapWidth, totalBitmapHeight);
502 dcAllButtons.SelectObject(bitmap);
503 dcAllButtons.SetBackground(*wxLIGHT_GREY_BRUSH);
504 dcAllButtons.Clear();
505
506 m_hBitmap = bitmap.GetHBITMAP();
507 HBITMAP hBitmap = (HBITMAP)m_hBitmap;
508 #else // !USE_BITMAP_MASKS
509 HBITMAP hBitmap = ::CreateCompatibleBitmap(ScreenHDC(),
510 totalBitmapWidth,
511 totalBitmapHeight);
512 if ( !hBitmap )
513 {
514 wxLogLastError(_T("CreateCompatibleBitmap"));
515
516 return FALSE;
517 }
518
519 m_hBitmap = (WXHBITMAP)hBitmap;
520
521 MemoryHDC memoryDC;
522 SelectInHDC hdcSelector(memoryDC, hBitmap);
523
524 MemoryHDC memoryDC2;
525 #endif // USE_BITMAP_MASKS/!USE_BITMAP_MASKS
526
527 // the button position
528 wxCoord x = 0;
529
530 // the number of buttons (not separators)
531 int nButtons = 0;
532
533 for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
534 {
535 wxToolBarToolBase *tool = node->GetData();
536 if ( tool->IsButton() )
537 {
538 const wxBitmap& bmp = tool->GetNormalBitmap();
539 if ( bmp.Ok() )
540 {
541 #if USE_BITMAP_MASKS
542 // notice the last parameter: do use mask
543 dcAllButtons.DrawBitmap(bmp, x, 0, TRUE);
544 #else // !USE_BITMAP_MASKS
545 SelectInHDC hdcSelector2(memoryDC2, GetHbitmapOf(bmp));
546 if ( !BitBlt(memoryDC,
547 x, 0, m_defaultWidth, m_defaultHeight,
548 memoryDC2,
549 0, 0, SRCCOPY) )
550 {
551 wxLogLastError(wxT("BitBlt"));
552 }
553 #endif // USE_BITMAP_MASKS/!USE_BITMAP_MASKS
554 }
555 else
556 {
557 wxFAIL_MSG( _T("invalid tool button bitmap") );
558 }
559
560 // still inc width and number of buttons because otherwise the
561 // subsequent buttons will all be shifted which is rather confusing
562 // (and like this you'd see immediately which bitmap was bad)
563 x += m_defaultWidth;
564 nButtons++;
565 }
566 }
567
568 #if USE_BITMAP_MASKS
569 dcAllButtons.SelectObject(wxNullBitmap);
570
571 // don't delete this HBITMAP!
572 bitmap.SetHBITMAP(0);
573 #endif // USE_BITMAP_MASKS/!USE_BITMAP_MASKS
574
575 // Map to system colours
576 hBitmap = (HBITMAP)MapBitmap((WXHBITMAP) hBitmap,
577 totalBitmapWidth, totalBitmapHeight);
578
579 bool addBitmap = TRUE;
580
581 if ( oldToolBarBitmap )
582 {
583 #ifdef TB_REPLACEBITMAP
584 if ( wxTheApp->GetComCtl32Version() >= 400 )
585 {
586 TBREPLACEBITMAP replaceBitmap;
587 replaceBitmap.hInstOld = NULL;
588 replaceBitmap.hInstNew = NULL;
589 replaceBitmap.nIDOld = (UINT) oldToolBarBitmap;
590 replaceBitmap.nIDNew = (UINT) hBitmap;
591 replaceBitmap.nButtons = nButtons;
592 if ( !::SendMessage(GetHwnd(), TB_REPLACEBITMAP,
593 0, (LPARAM) &replaceBitmap) )
594 {
595 wxFAIL_MSG(wxT("Could not replace the old bitmap"));
596 }
597
598 ::DeleteObject(oldToolBarBitmap);
599
600 // already done
601 addBitmap = FALSE;
602 }
603 else
604 #endif // TB_REPLACEBITMAP
605 {
606 // we can't replace the old bitmap, so we will add another one
607 // (awfully inefficient, but what else to do?) and shift the bitmap
608 // indices accordingly
609 addBitmap = TRUE;
610
611 bitmapId = m_nButtons;
612 }
613 }
614
615 if ( addBitmap ) // no old bitmap or we can't replace it
616 {
617 TBADDBITMAP addBitmap;
618 addBitmap.hInst = 0;
619 addBitmap.nID = (UINT) hBitmap;
620 if ( ::SendMessage(GetHwnd(), TB_ADDBITMAP,
621 (WPARAM) nButtons, (LPARAM)&addBitmap) == -1 )
622 {
623 wxFAIL_MSG(wxT("Could not add bitmap to toolbar"));
624 }
625 }
626 }
627
628 // don't call SetToolBitmapSize() as we don't want to change the values of
629 // m_defaultWidth/Height
630 if ( !::SendMessage(GetHwnd(), TB_SETBITMAPSIZE, 0,
631 MAKELONG(sizeBmp.x, sizeBmp.y)) )
632 {
633 wxLogLastError(_T("TB_SETBITMAPSIZE"));
634 }
635
636 // Next add the buttons and separators
637 // -----------------------------------
638
639 TBBUTTON *buttons = new TBBUTTON[nTools];
640
641 // this array will hold the indices of all controls in the toolbar
642 wxArrayInt controlIds;
643
644 bool lastWasRadio = FALSE;
645 int i = 0;
646 for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
647 {
648 wxToolBarToolBase *tool = node->GetData();
649
650 // don't add separators to the vertical toolbar with old comctl32.dll
651 // versions as they didn't handle this properly
652 if ( isVertical && tool->IsSeparator() &&
653 wxTheApp->GetComCtl32Version() <= 472 )
654 {
655 continue;
656 }
657
658
659 TBBUTTON& button = buttons[i];
660
661 wxZeroMemory(button);
662
663 bool isRadio = FALSE;
664 switch ( tool->GetStyle() )
665 {
666 case wxTOOL_STYLE_CONTROL:
667 button.idCommand = tool->GetId();
668 // fall through: create just a separator too
669
670 case wxTOOL_STYLE_SEPARATOR:
671 button.fsState = TBSTATE_ENABLED;
672 button.fsStyle = TBSTYLE_SEP;
673 break;
674
675 case wxTOOL_STYLE_BUTTON:
676 if ( !HasFlag(wxTB_NOICONS) )
677 button.iBitmap = bitmapId;
678
679 if ( HasFlag(wxTB_TEXT) )
680 {
681 const wxString& label = tool->GetLabel();
682 if ( !label.empty() )
683 {
684 button.iString = (int)label.c_str();
685 }
686 }
687
688 button.idCommand = tool->GetId();
689
690 if ( tool->IsEnabled() )
691 button.fsState |= TBSTATE_ENABLED;
692 if ( tool->IsToggled() )
693 button.fsState |= TBSTATE_CHECKED;
694
695 switch ( tool->GetKind() )
696 {
697 case wxITEM_RADIO:
698 button.fsStyle = TBSTYLE_CHECKGROUP;
699
700 if ( !lastWasRadio )
701 {
702 // the first item in the radio group is checked by
703 // default to be consistent with wxGTK and the menu
704 // radio items
705 button.fsState |= TBSTATE_CHECKED;
706
707 tool->Toggle(TRUE);
708 }
709
710 isRadio = TRUE;
711 break;
712
713 case wxITEM_CHECK:
714 button.fsStyle = TBSTYLE_CHECK;
715 break;
716
717 default:
718 wxFAIL_MSG( _T("unexpected toolbar button kind") );
719 // fall through
720
721 case wxITEM_NORMAL:
722 button.fsStyle = TBSTYLE_BUTTON;
723 }
724
725 bitmapId++;
726 break;
727 }
728
729 lastWasRadio = isRadio;
730
731 i++;
732 }
733
734 if ( !::SendMessage(GetHwnd(), TB_ADDBUTTONS, (WPARAM)i, (LPARAM)buttons) )
735 {
736 wxLogLastError(wxT("TB_ADDBUTTONS"));
737 }
738
739 delete [] buttons;
740
741 // Deal with the controls finally
742 // ------------------------------
743
744 // adjust the controls size to fit nicely in the toolbar
745 int y = 0;
746 size_t index = 0;
747 for ( node = m_tools.GetFirst(); node; node = node->GetNext(), index++ )
748 {
749 wxToolBarToolBase *tool = node->GetData();
750
751 // we calculate the running y coord for vertical toolbars so we need to
752 // get the items size for all items but for the horizontal ones we
753 // don't need to deal with the non controls
754 bool isControl = tool->IsControl();
755 if ( !isControl && !isVertical )
756 continue;
757
758 // note that we use TB_GETITEMRECT and not TB_GETRECT because the
759 // latter only appeared in v4.70 of comctl32.dll
760 RECT r;
761 if ( !SendMessage(GetHwnd(), TB_GETITEMRECT,
762 index, (LPARAM)(LPRECT)&r) )
763 {
764 wxLogLastError(wxT("TB_GETITEMRECT"));
765 }
766
767 if ( !isControl )
768 {
769 // can only be control if isVertical
770 y += r.bottom - r.top;
771
772 continue;
773 }
774
775 wxControl *control = tool->GetControl();
776
777 wxSize size = control->GetSize();
778
779 // the position of the leftmost controls corner
780 int left = -1;
781
782 // TB_SETBUTTONINFO message is only supported by comctl32.dll 4.71+
783 #ifdef TB_SETBUTTONINFO
784 // available in headers, now check whether it is available now
785 // (during run-time)
786 if ( wxTheApp->GetComCtl32Version() >= 471 )
787 {
788 // set the (underlying) separators width to be that of the
789 // control
790 TBBUTTONINFO tbbi;
791 tbbi.cbSize = sizeof(tbbi);
792 tbbi.dwMask = TBIF_SIZE;
793 tbbi.cx = size.x;
794 if ( !SendMessage(GetHwnd(), TB_SETBUTTONINFO,
795 tool->GetId(), (LPARAM)&tbbi) )
796 {
797 // the id is probably invalid?
798 wxLogLastError(wxT("TB_SETBUTTONINFO"));
799 }
800 }
801 else
802 #endif // comctl32.dll 4.71
803 // TB_SETBUTTONINFO unavailable
804 {
805 // try adding several separators to fit the controls width
806 int widthSep = r.right - r.left;
807 left = r.left;
808
809 TBBUTTON tbb;
810 wxZeroMemory(tbb);
811 tbb.idCommand = 0;
812 tbb.fsState = TBSTATE_ENABLED;
813 tbb.fsStyle = TBSTYLE_SEP;
814
815 size_t nSeparators = size.x / widthSep;
816 for ( size_t nSep = 0; nSep < nSeparators; nSep++ )
817 {
818 if ( !SendMessage(GetHwnd(), TB_INSERTBUTTON,
819 index, (LPARAM)&tbb) )
820 {
821 wxLogLastError(wxT("TB_INSERTBUTTON"));
822 }
823
824 index++;
825 }
826
827 // remember the number of separators we used - we'd have to
828 // delete all of them later
829 ((wxToolBarTool *)tool)->SetSeparatorsCount(nSeparators);
830
831 // adjust the controls width to exactly cover the separators
832 control->SetSize((nSeparators + 1)*widthSep, -1);
833 }
834
835 // position the control itself correctly vertically
836 int height = r.bottom - r.top;
837 int diff = height - size.y;
838 if ( diff < 0 )
839 {
840 // the control is too high, resize to fit
841 control->SetSize(-1, height - 2);
842
843 diff = 2;
844 }
845
846 int top;
847 if ( isVertical )
848 {
849 left = 0;
850 top = y;
851
852 y += height + 2*GetMargins().y;
853 }
854 else // horizontal toolbar
855 {
856 if ( left == -1 )
857 left = r.left;
858
859 top = r.top;
860 }
861
862 control->Move(left, top + (diff + 1) / 2);
863 }
864
865 // the max index is the "real" number of buttons - i.e. counting even the
866 // separators which we added just for aligning the controls
867 m_nButtons = index;
868
869 if ( !isVertical )
870 {
871 if ( m_maxRows == 0 )
872 {
873 // if not set yet, only one row
874 SetRows(1);
875 }
876 }
877 else if ( m_nButtons > 0 ) // vertical non empty toolbar
878 {
879 if ( m_maxRows == 0 )
880 {
881 // if not set yet, have one column
882 SetRows(m_nButtons);
883 }
884 }
885
886 return TRUE;
887 }
888
889 // ----------------------------------------------------------------------------
890 // message handlers
891 // ----------------------------------------------------------------------------
892
893 bool wxToolBar::MSWCommand(WXUINT WXUNUSED(cmd), WXWORD id)
894 {
895 wxToolBarToolBase *tool = FindById((int)id);
896 if ( !tool )
897 return FALSE;
898
899 if ( tool->CanBeToggled() )
900 {
901 LRESULT state = ::SendMessage(GetHwnd(), TB_GETSTATE, id, 0);
902 tool->Toggle((state & TBSTATE_CHECKED) != 0);
903 }
904
905 bool toggled = tool->IsToggled();
906
907 // avoid sending the event when a radio button is released, this is not
908 // interesting
909 if ( !tool->CanBeToggled() || tool->GetKind() != wxITEM_RADIO || toggled )
910 {
911 // OnLeftClick() can veto the button state change - for buttons which
912 // may be toggled only, of couse
913 if ( !OnLeftClick((int)id, toggled) && tool->CanBeToggled() )
914 {
915 // revert back
916 toggled = !toggled;
917 tool->SetToggle(toggled);
918
919 ::SendMessage(GetHwnd(), TB_CHECKBUTTON, id, MAKELONG(toggled, 0));
920 }
921 }
922
923 return TRUE;
924 }
925
926 bool wxToolBar::MSWOnNotify(int WXUNUSED(idCtrl),
927 WXLPARAM lParam,
928 WXLPARAM *WXUNUSED(result))
929 {
930 #if wxUSE_TOOLTIPS
931 // First check if this applies to us
932 NMHDR *hdr = (NMHDR *)lParam;
933
934 // the tooltips control created by the toolbar is sometimes Unicode, even
935 // in an ANSI application - this seems to be a bug in comctl32.dll v5
936 UINT code = hdr->code;
937 if ( (code != (UINT) TTN_NEEDTEXTA) && (code != (UINT) TTN_NEEDTEXTW) )
938 return FALSE;
939
940 HWND toolTipWnd = (HWND)::SendMessage((HWND)GetHWND(), TB_GETTOOLTIPS, 0, 0);
941 if ( toolTipWnd != hdr->hwndFrom )
942 return FALSE;
943
944 LPTOOLTIPTEXT ttText = (LPTOOLTIPTEXT)lParam;
945 int id = (int)ttText->hdr.idFrom;
946
947 wxToolBarToolBase *tool = FindById(id);
948 if ( !tool )
949 return FALSE;
950
951 return HandleTooltipNotify(code, lParam, tool->GetShortHelp());
952 #else
953 return FALSE;
954 #endif
955 }
956
957 // ----------------------------------------------------------------------------
958 // toolbar geometry
959 // ----------------------------------------------------------------------------
960
961 void wxToolBar::SetToolBitmapSize(const wxSize& size)
962 {
963 wxToolBarBase::SetToolBitmapSize(size);
964
965 ::SendMessage(GetHwnd(), TB_SETBITMAPSIZE, 0, MAKELONG(size.x, size.y));
966 }
967
968 void wxToolBar::SetRows(int nRows)
969 {
970 if ( nRows == m_maxRows )
971 {
972 // avoid resizing the frame uselessly
973 return;
974 }
975
976 // TRUE in wParam means to create at least as many rows, FALSE -
977 // at most as many
978 RECT rect;
979 ::SendMessage(GetHwnd(), TB_SETROWS,
980 MAKEWPARAM(nRows, !(GetWindowStyle() & wxTB_VERTICAL)),
981 (LPARAM) &rect);
982
983 m_maxRows = nRows;
984
985 UpdateSize();
986 }
987
988 // The button size is bigger than the bitmap size
989 wxSize wxToolBar::GetToolSize() const
990 {
991 // TB_GETBUTTONSIZE is supported from version 4.70
992 #if defined(_WIN32_IE) && (_WIN32_IE >= 0x300 ) \
993 && !( defined(__GNUWIN32__) && !wxCHECK_W32API_VERSION( 1, 0 ) )
994 if ( wxTheApp->GetComCtl32Version() >= 470 )
995 {
996 DWORD dw = ::SendMessage(GetHwnd(), TB_GETBUTTONSIZE, 0, 0);
997
998 return wxSize(LOWORD(dw), HIWORD(dw));
999 }
1000 else
1001 #endif // comctl32.dll 4.70+
1002 {
1003 // defaults
1004 return wxSize(m_defaultWidth + 8, m_defaultHeight + 7);
1005 }
1006 }
1007
1008 static
1009 wxToolBarToolBase *GetItemSkippingDummySpacers(const wxToolBarToolsList& tools,
1010 size_t index )
1011 {
1012 wxToolBarToolsList::compatibility_iterator current = tools.GetFirst();
1013
1014 for ( ; current != 0; current = current->GetNext() )
1015 {
1016 if ( index == 0 )
1017 return current->GetData();
1018
1019 wxToolBarTool *tool = (wxToolBarTool *)current->GetData();
1020 size_t separators = tool->GetSeparatorsCount();
1021
1022 // if it is a normal button, sepcount == 0, so skip 1 item (the button)
1023 // otherwise, skip as many items as the separator count, plus the
1024 // control itself
1025 index -= separators ? separators + 1 : 1;
1026 }
1027
1028 return 0;
1029 }
1030
1031 wxToolBarToolBase *wxToolBar::FindToolForPosition(wxCoord x, wxCoord y) const
1032 {
1033 POINT pt;
1034 pt.x = x;
1035 pt.y = y;
1036 int index = (int)::SendMessage(GetHwnd(), TB_HITTEST, 0, (LPARAM)&pt);
1037 // MBN: when the point ( x, y ) is close to the toolbar border
1038 // TB_HITTEST returns m_nButtons ( not -1 )
1039 if ( index < 0 || (size_t)index >= m_nButtons )
1040 {
1041 // it's a separator or there is no tool at all there
1042 return (wxToolBarToolBase *)NULL;
1043 }
1044
1045 // if comctl32 version < 4.71 wxToolBar95 adds dummy spacers
1046 #if defined(_WIN32_IE) && (_WIN32_IE >= 0x400 )
1047 if ( wxTheApp->GetComCtl32Version() >= 471 )
1048 {
1049 return m_tools.Item((size_t)index)->GetData();
1050 }
1051 else
1052 #endif
1053 {
1054 return GetItemSkippingDummySpacers( m_tools, (size_t) index );
1055 }
1056 }
1057
1058 void wxToolBar::UpdateSize()
1059 {
1060 // the toolbar size changed
1061 SendMessage(GetHwnd(), TB_AUTOSIZE, 0, 0);
1062
1063 // we must also refresh the frame after the toolbar size (possibly) changed
1064 wxFrame *frame = wxDynamicCast(GetParent(), wxFrame);
1065 if ( frame )
1066 {
1067 frame->SendSizeEvent();
1068 }
1069 }
1070
1071 // ----------------------------------------------------------------------------
1072 // toolbar styles
1073 // ---------------------------------------------------------------------------
1074
1075 void wxToolBar::SetWindowStyleFlag(long style)
1076 {
1077 // the style bits whose changes force us to recreate the toolbar
1078 static const long MASK_NEEDS_RECREATE = wxTB_TEXT | wxTB_NOICONS;
1079
1080 const long styleOld = GetWindowStyle();
1081
1082 wxToolBarBase::SetWindowStyleFlag(style);
1083
1084 // don't recreate an empty toolbar: not only this is unnecessary, but it is
1085 // also fatal as we'd then try to recreate the toolbar when it's just being
1086 // created
1087 if ( GetToolsCount() &&
1088 (style & MASK_NEEDS_RECREATE) != (styleOld & MASK_NEEDS_RECREATE) )
1089 {
1090 // to remove the text labels, simply re-realizing the toolbar is enough
1091 // but I don't know of any way to add the text to an existing toolbar
1092 // other than by recreating it entirely
1093 Recreate();
1094 }
1095 }
1096
1097 // ----------------------------------------------------------------------------
1098 // tool state
1099 // ----------------------------------------------------------------------------
1100
1101 void wxToolBar::DoEnableTool(wxToolBarToolBase *tool, bool enable)
1102 {
1103 ::SendMessage(GetHwnd(), TB_ENABLEBUTTON,
1104 (WPARAM)tool->GetId(), (LPARAM)MAKELONG(enable, 0));
1105 }
1106
1107 void wxToolBar::DoToggleTool(wxToolBarToolBase *tool, bool toggle)
1108 {
1109 ::SendMessage(GetHwnd(), TB_CHECKBUTTON,
1110 (WPARAM)tool->GetId(), (LPARAM)MAKELONG(toggle, 0));
1111 }
1112
1113 void wxToolBar::DoSetToggle(wxToolBarToolBase *WXUNUSED(tool), bool WXUNUSED(toggle))
1114 {
1115 // VZ: AFAIK, the button has to be created either with TBSTYLE_CHECK or
1116 // without, so we really need to delete the button and recreate it here
1117 wxFAIL_MSG( _T("not implemented") );
1118 }
1119
1120 // ----------------------------------------------------------------------------
1121 // event handlers
1122 // ----------------------------------------------------------------------------
1123
1124 // Responds to colour changes, and passes event on to children.
1125 void wxToolBar::OnSysColourChanged(wxSysColourChangedEvent& event)
1126 {
1127 wxRGBToColour(m_backgroundColour, ::GetSysColor(COLOR_BTNFACE));
1128
1129 // Remap the buttons
1130 Realize();
1131
1132 // Relayout the toolbar
1133 int nrows = m_maxRows;
1134 m_maxRows = 0; // otherwise SetRows() wouldn't do anything
1135 SetRows(nrows);
1136
1137 Refresh();
1138
1139 // let the event propagate further
1140 event.Skip();
1141 }
1142
1143 void wxToolBar::OnMouseEvent(wxMouseEvent& event)
1144 {
1145 if (event.Leaving() && m_pInTool)
1146 {
1147 OnMouseEnter( -1 );
1148 event.Skip();
1149 return;
1150 }
1151
1152 if (event.RightDown())
1153 {
1154 // For now, we don't have an id. Later we could
1155 // try finding the tool.
1156 OnRightClick((int)-1, event.GetX(), event.GetY());
1157 }
1158 else
1159 {
1160 event.Skip();
1161 }
1162 }
1163
1164 bool wxToolBar::HandleSize(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
1165 {
1166 // calculate our minor dimension ourselves - we're confusing the standard
1167 // logic (TB_AUTOSIZE) with our horizontal toolbars and other hacks
1168 RECT r;
1169 if ( ::SendMessage(GetHwnd(), TB_GETITEMRECT, 0, (LPARAM)&r) )
1170 {
1171 int w, h;
1172
1173 if ( GetWindowStyle() & wxTB_VERTICAL )
1174 {
1175 w = r.right - r.left;
1176 if ( m_maxRows )
1177 {
1178 w *= (m_nButtons + m_maxRows - 1)/m_maxRows;
1179 }
1180 h = HIWORD(lParam);
1181 }
1182 else
1183 {
1184 w = LOWORD(lParam);
1185 if (HasFlag( wxTB_FLAT ))
1186 h = r.bottom - r.top - 3;
1187 else
1188 h = r.bottom - r.top;
1189 if ( m_maxRows )
1190 {
1191 // FIXME: hardcoded separator line height...
1192 h += HasFlag(wxTB_NODIVIDER) ? 4 : 6;
1193 h *= m_maxRows;
1194 }
1195 }
1196
1197 if ( MAKELPARAM(w, h) != lParam )
1198 {
1199 // size really changed
1200 SetSize(w, h);
1201 }
1202
1203 // message processed
1204 return TRUE;
1205 }
1206
1207 return FALSE;
1208 }
1209
1210 bool wxToolBar::HandlePaint(WXWPARAM wParam, WXLPARAM lParam)
1211 {
1212 // erase any dummy separators which we used for aligning the controls if
1213 // any here
1214
1215 // first of all, do we have any controls at all?
1216 wxToolBarToolsList::compatibility_iterator node;
1217 for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
1218 {
1219 if ( node->GetData()->IsControl() )
1220 break;
1221 }
1222
1223 if ( !node )
1224 {
1225 // no controls, nothing to erase
1226 return FALSE;
1227 }
1228
1229 // prepare the DC on which we'll be drawing
1230 wxClientDC dc(this);
1231 dc.SetBrush(wxBrush(GetBackgroundColour(), wxSOLID));
1232 dc.SetPen(*wxTRANSPARENT_PEN);
1233
1234 RECT r;
1235 if ( !GetUpdateRect(GetHwnd(), &r, FALSE) )
1236 {
1237 // nothing to redraw anyhow
1238 return FALSE;
1239 }
1240
1241 wxRect rectUpdate;
1242 wxCopyRECTToRect(r, rectUpdate);
1243
1244 dc.SetClippingRegion(rectUpdate);
1245
1246 // draw the toolbar tools, separators &c normally
1247 wxControl::MSWWindowProc(WM_PAINT, wParam, lParam);
1248
1249 // for each control in the toolbar find all the separators intersecting it
1250 // and erase them
1251 //
1252 // NB: this is really the only way to do it as we don't know if a separator
1253 // corresponds to a control (i.e. is a dummy one) or a real one
1254 // otherwise
1255 for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
1256 {
1257 wxToolBarToolBase *tool = node->GetData();
1258 if ( tool->IsControl() )
1259 {
1260 // get the control rect in our client coords
1261 wxControl *control = tool->GetControl();
1262 wxRect rectCtrl = control->GetRect();
1263
1264 // iterate over all buttons
1265 TBBUTTON tbb;
1266 int count = ::SendMessage(GetHwnd(), TB_BUTTONCOUNT, 0, 0);
1267 for ( int n = 0; n < count; n++ )
1268 {
1269 // is it a separator?
1270 if ( !::SendMessage(GetHwnd(), TB_GETBUTTON,
1271 n, (LPARAM)&tbb) )
1272 {
1273 wxLogDebug(_T("TB_GETBUTTON failed?"));
1274
1275 continue;
1276 }
1277
1278 if ( tbb.fsStyle != TBSTYLE_SEP )
1279 continue;
1280
1281 // get the bounding rect of the separator
1282 RECT r;
1283 if ( !::SendMessage(GetHwnd(), TB_GETITEMRECT,
1284 n, (LPARAM)&r) )
1285 {
1286 wxLogDebug(_T("TB_GETITEMRECT failed?"));
1287
1288 continue;
1289 }
1290
1291 // does it intersect the control?
1292 wxRect rectItem;
1293 wxCopyRECTToRect(r, rectItem);
1294 if ( rectCtrl.Intersects(rectItem) )
1295 {
1296 // yes, do erase it!
1297 dc.DrawRectangle(rectItem);
1298
1299 // Necessary in case we use a no-paint-on-size
1300 // style in the parent: the controls can disappear
1301 control->Refresh(FALSE);
1302 }
1303 }
1304 }
1305 }
1306
1307 return TRUE;
1308 }
1309
1310 void wxToolBar::HandleMouseMove(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
1311 {
1312 wxCoord x = GET_X_LPARAM(lParam),
1313 y = GET_Y_LPARAM(lParam);
1314 wxToolBarToolBase* tool = FindToolForPosition( x, y );
1315
1316 // cursor left current tool
1317 if( tool != m_pInTool && !tool )
1318 {
1319 m_pInTool = 0;
1320 OnMouseEnter( -1 );
1321 }
1322
1323 // cursor entered a tool
1324 if( tool != m_pInTool && tool )
1325 {
1326 m_pInTool = tool;
1327 OnMouseEnter( tool->GetId() );
1328 }
1329 }
1330
1331 long wxToolBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
1332 {
1333 switch ( nMsg )
1334 {
1335 case WM_SIZE:
1336 if ( HandleSize(wParam, lParam) )
1337 return 0;
1338 break;
1339
1340 case WM_MOUSEMOVE:
1341 // we don't handle mouse moves, so always pass the message to
1342 // wxControl::MSWWindowProc
1343 HandleMouseMove(wParam, lParam);
1344 break;
1345
1346 case WM_PAINT:
1347 if ( HandlePaint(wParam, lParam) )
1348 return 0;
1349 }
1350
1351 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
1352 }
1353
1354 // ----------------------------------------------------------------------------
1355 // private functions
1356 // ----------------------------------------------------------------------------
1357
1358 WXHBITMAP wxToolBar::MapBitmap(WXHBITMAP bitmap, int width, int height)
1359 {
1360 MemoryHDC hdcMem;
1361
1362 if ( !hdcMem )
1363 {
1364 wxLogLastError(_T("CreateCompatibleDC"));
1365
1366 return bitmap;
1367 }
1368
1369 SelectInHDC bmpInHDC(hdcMem, (HBITMAP)bitmap);
1370
1371 if ( !bmpInHDC )
1372 {
1373 wxLogLastError(_T("SelectObject"));
1374
1375 return bitmap;
1376 }
1377
1378 wxCOLORMAP *cmap = wxGetStdColourMap();
1379
1380 for ( int i = 0; i < width; i++ )
1381 {
1382 for ( int j = 0; j < height; j++ )
1383 {
1384 COLORREF pixel = ::GetPixel(hdcMem, i, j);
1385
1386 for ( size_t k = 0; k < wxSTD_COL_MAX; k++ )
1387 {
1388 COLORREF col = cmap[k].from;
1389 if ( abs(GetRValue(pixel) - GetRValue(col)) < 10 &&
1390 abs(GetGValue(pixel) - GetGValue(col)) < 10 &&
1391 abs(GetBValue(pixel) - GetBValue(col)) < 10 )
1392 {
1393 ::SetPixel(hdcMem, i, j, cmap[k].to);
1394 break;
1395 }
1396 }
1397 }
1398 }
1399
1400 return bitmap;
1401
1402 // VZ: I leave here my attempts to map the bitmap to the system colours
1403 // faster by using BitBlt() even though it's broken currently - but
1404 // maybe someone else can finish it? It should be faster than iterating
1405 // over all pixels...
1406 #if 0
1407 MemoryHDC hdcMask, hdcDst;
1408 if ( !hdcMask || !hdcDst )
1409 {
1410 wxLogLastError(_T("CreateCompatibleDC"));
1411
1412 return bitmap;
1413 }
1414
1415 // create the target bitmap
1416 HBITMAP hbmpDst = ::CreateCompatibleBitmap(hdcDst, width, height);
1417 if ( !hbmpDst )
1418 {
1419 wxLogLastError(_T("CreateCompatibleBitmap"));
1420
1421 return bitmap;
1422 }
1423
1424 // create the monochrome mask bitmap
1425 HBITMAP hbmpMask = ::CreateBitmap(width, height, 1, 1, 0);
1426 if ( !hbmpMask )
1427 {
1428 wxLogLastError(_T("CreateBitmap(mono)"));
1429
1430 ::DeleteObject(hbmpDst);
1431
1432 return bitmap;
1433 }
1434
1435 SelectInHDC bmpInDst(hdcDst, hbmpDst),
1436 bmpInMask(hdcMask, hbmpMask);
1437
1438 // for each colour:
1439 for ( n = 0; n < NUM_OF_MAPPED_COLOURS; n++ )
1440 {
1441 // create the mask for this colour
1442 ::SetBkColor(hdcMem, ColorMap[n].from);
1443 ::BitBlt(hdcMask, 0, 0, width, height, hdcMem, 0, 0, SRCCOPY);
1444
1445 // replace this colour with the target one in the dst bitmap
1446 HBRUSH hbr = ::CreateSolidBrush(ColorMap[n].to);
1447 HGDIOBJ hbrOld = ::SelectObject(hdcDst, hbr);
1448
1449 ::MaskBlt(hdcDst, 0, 0, width, height,
1450 hdcMem, 0, 0,
1451 hbmpMask, 0, 0,
1452 MAKEROP4(PATCOPY, SRCCOPY));
1453
1454 (void)::SelectObject(hdcDst, hbrOld);
1455 ::DeleteObject(hbr);
1456 }
1457
1458 ::DeleteObject((HBITMAP)bitmap);
1459
1460 return (WXHBITMAP)hbmpDst;
1461 #endif // 0
1462 }
1463
1464 #endif // wxUSE_TOOLBAR && Win95
1465