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