KiCad PCB EDA Suite
Loading...
Searching...
No Matches
paged_dialog.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 (C) 2019-2023 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <confirm.h>
22#include <widgets/wx_infobar.h>
23#include <widgets/wx_panel.h>
25#include <widgets/wx_treebook.h>
26
27#include <wx/button.h>
28#include <wx/grid.h>
29#include <wx/sizer.h>
30#include <wx/treebook.h>
31#include <wx/treectrl.h>
32#include <wx/listctrl.h>
33#include <wx/stc/stc.h>
34
35#include <paths.h>
36
37#include <launch_ext.h>
38
39#include <algorithm>
40
41// Maps from dialogTitle <-> pageTitle for keeping track of last-selected pages.
42// This is not a simple page index because some dialogs have dynamic page sets.
43std::map<wxString, wxString> g_lastPage;
44std::map<wxString, wxString> g_lastParentPage;
45
46
47PAGED_DIALOG::PAGED_DIALOG( wxWindow* aParent, const wxString& aTitle, bool aShowReset, bool aShowOpenFolder,
48 const wxString& aAuxiliaryAction, const wxSize& aInitialSize ) :
49 DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, aInitialSize,
50 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
51 m_auxiliaryButton( nullptr ),
52 m_resetButton( nullptr ),
53 m_openPrefsDirButton( nullptr ),
54 m_title( aTitle )
55{
56 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
57 SetSizer( mainSizer );
58
59 m_infoBar = new WX_INFOBAR( this );
60 mainSizer->Add( m_infoBar, 0, wxEXPAND, 0 );
61
62 WX_PANEL* treebookPanel = new WX_PANEL( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
63 wxBORDER_NONE | wxTAB_TRAVERSAL );
64 treebookPanel->SetBorders( false, false, false, true );
65 wxBoxSizer* treebookSizer = new wxBoxSizer( wxVERTICAL );
66 treebookPanel->SetSizer( treebookSizer );
67
68 m_treebook = new WX_TREEBOOK( treebookPanel, wxID_ANY );
69 m_treebook->SetFont( KIUI::GetControlFont( this ) );
70 m_treebook->SetFitToCurrentPage( true );
71
72 long treeCtrlFlags = m_treebook->GetTreeCtrl()->GetWindowStyleFlag();
73 treeCtrlFlags = ( treeCtrlFlags & ~wxBORDER_MASK ) | wxBORDER_NONE;
74 m_treebook->GetTreeCtrl()->SetWindowStyleFlag( treeCtrlFlags );
75
76 treebookSizer->Add( m_treebook, 1, wxEXPAND|wxBOTTOM, 2 );
77 mainSizer->Add( treebookPanel, 1, wxEXPAND, 0 );
78
79 m_buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
80
81 if( aShowReset )
82 {
83 m_resetButton = new wxButton( this, wxID_ANY, _( "Reset to Defaults" ) );
84 m_buttonsSizer->Add( m_resetButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
85 }
86
87 if( aShowOpenFolder )
88 {
89#ifdef __WXMAC__
90 m_openPrefsDirButton = new wxButton( this, wxID_ANY, _( "Reveal Preferences in Finder" ) );
91#else
92 m_openPrefsDirButton = new wxButton( this, wxID_ANY, _( "Open Preferences Directory" ) );
93#endif
94 m_buttonsSizer->Add( m_openPrefsDirButton, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, 5 );
95 }
96
97
98 if( !aAuxiliaryAction.IsEmpty() )
99 {
100 m_auxiliaryButton = new wxButton( this, wxID_ANY, aAuxiliaryAction );
101 m_buttonsSizer->Add( m_auxiliaryButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
102 }
103
104 m_buttonsSizer->AddStretchSpacer();
105
106 wxStdDialogButtonSizer* sdbSizer = new wxStdDialogButtonSizer();
107 wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
108 sdbSizer->AddButton( sdbSizerOK );
109 wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
110 sdbSizer->AddButton( sdbSizerCancel );
111 sdbSizer->Realize();
112
113 m_buttonsSizer->Add( sdbSizer, 1, 0, 5 );
114 mainSizer->Add( m_buttonsSizer, 0, wxALL|wxEXPAND, 5 );
115
117
118 // We normally save the dialog size and position based on its class-name. This class
119 // substitutes the title so that each distinctly-titled dialog can have its own saved
120 // size and position.
121 m_hash_key = aTitle;
122
124 {
125 m_auxiliaryButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onAuxiliaryAction,
126 this );
127 }
128
129 if( m_resetButton )
130 {
131 m_resetButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onResetButton, this );
132 }
133
135 {
136 m_openPrefsDirButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onOpenPreferencesButton, this );
137 }
138
139 m_treebook->Bind( wxEVT_CHAR_HOOK, &PAGED_DIALOG::onCharHook, this );
140 m_treebook->Bind( wxEVT_TREEBOOK_PAGE_CHANGED, &PAGED_DIALOG::onPageChanged, this );
141 m_treebook->Bind( wxEVT_TREEBOOK_PAGE_CHANGING, &PAGED_DIALOG::onPageChanging, this );
142}
143
144
146{
147 for( size_t i = 1; i < m_treebook->GetPageCount(); ++i )
148 m_macHack.push_back( true );
149
150 // For some reason adding page labels to the treeCtrl doesn't invalidate its bestSize
151 // cache so we have to do it by hand
152 m_treebook->GetTreeCtrl()->InvalidateBestSize();
153
154 for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
155 m_treebook->GetPage( i )->Layout();
156
157 m_treebook->Layout();
158 m_treebook->Fit();
159
161}
162
163
164void PAGED_DIALOG::SetInitialPage( const wxString& aPage, const wxString& aParentPage )
165{
166 g_lastPage[ m_title ] = aPage;
167 g_lastParentPage[ m_title ] = aParentPage;
168}
169
170
172{
173 // Store the current parentPageTitle/pageTitle hierarchy so we can re-select it
174 // next time.
175 wxString lastPage = wxEmptyString;
176 wxString lastParentPage = wxEmptyString;
177
178 int selected = m_treebook->GetSelection();
179
180 if( selected != wxNOT_FOUND )
181 {
182 lastPage = m_treebook->GetPageText( (unsigned) selected );
183
184 int parent = m_treebook->GetPageParent( (unsigned) selected );
185
186 if( parent != wxNOT_FOUND )
187 lastParentPage = m_treebook->GetPageText( (unsigned) parent );
188 }
189
190 g_lastPage[ m_title ] = lastPage;
191 g_lastParentPage[ m_title ] = lastParentPage;
192
194 {
195 m_auxiliaryButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onAuxiliaryAction,
196 this );
197 }
198
199 if( m_resetButton )
200 {
201 m_resetButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onResetButton, this );
202 }
203
205 {
206 m_openPrefsDirButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onOpenPreferencesButton, this );
207 }
208
209 m_treebook->Unbind( wxEVT_CHAR_HOOK, &PAGED_DIALOG::onCharHook, this );
210 m_treebook->Unbind( wxEVT_TREEBOOK_PAGE_CHANGED, &PAGED_DIALOG::onPageChanged, this );
211 m_treebook->Unbind( wxEVT_TREEBOOK_PAGE_CHANGING, &PAGED_DIALOG::onPageChanging, this );
212}
213
214
216{
218
219 // Call TransferDataToWindow() only once:
220 // this is enough on wxWidgets 3.1
221 if( !DIALOG_SHIM::TransferDataToWindow() )
222 return false;
223
224 // Search for a page matching the lastParentPageTitle/lastPageTitle hierarchy
225 wxString lastPage = g_lastPage[ m_title ];
226 wxString lastParentPage = g_lastParentPage[ m_title ];
227 int lastPageIndex = wxNOT_FOUND;
228
229 for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
230 {
231 if( m_treebook->GetPageText( i ) == lastPage )
232 {
233 if( lastParentPage.IsEmpty() )
234 {
235 lastPageIndex = i;
236 break;
237 }
238
239 if( m_treebook->GetPageParent( i ) >= 0
240 && m_treebook->GetPageText( (unsigned) m_treebook->GetPageParent( i ) ) == lastParentPage )
241 {
242 lastPageIndex = i;
243 break;
244 }
245 }
246 }
247
248 lastPageIndex = std::max( 0, lastPageIndex );
249 m_treebook->SetSelection( lastPageIndex );
250 UpdateResetButton( lastPageIndex );
251
252 return true;
253}
254
255
257{
258 bool ret = true;
259
260 // Call TransferDataFromWindow() only once:
261 // this is enough on wxWidgets 3.1
262 if( !DIALOG_SHIM::TransferDataFromWindow() )
263 ret = false;
264
265 return ret;
266}
267
268
270{
271 while( aParent )
272 {
273 if( PAGED_DIALOG* parentDialog = dynamic_cast<PAGED_DIALOG*>( aParent ) )
274 return parentDialog;
275
276 aParent = aParent->GetParent();
277 }
278
279 return nullptr;
280}
281
282
283void PAGED_DIALOG::SetError( const wxString& aMessage, const wxString& aPageName, int aCtrlId,
284 int aRow, int aCol )
285{
286 SetError( aMessage, FindWindow( aPageName ), FindWindow( aCtrlId ), aRow, aCol );
287}
288
289
290void PAGED_DIALOG::SetError( const wxString& aMessage, wxWindow* aPage, wxWindow* aCtrl,
291 int aRow, int aCol )
292{
293 m_infoBar->ShowMessageFor( aMessage, 10000, wxICON_WARNING );
294
295 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
296 {
297 textCtrl->SetSelection( -1, -1 );
298 textCtrl->SetFocus();
299 return;
300 }
301
302 if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
303 {
304 if( aRow > 0 )
305 {
306 int pos = scintilla->PositionFromLine( aRow - 1 ) + ( aCol - 1 );
307 scintilla->GotoPos( pos );
308 }
309
310 scintilla->SetFocus();
311 return;
312 }
313
314 if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
315 {
316 grid->SetFocus();
317 grid->MakeCellVisible( aRow, aCol );
318 grid->SetGridCursor( aRow, aCol );
319
320 grid->EnableCellEditControl( true );
321 grid->ShowCellEditControl();
322 return;
323 }
324}
325
326
328{
329 wxWindow* panel = m_treebook->ResolvePage( aPage );
330
331 // Enable the reset button only if the page is re-settable
332 if( m_resetButton )
333 {
334 if( panel && ( panel->GetWindowStyle() & wxRESETTABLE ) )
335 {
336 m_resetButton->SetLabel( wxString::Format( _( "Reset %s to Defaults" ),
337 m_treebook->GetPageText( aPage ) ) );
338 m_resetButton->SetToolTip( panel->GetHelpTextAtPoint( wxPoint( -INT_MAX, INT_MAX ),
339 wxHelpEvent::Origin_Unknown ) );
340 m_resetButton->Enable( true );
341 }
342 else
343 {
344 m_resetButton->SetLabel( _( "Reset to Defaults" ) );
345 m_resetButton->SetToolTip( wxString() );
346 m_resetButton->Enable( false );
347 }
348
349 m_resetButton->GetParent()->Layout();
350 }
351}
352
353
354void PAGED_DIALOG::onCharHook( wxKeyEvent& aEvent )
355{
356 if( dynamic_cast<wxTextEntry*>( aEvent.GetEventObject() )
357 || dynamic_cast<wxStyledTextCtrl*>( aEvent.GetEventObject() )
358 || dynamic_cast<wxListView*>( aEvent.GetEventObject() )
359 || dynamic_cast<wxGrid*>( FindFocus() ) )
360 {
361 aEvent.Skip();
362 return;
363 }
364
365 if( aEvent.GetKeyCode() == WXK_UP )
366 {
367 int page = m_treebook->GetSelection();
368
369 if( page >= 1 )
370 {
371 if( m_treebook->GetPage( page - 1 )->GetChildren().IsEmpty() )
372 m_treebook->SetSelection( std::max( page - 2, 0 ) );
373 else
374 m_treebook->SetSelection( page - 1 );
375 }
376
377 m_treebook->GetTreeCtrl()->SetFocus(); // Don't allow preview canvas to steal gridFocus
378 }
379 else if( aEvent.GetKeyCode() == WXK_DOWN )
380 {
381 int page = m_treebook->GetSelection();
382
383 m_treebook->SetSelection( std::min<int>( page + 1, m_treebook->GetPageCount() - 1 ) );
384
385 m_treebook->GetTreeCtrl()->SetFocus(); // Don't allow preview canvas to steal gridFocus
386 }
387 else
388 {
389 aEvent.Skip();
390 }
391}
392
393
394void PAGED_DIALOG::onPageChanged( wxBookCtrlEvent& event )
395{
396 size_t page = event.GetSelection();
397
398 // Use the first sub-page when a tree level node is selected.
399 if( m_treebook->GetCurrentPage()->GetChildren().IsEmpty()
400 && page + 1 < m_treebook->GetPageCount() )
401 {
402 m_treebook->ChangeSelection( ++page );
403 }
404
405 UpdateResetButton( page );
406
407 // Make sure the dialog size is enough to fit the page content.
408 m_treebook->InvalidateBestSize();
409
410 SetMinSize( wxDefaultSize );
411 wxSize minSize = GetBestSize();
412 minSize.IncTo( FromDIP( wxSize( 600, 500 ) ) );
413 minSize.DecTo( FromDIP( wxSize( 1500, 900 ) ) ); // Failsafe
414 SetMinSize( minSize );
415
416 wxSize currentSize = GetSize();
417 wxSize newSize = currentSize;
418 newSize.IncTo( minSize );
419
420 if( newSize != currentSize )
421 SetSize( newSize );
422
423#ifdef __WXMAC__
424 // Work around an OSX wxWidgets issue where the wxGrid children don't get placed correctly
425 // until the first resize event
426 if( page < m_macHack.size() && m_macHack[ page ] )
427 {
428 wxSize pageSize = m_treebook->GetPage( page )->GetSize();
429 pageSize.x += 1;
430 pageSize.y += 2;
431
432 m_treebook->GetPage( page )->SetSize( pageSize );
433 m_macHack[ page ] = false;
434 }
435#else
436 wxSizeEvent evt( wxDefaultSize );
437 wxQueueEvent( m_treebook, evt.Clone() );
438#endif
439}
440
441
442void PAGED_DIALOG::onPageChanging( wxBookCtrlEvent& aEvent )
443{
444 int currentPage = aEvent.GetOldSelection();
445
446 if( currentPage == wxNOT_FOUND )
447 return;
448
449 wxWindow* page = m_treebook->GetPage( currentPage );
450
451 wxCHECK( page, /* void */ );
452
453 // If there is a validation error on the current page, don't allow the page change.
454 if( !page->Validate() || !page->TransferDataFromWindow() )
455 {
456 aEvent.Veto();
457 return;
458 }
459}
460
461
462void PAGED_DIALOG::onResetButton( wxCommandEvent& aEvent )
463{
464 int sel = m_treebook->GetSelection();
465
466 if( sel == wxNOT_FOUND )
467 return;
468
469 // NB: dynamic_cast doesn't work over Kiway
470 wxWindow* panel = m_treebook->ResolvePage( sel );
471
472 if( panel )
473 {
474 wxCommandEvent resetCommand( wxEVT_COMMAND_BUTTON_CLICKED, ID_RESET_PANEL );
475 panel->ProcessWindowEvent( resetCommand );
476 }
477}
478
479void PAGED_DIALOG::onOpenPreferencesButton( wxCommandEvent& aEvent )
480{
481 wxString dir( PATHS::GetUserSettingsPath() );
482 LaunchExternal( dir );
483}
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition: dialog_shim.h:84
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
Definition: dialog_shim.h:206
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
WX_INFOBAR * m_infoBar
Definition: paged_dialog.h:72
bool TransferDataToWindow() override
~PAGED_DIALOG() override
PAGED_DIALOG(wxWindow *aParent, const wxString &aTitle, bool aShowReset, bool aShowOpenFolder, const wxString &aAuxiliaryAction=wxEmptyString, const wxSize &aInitialSize=wxDefaultSize)
wxButton * m_auxiliaryButton
Definition: paged_dialog.h:69
std::vector< bool > m_macHack
Definition: paged_dialog.h:79
wxButton * m_openPrefsDirButton
Definition: paged_dialog.h:71
bool TransferDataFromWindow() override
void finishInitialization()
void UpdateResetButton(int aPage)
void SetInitialPage(const wxString &aPage, const wxString &aParentPage=wxEmptyString)
virtual void onPageChanging(wxBookCtrlEvent &aEvent)
virtual void onCharHook(wxKeyEvent &aEvent)
virtual void onAuxiliaryAction(wxCommandEvent &aEvent)
Definition: paged_dialog.h:61
wxString m_title
Definition: paged_dialog.h:75
virtual void onOpenPreferencesButton(wxCommandEvent &aEvent)
static PAGED_DIALOG * GetDialog(wxWindow *aWindow)
virtual void onResetButton(wxCommandEvent &aEvent)
WX_TREEBOOK * m_treebook
Definition: paged_dialog.h:68
virtual void onPageChanged(wxBookCtrlEvent &aEvent)
wxBoxSizer * m_buttonsSizer
Definition: paged_dialog.h:77
void SetError(const wxString &aMessage, const wxString &aPageName, int aCtrlId, int aRow=-1, int aCol=-1)
wxButton * m_resetButton
Definition: paged_dialog.h:70
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition: paths.cpp:510
A modified version of the wxInfoBar class that allows us to:
Definition: wx_infobar.h:75
void ShowMessageFor(const wxString &aMessage, int aTime, int aFlags=wxICON_INFORMATION, MESSAGE_TYPE aType=WX_INFOBAR::MESSAGE_TYPE::GENERIC)
Show the infobar with the provided message and icon for a specific period of time.
Definition: wx_infobar.cpp:140
void SetBorders(bool aLeft, bool aRight, bool aTop, bool aBottom)
Definition: wx_panel.h:39
wxWindow * ResolvePage(size_t aPage)
This file is part of the common library.
const int minSize
Push and Shove router track width and via size dialog.
#define _(s)
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
Definition: launch_ext.cpp:25
KICOMMON_API wxFont GetControlFont(wxWindow *aWindow)
Definition: ui_common.cpp:157
std::map< wxString, wxString > g_lastPage
std::map< wxString, wxString > g_lastParentPage
#define wxRESETTABLE
#define ID_RESET_PANEL