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 The 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#include <widgets/ui_common.h>
27
28#include <wx/button.h>
29#include <wx/display.h>
30#include <wx/grid.h>
31#include <wx/sizer.h>
32#include <wx/treebook.h>
33#include <wx/treectrl.h>
34#include <wx/listctrl.h>
35#include <wx/stc/stc.h>
36
37#include <paths.h>
38
39#include <launch_ext.h>
40
41#include <algorithm>
42
43// Maps from dialogTitle <-> pageTitle for keeping track of last-selected pages.
44// This is not a simple page index because some dialogs have dynamic page sets.
45std::map<wxString, wxString> g_lastPage;
46std::map<wxString, wxString> g_lastParentPage;
47
48
49PAGED_DIALOG::PAGED_DIALOG( wxWindow* aParent, const wxString& aTitle, bool aShowReset,
50 bool aShowOpenFolder, const wxString& aAuxiliaryAction,
51 const wxSize& aInitialSize ) :
52 DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, aInitialSize,
53 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
54 m_auxiliaryButton( nullptr ),
55 m_resetButton( nullptr ),
56 m_openPrefsDirButton( nullptr ),
57 m_title( aTitle )
58{
59 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
60 SetSizer( mainSizer );
61
62 m_infoBar = new WX_INFOBAR( this );
63 mainSizer->Add( m_infoBar, 0, wxEXPAND, 0 );
64
65 WX_PANEL* treebookPanel = new WX_PANEL( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
66 wxBORDER_NONE | wxTAB_TRAVERSAL );
67 treebookPanel->SetBorders( false, false, false, true );
68 wxBoxSizer* treebookSizer = new wxBoxSizer( wxVERTICAL );
69 treebookPanel->SetSizer( treebookSizer );
70
71 m_treebook = new WX_TREEBOOK( treebookPanel, wxID_ANY );
72 m_treebook->SetFont( KIUI::GetControlFont( this ) );
73 m_treebook->SetFitToCurrentPage( true );
74
75 long treeCtrlFlags = m_treebook->GetTreeCtrl()->GetWindowStyleFlag();
76 treeCtrlFlags = ( treeCtrlFlags & ~wxBORDER_MASK ) | wxBORDER_NONE;
77 m_treebook->GetTreeCtrl()->SetWindowStyleFlag( treeCtrlFlags );
78
79 treebookSizer->Add( m_treebook, 1, wxEXPAND|wxBOTTOM, 2 );
80 mainSizer->Add( treebookPanel, 1, wxEXPAND, 0 );
81
82 m_buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
83
84 if( aShowReset )
85 {
86 m_resetButton = new wxButton( this, wxID_ANY, _( "Reset to Defaults" ) );
87 m_buttonsSizer->Add( m_resetButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
88 }
89
90 if( aShowOpenFolder )
91 {
92#ifdef __WXMAC__
93 m_openPrefsDirButton = new wxButton( this, wxID_ANY, _( "Reveal Preferences in Finder" ) );
94#else
95 m_openPrefsDirButton = new wxButton( this, wxID_ANY, _( "Open Preferences Directory" ) );
96#endif
98 wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, 5 );
99 }
100
101 if( !aAuxiliaryAction.IsEmpty() )
102 {
103 m_auxiliaryButton = new wxButton( this, wxID_ANY, aAuxiliaryAction );
104 m_buttonsSizer->Add( m_auxiliaryButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
105 }
106
107 m_buttonsSizer->AddStretchSpacer();
108
109 wxStdDialogButtonSizer* sdbSizer = new wxStdDialogButtonSizer();
110 wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
111 sdbSizer->AddButton( sdbSizerOK );
112 wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
113 sdbSizer->AddButton( sdbSizerCancel );
114 sdbSizer->Realize();
115
116 m_buttonsSizer->Add( sdbSizer, 1, 0, 5 );
117 mainSizer->Add( m_buttonsSizer, 0, wxALL|wxEXPAND, 5 );
118
120
121 // We normally save the dialog size and position based on its class-name. This class
122 // substitutes the title so that each distinctly-titled dialog can have its own saved
123 // size and position.
124 m_hash_key = aTitle;
125
127 m_auxiliaryButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onAuxiliaryAction, this );
128
129 if( m_resetButton )
130 m_resetButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onResetButton, this );
131
133 m_openPrefsDirButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onOpenPrefsDir, this );
134
135 m_treebook->Bind( wxEVT_CHAR_HOOK, &PAGED_DIALOG::onCharHook, this );
136 m_treebook->Bind( wxEVT_TREEBOOK_PAGE_CHANGED, &PAGED_DIALOG::onPageChanged, this );
137 m_treebook->Bind( wxEVT_TREEBOOK_PAGE_CHANGING, &PAGED_DIALOG::onPageChanging, this );
138}
139
140
142{
143 for( size_t i = 1; i < m_treebook->GetPageCount(); ++i )
144 m_macHack.push_back( true );
145
146 // For some reason adding page labels to the treeCtrl doesn't invalidate its bestSize
147 // cache so we have to do it by hand
148 m_treebook->GetTreeCtrl()->InvalidateBestSize();
149
150 for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
151 m_treebook->GetPage( i )->Layout();
152
153 m_treebook->Layout();
154 m_treebook->Fit();
155
156 // Add a bit of width to the treeCtrl for scrollbars
157 wxSize ctrlSize = m_treebook->GetTreeCtrl()->GetSize();
158 ctrlSize.x += 20;
159 m_treebook->GetTreeCtrl()->SetMinSize( ctrlSize );
160 m_treebook->Layout();
161
162 // Note: do NOT call finishDialogSettings() here. At this point the lazy pages have not
163 // yet been resolved, so the sizer minimum is computed from empty placeholder pages.
164 // On GTK+X11, finishDialogSettings() stores a "resize to sizer minimum" operation that
165 // is deferred until the window is realized by the window manager. If that deferred
166 // operation fires after onPageChanged() has already grown the dialog to fit the real
167 // page content, it shrinks the dialog back to the placeholder minimum, cutting off the
168 // bottom of the page. The dialog minimum and size are correctly established by
169 // onPageChanged() once the selected page is resolved.
170}
171
172
173void PAGED_DIALOG::SetInitialPage( const wxString& aPage, const wxString& aParentPage )
174{
175 g_lastPage[ m_title ] = aPage;
176 g_lastParentPage[ m_title ] = aParentPage;
177}
178
179
181{
182 // Store the current parentPageTitle/pageTitle hierarchy so we can re-select it
183 // next time.
184 wxString lastPage = wxEmptyString;
185 wxString lastParentPage = wxEmptyString;
186
187 int selected = m_treebook->GetSelection();
188
189 if( selected != wxNOT_FOUND )
190 {
191 lastPage = m_treebook->GetPageText( (unsigned) selected );
192
193 int parent = m_treebook->GetPageParent( (unsigned) selected );
194
195 if( parent != wxNOT_FOUND )
196 lastParentPage = m_treebook->GetPageText( (unsigned) parent );
197 }
198
199 g_lastPage[ m_title ] = lastPage;
200 g_lastParentPage[ m_title ] = lastParentPage;
201
203 m_auxiliaryButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onAuxiliaryAction, this );
204
205 if( m_resetButton )
206 m_resetButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onResetButton, this );
207
209 m_openPrefsDirButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onOpenPrefsDir, this );
210
211 m_treebook->Unbind( wxEVT_CHAR_HOOK, &PAGED_DIALOG::onCharHook, this );
212 m_treebook->Unbind( wxEVT_TREEBOOK_PAGE_CHANGED, &PAGED_DIALOG::onPageChanged, this );
213 m_treebook->Unbind( wxEVT_TREEBOOK_PAGE_CHANGING, &PAGED_DIALOG::onPageChanging, this );
214}
215
216
218{
220
221 // Call TransferDataToWindow() only once:
222 // this is enough on wxWidgets 3.1
223 if( !DIALOG_SHIM::TransferDataToWindow() )
224 return false;
225
226 // Search for a page matching the lastParentPageTitle/lastPageTitle hierarchy
227 wxString lastPage = g_lastPage[ m_title ];
228 wxString lastParentPage = g_lastParentPage[ m_title ];
229 int lastPageIndex = wxNOT_FOUND;
230
231 for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
232 {
233 if( m_treebook->GetPageText( i ) == lastPage )
234 {
235 if( lastParentPage.IsEmpty() )
236 {
237 lastPageIndex = i;
238 break;
239 }
240
241 if( m_treebook->GetPageParent( i ) >= 0
242 && m_treebook->GetPageText( (unsigned) m_treebook->GetPageParent( i ) ) == lastParentPage )
243 {
244 lastPageIndex = i;
245 break;
246 }
247 }
248 }
249
250 lastPageIndex = std::max( 0, lastPageIndex );
251 m_treebook->SetSelection( lastPageIndex );
252 UpdateResetButton( lastPageIndex );
253
254 return true;
255}
256
257
259{
260 bool ret = true;
261
262 // Call TransferDataFromWindow() only once:
263 // this is enough on wxWidgets 3.1
264 if( !DIALOG_SHIM::TransferDataFromWindow() )
265 ret = false;
266
267 return ret;
268}
269
270
272{
273 while( aParent )
274 {
275 if( PAGED_DIALOG* parentDialog = dynamic_cast<PAGED_DIALOG*>( aParent ) )
276 return parentDialog;
277
278 aParent = aParent->GetParent();
279 }
280
281 return nullptr;
282}
283
284
285void PAGED_DIALOG::SetError( const wxString& aMessage, const wxString& aPageName, int aCtrlId,
286 int aRow, int aCol )
287{
288 SetError( aMessage, FindWindow( aPageName ), FindWindow( aCtrlId ), aRow, aCol );
289}
290
291
292void PAGED_DIALOG::SetError( const wxString& aMessage, wxWindow* aPage, wxWindow* aCtrl,
293 int aRow, int aCol )
294{
295 m_infoBar->ShowMessageFor( aMessage, 10000, wxICON_WARNING );
296
297 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
298 {
299 textCtrl->SetSelection( -1, -1 );
300 textCtrl->SetFocus();
301 return;
302 }
303
304 if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
305 {
306 if( aRow > 0 )
307 {
308 int pos = scintilla->PositionFromLine( aRow - 1 ) + ( aCol - 1 );
309 scintilla->GotoPos( pos );
310 }
311
312 scintilla->SetFocus();
313 return;
314 }
315
316 if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
317 {
318 grid->SetFocus();
319 grid->MakeCellVisible( aRow, aCol );
320 grid->SetGridCursor( aRow, aCol );
321
322 grid->EnableCellEditControl( true );
323 grid->ShowCellEditControl();
324 return;
325 }
326}
327
328
330{
331 wxWindow* panel = m_treebook->ResolvePage( aPage );
332
333 // Enable the reset button only if the page is re-settable
334 if( m_resetButton )
335 {
336 if( panel && ( panel->GetWindowStyle() & wxRESETTABLE ) )
337 {
338 wxString name = m_treebook->GetPageText( aPage );
339 name.Replace( wxT( "&" ), wxT( "&&" ) );
340
341 m_resetButton->SetLabel( wxString::Format( _( "Reset %s to Defaults" ), name ) );
342 m_resetButton->SetToolTip( panel->GetHelpTextAtPoint( wxPoint( -INT_MAX, INT_MAX ),
343 wxHelpEvent::Origin_Unknown ) );
344 m_resetButton->Enable( true );
345 }
346 else
347 {
348 m_resetButton->SetLabel( _( "Reset to Defaults" ) );
349 m_resetButton->SetToolTip( wxString() );
350 m_resetButton->Enable( false );
351 }
352
353 m_resetButton->GetParent()->Layout();
354 }
355}
356
357
358void PAGED_DIALOG::onCharHook( wxKeyEvent& aEvent )
359{
360 if( dynamic_cast<wxTextEntry*>( aEvent.GetEventObject() )
361 || dynamic_cast<wxStyledTextCtrl*>( aEvent.GetEventObject() )
362 || dynamic_cast<wxListView*>( aEvent.GetEventObject() )
363 || dynamic_cast<wxTreeCtrl*>( aEvent.GetEventObject() )
364 || dynamic_cast<wxGrid*>( FindFocus() ) )
365 {
366 aEvent.Skip();
367 return;
368 }
369
370 if( aEvent.GetKeyCode() == WXK_UP )
371 {
372 int page = m_treebook->GetSelection();
373
374 if( page >= 1 )
375 {
376 wxWindow* prevPage = m_treebook->GetPage( page - 1 );
377
378 if( !prevPage || prevPage->GetChildren().IsEmpty() )
379 m_treebook->SetSelection( std::max( page - 2, 0 ) );
380 else
381 m_treebook->SetSelection( page - 1 );
382 }
383
384 m_treebook->GetTreeCtrl()->SetFocus(); // Don't allow preview canvas to steal gridFocus
385 }
386 else if( aEvent.GetKeyCode() == WXK_DOWN )
387 {
388 int page = m_treebook->GetSelection();
389
390 m_treebook->SetSelection( std::min<int>( page + 1, m_treebook->GetPageCount() - 1 ) );
391
392 m_treebook->GetTreeCtrl()->SetFocus(); // Don't allow preview canvas to steal gridFocus
393 }
394 else
395 {
396 aEvent.Skip();
397 }
398}
399
400
401void PAGED_DIALOG::onPageChanged( wxBookCtrlEvent& event )
402{
403 int sel = event.GetSelection();
404
405 if( sel == wxNOT_FOUND )
406 return;
407
408 size_t page = static_cast<size_t>( sel );
409
410 // Use the first sub-page when a tree level node is selected, but only if the node is
411 // expanded. When the user collapses a branch, wxTreebook auto-selects the parent node.
412 // Redirecting to the first child here would undo the collapse.
413 wxWindow* currentPage = m_treebook->GetCurrentPage();
414
415 if( currentPage && currentPage->GetChildren().IsEmpty()
416 && page + 1 < m_treebook->GetPageCount()
417 && m_treebook->IsNodeExpanded( page ) )
418 {
419 m_treebook->ChangeSelection( ++page );
420 }
421
422 UpdateResetButton( page );
423
424 // Make sure the dialog size is enough to fit the page content.
425 m_treebook->InvalidateBestSize();
426
427 SetMinSize( wxDefaultSize );
428 wxSize minSize = GetBestSize();
429 minSize.IncTo( FromDIP( wxSize( 600, 500 ) ) );
430 minSize.DecTo( FromDIP( wxSize( 1500, 900 ) ) ); // Failsafe
431
432 // Clamp to the current display so the resize border stays reachable (#24408).
433 int displayIdx = wxDisplay::GetFromWindow( this );
434
435 if( displayIdx == wxNOT_FOUND )
436 displayIdx = 0;
437
438 wxRect displayArea = wxDisplay( (unsigned int) displayIdx ).GetClientArea();
439 wxSize maxSize( std::max( 0, displayArea.width - FromDIP( 40 ) ),
440 std::max( 0, displayArea.height - FromDIP( 80 ) ) );
441 minSize.DecTo( maxSize );
442
443 SetMinSize( minSize );
444
445 wxSize currentSize = GetSize();
446 wxSize newSize = currentSize;
447 newSize.IncTo( minSize );
448
449 if( newSize != currentSize )
450 SetSize( newSize );
451
452#ifdef __WXMAC__
453 // Work around an OSX wxWidgets issue where the wxGrid children don't get placed correctly
454 // until the first resize event
455 if( page < m_macHack.size() && m_macHack[ page ] )
456 {
457 wxSize pageSize = m_treebook->GetPage( page )->GetSize();
458 pageSize.x += 1;
459 pageSize.y += 2;
460
461 m_treebook->GetPage( page )->SetSize( pageSize );
462 m_macHack[ page ] = false;
463 }
464#else
465 wxSizeEvent evt( wxDefaultSize );
466 wxQueueEvent( m_treebook, evt.Clone() );
467#endif
468}
469
470
471void PAGED_DIALOG::onPageChanging( wxBookCtrlEvent& aEvent )
472{
473 int currentPage = aEvent.GetOldSelection();
474
475 if( currentPage == wxNOT_FOUND )
476 return;
477
478 wxWindow* page = m_treebook->GetPage( currentPage );
479
480 wxCHECK( page, /* void */ );
481
482 // If there is a validation error on the current page, don't allow the page change.
483 if( !page->Validate() || !page->TransferDataFromWindow() )
484 {
485 aEvent.Veto();
486 return;
487 }
488}
489
490
491void PAGED_DIALOG::onResetButton( wxCommandEvent& aEvent )
492{
493 int sel = m_treebook->GetSelection();
494
495 if( sel == wxNOT_FOUND )
496 return;
497
498 // NB: dynamic_cast doesn't work over Kiway
499 wxWindow* panel = m_treebook->ResolvePage( sel );
500
501 if( panel )
502 {
503 wxCommandEvent resetCommand( wxEVT_COMMAND_BUTTON_CLICKED, ID_RESET_PANEL );
504 panel->ProcessWindowEvent( resetCommand );
505 }
506}
507
508void PAGED_DIALOG::onOpenPrefsDir( wxCommandEvent& aEvent )
509{
510 wxString dir( PATHS::GetUserSettingsPath() );
511 LaunchExternal( dir );
512}
const char * name
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
DIALOG_SHIM(wxWindow *aParent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE|wxRESIZE_BORDER, const wxString &name=wxDialogNameStr)
virtual void onOpenPrefsDir(wxCommandEvent &aEvent)
WX_INFOBAR * m_infoBar
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
std::vector< bool > m_macHack
wxButton * m_openPrefsDirButton
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)
wxString m_title
static PAGED_DIALOG * GetDialog(wxWindow *aWindow)
virtual void onResetButton(wxCommandEvent &aEvent)
WX_TREEBOOK * m_treebook
virtual void onPageChanged(wxBookCtrlEvent &aEvent)
wxBoxSizer * m_buttonsSizer
void SetError(const wxString &aMessage, const wxString &aPageName, int aCtrlId, int aRow=-1, int aCol=-1)
wxButton * m_resetButton
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition paths.cpp:634
A modified version of the wxInfoBar class that allows us to:
Definition wx_infobar.h:77
void SetBorders(bool aLeft, bool aRight, bool aTop, bool aBottom)
Definition wx_panel.h:39
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.
KICOMMON_API wxFont GetControlFont(wxWindow *aWindow)
std::map< wxString, wxString > g_lastPage
std::map< wxString, wxString > g_lastParentPage
#define wxRESETTABLE
#define ID_RESET_PANEL
Functions to provide common constants and other functions to assist in making a consistent UI.