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