KiCad PCB EDA Suite
Loading...
Searching...
No Matches
editor_tabs_panel.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23
24#include <wx/aui/auibook.h>
25#include <wx/aui/framemanager.h>
26#include <wx/event.h>
27#include <wx/intl.h>
28#include <wx/menu.h>
29#include <wx/sizer.h>
30
32
33
34// Offset into a private range so these ids never collide with the host frame.
35enum
36{
37 ID_TAB_CLOSE = wxID_HIGHEST + 1,
41};
42
43
44int EDITOR_TABS_MODEL::FindIndex( const wxString& aKey ) const
45{
46 for( size_t i = 0; i < m_entries.size(); ++i )
47 {
48 if( m_entries[i].key == aKey )
49 return static_cast<int>( i );
50 }
51
52 return -1;
53}
54
55
57{
58 for( size_t i = 0; i < m_entries.size(); ++i )
59 {
60 const ENTRY& e = m_entries[i];
61
62 if( e.preview && !e.modified )
63 return static_cast<int>( i );
64 }
65
66 return -1;
67}
68
69
70int EDITOR_TABS_MODEL::OpenDocument( const wxString& aKey, bool aAsPreview )
71{
72 const int existing = FindIndex( aKey );
73
74 if( existing >= 0 )
75 {
76 if( !aAsPreview )
77 m_entries[existing].preview = false;
78
79 return existing;
80 }
81
82 if( aAsPreview )
83 {
84 const int reuse = PreviewIndex();
85
86 if( reuse >= 0 )
87 {
88 m_entries[reuse].key = aKey;
89 m_entries[reuse].preview = true;
90 m_entries[reuse].modified = false;
91 return reuse;
92 }
93 }
94
95 m_entries.push_back( ENTRY{ aKey, aAsPreview, false } );
96
97 return static_cast<int>( m_entries.size() ) - 1;
98}
99
100
101void EDITOR_TABS_MODEL::CloseDocument( const wxString& aKey )
102{
103 const int idx = FindIndex( aKey );
104
105 if( idx >= 0 )
106 m_entries.erase( m_entries.begin() + idx );
107}
108
109
110void EDITOR_TABS_MODEL::MarkModified( const wxString& aKey, bool aModified )
111{
112 const int idx = FindIndex( aKey );
113
114 if( idx < 0 )
115 return;
116
117 m_entries[idx].modified = aModified;
118
119 // Promotion sticks even after the dirty flag later clears.
120 if( aModified )
121 m_entries[idx].preview = false;
122}
123
124
125void EDITOR_TABS_MODEL::Promote( const wxString& aKey )
126{
127 const int idx = FindIndex( aKey );
128
129 if( idx >= 0 )
130 m_entries[idx].preview = false;
131}
132
133
134void EDITOR_TABS_MODEL::Rename( const wxString& aOldKey, const wxString& aNewKey )
135{
136 const int idx = FindIndex( aOldKey );
137
138 if( idx >= 0 )
139 m_entries[idx].key = aNewKey;
140}
141
142
143bool EDITOR_TABS_MODEL::CanCloseWithoutPrompt( const wxString& aKey ) const
144{
145 const int idx = FindIndex( aKey );
146
147 if( idx < 0 )
148 return true;
149
150 return !m_entries[idx].modified;
151}
152
153
154EDITOR_TABS_PANEL::EDITOR_TABS_PANEL( wxWindow* aParent, EDA_DRAW_PANEL_GAL* aSharedCanvas ) :
155 wxPanel( aParent ),
156 m_sharedCanvas( aSharedCanvas )
157{
158 // wxWidgets 3.3 made wxAuiTabCtrl an internal class that only works as a child of a notebook, so
159 // the strip is a real wxAuiNotebook. Its (empty) page area is collapsed by updateTabStripHeight and
160 // the host-owned canvas sits below it as a sibling; documents are tracked by hidden key windows.
161 //
162 // No wxAUI_NB_TAB_MOVE: the model, m_pageWindows and the host context vectors are index-aligned
163 // with the notebook page order, and there is no reorder handler, so user drag-reordering would
164 // desync them and target the wrong document.
165 m_tabs = new wxAuiNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
166 wxAUI_NB_CLOSE_ON_ALL_TABS | wxAUI_NB_SCROLL_BUTTONS );
167
168 m_tabs->SetArtProvider( new KICAD_TAB_ART( [this]( wxWindow* aPageWindow ) -> TAB_VISUAL_STATE
169 {
170 return visualStateForIndex(
171 indexOfWindow( aPageWindow ) );
172 } ) );
173
174 // Floor the height before first layout so a zero-height paint never trips GTK's invalid bitmap
175 // size assert. updateTabStripHeight refines it to the measured tab height once pages exist.
176 m_tabs->SetMinSize( wxSize( -1, FromDIP( 28 ) ) );
177
178 m_sizer = new wxBoxSizer( wxVERTICAL );
179 m_sizer->Add( m_tabs, 0, wxEXPAND );
180
181 if( m_sharedCanvas )
182 {
183 // Canvas is host-owned so remember its parent to hand it back and avoid ~wxPanel freeing it.
184 // It is reparented in once and never again so a tab switch never disturbs the GL context.
186 m_sharedCanvas->Reparent( this );
187 m_sizer->Add( m_sharedCanvas, 1, wxEXPAND );
188 }
189
190 SetSizer( m_sizer );
191
192 m_tabs->Bind( wxEVT_AUINOTEBOOK_PAGE_CHANGED, &EDITOR_TABS_PANEL::onPageChanged, this );
193 m_tabs->Bind( wxEVT_AUINOTEBOOK_PAGE_CLOSE, &EDITOR_TABS_PANEL::onPageClose, this );
194 m_tabs->Bind( wxEVT_AUINOTEBOOK_TAB_RIGHT_DOWN, &EDITOR_TABS_PANEL::onTabRightDown, this );
195
196 // The notebook has no per-tab double-click event, so read the raw double-click on the tab strip
197 // itself and hit-test it back to a page for preview promotion. updateTabStripHeight() keeps this
198 // binding attached to the live tab-strip control as pages come and go.
200}
201
202
204{
205 // Hand the host-owned canvas back so the frame frees it exactly once. Idempotent once null.
206 if( !m_sharedCanvas )
207 return;
208
209 // Drop the sizer's reference first so no stale wxSizerItem lingers after the reparent.
210 if( m_sizer )
211 m_sizer->Detach( m_sharedCanvas );
212
214 m_sharedCanvas = nullptr;
215}
216
217
219{
220 // Hand the canvas back before child teardown so wx never frees it. Fallback for when no host did.
222
223 // The notebook owns the per-tab key windows and frees them with itself, so just drop our handles.
224 m_pageWindows.clear();
225}
226
227
228int EDITOR_TABS_PANEL::indexOfWindow( wxWindow* aWindow ) const
229{
230 for( size_t i = 0; i < m_pageWindows.size(); ++i )
231 {
232 if( m_pageWindows[i] == aWindow )
233 return static_cast<int>( i );
234 }
235
236 return -1;
237}
238
239
241{
242 // wxAuiNotebook owns the tab-strip control and can destroy and recreate it (wx 3.2 frees the tab
243 // frame when the last page closes and rebuilds it on the next add), so bind to whatever control is
244 // live now. Unbind first so re-binding the same control never stacks a second handler; a control
245 // that was never bound makes the Unbind a harmless no-op, and a destroyed control already dropped
246 // its binding with itself, so no stale pointer is ever dereferenced.
247 wxAuiTabCtrl* tabCtrl = m_tabs->GetActiveTabCtrl();
248
249 if( !tabCtrl )
250 return;
251
252 tabCtrl->Unbind( wxEVT_LEFT_DCLICK, &EDITOR_TABS_PANEL::onTabDClick, this );
253 tabCtrl->Bind( wxEVT_LEFT_DCLICK, &EDITOR_TABS_PANEL::onTabDClick, this );
254}
255
256
258{
259 // The notebook can rebuild its tab-strip control as pages come and go, so keep the double-click
260 // handler attached to the live control.
262
263 // Clamp the notebook to its tab-strip height so its unused page area collapses below the visible
264 // region and the shared canvas owns the rest of the pane.
265 const bool hasPages = !m_pageWindows.empty();
266
267 int height = hasPages ? m_tabs->GetTabCtrlHeight() : 0;
268
269 // GetTabCtrlHeight reports 0 before the strip has a valid measuring font, so fall back to a floor.
270 if( hasPages && height <= 0 )
271 height = FromDIP( 28 );
272
273 // Collapse the row when there are no tabs, but keep the control's own min height above zero so a
274 // stray paint never sees a zero-height client area and trips the GTK assert.
275 if( m_sizer )
276 m_sizer->SetItemMinSize( m_tabs, -1, height );
277
278 m_tabs->SetMinSize( wxSize( -1, hasPages ? height : FromDIP( 28 ) ) );
279 m_tabs->Show( hasPages );
280
281 if( m_sharedCanvas )
282 m_sharedCanvas->Show( hasPages );
283
284 Layout();
285}
286
287
289{
290 if( aIdx < 0 || aIdx >= static_cast<int>( m_model.Entries().size() ) )
291 return TAB_VISUAL_STATE{};
292
294 return onQueryVisualState( aIdx );
295
296 const EDITOR_TABS_MODEL::ENTRY& e = m_model.Entries()[aIdx];
297
299}
300
301
302int EDITOR_TABS_PANEL::AddTab( const wxString& aKey, const wxString& aLabel, bool aAsPreview )
303{
304 const int existing = m_model.FindIndex( aKey );
305
306 if( existing >= 0 )
307 {
308 m_model.OpenDocument( aKey, aAsPreview );
309 SelectTab( existing );
310 return existing;
311 }
312
313 // Capture the preview slot's key before OpenDocument overwrites it, else a reused slot leaves the
314 // old key as a dead step in the MRU order.
315 wxString reusedOldKey;
316
317 if( aAsPreview )
318 {
319 const int previewSlot = m_model.PreviewIndex();
320
321 if( previewSlot >= 0 )
322 reusedOldKey = m_model.Entries()[previewSlot].key;
323 }
324
325 const int reuse = m_model.OpenDocument( aKey, aAsPreview );
326
327 if( reuse < static_cast<int>( m_pageWindows.size() ) )
328 {
329 m_tabs->SetPageText( reuse, aLabel );
330
331 if( !reusedOldKey.empty() && reusedOldKey != aKey )
332 forgetMru( reusedOldKey );
333 }
334 else
335 {
336 wxWindow* key = new wxWindow( m_tabs, wxID_ANY );
337 key->Hide();
338
339 // The notebook force-selects its first page, so swallow that change event and drive activation
340 // explicitly through SelectTab below.
341 m_activating = true;
342 m_tabs->AddPage( key, aLabel, false );
343 m_activating = false;
344
345 m_pageWindows.push_back( key );
346 }
347
349
350 touchMru( aKey );
351 SelectTab( reuse );
352 m_tabs->Refresh();
353
354 return reuse;
355}
356
357
359{
360 closeTabInternal( aIdx );
361}
362
363
365{
366 if( aIdx < 0 || aIdx >= static_cast<int>( m_pageWindows.size() ) )
367 return;
368
369 // Remember the active tab as a key, not an index, since indices shift on removal.
370 const int activeBefore = GetActiveTab();
371 const wxString activeBeforeKey =
372 ( activeBefore >= 0 && activeBefore < static_cast<int>( m_model.Entries().size() ) )
373 ? m_model.Entries()[activeBefore].key
374 : wxString();
375
376 // Every close entry point funnels through here so the user is prompted exactly once.
378 return;
379
380 const wxString key = m_model.Entries()[aIdx].key;
381
382 // DeletePage frees the key window and reselects a neighbour; swallow that change event since the
383 // successor selection is chosen explicitly below.
384 m_activating = true;
385 m_tabs->DeletePage( static_cast<size_t>( aIdx ) );
386 m_activating = false;
387
388 m_pageWindows.erase( m_pageWindows.begin() + aIdx );
389 m_model.CloseDocument( key );
390 forgetMru( key );
391
393
394 const int newCount = static_cast<int>( m_pageWindows.size() );
395
396 if( newCount <= 0 )
397 {
398 m_tabs->Refresh();
399 return;
400 }
401
403 {
404 // The host already installed the successor document, so select the page visually only and
405 // keep the MRU consistent. Re-entering activation would double-install or hit a freed index.
406 int visualIdx;
407
408 if( activeBeforeKey.empty() || activeBeforeKey == key )
409 visualIdx = std::min( aIdx, newCount - 1 );
410 else
411 visualIdx = m_model.FindIndex( activeBeforeKey );
412
413 if( visualIdx >= 0 && visualIdx < newCount )
414 {
415 // ChangeSelection updates the strip without firing a change event, so the host is not
416 // re-notified to install a document it already installed.
417 m_tabs->ChangeSelection( static_cast<size_t>( visualIdx ) );
418 touchMru( m_model.Entries()[visualIdx].key );
419 }
420 }
421 else
422 {
423 SelectTab( std::min( aIdx, newCount - 1 ) );
424 }
425
426 m_tabs->Refresh();
427}
428
429
431{
432 if( aKeepIdx < 0 || aKeepIdx >= static_cast<int>( m_pageWindows.size() ) )
433 return;
434
435 const wxString keepKey = m_model.Entries()[aKeepIdx].key;
436
437 // Close from the back so earlier indices stay valid.
438 for( int i = static_cast<int>( m_pageWindows.size() ) - 1; i >= 0; --i )
439 {
440 if( m_model.Entries()[i].key != keepKey )
441 CloseTab( i );
442 }
443}
444
445
447{
448 // A negative anchor would otherwise close every tab.
449 if( aIdx < 0 || aIdx >= static_cast<int>( m_pageWindows.size() ) )
450 return;
451
452 for( int i = static_cast<int>( m_pageWindows.size() ) - 1; i > aIdx; --i )
453 CloseTab( i );
454}
455
456
458{
459 for( int i = static_cast<int>( m_pageWindows.size() ) - 1; i >= 0; --i )
460 CloseTab( i );
461}
462
463
464void EDITOR_TABS_PANEL::MarkModified( int aIdx, bool aModified )
465{
466 if( aIdx < 0 || aIdx >= static_cast<int>( m_model.Entries().size() ) )
467 return;
468
469 // Clearing the preview flag stops the next library-open from replacing this edited document.
470 m_model.MarkModified( m_model.Entries()[aIdx].key, aModified );
472}
473
474
476{
477 if( aIdx < 0 || aIdx >= static_cast<int>( m_model.Entries().size() ) )
478 return;
479
480 if( !m_model.Entries()[aIdx].preview )
481 return;
482
483 m_model.Promote( m_model.Entries()[aIdx].key );
485}
486
487
489{
490 if( aIdx < 0 || aIdx >= static_cast<int>( m_pageWindows.size() ) )
491 return;
492
493 // ChangeSelection moves the strip without firing a change event, so activation is driven once,
494 // here, rather than also arriving through onPageChanged.
495 m_tabs->ChangeSelection( static_cast<size_t>( aIdx ) );
496 m_tabs->Refresh();
497 activateTab( aIdx );
498}
499
500
502{
503 if( aIdx < 0 || aIdx >= static_cast<int>( m_model.Entries().size() ) )
504 return;
505
506 // Guard against re-entrancy from a programmatic SetActivePage so the host is notified once.
507 if( m_activating )
508 return;
509
510 m_activating = true;
511 touchMru( m_model.Entries()[aIdx].key );
512
513 if( onActivateTab )
514 onActivateTab( aIdx );
515
516 m_activating = false;
517}
518
519
520void EDITOR_TABS_PANEL::AdvanceTab( bool aForward )
521{
522 if( m_pageWindows.size() < 2 )
523 return;
524
525 // Cycle the MRU order so repeated Ctrl+Tab walks recently visited tabs first.
526 const int active = GetActiveTab();
527
528 if( active < 0 )
529 return;
530
531 const wxString activeKey = m_model.Entries()[active].key;
532 int pos = -1;
533
534 for( size_t i = 0; i < m_mru.size(); ++i )
535 {
536 if( m_mru[i] == activeKey )
537 {
538 pos = static_cast<int>( i );
539 break;
540 }
541 }
542
543 if( pos < 0 )
544 return;
545
546 const int count = static_cast<int>( m_mru.size() );
547 const int next = aForward ? ( pos + 1 ) % count : ( pos - 1 + count ) % count;
548 const int idx = m_model.FindIndex( m_mru[next] );
549
550 if( idx >= 0 )
551 SelectTab( idx );
552}
553
554
556{
557 return m_tabs->GetSelection();
558}
559
560
561int EDITOR_TABS_PANEL::FindTab( const wxString& aKey ) const
562{
563 return m_model.FindIndex( aKey );
564}
565
566
567void EDITOR_TABS_PANEL::RenameTab( const wxString& aOldKey, const wxString& aNewKey, const wxString& aNewLabel )
568{
569 const int idx = m_model.FindIndex( aOldKey );
570
571 if( idx < 0 )
572 return;
573
574 m_model.Rename( aOldKey, aNewKey );
575
576 // The notebook page index is aligned with the model index.
577 if( idx < static_cast<int>( m_pageWindows.size() ) )
578 m_tabs->SetPageText( idx, aNewLabel );
579
580 // Keep the Ctrl+Tab order pointing at the new key.
581 for( wxString& key : m_mru )
582 {
583 if( key == aOldKey )
584 key = aNewKey;
585 }
586}
587
588
590{
591 m_tabs->Refresh();
592}
593
594
595void EDITOR_TABS_PANEL::touchMru( const wxString& aKey )
596{
597 forgetMru( aKey );
598 m_mru.insert( m_mru.begin(), aKey );
599}
600
601
602void EDITOR_TABS_PANEL::forgetMru( const wxString& aKey )
603{
604 m_mru.erase( std::remove( m_mru.begin(), m_mru.end(), aKey ), m_mru.end() );
605}
606
607
608void EDITOR_TABS_PANEL::onPageChanged( wxAuiNotebookEvent& aEvent )
609{
610 // The notebook drives its own active page, so a user tab switch surfaces here as the activation
611 // path. Programmatic selection uses ChangeSelection and is fired explicitly, not through here.
612 const int idx = aEvent.GetSelection();
613
614 if( idx >= 0 && idx < static_cast<int>( m_model.Entries().size() ) )
615 activateTab( idx );
616
617 aEvent.Skip();
618}
619
620
621void EDITOR_TABS_PANEL::onPageClose( wxAuiNotebookEvent& aEvent )
622{
623 const int idx = aEvent.GetSelection();
624
625 // Veto the control's own removal and route through CloseTab, which owns the single close prompt.
626 aEvent.Veto();
627 CloseTab( idx );
628}
629
630
631void EDITOR_TABS_PANEL::onTabRightDown( wxAuiNotebookEvent& aEvent )
632{
633 m_contextMenuIdx = aEvent.GetSelection();
634
635 if( m_contextMenuIdx < 0 )
636 return;
637
638 wxMenu menu;
639 menu.Append( ID_TAB_CLOSE, _( "Close Tab" ) );
640 menu.Append( ID_TAB_CLOSE_OTHERS, _( "Close Other Tabs" ) );
641 menu.Append( ID_TAB_CLOSE_TO_RIGHT, _( "Close Tabs to the Right" ) );
642 menu.Append( ID_TAB_CLOSE_ALL, _( "Close All Tabs" ) );
643
644 menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &EDITOR_TABS_PANEL::onContextMenu, this );
645
646 PopupMenu( &menu );
647}
648
649
650void EDITOR_TABS_PANEL::onTabDClick( wxMouseEvent& aEvent )
651{
652 aEvent.Skip();
653
654 // The handler is bound on the tab strip control, so the click is in its coordinates and hit-tests
655 // against the same control.
656 wxAuiTabCtrl* tabCtrl = m_tabs->GetActiveTabCtrl();
657
658 if( !tabCtrl )
659 return;
660
661 wxWindow* page = nullptr;
662
663 if( tabCtrl->TabHitTest( aEvent.GetX(), aEvent.GetY(), &page ) )
664 PromoteTab( indexOfWindow( page ) );
665}
666
667
668void EDITOR_TABS_PANEL::onContextMenu( wxCommandEvent& aEvent )
669{
670 const int idx = m_contextMenuIdx;
671
672 if( idx < 0 )
673 return;
674
675 switch( aEvent.GetId() )
676 {
677 case ID_TAB_CLOSE: CloseTab( idx ); break;
678 case ID_TAB_CLOSE_OTHERS: CloseOthers( idx ); break;
679 case ID_TAB_CLOSE_TO_RIGHT: CloseToRight( idx ); break;
680 case ID_TAB_CLOSE_ALL: CloseAll(); break;
681 default: break;
682 }
683}
int PreviewIndex() const
Index of the current reusable preview tab, or -1 if none.
bool CanCloseWithoutPrompt(const wxString &aKey) const
True when the document has no unsaved edits and can be closed silently.
void Rename(const wxString &aOldKey, const wxString &aNewKey)
Re-key the entry for aOldKey to aNewKey, keeping its position and state.
void Promote(const wxString &aKey)
Clear the preview flag so the tab becomes permanent and is no longer reused.
void MarkModified(const wxString &aKey, bool aModified)
Update the modified flag.
int FindIndex(const wxString &aKey) const
int OpenDocument(const wxString &aKey, bool aAsPreview)
Return the index to display the document at, reusing a preview slot or appending a new tab.
std::vector< ENTRY > m_entries
void CloseDocument(const wxString &aKey)
Drop the document with aKey, freeing the preview slot if it held it.
void closeTabInternal(int aIdx)
Close the tab at aIdx, prompting the host exactly once.
std::function< TAB_VISUAL_STATE(int)> onQueryVisualState
Host reports the visual state (modified/preview) for the tab at the given index.
void bindTabDClick()
(Re)bind the double-click handler to the notebook's current tab-strip control.
TAB_VISUAL_STATE visualStateForIndex(int aIdx) const
wxAuiNotebook * m_tabs
int indexOfWindow(wxWindow *aWindow) const
Map a notebook page window pointer to its current tab index, or -1.
void onPageChanged(wxAuiNotebookEvent &aEvent)
void CloseToRight(int aIdx)
void onContextMenu(wxCommandEvent &aEvent)
bool m_suppressActivateOnClose
When set, closeTabInternal selects the fallback page without firing onActivateTab.
void AdvanceTab(bool aForward)
std::vector< wxString > m_mru
Most-recently-used key order for Ctrl+Tab cycling; front is most recent.
void RenameTab(const wxString &aOldKey, const wxString &aNewKey, const wxString &aNewLabel)
Re-key the tab aOldKey to aNewKey and relabel it to aNewLabel.
void touchMru(const wxString &aKey)
Bump aKey to the front of the MRU order.
void onTabDClick(wxMouseEvent &aEvent)
void PromoteTab(int aIdx)
Convert a preview tab into a permanent one, dropping its italic styling.
std::function< void(int)> onActivateTab
Host swaps the active document context to the tab at the given index.
std::function< bool(int)> onCloseTabRequested
Host prompts as needed; return false to veto the close.
void MarkModified(int aIdx, bool aModified)
Mark the tab modified.
void onTabRightDown(wxAuiNotebookEvent &aEvent)
void updateTabStripHeight()
Clamp the notebook to its tab-strip height and re-Layout so its (unused) page area collapses and the ...
int AddTab(const wxString &aKey, const wxString &aLabel, bool aAsPreview)
void forgetMru(const wxString &aKey)
Remove aKey from the MRU order.
EDITOR_TABS_PANEL(wxWindow *aParent, EDA_DRAW_PANEL_GAL *aSharedCanvas)
EDA_DRAW_PANEL_GAL * m_sharedCanvas
void CloseOthers(int aKeepIdx)
void onPageClose(wxAuiNotebookEvent &aEvent)
std::vector< wxWindow * > m_pageWindows
Hidden per-tab key windows; index-aligned with the model entries.
void activateTab(int aIdx)
Activate the tab at aIdx without re-entering the change handler.
EDITOR_TABS_MODEL m_model
void ReleaseSharedCanvas()
Hand the host-owned canvas back to its original parent.
wxWindow * m_originalCanvasParent
The canvas's parent before it was borrowed, reparented back on destruction so it is not freed as a ch...
bool m_activating
Guards activateTab() against re-entrancy from programmatic SetActivePage().
int FindTab(const wxString &aKey) const
A wxAuiTabArt that renders editor tabs with preview/modified decorations.
#define _(s)
@ ID_TAB_CLOSE_TO_RIGHT
@ ID_TAB_CLOSE_OTHERS
@ ID_TAB_CLOSE_ALL
@ ID_TAB_CLOSE
TAB_VISUAL_STATE ResolveTabVisualState(bool aPreview, bool aModified)
Resolve a tab's decorations from its document state flags.
CITER next(CITER it)
Definition ptree.cpp:120
Visual decorations derived from document state: preview is italic, modified is bold with a leading as...