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