Add EVT_RIBBONBAR_TAB_LEFT_DCLICK event.
[wxWidgets.git] / src / ribbon / bar.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/ribbon/bar.cpp
3 // Purpose: Top-level component of the ribbon-bar-style interface
4 // Author: Peter Cawley
5 // Modified by:
6 // Created: 2009-05-23
7 // RCS-ID: $Id$
8 // Copyright: (C) Peter Cawley
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #ifdef __BORLANDC__
15 #pragma hdrstop
16 #endif
17
18 #if wxUSE_RIBBON
19
20 #include "wx/ribbon/bar.h"
21 #include "wx/ribbon/art.h"
22 #include "wx/dcbuffer.h"
23
24 #ifndef WX_PRECOMP
25 #endif
26
27 #ifdef __WXMSW__
28 #include "wx/msw/private.h"
29 #endif
30
31 #include "wx/arrimpl.cpp"
32
33 WX_DEFINE_USER_EXPORTED_OBJARRAY(wxRibbonPageTabInfoArray)
34
35 wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_PAGE_CHANGED, wxRibbonBarEvent);
36 wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_PAGE_CHANGING, wxRibbonBarEvent);
37 wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_TAB_MIDDLE_DOWN, wxRibbonBarEvent);
38 wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_TAB_MIDDLE_UP, wxRibbonBarEvent);
39 wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_TAB_RIGHT_DOWN, wxRibbonBarEvent);
40 wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_TAB_RIGHT_UP, wxRibbonBarEvent);
41 wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_TAB_LEFT_DCLICK, wxRibbonBarEvent);
42
43 IMPLEMENT_CLASS(wxRibbonBar, wxRibbonControl)
44 IMPLEMENT_DYNAMIC_CLASS(wxRibbonBarEvent, wxNotifyEvent)
45
46 BEGIN_EVENT_TABLE(wxRibbonBar, wxRibbonControl)
47 EVT_ERASE_BACKGROUND(wxRibbonBar::OnEraseBackground)
48 EVT_LEAVE_WINDOW(wxRibbonBar::OnMouseLeave)
49 EVT_LEFT_DOWN(wxRibbonBar::OnMouseLeftDown)
50 EVT_LEFT_UP(wxRibbonBar::OnMouseLeftUp)
51 EVT_MIDDLE_DOWN(wxRibbonBar::OnMouseMiddleDown)
52 EVT_MIDDLE_UP(wxRibbonBar::OnMouseMiddleUp)
53 EVT_MOTION(wxRibbonBar::OnMouseMove)
54 EVT_PAINT(wxRibbonBar::OnPaint)
55 EVT_RIGHT_DOWN(wxRibbonBar::OnMouseRightDown)
56 EVT_RIGHT_UP(wxRibbonBar::OnMouseRightUp)
57 EVT_LEFT_DCLICK(wxRibbonBar::OnMouseDoubleClick)
58 EVT_SIZE(wxRibbonBar::OnSize)
59 END_EVENT_TABLE()
60
61 void wxRibbonBar::AddPage(wxRibbonPage *page)
62 {
63 wxRibbonPageTabInfo info;
64
65 info.page = page;
66 info.active = false;
67 info.hovered = false;
68 // info.rect not set (intentional)
69
70 wxClientDC dcTemp(this);
71 wxString label = wxEmptyString;
72 if(m_flags & wxRIBBON_BAR_SHOW_PAGE_LABELS)
73 label = page->GetLabel();
74 wxBitmap icon = wxNullBitmap;
75 if(m_flags & wxRIBBON_BAR_SHOW_PAGE_ICONS)
76 icon = page->GetIcon();
77 m_art->GetBarTabWidth(dcTemp, this, label, icon,
78 &info.ideal_width,
79 &info.small_begin_need_separator_width,
80 &info.small_must_have_separator_width,
81 &info.minimum_width);
82
83 if(m_pages.IsEmpty())
84 {
85 m_tabs_total_width_ideal = info.ideal_width;
86 m_tabs_total_width_minimum = info.minimum_width;
87 }
88 else
89 {
90 int sep = m_art->GetMetric(wxRIBBON_ART_TAB_SEPARATION_SIZE);
91 m_tabs_total_width_ideal += sep + info.ideal_width;
92 m_tabs_total_width_minimum += sep + info.minimum_width;
93 }
94 m_pages.Add(info);
95
96 page->Hide(); // Most likely case is that this new page is not the active tab
97 page->SetArtProvider(m_art);
98
99 if(m_pages.GetCount() == 1)
100 {
101 SetActivePage((size_t)0);
102 }
103 }
104
105 bool wxRibbonBar::DismissExpandedPanel()
106 {
107 if(m_current_page == -1)
108 return false;
109 return m_pages.Item(m_current_page).page->DismissExpandedPanel();
110 }
111
112 void wxRibbonBar::SetWindowStyleFlag(long style)
113 {
114 m_flags = style;
115 if(m_art)
116 m_art->SetFlags(style);
117 }
118
119 long wxRibbonBar::GetWindowStyleFlag() const
120 {
121 return m_flags;
122 }
123
124 bool wxRibbonBar::Realize()
125 {
126 bool status = true;
127
128 wxClientDC dcTemp(this);
129 int sep = m_art->GetMetric(wxRIBBON_ART_TAB_SEPARATION_SIZE);
130 size_t numtabs = m_pages.GetCount();
131 size_t i;
132 for(i = 0; i < numtabs; ++i)
133 {
134 wxRibbonPageTabInfo& info = m_pages.Item(i);
135 RepositionPage(info.page);
136 if(!info.page->Realize())
137 {
138 status = false;
139 }
140 wxString label = wxEmptyString;
141 if(m_flags & wxRIBBON_BAR_SHOW_PAGE_LABELS)
142 label = info.page->GetLabel();
143 wxBitmap icon = wxNullBitmap;
144 if(m_flags & wxRIBBON_BAR_SHOW_PAGE_ICONS)
145 icon = info.page->GetIcon();
146 m_art->GetBarTabWidth(dcTemp, this, label, icon,
147 &info.ideal_width,
148 &info.small_begin_need_separator_width,
149 &info.small_must_have_separator_width,
150 &info.minimum_width);
151
152 if(i == 0)
153 {
154 m_tabs_total_width_ideal = info.ideal_width;
155 m_tabs_total_width_minimum = info.minimum_width;
156 }
157 else
158 {
159 m_tabs_total_width_ideal += sep + info.ideal_width;
160 m_tabs_total_width_minimum += sep + info.minimum_width;
161 }
162 }
163 m_tab_height = m_art->GetTabCtrlHeight(dcTemp, this, m_pages);
164
165 RecalculateMinSize();
166 RecalculateTabSizes();
167 Refresh();
168
169 return status;
170 }
171
172 void wxRibbonBar::OnMouseMove(wxMouseEvent& evt)
173 {
174 int x = evt.GetX();
175 int y = evt.GetY();
176 int hovered_page = -1;
177 bool refresh_tabs = false;
178 if(y < m_tab_height)
179 {
180 // It is quite likely that the mouse moved a small amount and is still over the same tab
181 if(m_current_hovered_page != -1 && m_pages.Item((size_t)m_current_hovered_page).rect.Contains(x, y))
182 {
183 hovered_page = m_current_hovered_page;
184 // But be careful, if tabs can be scrolled, then parts of the tab rect may not be valid
185 if(m_tab_scroll_buttons_shown)
186 {
187 if(x >= m_tab_scroll_right_button_rect.GetX() || x < m_tab_scroll_left_button_rect.GetRight())
188 {
189 hovered_page = -1;
190 }
191 }
192 }
193 else
194 {
195 HitTestTabs(evt.GetPosition(), &hovered_page);
196 }
197 }
198 if(hovered_page != m_current_hovered_page)
199 {
200 if(m_current_hovered_page != -1)
201 {
202 m_pages.Item((int)m_current_hovered_page).hovered = false;
203 }
204 m_current_hovered_page = hovered_page;
205 if(m_current_hovered_page != -1)
206 {
207 m_pages.Item((int)m_current_hovered_page).hovered = true;
208 }
209 refresh_tabs = true;
210 }
211 if(m_tab_scroll_buttons_shown)
212 {
213 #define SET_FLAG(variable, flag) \
214 { if(((variable) & (flag)) != (flag)) { variable |= (flag); refresh_tabs = true; }}
215 #define UNSET_FLAG(variable, flag) \
216 { if((variable) & (flag)) { variable &= ~(flag); refresh_tabs = true; }}
217
218 if(m_tab_scroll_left_button_rect.Contains(x, y))
219 SET_FLAG(m_tab_scroll_left_button_state, wxRIBBON_SCROLL_BTN_HOVERED)
220 else
221 UNSET_FLAG(m_tab_scroll_left_button_state, wxRIBBON_SCROLL_BTN_HOVERED)
222
223 if(m_tab_scroll_right_button_rect.Contains(x, y))
224 SET_FLAG(m_tab_scroll_right_button_state, wxRIBBON_SCROLL_BTN_HOVERED)
225 else
226 UNSET_FLAG(m_tab_scroll_right_button_state, wxRIBBON_SCROLL_BTN_HOVERED)
227 #undef SET_FLAG
228 #undef UNSET_FLAG
229 }
230 if(refresh_tabs)
231 {
232 RefreshTabBar();
233 }
234 }
235
236 void wxRibbonBar::OnMouseLeave(wxMouseEvent& WXUNUSED(evt))
237 {
238 // The ribbon bar is (usually) at the top of a window, and at least on MSW, the mouse
239 // can leave the window quickly and leave a tab in the hovered state.
240 bool refresh_tabs = false;
241 if(m_current_hovered_page != -1)
242 {
243 m_pages.Item((int)m_current_hovered_page).hovered = false;
244 m_current_hovered_page = -1;
245 refresh_tabs = true;
246 }
247 if(m_tab_scroll_left_button_state & wxRIBBON_SCROLL_BTN_HOVERED)
248 {
249 m_tab_scroll_left_button_state &= ~wxRIBBON_SCROLL_BTN_HOVERED;
250 refresh_tabs = true;
251 }
252 if(m_tab_scroll_right_button_state & wxRIBBON_SCROLL_BTN_HOVERED)
253 {
254 m_tab_scroll_right_button_state &= ~wxRIBBON_SCROLL_BTN_HOVERED;
255 refresh_tabs = true;
256 }
257 if(refresh_tabs)
258 {
259 RefreshTabBar();
260 }
261 }
262
263 wxRibbonPage* wxRibbonBar::GetPage(int n)
264 {
265 if(n < 0 || (size_t)n >= m_pages.GetCount())
266 return 0;
267 return m_pages.Item(n).page;
268 }
269
270 bool wxRibbonBar::SetActivePage(size_t page)
271 {
272 if(m_current_page == (int)page)
273 {
274 return true;
275 }
276
277 if(page >= m_pages.GetCount())
278 {
279 return false;
280 }
281
282 if(m_current_page != -1)
283 {
284 m_pages.Item((size_t)m_current_page).active = false;
285 m_pages.Item((size_t)m_current_page).page->Hide();
286 }
287 m_current_page = (int)page;
288 m_pages.Item(page).active = true;
289 {
290 wxRibbonPage* wnd = m_pages.Item(page).page;
291 RepositionPage(wnd);
292 wnd->Layout();
293 wnd->Show();
294 }
295 Refresh();
296
297 return true;
298 }
299
300 bool wxRibbonBar::SetActivePage(wxRibbonPage* page)
301 {
302 size_t numpages = m_pages.GetCount();
303 size_t i;
304 for(i = 0; i < numpages; ++i)
305 {
306 if(m_pages.Item(i).page == page)
307 {
308 return SetActivePage(i);
309 }
310 }
311 return false;
312 }
313
314 int wxRibbonBar::GetActivePage() const
315 {
316 return m_current_page;
317 }
318
319 void wxRibbonBar::SetTabCtrlMargins(int left, int right)
320 {
321 m_tab_margin_left = left;
322 m_tab_margin_right = right;
323
324 RecalculateTabSizes();
325 }
326
327 static int OrderPageTabInfoBySmallWidthAsc(wxRibbonPageTabInfo **first, wxRibbonPageTabInfo **second)
328 {
329 return (**first).small_must_have_separator_width - (**second).small_must_have_separator_width;
330 }
331
332 void wxRibbonBar::RecalculateTabSizes()
333 {
334 size_t numtabs = m_pages.GetCount();
335
336 if(numtabs == 0)
337 return;
338
339 int width = GetSize().GetWidth() - m_tab_margin_left - m_tab_margin_right;
340 int tabsep = m_art->GetMetric(wxRIBBON_ART_TAB_SEPARATION_SIZE);
341 int x = m_tab_margin_left;
342 const int y = 0;
343
344 if(width >= m_tabs_total_width_ideal)
345 {
346 // Simple case: everything at ideal width
347 size_t i;
348 for(i = 0; i < numtabs; ++i)
349 {
350 wxRibbonPageTabInfo& info = m_pages.Item(i);
351 info.rect.x = x;
352 info.rect.y = y;
353 info.rect.width = info.ideal_width;
354 info.rect.height = m_tab_height;
355 x += info.rect.width + tabsep;
356 }
357 m_tab_scroll_buttons_shown = false;
358 m_tab_scroll_left_button_rect.SetWidth(0);
359 m_tab_scroll_right_button_rect.SetWidth(0);
360 }
361 else if(width < m_tabs_total_width_minimum)
362 {
363 // Simple case: everything minimum with scrollbar
364 size_t i;
365 for(i = 0; i < numtabs; ++i)
366 {
367 wxRibbonPageTabInfo& info = m_pages.Item(i);
368 info.rect.x = x;
369 info.rect.y = y;
370 info.rect.width = info.minimum_width;
371 info.rect.height = m_tab_height;
372 x += info.rect.width + tabsep;
373 }
374 if(!m_tab_scroll_buttons_shown)
375 {
376 m_tab_scroll_left_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
377 m_tab_scroll_right_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
378 m_tab_scroll_buttons_shown = true;
379 }
380 {
381 wxClientDC temp_dc(this);
382 m_tab_scroll_left_button_rect.SetWidth(m_art->GetScrollButtonMinimumSize(temp_dc, this, wxRIBBON_SCROLL_BTN_LEFT | wxRIBBON_SCROLL_BTN_NORMAL | wxRIBBON_SCROLL_BTN_FOR_TABS).GetWidth());
383 m_tab_scroll_left_button_rect.SetHeight(m_tab_height);
384 m_tab_scroll_left_button_rect.SetX(m_tab_margin_left);
385 m_tab_scroll_left_button_rect.SetY(0);
386 m_tab_scroll_right_button_rect.SetWidth(m_art->GetScrollButtonMinimumSize(temp_dc, this, wxRIBBON_SCROLL_BTN_RIGHT | wxRIBBON_SCROLL_BTN_NORMAL | wxRIBBON_SCROLL_BTN_FOR_TABS).GetWidth());
387 m_tab_scroll_right_button_rect.SetHeight(m_tab_height);
388 m_tab_scroll_right_button_rect.SetX(GetClientSize().GetWidth() - m_tab_margin_right - m_tab_scroll_right_button_rect.GetWidth());
389 m_tab_scroll_right_button_rect.SetY(0);
390 }
391 if(m_tab_scroll_amount == 0)
392 {
393 m_tab_scroll_left_button_rect.SetWidth(0);
394 }
395 else if(m_tab_scroll_amount + width >= m_tabs_total_width_minimum)
396 {
397 m_tab_scroll_amount = m_tabs_total_width_minimum - width;
398 m_tab_scroll_right_button_rect.SetX(m_tab_scroll_right_button_rect.GetX() + m_tab_scroll_right_button_rect.GetWidth());
399 m_tab_scroll_right_button_rect.SetWidth(0);
400 }
401 for(i = 0; i < numtabs; ++i)
402 {
403 wxRibbonPageTabInfo& info = m_pages.Item(i);
404 info.rect.x -= m_tab_scroll_amount;
405 }
406 }
407 else
408 {
409 m_tab_scroll_buttons_shown = false;
410 m_tab_scroll_left_button_rect.SetWidth(0);
411 m_tab_scroll_right_button_rect.SetWidth(0);
412 // Complex case: everything sized such that: minimum <= width < ideal
413 /*
414 Strategy:
415 1) Uniformly reduce all tab widths from ideal to small_must_have_separator_width
416 2) Reduce the largest tab by 1 pixel, repeating until all tabs are same width (or at minimum)
417 3) Uniformly reduce all tabs down to their minimum width
418 */
419 int smallest_tab_width = INT_MAX;
420 int total_small_width = tabsep * (numtabs - 1);
421 size_t i;
422 for(i = 0; i < numtabs; ++i)
423 {
424 wxRibbonPageTabInfo& info = m_pages.Item(i);
425 if(info.small_must_have_separator_width < smallest_tab_width)
426 {
427 smallest_tab_width = info.small_must_have_separator_width;
428 }
429 total_small_width += info.small_must_have_separator_width;
430 }
431 if(width >= total_small_width)
432 {
433 // Do (1)
434 int total_delta = m_tabs_total_width_ideal - total_small_width;
435 total_small_width -= tabsep * (numtabs - 1);
436 width -= tabsep * (numtabs - 1);
437 for(i = 0; i < numtabs; ++i)
438 {
439 wxRibbonPageTabInfo& info = m_pages.Item(i);
440 int delta = info.ideal_width - info.small_must_have_separator_width;
441 info.rect.x = x;
442 info.rect.y = y;
443 info.rect.width = info.small_must_have_separator_width + delta * (width - total_small_width) / total_delta;
444 info.rect.height = m_tab_height;
445
446 x += info.rect.width + tabsep;
447 total_delta -= delta;
448 total_small_width -= info.small_must_have_separator_width;
449 width -= info.rect.width;
450 }
451 }
452 else
453 {
454 total_small_width = tabsep * (numtabs - 1);
455 for(i = 0; i < numtabs; ++i)
456 {
457 wxRibbonPageTabInfo& info = m_pages.Item(i);
458 if(info.minimum_width < smallest_tab_width)
459 {
460 total_small_width += smallest_tab_width;
461 }
462 else
463 {
464 total_small_width += info.minimum_width;
465 }
466 }
467 if(width >= total_small_width)
468 {
469 // Do (2)
470 wxRibbonPageTabInfoArray sorted_pages;
471 for(i = 0; i < numtabs; ++i)
472 {
473 // Sneaky obj array trickery to not copy the tab descriptors
474 sorted_pages.Add(&m_pages.Item(i));
475 }
476 sorted_pages.Sort(OrderPageTabInfoBySmallWidthAsc);
477 width -= tabsep * (numtabs - 1);
478 for(i = 0; i < numtabs; ++i)
479 {
480 wxRibbonPageTabInfo& info = sorted_pages.Item(i);
481 if(info.small_must_have_separator_width * (int)(numtabs - i) <= width)
482 {
483 info.rect.width = info.small_must_have_separator_width;;
484 }
485 else
486 {
487 info.rect.width = width / (numtabs - i);
488 }
489 width -= info.rect.width;
490 }
491 for(i = 0; i < numtabs; ++i)
492 {
493 wxRibbonPageTabInfo& info = m_pages.Item(i);
494 info.rect.x = x;
495 info.rect.y = y;
496 info.rect.height = m_tab_height;
497 x += info.rect.width + tabsep;
498 sorted_pages.Detach(numtabs - (i + 1));
499 }
500 }
501 else
502 {
503 // Do (3)
504 total_small_width = (smallest_tab_width + tabsep) * numtabs - tabsep;
505 int total_delta = total_small_width - m_tabs_total_width_minimum;
506 total_small_width = m_tabs_total_width_minimum - tabsep * (numtabs - 1);
507 width -= tabsep * (numtabs - 1);
508 for(i = 0; i < numtabs; ++i)
509 {
510 wxRibbonPageTabInfo& info = m_pages.Item(i);
511 int delta = smallest_tab_width - info.minimum_width;
512 info.rect.x = x;
513 info.rect.y = y;
514 info.rect.width = info.minimum_width + delta * (width - total_small_width) / total_delta;
515 info.rect.height = m_tab_height;
516
517 x += info.rect.width + tabsep;
518 total_delta -= delta;
519 total_small_width -= info.minimum_width;
520 width -= info.rect.width;
521 }
522 }
523 }
524 }
525 }
526
527 wxRibbonBar::wxRibbonBar()
528 {
529 m_flags = 0;
530 m_tabs_total_width_ideal = 0;
531 m_tabs_total_width_minimum = 0;
532 m_tab_margin_left = 0;
533 m_tab_margin_right = 0;
534 m_tab_height = 0;
535 m_tab_scroll_amount = 0;
536 m_current_page = -1;
537 m_current_hovered_page = -1;
538 m_tab_scroll_left_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
539 m_tab_scroll_right_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
540 m_tab_scroll_buttons_shown = false;
541 }
542
543 wxRibbonBar::wxRibbonBar(wxWindow* parent,
544 wxWindowID id,
545 const wxPoint& pos,
546 const wxSize& size,
547 long style)
548 : wxRibbonControl(parent, id, pos, size, wxBORDER_NONE)
549 {
550 CommonInit(style);
551 }
552
553 wxRibbonBar::~wxRibbonBar()
554 {
555 SetArtProvider(NULL);
556 }
557
558 bool wxRibbonBar::Create(wxWindow* parent,
559 wxWindowID id,
560 const wxPoint& pos,
561 const wxSize& size,
562 long style)
563 {
564 if(!wxRibbonControl::Create(parent, id, pos, size, wxBORDER_NONE))
565 return false;
566
567 CommonInit(style);
568
569 return true;
570 }
571
572 void wxRibbonBar::CommonInit(long style)
573 {
574 SetName(wxT("wxRibbonBar"));
575
576 m_flags = style;
577 m_tabs_total_width_ideal = 0;
578 m_tabs_total_width_minimum = 0;
579 m_tab_margin_left = 50;
580 m_tab_margin_right = 20;
581 m_tab_height = 20; // initial guess
582 m_tab_scroll_amount = 0;
583 m_current_page = -1;
584 m_current_hovered_page = -1;
585 m_tab_scroll_left_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
586 m_tab_scroll_right_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
587 m_tab_scroll_buttons_shown = false;
588
589 if(m_art == NULL)
590 {
591 SetArtProvider(new wxRibbonDefaultArtProvider);
592 }
593 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
594 }
595
596 void wxRibbonBar::SetArtProvider(wxRibbonArtProvider* art)
597 {
598 wxRibbonArtProvider *old = m_art;
599 m_art = art;
600
601 if(art)
602 {
603 art->SetFlags(m_flags);
604 }
605 size_t numpages = m_pages.GetCount();
606 size_t i;
607 for(i = 0; i < numpages; ++i)
608 {
609 wxRibbonPage *page = m_pages.Item(i).page;
610 if(page->GetArtProvider() != art)
611 {
612 page->SetArtProvider(art);
613 }
614 }
615
616 delete old;
617 }
618
619 void wxRibbonBar::OnPaint(wxPaintEvent& WXUNUSED(evt))
620 {
621 wxAutoBufferedPaintDC dc(this);
622
623 if(GetUpdateRegion().Contains(0, 0, GetClientSize().GetWidth(), m_tab_height) == wxOutRegion)
624 {
625 // Nothing to do in the tab area, and the page area is handled by the active page
626 return;
627 }
628
629 DoEraseBackground(dc);
630
631 size_t numtabs = m_pages.GetCount();
632 double sep_visibility = 0.0;
633 bool draw_sep = false;
634 wxRect tabs_rect(m_tab_margin_left, 0, GetClientSize().GetWidth() - m_tab_margin_left - m_tab_margin_right, m_tab_height);
635 if(m_tab_scroll_buttons_shown)
636 {
637 tabs_rect.x += m_tab_scroll_left_button_rect.GetWidth();
638 tabs_rect.width -= m_tab_scroll_left_button_rect.GetWidth() + m_tab_scroll_right_button_rect.GetWidth();
639 }
640 size_t i;
641 for(i = 0; i < numtabs; ++i)
642 {
643 wxRibbonPageTabInfo& info = m_pages.Item(i);
644
645 dc.DestroyClippingRegion();
646 if(m_tab_scroll_buttons_shown)
647 {
648 if(!tabs_rect.Intersects(info.rect))
649 continue;
650 dc.SetClippingRegion(tabs_rect);
651 }
652 dc.SetClippingRegion(info.rect);
653 m_art->DrawTab(dc, this, info);
654
655 if(info.rect.width < info.small_begin_need_separator_width)
656 {
657 draw_sep = true;
658 if(info.rect.width < info.small_must_have_separator_width)
659 {
660 sep_visibility += 1.0;
661 }
662 else
663 {
664 sep_visibility += (double)(info.small_begin_need_separator_width - info.rect.width) / (double)(info.small_begin_need_separator_width - info.small_must_have_separator_width);
665 }
666 }
667 }
668 if(draw_sep)
669 {
670 wxRect rect = m_pages.Item(0).rect;
671 rect.width = m_art->GetMetric(wxRIBBON_ART_TAB_SEPARATION_SIZE);
672 sep_visibility /= (double)numtabs;
673 for(i = 0; i < numtabs - 1; ++i)
674 {
675 wxRibbonPageTabInfo& info = m_pages.Item(i);
676 rect.x = info.rect.x + info.rect.width;
677
678 if(m_tab_scroll_buttons_shown && !tabs_rect.Intersects(rect))
679 {
680 continue;
681 }
682
683 dc.DestroyClippingRegion();
684 dc.SetClippingRegion(rect);
685 m_art->DrawTabSeparator(dc, this, rect, sep_visibility);
686 }
687 }
688 if(m_tab_scroll_buttons_shown)
689 {
690 dc.DestroyClippingRegion();
691 if(m_tab_scroll_left_button_rect.GetWidth() != 0)
692 {
693 m_art->DrawScrollButton(dc, this, m_tab_scroll_left_button_rect, wxRIBBON_SCROLL_BTN_LEFT | m_tab_scroll_left_button_state | wxRIBBON_SCROLL_BTN_FOR_TABS);
694 }
695 if(m_tab_scroll_right_button_rect.GetWidth() != 0)
696 {
697 m_art->DrawScrollButton(dc, this, m_tab_scroll_right_button_rect, wxRIBBON_SCROLL_BTN_RIGHT | m_tab_scroll_right_button_state | wxRIBBON_SCROLL_BTN_FOR_TABS);
698 }
699 }
700 }
701
702 void wxRibbonBar::OnEraseBackground(wxEraseEvent& WXUNUSED(evt))
703 {
704 // Background painting done in main paint handler to reduce screen flicker
705 }
706
707 void wxRibbonBar::DoEraseBackground(wxDC& dc)
708 {
709 wxRect tabs(GetSize());
710 tabs.height = m_tab_height;
711 m_art->DrawTabCtrlBackground(dc, this, tabs);
712 }
713
714 void wxRibbonBar::OnSize(wxSizeEvent& evt)
715 {
716 RecalculateTabSizes();
717 if(m_current_page != -1)
718 {
719 RepositionPage(m_pages.Item(m_current_page).page);
720 }
721 RefreshTabBar();
722
723 evt.Skip();
724 }
725
726 void wxRibbonBar::RepositionPage(wxRibbonPage *page)
727 {
728 int w, h;
729 GetSize(&w, &h);
730 page->SetSizeWithScrollButtonAdjustment(0, m_tab_height, w, h - m_tab_height);
731 }
732
733 wxRibbonPageTabInfo* wxRibbonBar::HitTestTabs(wxPoint position, int* index)
734 {
735 wxRect tabs_rect(m_tab_margin_left, 0, GetClientSize().GetWidth() - m_tab_margin_left - m_tab_margin_right, m_tab_height);
736 if(m_tab_scroll_buttons_shown)
737 {
738 tabs_rect.SetX(tabs_rect.GetX() + m_tab_scroll_left_button_rect.GetWidth());
739 tabs_rect.SetWidth(tabs_rect.GetWidth() - m_tab_scroll_left_button_rect.GetWidth() - m_tab_scroll_right_button_rect.GetWidth());
740 }
741 if(tabs_rect.Contains(position))
742 {
743 size_t numtabs = m_pages.GetCount();
744 size_t i;
745 for(i = 0; i < numtabs; ++i)
746 {
747 wxRibbonPageTabInfo& info = m_pages.Item(i);
748 if(info.rect.Contains(position))
749 {
750 if(index != NULL)
751 {
752 *index = (int)i;
753 }
754 return &info;
755 }
756 }
757 }
758 if(index != NULL)
759 {
760 *index = -1;
761 }
762 return NULL;
763 }
764
765 void wxRibbonBar::OnMouseLeftDown(wxMouseEvent& evt)
766 {
767 wxRibbonPageTabInfo *tab = HitTestTabs(evt.GetPosition());
768 if(tab && tab != &m_pages.Item(m_current_page))
769 {
770 wxRibbonBarEvent query(wxEVT_COMMAND_RIBBONBAR_PAGE_CHANGING, GetId(), tab->page);
771 query.SetEventObject(this);
772 ProcessWindowEvent(query);
773 if(query.IsAllowed())
774 {
775 SetActivePage(query.GetPage());
776
777 wxRibbonBarEvent notification(wxEVT_COMMAND_RIBBONBAR_PAGE_CHANGED, GetId(), m_pages.Item(m_current_page).page);
778 notification.SetEventObject(this);
779 ProcessWindowEvent(notification);
780 }
781 }
782 else if(tab == NULL)
783 {
784 if(m_tab_scroll_left_button_rect.Contains(evt.GetPosition()))
785 {
786 m_tab_scroll_left_button_state |= wxRIBBON_SCROLL_BTN_ACTIVE | wxRIBBON_SCROLL_BTN_HOVERED;
787 RefreshTabBar();
788 }
789 else if(m_tab_scroll_right_button_rect.Contains(evt.GetPosition()))
790 {
791 m_tab_scroll_right_button_state |= wxRIBBON_SCROLL_BTN_ACTIVE | wxRIBBON_SCROLL_BTN_HOVERED;
792 RefreshTabBar();
793 }
794 }
795 }
796
797 void wxRibbonBar::OnMouseLeftUp(wxMouseEvent& WXUNUSED(evt))
798 {
799 if(!m_tab_scroll_buttons_shown)
800 {
801 return;
802 }
803
804 int amount = 0;
805 if(m_tab_scroll_left_button_state & wxRIBBON_SCROLL_BTN_ACTIVE)
806 {
807 amount = -1;
808 }
809 else if(m_tab_scroll_right_button_state & wxRIBBON_SCROLL_BTN_ACTIVE)
810 {
811 amount = 1;
812 }
813 if(amount != 0)
814 {
815 m_tab_scroll_left_button_state &= ~wxRIBBON_SCROLL_BTN_ACTIVE;
816 m_tab_scroll_right_button_state &= ~wxRIBBON_SCROLL_BTN_ACTIVE;
817 ScrollTabBar(amount * 8);
818 }
819 }
820
821 void wxRibbonBar::ScrollTabBar(int amount)
822 {
823 bool show_left = true;
824 bool show_right = true;
825 if(m_tab_scroll_amount + amount <= 0)
826 {
827 amount = -m_tab_scroll_amount;
828 show_left = false;
829 }
830 else if(m_tab_scroll_amount + amount + (GetClientSize().GetWidth() - m_tab_margin_left - m_tab_margin_right) >= m_tabs_total_width_minimum)
831 {
832 amount = m_tabs_total_width_minimum - m_tab_scroll_amount - (GetClientSize().GetWidth() - m_tab_margin_left - m_tab_margin_right);
833 show_right = false;
834 }
835 if(amount == 0)
836 {
837 return;
838 }
839 m_tab_scroll_amount += amount;
840 size_t numtabs = m_pages.GetCount();
841 size_t i;
842 for(i = 0; i < numtabs; ++i)
843 {
844 wxRibbonPageTabInfo& info = m_pages.Item(i);
845 info.rect.SetX(info.rect.GetX() - amount);
846 }
847 if(show_right != (m_tab_scroll_right_button_rect.GetWidth() != 0) ||
848 show_left != (m_tab_scroll_left_button_rect.GetWidth() != 0))
849 {
850 wxClientDC temp_dc(this);
851 if(show_left)
852 {
853 m_tab_scroll_left_button_rect.SetWidth(m_art->GetScrollButtonMinimumSize(temp_dc, this, wxRIBBON_SCROLL_BTN_LEFT | wxRIBBON_SCROLL_BTN_NORMAL | wxRIBBON_SCROLL_BTN_FOR_TABS).GetWidth());
854 }
855 else
856 {
857 m_tab_scroll_left_button_rect.SetWidth(0);
858 }
859
860 if(show_right)
861 {
862 if(m_tab_scroll_right_button_rect.GetWidth() == 0)
863 {
864 m_tab_scroll_right_button_rect.SetWidth(m_art->GetScrollButtonMinimumSize(temp_dc, this, wxRIBBON_SCROLL_BTN_RIGHT | wxRIBBON_SCROLL_BTN_NORMAL | wxRIBBON_SCROLL_BTN_FOR_TABS).GetWidth());
865 m_tab_scroll_right_button_rect.SetX(m_tab_scroll_right_button_rect.GetX() - m_tab_scroll_right_button_rect.GetWidth());
866 }
867 }
868 else
869 {
870 if(m_tab_scroll_right_button_rect.GetWidth() != 0)
871 {
872 m_tab_scroll_right_button_rect.SetX(m_tab_scroll_right_button_rect.GetX() + m_tab_scroll_right_button_rect.GetWidth());
873 m_tab_scroll_right_button_rect.SetWidth(0);
874 }
875 }
876 }
877
878 RefreshTabBar();
879 }
880
881 void wxRibbonBar::RefreshTabBar()
882 {
883 wxRect tab_rect(0, 0, GetClientSize().GetWidth(), m_tab_height);
884 Refresh(false, &tab_rect);
885 }
886
887 void wxRibbonBar::OnMouseMiddleDown(wxMouseEvent& evt)
888 {
889 DoMouseButtonCommon(evt, wxEVT_COMMAND_RIBBONBAR_TAB_MIDDLE_DOWN);
890 }
891
892 void wxRibbonBar::OnMouseMiddleUp(wxMouseEvent& evt)
893 {
894 DoMouseButtonCommon(evt, wxEVT_COMMAND_RIBBONBAR_TAB_MIDDLE_UP);
895 }
896
897 void wxRibbonBar::OnMouseRightDown(wxMouseEvent& evt)
898 {
899 DoMouseButtonCommon(evt, wxEVT_COMMAND_RIBBONBAR_TAB_RIGHT_DOWN);
900 }
901
902 void wxRibbonBar::OnMouseRightUp(wxMouseEvent& evt)
903 {
904 DoMouseButtonCommon(evt, wxEVT_COMMAND_RIBBONBAR_TAB_RIGHT_UP);
905 }
906
907 void wxRibbonBar::OnMouseDoubleClick(wxMouseEvent& evt)
908 {
909 DoMouseButtonCommon(evt, wxEVT_COMMAND_RIBBONBAR_TAB_LEFT_DCLICK);
910 }
911
912 void wxRibbonBar::DoMouseButtonCommon(wxMouseEvent& evt, wxEventType tab_event_type)
913 {
914 wxRibbonPageTabInfo *tab = HitTestTabs(evt.GetPosition());
915 if(tab)
916 {
917 wxRibbonBarEvent notification(tab_event_type, GetId(), tab->page);
918 notification.SetEventObject(this);
919 ProcessWindowEvent(notification);
920 }
921 }
922
923 void wxRibbonBar::RecalculateMinSize()
924 {
925 wxSize min_size(wxDefaultCoord, wxDefaultCoord);
926 size_t numtabs = m_pages.GetCount();
927 if(numtabs != 0)
928 {
929 min_size = m_pages.Item(0).page->GetMinSize();
930
931 size_t i;
932 for(i = 1; i < numtabs; ++i)
933 {
934 wxRibbonPageTabInfo& info = m_pages.Item(i);
935 wxSize page_min = info.page->GetMinSize();
936
937 min_size.x = wxMax(min_size.x, page_min.x);
938 min_size.y = wxMax(min_size.y, page_min.y);
939 }
940 }
941 if(min_size.y != wxDefaultCoord)
942 {
943 // TODO: Decide on best course of action when min height is unspecified
944 // - should we specify it to the tab minimum, or leave it unspecified?
945 min_size.IncBy(0, m_tab_height);
946 }
947
948 m_minWidth = min_size.GetWidth();
949 m_minHeight = min_size.GetHeight();
950 }
951
952 wxSize wxRibbonBar::DoGetBestSize() const
953 {
954 wxSize best(0, 0);
955 if(m_current_page != -1)
956 {
957 best = m_pages.Item(m_current_page).page->GetBestSize();
958 }
959 if(best.GetHeight() == wxDefaultCoord)
960 {
961 best.SetHeight(m_tab_height);
962 }
963 else
964 {
965 best.IncBy(0, m_tab_height);
966 }
967 return best;
968 }
969
970 #endif // wxUSE_RIBBON