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