[start of] a wxUniversal implementation of wxToolBar
[wxWidgets.git] / src / univ / toolbar.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/univ/toolbar.cpp
3 // Purpose: implementation of wxToolBar for wxUniversal
4 // Author: Robert Roebling, Vadim Zeitlin (universalization)
5 // Modified by:
6 // Created: 20.02.02
7 // Id: $Id$
8 // Copyright: (c) 2001 Robert Roebling,
9 // (c) 2002 SciTech Software, Inc. (www.scitechsoft.com)
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////////
12
13 // ============================================================================
14 // declarations
15 // ============================================================================
16
17 // ----------------------------------------------------------------------------
18 // headers
19 // ----------------------------------------------------------------------------
20
21 #ifdef __GNUG__
22 #pragma implementation "univtoolbar.h"
23 #endif
24
25 // For compilers that support precompilation, includes "wx.h".
26 #include "wx/wxprec.h"
27
28 #ifdef __BORLANDC__
29 #pragma hdrstop
30 #endif
31
32 #ifndef WX_PRECOMP
33 #include "wx/utils.h"
34 #include "wx/app.h"
35
36 #include "wx/univ/renderer.h"
37 #endif
38
39 #include "wx/toolbar.h"
40 #include "wx/image.h"
41
42 // ----------------------------------------------------------------------------
43 // constants
44 // ----------------------------------------------------------------------------
45
46 // value meaning that m_widthSeparator is not initialized
47 static const wxCoord INVALID_WIDTH = -1;
48
49 // ----------------------------------------------------------------------------
50 // wxToolBarTool: our implementation of wxToolBarToolBase
51 // ----------------------------------------------------------------------------
52
53 class WXDLLEXPORT wxToolBarTool : public wxToolBarToolBase
54 {
55 public:
56 wxToolBarTool( wxToolBarBase *tbar = (wxToolBarBase *)NULL,
57 int id = wxID_SEPARATOR,
58 const wxBitmap& bitmap1 = wxNullBitmap,
59 const wxBitmap& bitmap2 = wxNullBitmap,
60 bool toggle = FALSE,
61 wxObject *clientData = (wxObject *) NULL,
62 const wxString& shortHelpString = wxEmptyString,
63 const wxString& longHelpString = wxEmptyString )
64 : wxToolBarToolBase(tbar, id, bitmap1, bitmap2, toggle, clientData,
65 shortHelpString, longHelpString)
66 {
67 // no position yet
68 m_x =
69 m_y = -1;
70 }
71
72 public:
73 // the tool position (the size is known by the toolbar itself)
74 int m_x,
75 m_y;
76 };
77
78 // ============================================================================
79 // wxToolBar implementation
80 // ============================================================================
81
82 IMPLEMENT_DYNAMIC_CLASS(wxToolBar, wxControl);
83
84 // ----------------------------------------------------------------------------
85 // wxToolBar creation
86 // ----------------------------------------------------------------------------
87
88 void wxToolBar::Init()
89 {
90 // no tools yet
91 m_needsLayout = FALSE;
92
93 // unknown widths for the tools and separators
94 m_widthSeparator = INVALID_WIDTH;
95
96 m_maxWidth =
97 m_maxHeight = 0;
98
99 m_toolPressed =
100 m_toolCurrent = NULL;
101
102 wxRenderer *renderer = GetRenderer();
103
104 SetToolBitmapSize(renderer->GetToolBarButtonSize(&m_widthSeparator));
105 SetMargins(renderer->GetToolBarMargin());
106 }
107
108 bool wxToolBar::Create(wxWindow *parent,
109 wxWindowID id,
110 const wxPoint& pos,
111 const wxSize& size,
112 long style,
113 const wxString& name)
114 {
115 if ( !wxToolBarBase::Create(parent, id, pos, size, style,
116 wxDefaultValidator, name) )
117 {
118 return FALSE;
119 }
120
121 CreateInputHandler(wxINP_HANDLER_TOOLBAR);
122
123 SetBestSize(size);
124
125 return TRUE;
126 }
127
128 wxToolBar::~wxToolBar()
129 {
130 }
131
132 // ----------------------------------------------------------------------------
133 // wxToolBar tool-related methods
134 // ----------------------------------------------------------------------------
135
136 wxToolBarToolBase *wxToolBar::FindToolForPosition(wxCoord x, wxCoord y) const
137 {
138 // check the "other" direction first: it must be inside the toolbar or we
139 // don't risk finding anything
140 if ( IsVertical() )
141 {
142 if ( x < 0 || x > m_maxWidth )
143 return NULL;
144
145 // we always use x, even for a vertical toolbar, this makes the code
146 // below simpler
147 x = y;
148 }
149 else // horizontal
150 {
151 if ( y < 0 || y > m_maxHeight )
152 return NULL;
153 }
154
155 for ( wxToolBarToolsList::Node *node = m_tools.GetFirst();
156 node;
157 node = node->GetNext() )
158 {
159 wxToolBarToolBase *tool = node->GetData();
160 wxRect rectTool = GetToolRect(tool);
161
162 wxCoord startTool, endTool;
163 GetRectLimits(rectTool, &startTool, &endTool);
164
165 if ( x >= startTool && x <= endTool )
166 {
167 // don't return the separators from here, they don't accept any
168 // input anyhow
169 return tool->IsSeparator() ? NULL : tool;
170 }
171 }
172
173 return NULL;
174 }
175
176 void wxToolBar::SetToolShortHelp(int id, const wxString& help)
177 {
178 wxToolBarToolBase *tool = FindById(id);
179
180 wxCHECK_RET( tool, _T("SetToolShortHelp: no such tool") );
181
182 tool->SetShortHelp(help);
183 }
184
185 bool wxToolBar::DoInsertTool(size_t WXUNUSED(pos),
186 wxToolBarToolBase * WXUNUSED(tool))
187 {
188 // recalculate the toolbar geometry before redrawing it the next time
189 m_needsLayout = TRUE;
190
191 // and ensure that we indeed are going to redraw
192 Refresh();
193
194 return TRUE;
195 }
196
197 bool wxToolBar::DoDeleteTool(size_t WXUNUSED(pos),
198 wxToolBarToolBase * WXUNUSED(tool))
199 {
200 // as above
201 m_needsLayout = TRUE;
202
203 Refresh();
204
205 return TRUE;
206 }
207
208 void wxToolBar::DoEnableTool(wxToolBarToolBase *tool, bool enable)
209 {
210 // created disabled-state bitmap on demand
211 if ( !enable && !tool->GetDisabledBitmap().Ok() )
212 {
213 wxImage image( tool->GetNormalBitmap() );
214
215 // TODO: don't hardcode 180
216 unsigned char bg_red = 180;
217 unsigned char bg_green = 180;
218 unsigned char bg_blue = 180;
219
220 unsigned char mask_red = image.GetMaskRed();
221 unsigned char mask_green = image.GetMaskGreen();
222 unsigned char mask_blue = image.GetMaskBlue();
223
224 bool has_mask = image.HasMask();
225
226 int x,y;
227 for (y = 0; y < image.GetHeight(); y++)
228 {
229 for (x = 0; x < image.GetWidth(); x++)
230 {
231 unsigned char red = image.GetRed(x,y);
232 unsigned char green = image.GetGreen(x,y);
233 unsigned char blue = image.GetBlue(x,y);
234 if (!has_mask || red != mask_red || green != mask_green || blue != mask_blue)
235 {
236 red = (((wxInt32) red - bg_red) >> 1) + bg_red;
237 green = (((wxInt32) green - bg_green) >> 1) + bg_green;
238 blue = (((wxInt32) blue - bg_blue) >> 1) + bg_blue;
239 image.SetRGB( x, y, red, green, blue );
240 }
241 }
242 }
243
244 for (y = 0; y < image.GetHeight(); y++)
245 {
246 for (x = y % 2; x < image.GetWidth(); x += 2)
247 {
248 unsigned char red = image.GetRed(x,y);
249 unsigned char green = image.GetGreen(x,y);
250 unsigned char blue = image.GetBlue(x,y);
251 if (!has_mask || red != mask_red || green != mask_green || blue != mask_blue)
252 {
253 red = (((wxInt32) red - bg_red) >> 1) + bg_red;
254 green = (((wxInt32) green - bg_green) >> 1) + bg_green;
255 blue = (((wxInt32) blue - bg_blue) >> 1) + bg_blue;
256 image.SetRGB( x, y, red, green, blue );
257 }
258 }
259 }
260
261 tool->SetDisabledBitmap( image.ConvertToBitmap() );
262 }
263
264 RefreshTool(tool);
265 }
266
267 void wxToolBar::DoToggleTool(wxToolBarToolBase *tool, bool WXUNUSED(toggle))
268 {
269 // note that if we're called the tool did change state (the base class
270 // checks for it), so it's not necessary to check for this again here
271 RefreshTool(tool);
272 }
273
274 void wxToolBar::DoSetToggle(wxToolBarToolBase *tool, bool WXUNUSED(toggle))
275 {
276 RefreshTool(tool);
277 }
278
279 wxToolBarToolBase *wxToolBar::CreateTool(int id,
280 const wxBitmap& bitmap1,
281 const wxBitmap& bitmap2,
282 bool toggle,
283 wxObject *clientData,
284 const wxString& shortHelpString,
285 const wxString& longHelpString)
286 {
287 return new wxToolBarTool( this, id, bitmap1, bitmap2, toggle,
288 clientData, shortHelpString, longHelpString);
289 }
290
291 wxToolBarToolBase *wxToolBar::CreateTool(wxControl *control)
292 {
293 wxFAIL_MSG( wxT("Toolbar doesn't support controls yet (TODO)") );
294
295 return NULL;
296 }
297
298 // ----------------------------------------------------------------------------
299 // wxToolBar geometry
300 // ----------------------------------------------------------------------------
301
302 wxRect wxToolBar::GetToolRect(wxToolBarToolBase *toolBase) const
303 {
304 const wxToolBarTool *tool = (wxToolBarTool *)toolBase;
305
306 wxRect rect;
307
308 wxCHECK_MSG( tool, rect, _T("GetToolRect: NULL tool") );
309
310 // ensure that we always have the valid tool position
311 if ( m_needsLayout )
312 {
313 wxConstCast(this, wxToolBar)->DoLayout();
314 }
315
316 rect.x = tool->m_x - m_xMargin;
317 rect.y = tool->m_y - m_yMargin;
318
319 if ( IsVertical() )
320 {
321 rect.width = m_defaultWidth;
322 rect.height = tool->IsSeparator() ? m_widthSeparator : m_defaultHeight;
323 }
324 else // horizontal
325 {
326 rect.width = tool->IsSeparator() ? m_widthSeparator : m_defaultWidth;
327 rect.height = m_defaultHeight;
328 }
329
330 rect.width += 2*m_xMargin;
331 rect.height += 2*m_yMargin;
332
333 return rect;
334 }
335
336 bool wxToolBar::Realize()
337 {
338 if ( !wxToolBarBase::Realize() )
339 return FALSE;
340
341 m_needsLayout = TRUE;
342 DoLayout();
343
344 SetBestSize(wxDefaultSize);
345
346 return TRUE;
347 }
348
349 void wxToolBar::DoLayout()
350 {
351 wxASSERT_MSG( m_needsLayout, _T("why are we called?") );
352
353 m_needsLayout = FALSE;
354
355 wxCoord x = m_xMargin,
356 y = m_yMargin;
357
358 const wxCoord widthTool = IsVertical() ? m_defaultHeight : m_defaultWidth;
359 wxCoord margin = IsVertical() ? m_xMargin : m_yMargin,
360 *pCur = IsVertical() ? &y : &x;
361
362 // calculate the positions of all elements
363 for ( wxToolBarToolsList::Node *node = m_tools.GetFirst();
364 node;
365 node = node->GetNext() )
366 {
367 wxToolBarTool *tool = (wxToolBarTool *) node->GetData();
368
369 tool->m_x = x;
370 tool->m_y = y;
371
372 *pCur += (tool->IsSeparator() ? m_widthSeparator : widthTool) + margin;
373 }
374
375 // calculate the total toolbar size
376 wxCoord xMin = m_defaultWidth + 2*m_xMargin,
377 yMin = m_defaultHeight + 2*m_yMargin;
378
379 m_maxWidth = x < xMin ? xMin : x;
380 m_maxHeight = y < yMin ? yMin : y;
381 }
382
383 wxSize wxToolBar::DoGetBestClientSize() const
384 {
385 return wxSize(m_maxWidth, m_maxHeight);
386 }
387
388 // ----------------------------------------------------------------------------
389 // wxToolBar drawing
390 // ----------------------------------------------------------------------------
391
392 void wxToolBar::RefreshTool(wxToolBarToolBase *tool)
393 {
394 RefreshRect(GetToolRect(tool));
395 }
396
397 void wxToolBar::GetRectLimits(const wxRect& rect,
398 wxCoord *start,
399 wxCoord *end) const
400 {
401 wxCHECK_RET( start && end, _T("NULL pointer in GetRectLimits") );
402
403 if ( IsVertical() )
404 {
405 *start = rect.GetTop();
406 *end = rect.GetBottom();
407 }
408 else // horizontal
409 {
410 *start = rect.GetLeft();
411 *end = rect.GetRight();
412 }
413 }
414
415 void wxToolBar::DoDraw(wxControlRenderer *renderer)
416 {
417 // prepare the variables used below
418 wxDC& dc = renderer->GetDC();
419 wxRenderer *rend = renderer->GetRenderer();
420 // dc.SetFont(GetFont()); -- uncomment when we support labels
421
422 // draw the border separating us from the menubar (if there is no menubar
423 // we probably shouldn't draw it?)
424 if ( !IsVertical() )
425 {
426 rend->DrawHorizontalLine(dc, 0, 0, GetClientSize().x);
427 }
428
429 // get the update rect and its limits depending on the orientation
430 wxRect rectUpdate = GetUpdateClientRect();
431 wxCoord start, end;
432 GetRectLimits(rectUpdate, &start, &end);
433
434 // and redraw all the tools intersecting it
435 for ( wxToolBarToolsList::Node *node = m_tools.GetFirst();
436 node;
437 node = node->GetNext() )
438 {
439 wxToolBarToolBase *tool = node->GetData();
440 wxRect rectTool = GetToolRect(tool);
441 wxCoord startTool, endTool;
442 GetRectLimits(rectTool, &startTool, &endTool);
443
444 if ( endTool < start )
445 {
446 // we're still to the left of the area to redraw
447 continue;
448 }
449
450 if ( startTool > end )
451 {
452 // we're beyond the area to redraw, nothing left to do
453 break;
454 }
455
456 // deal with the flags
457 int flags = 0;
458
459 if ( tool->IsEnabled() )
460 {
461 // the toolbars without wxTB_FLAT don't react to the mouse hovering
462 if ( HasFlag(wxTB_FLAT) && (tool == m_toolCurrent) )
463 flags |= wxCONTROL_CURRENT;
464 }
465 else // disabled tool
466 {
467 flags |= wxCONTROL_DISABLED;
468 }
469
470 if ( tool->IsToggled() )
471 flags |= wxCONTROL_PRESSED;
472
473 wxString label;
474 wxBitmap bitmap;
475 if ( !tool->IsSeparator() )
476 {
477 label = tool->GetLabel();
478 bitmap = tool->GetBitmap();
479 }
480 //else: leave both the label and the bitmap invalid to draw a separator
481
482 rend->DrawToolBarButton(dc, label, bitmap, rectTool, flags);
483 }
484 }
485
486 // ----------------------------------------------------------------------------
487 // wxToolBar actions
488 // ----------------------------------------------------------------------------
489
490 void wxToolBar::Press()
491 {
492 wxCHECK_RET( m_toolCurrent, _T("no tool to press?") );
493
494 m_toolPressed = m_toolCurrent;
495 if ( !m_toolPressed->IsToggled() )
496 {
497 m_toolPressed->Toggle(TRUE);
498
499 RefreshTool(m_toolPressed);
500 }
501 }
502
503 void wxToolBar::Release()
504 {
505 wxCHECK_RET( m_toolPressed, _T("no tool to release?") );
506
507 if ( m_toolPressed->IsToggled() )
508 {
509 m_toolPressed->Toggle(FALSE);
510
511 RefreshTool(m_toolPressed);
512
513 m_toolPressed = NULL;
514 }
515 }
516
517 void wxToolBar::Toggle()
518 {
519 wxCHECK_RET( m_toolPressed, _T("no tool to toggle?") );
520
521 // the togglable tools should keep their state when the mouse is released
522 if ( !m_toolPressed->CanBeToggled() )
523 {
524 m_toolPressed->Toggle();
525 }
526
527 RefreshTool(m_toolPressed);
528
529 m_toolCurrent = m_toolPressed;
530 m_toolPressed = NULL;
531
532 Click();
533 }
534
535 void wxToolBar::Click()
536 {
537 wxCHECK_RET( m_toolCurrent, _T("no tool to click?") );
538
539 OnLeftClick(m_toolCurrent->GetId(), m_toolCurrent->IsToggled());
540 }
541
542 bool wxToolBar::PerformAction(const wxControlAction& action,
543 long numArg,
544 const wxString& strArg)
545 {
546 if ( action == wxACTION_TOOLBAR_TOGGLE )
547 Toggle();
548 else if ( action == wxACTION_TOOLBAR_PRESS )
549 Press();
550 else if ( action == wxACTION_TOOLBAR_RELEASE )
551 Release();
552 else if ( action == wxACTION_TOOLBAR_CLICK )
553 Click();
554 else if ( action == wxACTION_TOOLBAR_ENTER )
555 {
556 wxToolBarToolBase *toolCurrentOld = m_toolCurrent;
557 m_toolCurrent = FindById((int)numArg);
558
559 if ( m_toolCurrent != toolCurrentOld )
560 {
561 // the appearance of the current tool only changes for the flat
562 // toolbars
563 if ( HasFlag(wxTB_FLAT) )
564 {
565 // and only if the tool was/is enabled
566 if ( toolCurrentOld && toolCurrentOld->IsEnabled() )
567 RefreshTool(toolCurrentOld);
568
569 if ( m_toolCurrent )
570 {
571 if ( m_toolCurrent->IsEnabled() )
572 RefreshTool(m_toolCurrent);
573 }
574 else
575 {
576 wxFAIL_MSG( _T("no current tool in wxACTION_TOOLBAR_ENTER?") );
577 }
578 }
579 }
580 }
581 else if ( action == wxACTION_TOOLBAR_LEAVE )
582 {
583 if ( m_toolCurrent )
584 {
585 wxToolBarToolBase *toolCurrentOld = m_toolCurrent;
586 m_toolCurrent = NULL;
587
588 RefreshTool(toolCurrentOld);
589 }
590 }
591 else
592 return wxControl::PerformAction(action, numArg, strArg);
593
594 return TRUE;
595 }
596
597 // ============================================================================
598 // wxStdToolbarInputHandler implementation
599 // ============================================================================
600
601 wxStdToolbarInputHandler::wxStdToolbarInputHandler(wxInputHandler *handler)
602 : wxStdButtonInputHandler(handler)
603 {
604 }
605
606 bool wxStdToolbarInputHandler::HandleKey(wxInputConsumer *consumer,
607 const wxKeyEvent& event,
608 bool pressed)
609 {
610 // TODO: when we have a current button we should allow the arrow
611 // keys to move it
612 return wxStdInputHandler::HandleKey(consumer, event, pressed);
613 }
614
615 bool wxStdToolbarInputHandler::HandleMouseMove(wxInputConsumer *consumer,
616 const wxMouseEvent& event)
617 {
618 if ( !wxStdButtonInputHandler::HandleMouseMove(consumer, event) )
619 {
620 wxToolBarToolBase *tool;
621
622 if ( event.Leaving() )
623 {
624 tool = NULL;
625 }
626 else
627 {
628 wxToolBar *tbar = wxStaticCast(consumer->GetInputWindow(), wxToolBar);
629 tool = tbar->FindToolForPosition(event.GetX(), event.GetY());
630 }
631
632 if ( tool )
633 consumer->PerformAction(wxACTION_TOOLBAR_ENTER, tool->GetId());
634 else
635 consumer->PerformAction(wxACTION_TOOLBAR_LEAVE);
636
637 return TRUE;
638 }
639
640 return FALSE;
641 }
642
643 bool wxStdToolbarInputHandler::HandleFocus(wxInputConsumer *consumer,
644 const wxFocusEvent& event)
645 {
646 // we shouldn't be left with a highlighted button
647 consumer->PerformAction(wxACTION_TOOLBAR_LEAVE);
648
649 return TRUE;
650 }
651
652 bool wxStdToolbarInputHandler::HandleActivation(wxInputConsumer *consumer,
653 bool activated)
654 {
655 // as above
656 if ( !activated )
657 consumer->PerformAction(wxACTION_TOOLBAR_LEAVE);
658
659 return TRUE;
660 }
661