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