1 /*
2  * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
3  * http://www.gnu.org/licenses/lgpl-3.0.html
4  *
5  * $Revision: 11431 $
6  * $Id: cbauibook.cpp 11431 2018-08-04 05:40:37Z ollydbg $
7  * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/sdk/cbauibook.cpp $
8  */
9 
10 
11 #include "sdk_precomp.h"
12 
13 #ifndef CB_PRECOMP
14     #include "cbauibook.h"
15     #include "cbeditor.h"
16     #include "cbproject.h"
17     #include "configmanager.h"
18     #include "editormanager.h"
19     #include "manager.h"
20     #include "projectmanager.h"
21 
22     #include <wx/app.h>
23     #include <wx/dcclient.h>
24     #include <wx/regex.h>
25 #endif
26 
27 #include <wx/tooltip.h>
28 #include <wx/wupdlock.h>
29 
30 // static
31 bool cbAuiNotebook::s_AllowMousewheel = true;
32 cbAuiNotebookArray cbAuiNotebook::s_cbAuiNotebookArray;
33 wxString cbAuiNotebook::s_modKeys = _T("Ctrl");
34 bool cbAuiNotebook::s_modToAdvance = false;
35 int cbAuiNotebook::s_advanceDirection = 1;
36 int cbAuiNotebook::s_moveDirection = 1;
37 
38 
BEGIN_EVENT_TABLE(cbAuiNotebook,wxAuiNotebook)39 BEGIN_EVENT_TABLE(cbAuiNotebook, wxAuiNotebook)
40 #if wxCHECK_VERSION(3, 0, 0)
41     EVT_NAVIGATION_KEY(cbAuiNotebook::OnNavigationKeyNotebook)
42 #else
43     EVT_NAVIGATION_KEY(cbAuiNotebook::OnNavigationKey)
44 #endif
45     EVT_IDLE(cbAuiNotebook::OnIdle)
46     EVT_AUINOTEBOOK_DRAG_DONE(wxID_ANY, cbAuiNotebook::OnDragDone)
47 END_EVENT_TABLE()
48 
49 cbAuiNotebook::cbAuiNotebook(wxWindow* pParent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
50         : wxAuiNotebook(pParent, id, pos, size, style),
51 #ifdef __WXMSW__
52           m_LastSelected(wxNOT_FOUND),
53           m_LastId(0),
54 #endif
55 #if !wxCHECK_VERSION(3, 0, 0)
56           m_HasToolTip(false),
57 #endif
58           m_SetZoomOnIdle(false),
59           m_MinimizeFreeSpaceOnIdle(false),
60           m_TabCtrlSize(wxDefaultSize)
61 {
62     //ctor
63 #ifdef __WXGTK__
64     m_mgr.SetFlags((m_mgr.GetFlags() | wxAUI_MGR_VENETIAN_BLINDS_HINT) & ~wxAUI_MGR_TRANSPARENT_HINT);
65 #endif  // #ifdef __WXGTK__
66     ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("app"));
67 #if defined __WXMSW__ && wxCHECK_VERSION(3, 0, 0)
68     wxToolTip::SetMaxWidth(-1);
69 #endif
70     s_AllowMousewheel = cfg->ReadBool(_T("/environment/tabs_use_mousewheel"),true);
71     s_modKeys = cfg->Read(_T("/environment/tabs_mousewheel_modifier"),_T("Ctrl"));
72     s_modToAdvance = cfg->ReadBool(_T("/environment/tabs_mousewheel_advance"),false);
73     cbAuiNotebook::InvertAdvanceDirection(cfg->ReadBool(_T("/environment/tabs_invert_advance"),false));
74     cbAuiNotebook::InvertMoveDirection(cfg->ReadBool(_T("/environment/tabs_invert_move"),false));
75 
76     if (s_cbAuiNotebookArray.Index(this) == wxNOT_FOUND)
77         s_cbAuiNotebookArray.Add(this);
78 }
79 
~cbAuiNotebook()80 cbAuiNotebook::~cbAuiNotebook()
81 {
82     s_cbAuiNotebookArray.Remove(this);
83 }
84 
CheckKeyModifier()85 bool cbAuiNotebook::CheckKeyModifier()
86 {
87     bool result = true;
88     // this search must be case-insensitive
89     wxString str = s_modKeys;
90     str.MakeUpper();
91 
92     if (result && str.Contains(wxT("ALT")))
93         result = wxGetKeyState(WXK_ALT);
94     if (result && str.Contains(wxT("CTRL")))
95         result = wxGetKeyState(WXK_CONTROL);
96 #if defined(__WXMAC__) || defined(__WXCOCOA__)
97     if (result && str.Contains(wxT("XCTRL")))
98         result = wxGetKeyState(WXK_COMMAND);
99 #endif
100     if (result && str.Contains(wxT("SHIFT")))
101         result = wxGetKeyState(WXK_SHIFT);
102     return result;
103 }
104 
UpdateTabControlsArray()105 void cbAuiNotebook::UpdateTabControlsArray()
106 {
107     cbAuiTabCtrlArray saveTabCtrls = m_TabCtrls;
108     m_TabCtrls.Clear();
109     // first get all tab-controls
110     const size_t tab_Count = GetPageCount();
111     for (size_t i = 0; i < tab_Count; ++i)
112     {
113         wxAuiTabCtrl* tabCtrl = nullptr;
114         int idx = -1;
115         if (FindTab(GetPage(i), &tabCtrl, &idx))
116         {
117             if (tabCtrl && m_TabCtrls.Index(tabCtrl) == wxNOT_FOUND)
118                 m_TabCtrls.Add(tabCtrl);
119         }
120         else
121             continue;
122     }
123     bool needEventRebind = m_TabCtrls.GetCount() != saveTabCtrls.GetCount();
124     if (!needEventRebind)
125     {
126         for (size_t i = 0; i < m_TabCtrls.GetCount(); ++i)
127         {
128             if (saveTabCtrls.Index(m_TabCtrls[i]) == wxNOT_FOUND)
129             {
130                 needEventRebind = true;
131                 break;
132             }
133         }
134     }
135     if (needEventRebind)
136     {
137         ResetTabCtrlEvents();
138     }
139     // make sure the active page is valid and shown
140     for (size_t i = 0; i < m_TabCtrls.GetCount(); ++i)
141     {
142         int pageCount = m_TabCtrls[i]->GetPageCount();
143         if (m_TabCtrls[i]->GetActivePage() < 0 && pageCount > 0)
144              m_TabCtrls[i]->SetActivePage((size_t)0);
145         if (m_TabCtrls[i]->GetActivePage() >= pageCount && pageCount > 0)
146              m_TabCtrls[i]->SetActivePage(pageCount - 1);
147         m_TabCtrls[i]->DoShowHide();
148     }
149 }
150 
ResetTabCtrlEvents()151 void cbAuiNotebook::ResetTabCtrlEvents()
152 {
153     for (size_t i = 0; i < m_TabCtrls.GetCount(); ++i)
154     {
155 #if !wxCHECK_VERSION(3, 0, 0)
156         m_TabCtrls[i]->Disconnect(wxEVT_MOTION, wxMouseEventHandler(cbAuiNotebook::OnMotion));
157         m_TabCtrls[i]->Connect(wxEVT_MOTION ,   wxMouseEventHandler(cbAuiNotebook::OnMotion));
158 #endif
159         m_TabCtrls[i]->Disconnect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(cbAuiNotebook::OnTabCtrlDblClick));
160         m_TabCtrls[i]->Connect(wxEVT_LEFT_DCLICK,    wxMouseEventHandler(cbAuiNotebook::OnTabCtrlDblClick));
161         m_TabCtrls[i]->Disconnect(wxEVT_SIZE,        wxSizeEventHandler(cbAuiNotebook::OnResize));
162         m_TabCtrls[i]->Connect(wxEVT_SIZE,           wxSizeEventHandler(cbAuiNotebook::OnResize));
163         m_TabCtrls[i]->Disconnect(wxEVT_MOUSEWHEEL,  wxMouseEventHandler(cbAuiNotebook::OnTabCtrlMouseWheel));
164 #ifdef __WXMSW__
165         m_TabCtrls[i]->Disconnect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(cbAuiNotebook::OnEnterTabCtrl));
166         m_TabCtrls[i]->Disconnect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(cbAuiNotebook::OnLeaveTabCtrl));
167 #endif
168         if (GetPageCount() > 1)
169         {
170             if (s_AllowMousewheel)
171                 m_TabCtrls[i]->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(cbAuiNotebook::OnTabCtrlMouseWheel));
172 #ifdef __WXMSW__
173             m_TabCtrls[i]->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(cbAuiNotebook::OnEnterTabCtrl));
174             m_TabCtrls[i]->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(cbAuiNotebook::OnLeaveTabCtrl));
175 #endif
176         }
177     }
178 }
179 
FocusActiveTabCtrl()180 void cbAuiNotebook::FocusActiveTabCtrl()
181 {
182     UpdateTabControlsArray();
183     int sel = GetSelection();
184     if (sel < 0)
185         return;
186 
187     wxWindow* wnd = GetPage(static_cast<size_t>(sel));
188     if (!wnd)
189         return;
190 
191     for (size_t i = 0; i < m_TabCtrls.GetCount(); ++i)
192     {
193         wxWindow* win = m_TabCtrls[i]->GetWindowFromIdx(m_TabCtrls[i]->GetActivePage());
194         if (win && (win == wnd))
195         {
196             m_TabCtrls[i]->SetFocus();
197             break;
198         }
199     }
200 }
201 
GetTabCtrl(wxWindow * page)202 wxAuiTabCtrl* cbAuiNotebook::GetTabCtrl(wxWindow *page)
203 {
204     if (page)
205     {
206         wxAuiTabCtrl *tabCtrl = nullptr;
207         int idx;
208         if (!FindTab(page, &tabCtrl, &idx))
209             return nullptr;
210         return tabCtrl;
211     }
212     else
213         return GetActiveTabCtrl();
214 }
215 
GetPagesInTabCtrl(std::vector<wxWindow * > & result,wxWindow * page)216 void cbAuiNotebook::GetPagesInTabCtrl(std::vector<wxWindow*> &result, wxWindow *page)
217 {
218     result.clear();
219     wxAuiTabCtrl* tabCtrl = GetTabCtrl(page);
220     if (tabCtrl)
221     {
222         int count = tabCtrl->GetPageCount();
223         for (int ii = 0; ii < count; ++ii)
224             result.push_back(tabCtrl->GetPage(ii).window);
225     }
226 }
227 
SetZoom(int zoom)228 void cbAuiNotebook::SetZoom(int zoom)
229 {
230     // we only set zoom-factor for active (visible) tabs,
231     // all others are set if system is idle
232     UpdateTabControlsArray();
233     for (size_t i = 0; i < m_TabCtrls.GetCount(); ++i)
234     {
235         wxWindow* win = m_TabCtrls[i]->GetWindowFromIdx(m_TabCtrls[i]->GetActivePage());
236         if (win && static_cast<EditorBase*>(win)->IsBuiltinEditor())
237             static_cast<cbEditor*>(win)->SetZoom(zoom);
238     }
239     m_SetZoomOnIdle = true;
240 }
241 
OnIdle(cb_unused wxIdleEvent & event)242 void cbAuiNotebook::OnIdle(cb_unused wxIdleEvent& event)
243 {
244     if (m_SetZoomOnIdle)
245     {
246         m_SetZoomOnIdle = false;
247         int zoom = Manager::Get()->GetEditorManager()->GetZoom();
248         for (size_t i = 0; i < GetPageCount(); ++i)
249         {
250             wxWindow* win = GetPage(i);
251             if (win && static_cast<EditorBase*>(win)->IsBuiltinEditor())
252                 static_cast<cbEditor*>(win)->SetZoom(zoom);
253         }
254     }
255 
256     if (m_MinimizeFreeSpaceOnIdle)
257     {
258         m_MinimizeFreeSpaceOnIdle = false;
259         UpdateTabControlsArray();
260         for (size_t i = 0; i < m_TabCtrls.GetCount(); ++i)
261             MinimizeFreeSpace(m_TabCtrls[i]);
262     }
263 
264 }
265 
OnDragDone(cb_unused wxAuiNotebookEvent & event)266 void cbAuiNotebook::OnDragDone(cb_unused wxAuiNotebookEvent& event)
267 {
268     UpdateTabControlsArray();
269 }
270 
271 #ifdef __WXMSW__
OnEnterTabCtrl(wxMouseEvent & event)272 void cbAuiNotebook::OnEnterTabCtrl(wxMouseEvent& event)
273 {
274     if (!wxTheApp->IsActive())
275         return;
276 
277     wxAuiTabCtrl* tabCtrl = (wxAuiTabCtrl*)event.GetEventObject();
278     if (tabCtrl)
279     {
280         cbAuiNotebook* nb = (cbAuiNotebook*)tabCtrl->GetParent();
281         if (nb)
282         {
283             if (   s_AllowMousewheel
284                 && (nb->m_LastSelected == wxNOT_FOUND)
285                 && (nb->m_LastId == 0) )
286             {
287                 nb->StoreFocus();
288                 tabCtrl->SetFocus();
289             }
290         }
291     }
292 }
293 
OnLeaveTabCtrl(wxMouseEvent & event)294 void cbAuiNotebook::OnLeaveTabCtrl(wxMouseEvent& event)
295 {
296     if (!wxTheApp->IsActive())
297         return;
298 
299     wxAuiTabCtrl* tabCtrl = (wxAuiTabCtrl*)event.GetEventObject();
300     if (tabCtrl)
301     {
302         cbAuiNotebook* nb = (cbAuiNotebook*)tabCtrl->GetParent();
303         if (nb)
304             nb->RestoreFocus();
305     }
306 
307 }
308 
IsFocusStored(wxWindow * page)309 bool cbAuiNotebook::IsFocusStored(wxWindow* page)
310 {
311     wxWindow* win = FindWindowById(m_LastId);
312     while (win)
313     {
314         if (win == page)
315             return true;
316         win = win->GetParent();
317     }
318     return false;
319 }
320 
StoreFocus()321 void cbAuiNotebook::StoreFocus()
322 {
323     // save Id of last focused window and last selected tab
324     wxWindow* win = wxWindow::FindFocus();
325     if (win)
326         m_LastId = win->GetId();
327     else
328         m_LastId = 0;
329     m_LastSelected = GetSelection();
330 }
331 
RestoreFocus()332 void cbAuiNotebook::RestoreFocus()
333 {
334     // if selected tab has changed, we set the focus on the window it belongs too
335     if ((m_LastSelected != wxNOT_FOUND) && (GetSelection() != m_LastSelected))
336     {
337         wxWindow* win = GetPage(GetSelection());
338         if (win)
339             win->SetFocus();
340     }
341     // otherwise, we restore the former focus, if the window
342     // with the saved Id still exists
343     else if (m_LastId != 0)
344     {
345         wxWindow* win = FindWindowById(m_LastId);
346         if (win)
347             win->SetFocus();
348     }
349     m_LastSelected = wxNOT_FOUND;
350     m_LastId = 0;
351 }
352 #endif // #ifdef __WXMSW__
353 
OnTabCtrlDblClick(wxMouseEvent & event)354 void cbAuiNotebook::OnTabCtrlDblClick(wxMouseEvent& event)
355 {
356     wxWindow* win = nullptr;
357     wxAuiTabCtrl* tabCtrl = (wxAuiTabCtrl*)event.GetEventObject();
358     if (tabCtrl && tabCtrl->TabHitTest(event.GetX(), event.GetY(), &win))
359     {
360         if (win != nullptr)
361         {
362             // send double-click-event
363             CodeBlocksEvent theEvent(cbEVT_CBAUIBOOK_LEFT_DCLICK, GetParent()->GetId());
364             theEvent.SetEventObject(win);
365             GetParent()->GetEventHandler()->ProcessEvent(theEvent);
366         }
367     }
368 }
369 
OnTabCtrlMouseWheel(wxMouseEvent & event)370 void cbAuiNotebook::OnTabCtrlMouseWheel(wxMouseEvent& event)
371 {
372     wxAuiTabCtrl* tabCtrl = (wxAuiTabCtrl*)event.GetEventObject();
373     if (!tabCtrl)
374         return;
375     cbAuiNotebook* nb = (cbAuiNotebook*)tabCtrl->GetParent();
376     if (!nb)
377         return;
378 
379     nb->SetSelection(nb->GetPageIndex(tabCtrl->GetWindowFromIdx(tabCtrl->GetActivePage())));
380 
381     bool modkeys = CheckKeyModifier();
382 
383     bool advance = (!s_modToAdvance && !modkeys) || (s_modToAdvance &&  modkeys);
384 
385     if (advance)
386         nb->AdvanceSelection((event.GetWheelRotation() * s_advanceDirection) < 0);
387     else
388     {
389         size_t tabOffset = tabCtrl->GetTabOffset();
390         size_t lastTabIdx = tabCtrl->GetPageCount()-1;
391         wxWindow* win = nb->GetPage(nb->GetSelection());
392         if (win)
393         {
394             wxClientDC dc(win);
395             if ((event.GetWheelRotation() * s_moveDirection) > 0)
396             {
397                 if (!tabCtrl->IsTabVisible(lastTabIdx,tabOffset,&dc,win))
398                     tabOffset++;
399             }
400             else
401             {
402                 if (tabOffset > 0)
403                     tabOffset--;
404             }
405             tabCtrl->SetTabOffset(tabOffset);
406             nb->Refresh();
407         }
408     }
409 }
410 
OnResize(wxSizeEvent & event)411 void cbAuiNotebook::OnResize(wxSizeEvent& event)
412 {
413     wxAuiTabCtrl* tabCtrl = (wxAuiTabCtrl*)event.GetEventObject();
414     if (tabCtrl)
415     {
416         cbAuiNotebook* nb = (cbAuiNotebook*)tabCtrl->GetParent();
417         if (nb)
418         {
419             if (nb->m_TabCtrlSize != event.GetSize())
420             {
421                 nb->m_TabCtrlSize = event.GetSize();
422                 nb->MinimizeFreeSpace();
423             }
424         }
425     }
426     event.Skip();
427 }
428 
429 #if !wxCHECK_VERSION(3, 0, 0)
OnMotion(wxMouseEvent & event)430 void cbAuiNotebook::OnMotion(wxMouseEvent& event)
431 {
432     event.Skip();
433     wxAuiTabCtrl* tabCtrl = (wxAuiTabCtrl*)event.GetEventObject();
434     if (!tabCtrl)
435         return;
436     cbAuiNotebook* nb = (cbAuiNotebook*)tabCtrl->GetParent();
437     if (!nb || !nb->m_HasToolTip)
438         return;
439 
440     wxWindow* win = nullptr;
441     if (event.Moving() && tabCtrl->TabHitTest(event.m_x, event.m_y, &win))
442     {
443         if (!win)
444         {
445             tabCtrl->UnsetToolTip();
446             return;
447         }
448         wxString text(win->GetName());
449         // If the text changes, set it else, keep old, to avoid
450         // 'moving tooltip' effect
451         wxToolTip* tooltip = tabCtrl->GetToolTip();
452         if (!tooltip || tooltip->GetTip() != text)
453             tabCtrl->SetToolTip(text);
454     }
455     else
456         tabCtrl->UnsetToolTip();
457 }
458 
SetPageToolTip(size_t idx,const wxString & text)459 bool cbAuiNotebook::SetPageToolTip(size_t idx, const wxString & text)
460 {
461     if (!m_HasToolTip)
462         UpdateTabControlsArray();
463 
464     m_HasToolTip = true;
465     wxWindow* win = GetPage(idx);
466     if (win && win->GetName() != text)
467         win->SetName(text);
468     else
469         return false;
470     return true;
471 }
GetPageToolTip(size_t idx)472 wxString cbAuiNotebook::GetPageToolTip(size_t idx)
473 {
474     wxWindow* win = GetPage(idx);
475     if (win)
476         return win->GetName();
477     return wxEmptyString;
478 }
479 #endif
MinimizeFreeSpace()480 void cbAuiNotebook::MinimizeFreeSpace()
481 {
482     if (GetPageCount() < 2)
483         return;
484     m_MinimizeFreeSpaceOnIdle = true;
485 }
486 
MinimizeFreeSpace(wxAuiTabCtrl * tabCtrl)487 void cbAuiNotebook::MinimizeFreeSpace(wxAuiTabCtrl* tabCtrl)
488 {
489     if (!tabCtrl || tabCtrl->GetPageCount() < 2 || !IsWindowReallyShown(this))
490         return;
491 
492     int ctrl_idx = tabCtrl->GetActivePage();
493     wxWindow* win = GetPage(ctrl_idx);
494     if (win)
495     {
496         int tabOffset = tabCtrl->GetTabOffset();
497 
498         wxClientDC dc(win);
499         size_t lastTabIdx = tabCtrl->GetPageCount() - 1;
500 
501         if (!tabCtrl->IsTabVisible(ctrl_idx, tabOffset, & dc, win))
502         {
503             for (int i = lastTabIdx ; i >= 0; --i)
504             {
505                 if (tabCtrl->IsTabVisible(ctrl_idx, i, & dc, win))
506                 {
507                     tabOffset = i;
508                     break;
509                 }
510             }
511         }
512         while (tabOffset > 0 && tabCtrl->IsTabVisible(lastTabIdx, tabOffset-1, & dc, win))
513             --tabOffset;
514 
515         tabCtrl->SetTabOffset(tabOffset);
516     }
517     tabCtrl->Refresh();
518 }
519 
DeletePage(size_t page)520 bool cbAuiNotebook::DeletePage(size_t page)
521 {
522 #ifdef __WXMSW__
523     if (IsFocusStored(GetPage(page)))
524     {
525         m_LastSelected = wxNOT_FOUND;
526         m_LastId = 0;
527     }
528 #endif // #ifdef __WXMSW__
529     bool result = wxAuiNotebook::DeletePage(page);
530     MinimizeFreeSpace();
531     return result;
532 }
533 
RemovePage(size_t page)534 bool cbAuiNotebook::RemovePage(size_t page)
535 {
536 #ifdef __WXMSW__
537     if (IsFocusStored(GetPage(page)))
538     {
539         m_LastSelected = wxNOT_FOUND;
540         m_LastId = 0;
541     }
542 #endif // #ifdef __WXMSW__
543     bool result = wxAuiNotebook::RemovePage(page);
544     MinimizeFreeSpace();
545     return result;
546 }
547 
MovePage(wxWindow * page,size_t new_idx)548 bool cbAuiNotebook::MovePage(wxWindow* page, size_t new_idx)
549 {
550     UpdateTabControlsArray();
551     bool result = false;
552     if (m_TabCtrls.GetCount() > 0)
553     {
554         result = m_TabCtrls[0]->MovePage(page, new_idx);
555         Refresh();
556         MinimizeFreeSpace();
557     }
558     return result;
559 }
560 
AddPage(wxWindow * page,const wxString & caption,bool select,const wxBitmap & bitmap)561 bool cbAuiNotebook::AddPage(wxWindow* page,
562                             const wxString& caption,
563                             bool select,
564                             const wxBitmap& bitmap)
565 {
566     bool result = wxAuiNotebook::AddPage(page, caption, select, bitmap);
567     MinimizeFreeSpace();
568     return result;
569 }
570 
InsertPage(size_t page_idx,wxWindow * page,const wxString & caption,bool select,const wxBitmap & bitmap)571 bool cbAuiNotebook::InsertPage(size_t page_idx,
572                                wxWindow* page,
573                                const wxString& caption,
574                                bool select,
575                                const wxBitmap& bitmap)
576 {
577     bool result = wxAuiNotebook::InsertPage(page_idx, page, caption, select, bitmap);
578     MinimizeFreeSpace();
579     return result;
580 }
581 
GetTabPositionFromIndex(int index)582 int cbAuiNotebook::GetTabPositionFromIndex(int index)
583 {
584     if (GetPageCount() <= 0)
585         return wxNOT_FOUND;
586 
587     UpdateTabControlsArray();
588 
589     wxAuiTabCtrl* tabCtrl = nullptr;
590     int idx = -1;
591 
592     if (!FindTab(GetPage(index), &tabCtrl, &idx))
593         return wxNOT_FOUND;
594 
595     if (!tabCtrl || idx < 0)
596         return wxNOT_FOUND;
597 
598     int indexOffset = 0;
599     wxAuiPaneInfoArray& all_panes = m_mgr.GetAllPanes();
600     const size_t pane_count = all_panes.GetCount();
601     for (size_t i = 0; i < pane_count; ++i)
602     {
603         wxAuiPaneInfo& pane = all_panes[i];
604         if (pane.name == wxT("dummy"))
605             continue;
606 
607         if (pane.window == GetTabFrameFromTabCtrl(tabCtrl))
608             break;
609 
610         for (size_t j = 0; j < m_TabCtrls.GetCount(); ++j)
611         {
612             if (pane.window == GetTabFrameFromTabCtrl(m_TabCtrls[j]))
613             {
614                 indexOffset += m_TabCtrls[j]->GetPageCount();
615                 break;
616             }
617         }
618     }
619     return idx + indexOffset;
620 }
621 
AdvanceSelection(bool forward)622 void cbAuiNotebook::AdvanceSelection(bool forward)
623 {
624     if (GetPageCount() <= 1)
625         return;
626 
627     int currentSelection = GetSelection();
628 
629     wxAuiTabCtrl* tabCtrl = nullptr;
630     int idx = -1;
631 
632     if (!FindTab(GetPage(currentSelection), &tabCtrl, &idx))
633         return;
634 
635     if (!tabCtrl || idx < 0)
636         return;
637 
638     wxWindow* page = nullptr;
639     size_t maxPages = tabCtrl->GetPageCount();
640 
641     forward?idx++:idx--;
642 
643     if (idx < 0)
644         idx = maxPages - 1;
645 
646     if ((size_t)idx < maxPages)
647         page = tabCtrl->GetPage(idx).window;
648 
649     if (!page && maxPages > 0)
650         page = tabCtrl->GetPage(0).window;
651 
652     if (page)
653     {
654         currentSelection = GetPageIndex(page);
655         SetSelection(currentSelection);
656     }
657 }
658 
659 #if wxCHECK_VERSION(3, 0, 0)
OnNavigationKeyNotebook(wxNavigationKeyEvent & event)660 void cbAuiNotebook::OnNavigationKeyNotebook(wxNavigationKeyEvent& event)
661 #else
662 void cbAuiNotebook::OnNavigationKey(wxNavigationKeyEvent& event)
663 #endif
664 {
665     // if we change window, we call our own AdvanceSelection
666     if ( event.IsWindowChange() )
667         AdvanceSelection(event.GetDirection());
668     else // otherwise we call the event-handler from the parent-class
669     {
670 #if wxCHECK_VERSION(3, 0, 0)
671         wxAuiNotebook::OnNavigationKeyNotebook(event);
672 #else
673         wxAuiNotebook::OnNavigationKey(event);
674 #endif
675     }
676 }
677 
SavePerspective(const wxString projectTitle)678 wxString cbAuiNotebook::SavePerspective(const wxString projectTitle)
679 {
680     // Build list of panes/tabs
681     wxString tabs, tabsTmp;
682     wxArrayString panes;
683 
684     // first get all tab-controls
685     UpdateTabControlsArray();
686 
687     wxAuiPaneInfoArray& all_panes = m_mgr.GetAllPanes();
688     const size_t pane_count = all_panes.GetCount();
689     for (size_t i = 0; i < pane_count; ++i)
690     {
691         wxAuiPaneInfo& pane = all_panes.Item(i);
692         if (pane.name == wxT("dummy"))
693             continue;
694 
695         wxAuiTabCtrl* tabCtrl = nullptr;
696         for (size_t j = 0; j < m_TabCtrls.GetCount(); ++j)
697         {
698             if (pane.window == GetTabFrameFromTabCtrl(m_TabCtrls.Item(j)))
699             {
700                 tabCtrl = m_TabCtrls.Item(j);
701                 break;
702             }
703         }
704         if (tabCtrl)
705         {
706             tabsTmp.Clear();
707             // add tab id's
708             size_t page_count = tabCtrl->GetPageCount();
709             for (size_t p = 0; p < page_count; ++p)
710             {
711                 wxAuiNotebookPage& page = tabCtrl->GetPage(p);
712                 const size_t page_idx = m_tabs.GetIdxFromWindow(page.window);
713 
714                 wxString id = UniqueIdFromTooltip(GetPageToolTip(page_idx));
715 
716                 // file does not belong to any project, so don't save
717                 if (id.BeforeLast(':').empty())
718                     continue;
719 
720                 // if we save a project (projectTitle non empty), but file does not belong to the project
721                 // skip it
722                 if (!projectTitle.empty() && id.BeforeLast(':') != projectTitle)
723                     continue;
724 
725                 if (!tabsTmp.empty())
726                     tabsTmp += wxT(",");
727 
728 #if wxCHECK_VERSION(3, 0, 0)
729                 if ((int)page_idx == m_curPage)
730 #else
731                 if ((int)page_idx == m_curpage)
732 #endif
733                     tabsTmp += wxT("*");
734                 else if ((int)p == tabCtrl->GetActivePage())
735                     tabsTmp += wxT("+");
736 
737                 tabsTmp += wxString::Format(wxT("%lu"), static_cast<unsigned long>(page_idx));
738                 tabsTmp += wxT(";");
739                 tabsTmp += id;
740             }
741             if (!tabsTmp.empty())
742             {
743                 if (!tabs.empty())
744                     tabs += wxT("|");
745 
746                 panes.Add(pane.name);
747                 tabs += pane.name;
748                 tabs += wxT("=");
749                 tabs += tabsTmp;
750             }
751         }
752     }
753     tabs += wxT("@");
754 
755     tabsTmp = m_mgr.SavePerspective();
756 
757     wxArrayString arTabsTmp = GetArrayFromString(tabsTmp, wxT("|"));
758 
759     for (size_t i = arTabsTmp.GetCount(); i > 0 ; )
760     {
761         if (arTabsTmp.Item(--i).StartsWith(wxT("name=")))
762         {
763             wxString strTmp = arTabsTmp.Item(i).AfterFirst('=').BeforeFirst(';');
764             if (strTmp == wxT("dummy"))
765                 continue;
766             if (panes.Index(strTmp) < 0)
767                 arTabsTmp.RemoveAt(i);
768         }
769     }
770 
771     tabsTmp = GetStringFromArray(arTabsTmp, wxT("|"));
772 
773     // Add frame perspective
774     tabs += tabsTmp;
775 
776     return tabs;
777 }
778 
UniqueIdFromTooltip(const wxString & text)779 wxString cbAuiNotebook::UniqueIdFromTooltip(const wxString& text)
780 {
781     ProjectFile* pf = nullptr;
782     cbProject* prj = nullptr;
783     wxString id =  wxT("");
784     wxString fn = text.BeforeFirst(wxT('\n'));
785     prj = Manager::Get()->GetProjectManager()->FindProjectForFile(fn, &pf, false, true);
786     if (prj && pf)
787         id = prj->GetTitle() + wxT(':') + pf->relativeFilename;
788     return id;
789 }
790 
GetTabIndexFromTooltip(const wxString & text)791 int cbAuiNotebook::GetTabIndexFromTooltip(const wxString& text)
792 {
793     if (text == wxT(""))
794         return -1;
795     for (size_t i = 0; i < m_tabs.GetPageCount(); ++i)
796     {
797         if (UniqueIdFromTooltip(GetPageToolTip(i)) == text)
798             return i;
799     }
800     return -1;
801 }
802 
LoadPerspective(const wxString & layout,bool mergeLayouts)803 bool cbAuiNotebook::LoadPerspective(const wxString& layout, bool mergeLayouts)
804 {
805     if (layout.IsEmpty())
806         return false;
807 
808     wxString tabs = layout.BeforeFirst (wxT ('@') );
809     // Remove all tab ctrls (but still keep them in main index)
810     const size_t tab_count = m_tabs.GetPageCount();
811     for (size_t i = 0; i < tab_count; ++i)
812     {
813         // only remove tabs that are in the layout-string, do not touch others,
814         // so the layout of an already loaded workspace or project will not get destroyed, if the
815         // current layout did not know the tab. The layout file which is loaded last takes precedence,
816         // if there are two or more layout strings containing the actual tab.
817         if ( tabs.Find( UniqueIdFromTooltip(GetPageToolTip(i)) ) < 0 )
818             continue;
819 
820         wxWindow* wnd = m_tabs.GetWindowFromIdx (i);
821 
822         // find out which onscreen tab ctrl owns this tab
823         wxAuiTabCtrl* ctrl;
824         int ctrl_idx;
825         if ( !FindTab(wnd, &ctrl, &ctrl_idx) )
826             return false;
827 
828         // remove the tab from ctrl
829         if ( !ctrl->RemovePage(wnd) )
830             return false;
831     }
832     RemoveEmptyTabFrames();
833     wxString currentLayout;
834     if (mergeLayouts)
835     {
836         currentLayout = m_mgr.SavePerspective();
837         wxString tempLayout;
838         while (!currentLayout.empty())
839         {
840             if ( currentLayout.BeforeFirst('|').StartsWith(_("layout2")) ||
841                  currentLayout.BeforeFirst('|').StartsWith(_("name=dummy")) )
842             {
843                 currentLayout = currentLayout.AfterFirst(('|'));
844                 currentLayout.Trim();
845                 currentLayout.Trim(true);
846             }
847             else
848             {
849                 wxString pane_part = currentLayout.BeforeFirst('|');
850                 pane_part.Trim();
851                 pane_part.Trim(true);
852                 if (!pane_part.empty())
853                     tempLayout += pane_part + wxT("|");
854 
855                 currentLayout = currentLayout.AfterFirst('|');
856                 currentLayout.Trim();
857                 currentLayout.Trim(true);
858             }
859         }
860         currentLayout = tempLayout;
861         if (currentLayout.empty())
862             mergeLayouts = false;
863     }
864 
865     size_t sel_page = 0;
866     int active_tab = 0;
867     bool found = false;
868 
869     wxString frames = layout.AfterFirst (wxT ('@') );
870     // if we load an additional project to an exiting layout, the first new tab always goes into a new frame
871     bool firstTabInCtrl =! currentLayout.empty();
872     // This creates a new tabframe if none exists; a workaround, because we can not directly access
873     // the needed wxTabFrame class, because it is not exported.
874     // This also takes care of all needed pane-info
875     wxAuiTabCtrl* dest_tabs = GetActiveTabCtrl();
876     while (1)
877     {
878         const wxString tab_part = tabs.BeforeFirst(wxT('|'));
879 
880         // if the string is empty, we're done parsing
881         if (tab_part.empty())
882             break;
883 
884         // Get pane name
885         wxString pane_name = tab_part.BeforeFirst(wxT('='));
886 
887         // create a new tab frame
888 #if wxCHECK_VERSION(3, 0, 0)
889         m_curPage = -1;
890 #else
891         m_curpage = -1;
892 #endif
893 
894         // Get list of tab id's and move them to pane
895         wxString tab_list = tab_part.AfterFirst(wxT('='));
896         while (1)
897         {
898             wxString tab = tab_list.BeforeFirst(wxT(','));
899             wxString name = tab.AfterFirst(wxT(';'));
900             tab = tab.BeforeFirst(wxT(';'));
901 
902             if (tab.empty())
903                 break;
904             tab_list = tab_list.AfterFirst(wxT(','));
905 
906             // Check if this page has an 'active' marker
907             const wxChar c = tab[0];
908             if (c == wxT('+') || c == wxT('*'))
909                 tab = tab.Mid(1);
910 
911             // Move tab to pane
912             const int index_in_m_tabs = GetTabIndexFromTooltip(name);
913             if (index_in_m_tabs < 0)
914                 continue;
915 
916             wxAuiNotebookPage& page = m_tabs.GetPage(index_in_m_tabs);
917 
918             // save the actual active tab, because it will be set to 0 after a Split()
919             active_tab = dest_tabs->GetActivePage();
920             const size_t newpage_idx = dest_tabs->GetPageCount();
921             dest_tabs->InsertPage(page.window, page, newpage_idx);
922 
923             if (c == wxT('+'))
924                 dest_tabs->SetActivePage(newpage_idx);
925             else if (c == wxT('*'))
926                 sel_page = index_in_m_tabs;
927 
928             // If we should be the first tab in a tab-ctrl we switch to the next existing tab,
929             // or create a new one  by calling Split() and update the dest_tabs accordingly.
930             if (firstTabInCtrl)
931             {
932                 int nextIndex = m_TabCtrls.Index(dest_tabs) + 1;
933                 if (nextIndex == 0 || nextIndex >= static_cast<int>(m_TabCtrls.GetCount()))
934                 {
935                     Split(index_in_m_tabs, wxRIGHT);
936                     // reset the active tab, because a Split() set it to zero
937                     dest_tabs->SetActivePage(active_tab);
938                     dest_tabs = GetActiveTabCtrl();
939                 }
940                 else
941                     dest_tabs = m_TabCtrls.Item(nextIndex);
942             }
943             // Change the pane name to the one we have stored in the layout-string.
944             wxAuiPaneInfo& pane = m_mgr.GetPane( GetTabFrameFromTabCtrl(dest_tabs) );
945             if (pane.name != pane_name)
946             {
947                 tab.Replace(pane_name, pane.name);
948                 frames.Replace(pane_name, pane.name);
949                 pane_name = pane.name;
950             }
951             firstTabInCtrl = false;
952             found = true;
953         }
954         // We come here after at least one tabctrl is filled, so the next tab should go in a new one
955         firstTabInCtrl = true;
956 
957         tabs = tabs.AfterFirst(wxT('|'));
958     }
959 
960     // Check for windows not readded to the notebook and add the at the end.
961     for (size_t i = 0; i < tab_count; ++i)
962     {
963         wxAuiNotebookPage& page = m_tabs.GetPage(i);
964 
965         // find out which onscreen tab ctrl owns this tab
966         // if none then add it to the last used tabctrl
967         wxAuiTabCtrl* ctrl;
968         int ctrl_idx;
969         if ( !FindTab(page.window, &ctrl, &ctrl_idx) )
970         {
971             const size_t newpage_idx = dest_tabs->GetPageCount();
972             dest_tabs->InsertPage (page.window, page, newpage_idx);
973         }
974 
975     }
976 
977     if (mergeLayouts)
978     {
979         wxRegEx reDockSize(_T("(dock_size[()0-9,]+=)[0-9]+"));
980         const wxString replacement(wxT("\\1-1"));
981         // Make a centered frame left docked
982         frames.Replace(wxString::Format(wxT("dock_size(%d"), wxAUI_DOCK_CENTER), wxString::Format(wxT("dock_size(%d"), wxAUI_DOCK_LEFT));
983         frames.Replace(wxString::Format(wxT("dir=%d"), wxAUI_DOCK_CENTER), wxString::Format(wxT("dir=%d"), wxAUI_DOCK_LEFT));
984         if (reDockSize.Matches(frames))
985             reDockSize.ReplaceAll(&frames,replacement);
986         if (reDockSize.Matches(currentLayout))
987             reDockSize.ReplaceAll(&currentLayout,replacement);
988         while (!currentLayout.empty())
989         {
990             wxString pane_part = currentLayout.BeforeFirst('|');
991             pane_part.Trim();
992             pane_part.Trim(true);
993             if (!pane_part.empty())
994                 frames += pane_part + wxT("|");
995             currentLayout = currentLayout.AfterFirst('|');
996             currentLayout.Trim();
997             currentLayout.Trim(true);
998         }
999     }
1000 
1001     if (found)
1002         m_mgr.LoadPerspective(frames);
1003     RemoveEmptyTabFrames();
1004 
1005     // Force refresh of selection
1006 #if wxCHECK_VERSION(3, 0, 0)
1007     m_curPage = -1;
1008 #else
1009     m_curpage = -1;
1010 #endif
1011     SetSelection(sel_page);
1012 
1013     UpdateTabControlsArray();
1014     return true;
1015 }
1016 
1017 
1018 //bool cbAuiNotebook::LoadPerspective(const wxString& layout) {
1019 //   // Remove all tab ctrls (but still keep them in main index)
1020 //   const size_t tab_count = m_tabs.GetPageCount();
1021 //   for (size_t i = 0; i < tab_count; ++i) {
1022 //      wxWindow* wnd = m_tabs.GetWindowFromIdx(i);
1023 //
1024 //      // find out which onscreen tab ctrl owns this tab
1025 //      wxAuiTabCtrl* ctrl;
1026 //      int ctrl_idx;
1027 //      if (!FindTab(wnd, &ctrl, &ctrl_idx))
1028 //         return false;
1029 //
1030 //      // remove the tab from ctrl
1031 //      if (!ctrl->RemovePage(wnd))
1032 //         return false;
1033 //   }
1034 //   RemoveEmptyTabFrames();
1035 //
1036 //   size_t sel_page = 0;
1037 //
1038 //   wxString tabs = layout.BeforeFirst(wxT('@'));
1039 //   while (1)
1040 //    {
1041 //      const wxString tab_part = tabs.BeforeFirst(wxT('|'));
1042 //
1043 //      // if the string is empty, we're done parsing
1044 //        if (tab_part.empty())
1045 //            break;
1046 //
1047 //      // Get pane name
1048 //      const wxString pane_name = tab_part.BeforeFirst(wxT('='));
1049 //
1050 //      // create a new tab frame
1051 //      wxTabFrame* new_tabs = new wxTabFrame;
1052 //      new_tabs->m_tabs = new wxAuiTabCtrl(this,
1053 //                                 m_tab_id_counter++);
1054 ////                            wxDefaultPosition,
1055 ////                            wxDefaultSize,
1056 ////                            wxNO_BORDER|wxWANTS_CHARS);
1057 //      new_tabs->m_tabs->SetArtProvider(m_tabs.GetArtProvider()->Clone());
1058 //      new_tabs->SetTabCtrlHeight(m_tab_ctrl_height);
1059 //      new_tabs->m_tabs->SetFlags(m_flags);
1060 //      wxAuiTabCtrl *dest_tabs = new_tabs->m_tabs;
1061 //
1062 //      // create a pane info structure with the information
1063 //      // about where the pane should be added
1064 //      wxAuiPaneInfo pane_info = wxAuiPaneInfo().Name(pane_name).Bottom().CaptionVisible(false);
1065 //      m_mgr.AddPane(new_tabs, pane_info);
1066 //
1067 //      // Get list of tab id's and move them to pane
1068 //      wxString tab_list = tab_part.AfterFirst(wxT('='));
1069 //      while (1)
1070 //      {
1071 //         wxString tab = tab_list.BeforeFirst(wxT(','));
1072 //         if (tab.empty()) break;
1073 //         tab_list = tab_list.AfterFirst(wxT(','));
1074 //
1075 //         // Check if this page has an 'active' marker
1076 //         const wxChar c = tab[0];
1077 //         if (c == wxT('+') || c == wxT('*'))
1078 //            tab = tab.Mid(1);
1079 //
1080 //         const size_t tab_idx = wxAtoi(tab.c_str());
1081 //         if (tab_idx >= GetPageCount()) continue;
1082 //
1083 //         // Move tab to pane
1084 //         wxAuiNotebookPage& page = m_tabs.GetPage(tab_idx);
1085 //         const size_t newpage_idx = dest_tabs->GetPageCount();
1086 //         dest_tabs->InsertPage(page.window, page, newpage_idx);
1087 //
1088 //         if      ( c == wxT('+')) dest_tabs->SetActivePage(newpage_idx);
1089 //         else if ( c == wxT('*')) sel_page = tab_idx;
1090 //      }
1091 //      dest_tabs->DoShowHide();
1092 //
1093 //      tabs = tabs.AfterFirst(wxT('|'));
1094 //   }
1095 //
1096 //   // Load the frame perspective
1097 //   const wxString frames = layout.AfterFirst(wxT('@'));
1098 //   m_mgr.LoadPerspective(frames);
1099 //
1100 //   // Force refresh of selection
1101 //   m_curpage = -1;
1102 //   SetSelection(sel_page);
1103 //
1104 //   return true;
1105 //}
1106 
1107 // static functions(common to all cbAuiNotebooks)
1108 
AllowScrolling(bool allow)1109 void cbAuiNotebook::AllowScrolling(bool allow)
1110 {
1111     s_AllowMousewheel = allow;
1112     for (size_t i = 0; i < s_cbAuiNotebookArray.GetCount(); ++i)
1113     {
1114         s_cbAuiNotebookArray[i]->UpdateTabControlsArray();
1115         s_cbAuiNotebookArray[i]->ResetTabCtrlEvents();
1116     }
1117 }
1118 
SetModKeys(wxString keys)1119 void cbAuiNotebook::SetModKeys(wxString keys)
1120 {
1121     s_modKeys = keys;
1122 }
1123 
UseModToAdvance(bool use)1124 void cbAuiNotebook::UseModToAdvance(bool use)
1125 {
1126     s_modToAdvance = use;
1127 }
1128 
InvertAdvanceDirection(bool invert)1129 void cbAuiNotebook::InvertAdvanceDirection(bool invert)
1130 {
1131     s_advanceDirection=invert ? -1 : 1;
1132 }
1133 
InvertMoveDirection(bool invert)1134 void cbAuiNotebook::InvertMoveDirection(bool invert)
1135 {
1136     s_moveDirection=invert ? -1 : 1;
1137 }
1138