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