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 <algorithm>
36
37// Maps from dialogTitle <-> pageTitle for keeping track of last-selected pages.
38// This is not a simple page index because some dialogs have dynamic page sets.
39std::map<wxString, wxString> g_lastPage;
40std::map<wxString, wxString> g_lastParentPage;
41
42
43PAGED_DIALOG::PAGED_DIALOG( wxWindow* aParent, const wxString& aTitle, bool aShowReset,
44 const wxString& aAuxiliaryAction ) :
45 DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxDefaultSize,
46 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
47 m_auxiliaryButton( nullptr ),
48 m_resetButton( nullptr ),
49 m_cancelButton( nullptr ),
50 m_title( aTitle )
51{
52 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
53 SetSizer( mainSizer );
54
55 m_infoBar = new WX_INFOBAR( this );
56 mainSizer->Add( m_infoBar, 0, wxEXPAND, 0 );
57
58 WX_PANEL* treebookPanel = new WX_PANEL( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
59 wxBORDER_NONE | wxTAB_TRAVERSAL );
60 treebookPanel->SetBorders( false, false, false, true );
61 wxBoxSizer* treebookSizer = new wxBoxSizer( wxVERTICAL );
62 treebookPanel->SetSizer( treebookSizer );
63
64 m_treebook = new WX_TREEBOOK( treebookPanel, wxID_ANY );
65 m_treebook->SetFont( KIUI::GetControlFont( this ) );
66
67 long treeCtrlFlags = m_treebook->GetTreeCtrl()->GetWindowStyleFlag();
68 treeCtrlFlags = ( treeCtrlFlags & ~wxBORDER_MASK ) | wxBORDER_NONE;
69 m_treebook->GetTreeCtrl()->SetWindowStyleFlag( treeCtrlFlags );
70
71 treebookSizer->Add( m_treebook, 1, wxEXPAND|wxBOTTOM, 2 );
72 mainSizer->Add( treebookPanel, 1, wxEXPAND, 0 );
73
74 m_buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
75
76 if( aShowReset )
77 {
78 m_resetButton = new wxButton( this, wxID_ANY, _( "Reset to Defaults" ) );
79 m_buttonsSizer->Add( m_resetButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
80 }
81
82 if( !aAuxiliaryAction.IsEmpty() )
83 {
84 m_auxiliaryButton = new wxButton( this, wxID_ANY, aAuxiliaryAction );
85 m_buttonsSizer->Add( m_auxiliaryButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
86 }
87
88 m_buttonsSizer->AddStretchSpacer();
89
90 wxStdDialogButtonSizer* sdbSizer = new wxStdDialogButtonSizer();
91 wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
92 sdbSizer->AddButton( sdbSizerOK );
93 wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
94 sdbSizer->AddButton( sdbSizerCancel );
95 sdbSizer->Realize();
96
97 m_buttonsSizer->Add( sdbSizer, 1, 0, 5 );
98 mainSizer->Add( m_buttonsSizer, 0, wxALL|wxEXPAND, 5 );
99
101
102 // We normally save the dialog size and position based on its class-name. This class
103 // substitutes the title so that each distinctly-titled dialog can have its own saved
104 // size and position.
105 m_hash_key = aTitle;
106
108 {
109 m_auxiliaryButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onAuxiliaryAction,
110 this );
111 }
112
113 if( m_resetButton )
114 {
115 m_resetButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onResetButton, this );
116 }
117
118 m_treebook->Bind( wxEVT_CHAR_HOOK, &PAGED_DIALOG::onCharHook, this );
119 m_treebook->Bind( wxEVT_TREEBOOK_PAGE_CHANGED, &PAGED_DIALOG::onPageChanged, this );
120 m_treebook->Bind( wxEVT_TREEBOOK_PAGE_CHANGING, &PAGED_DIALOG::onPageChanging, this );
121}
122
123
125{
126 for( size_t i = 1; i < m_treebook->GetPageCount(); ++i )
127 m_macHack.push_back( true );
128
129 // For some reason adding page labels to the treeCtrl doesn't invalidate its bestSize
130 // cache so we have to do it by hand
131 m_treebook->GetTreeCtrl()->InvalidateBestSize();
132
133 for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
134 m_treebook->GetPage( i )->Layout();
135
136 m_treebook->Layout();
137 m_treebook->Fit();
138
140
141 Centre( wxBOTH );
142}
143
144
145void PAGED_DIALOG::SetInitialPage( const wxString& aPage, const wxString& aParentPage )
146{
147 g_lastPage[ m_title ] = aPage;
148 g_lastParentPage[ m_title ] = aParentPage;
149}
150
151
153{
154 // Store the current parentPageTitle/pageTitle hierarchy so we can re-select it
155 // next time.
156 wxString lastPage = wxEmptyString;
157 wxString lastParentPage = wxEmptyString;
158
159 int selected = m_treebook->GetSelection();
160
161 if( selected != wxNOT_FOUND )
162 {
163 lastPage = m_treebook->GetPageText( (unsigned) selected );
164
165 int parent = m_treebook->GetPageParent( (unsigned) selected );
166
167 if( parent != wxNOT_FOUND )
168 lastParentPage = m_treebook->GetPageText( (unsigned) parent );
169 }
170
171 g_lastPage[ m_title ] = lastPage;
172 g_lastParentPage[ m_title ] = lastParentPage;
173
175 {
176 m_auxiliaryButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onAuxiliaryAction,
177 this );
178 }
179
180 if( m_resetButton )
181 {
182 m_resetButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onResetButton, this );
183 }
184
185 m_treebook->Unbind( wxEVT_CHAR_HOOK, &PAGED_DIALOG::onCharHook, this );
186 m_treebook->Unbind( wxEVT_TREEBOOK_PAGE_CHANGED, &PAGED_DIALOG::onPageChanged, this );
187 m_treebook->Unbind( wxEVT_TREEBOOK_PAGE_CHANGING, &PAGED_DIALOG::onPageChanging, this );
188}
189
190
192{
194
195 // Call TransferDataToWindow() only once:
196 // this is enough on wxWidgets 3.1
197 if( !DIALOG_SHIM::TransferDataToWindow() )
198 return false;
199
200 // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
201 // so we have to call it for each page
202#if !wxCHECK_VERSION( 3, 1, 0 )
203 for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
204 {
205 wxWindow* page = m_treebook->GetPage( i );
206
207 if( !page->TransferDataToWindow() )
208 return false;
209 }
210#endif
211
212 // Search for a page matching the lastParentPageTitle/lastPageTitle hierarchy
213 wxString lastPage = g_lastPage[ m_title ];
214 wxString lastParentPage = g_lastParentPage[ m_title ];
215 int lastPageIndex = wxNOT_FOUND;
216
217 for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
218 {
219 if( m_treebook->GetPageText( i ) == lastPage )
220 {
221 if( lastParentPage.IsEmpty() )
222 {
223 lastPageIndex = i;
224 break;
225 }
226
227 if( m_treebook->GetPageParent( i ) >= 0
228 && m_treebook->GetPageText( (unsigned) m_treebook->GetPageParent( i ) ) == lastParentPage )
229 {
230 lastPageIndex = i;
231 break;
232 }
233 }
234 }
235
236 lastPageIndex = std::max( 0, lastPageIndex );
237 m_treebook->ChangeSelection( lastPageIndex );
238 UpdateResetButton( lastPageIndex );
239
240 return true;
241}
242
243
245{
246 bool ret = true;
247
248 // Call TransferDataFromWindow() only once:
249 // this is enough on wxWidgets 3.1
250 if( !DIALOG_SHIM::TransferDataFromWindow() )
251 ret = false;
252
253 // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
254 // so we have to call it for each page
255#if !wxCHECK_VERSION( 3, 1, 0 )
256 for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
257 {
258 wxWindow* page = m_treebook->GetPage( i );
259
260 if( !page->TransferDataFromWindow() )
261 {
262 m_treebook->ChangeSelection( i );
263 ret = false;
264 break;
265 }
266 }
267#endif
268
269 return ret;
270}
271
272
274{
275 while( aParent )
276 {
277 if( PAGED_DIALOG* parentDialog = dynamic_cast<PAGED_DIALOG*>( aParent ) )
278 return parentDialog;
279
280 aParent = aParent->GetParent();
281 }
282
283 return nullptr;
284}
285
286
287void PAGED_DIALOG::SetError( const wxString& aMessage, const wxString& aPageName, int aCtrlId,
288 int aRow, int aCol )
289{
290 SetError( aMessage, FindWindow( aPageName ), FindWindow( aCtrlId ), aRow, aCol );
291}
292
293
294void PAGED_DIALOG::SetError( const wxString& aMessage, wxWindow* aPage, wxWindow* aCtrl,
295 int aRow, int aCol )
296{
297 m_infoBar->ShowMessageFor( aMessage, 10000, wxICON_WARNING );
298
299 if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
300 {
301 textCtrl->SetSelection( -1, -1 );
302 textCtrl->SetFocus();
303 return;
304 }
305
306 if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
307 {
308 if( aRow > 0 )
309 {
310 int pos = scintilla->PositionFromLine( aRow - 1 ) + ( aCol - 1 );
311 scintilla->GotoPos( pos );
312 }
313
314 scintilla->SetFocus();
315 return;
316 }
317
318 if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
319 {
320 grid->SetFocus();
321 grid->MakeCellVisible( aRow, aCol );
322 grid->SetGridCursor( aRow, aCol );
323
324 grid->EnableCellEditControl( true );
325 grid->ShowCellEditControl();
326 return;
327 }
328}
329
330
332{
333 wxWindow* panel = m_treebook->ResolvePage( aPage );
334
335 // Enable the reset button only if the page is re-settable
336 if( m_resetButton )
337 {
338 if( panel && ( panel->GetWindowStyle() & wxRESETTABLE ) )
339 {
340 m_resetButton->SetLabel( wxString::Format( _( "Reset %s to Defaults" ),
341 m_treebook->GetPageText( aPage ) ) );
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 {
364 aEvent.Skip();
365 return;
366 }
367
368 if( aEvent.GetKeyCode() == WXK_UP )
369 {
370 int page = m_treebook->GetSelection();
371
372 if( page >= 1 )
373 {
374 if( m_treebook->GetPage( page - 1 )->GetChildren().IsEmpty() )
375 m_treebook->SetSelection( std::max( page - 2, 0 ) );
376 else
377 m_treebook->SetSelection( page - 1 );
378 }
379
380 m_treebook->GetTreeCtrl()->SetFocus(); // Don't allow preview canvas to steal focus
381 }
382 else if( aEvent.GetKeyCode() == WXK_DOWN )
383 {
384 int page = m_treebook->GetSelection();
385
386 m_treebook->SetSelection( std::min<int>( page + 1, m_treebook->GetPageCount() - 1 ) );
387
388 m_treebook->GetTreeCtrl()->SetFocus(); // Don't allow preview canvas to steal focus
389 }
390 else
391 {
392 aEvent.Skip();
393 }
394}
395
396
397void PAGED_DIALOG::onPageChanged( wxBookCtrlEvent& event )
398{
399 size_t page = event.GetSelection();
400
401 // Use the first sub-page when a tree level node is selected.
402 if( m_treebook->GetCurrentPage()->GetChildren().IsEmpty()
403 && page + 1 < m_treebook->GetPageCount() )
404 {
405 m_treebook->ChangeSelection( ++page );
406 }
407
408 UpdateResetButton( page );
409
410#ifdef __WXMAC__
411 // Work around an OSX wxWidgets issue where the wxGrid children don't get placed correctly
412 // until the first resize event
413 if( page < m_macHack.size() && m_macHack[ page ] )
414 {
415 wxSize pageSize = m_treebook->GetPage( page )->GetSize();
416 pageSize.x += 1;
417 pageSize.y += 2;
418
419 m_treebook->GetPage( page )->SetSize( pageSize );
420 m_macHack[ page ] = false;
421 }
422#else
423 wxSizeEvent evt( wxDefaultSize );
424 wxQueueEvent( m_treebook, evt.Clone() );
425#endif
426}
427
428
429void PAGED_DIALOG::onPageChanging( wxBookCtrlEvent& aEvent )
430{
431 int currentPage = aEvent.GetOldSelection();
432
433 if( currentPage == wxNOT_FOUND )
434 return;
435
436 wxWindow* page = m_treebook->GetPage( currentPage );
437
438 wxCHECK( page, /* void */ );
439
440 // If there is a validation error on the current page, don't allow the page change.
441 if( !page->Validate() || !page->TransferDataFromWindow() )
442 {
443 aEvent.Veto();
444 return;
445 }
446}
447
448
449void PAGED_DIALOG::onResetButton( wxCommandEvent& aEvent )
450{
451 int sel = m_treebook->GetSelection();
452
453 if( sel == wxNOT_FOUND )
454 return;
455
456 // NB: dynamic_cast doesn't work over Kiway
457 wxWindow* panel = m_treebook->ResolvePage( sel );
458
459 if( panel )
460 {
461 wxCommandEvent resetCommand( wxEVT_COMMAND_BUTTON_CLICKED, ID_RESET_PANEL );
462 panel->ProcessWindowEvent( resetCommand );
463 }
464}
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition: dialog_shim.h:83
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
Definition: dialog_shim.h:204
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:70
bool TransferDataToWindow() override
~PAGED_DIALOG() override
wxButton * m_auxiliaryButton
Definition: paged_dialog.h:67
std::vector< bool > m_macHack
Definition: paged_dialog.h:77
bool TransferDataFromWindow() override
void finishInitialization()
void UpdateResetButton(int aPage)
PAGED_DIALOG(wxWindow *aParent, const wxString &aTitle, bool aShowReset, const wxString &aAuxiliaryAction=wxEmptyString)
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:60
wxString m_title
Definition: paged_dialog.h:73
static PAGED_DIALOG * GetDialog(wxWindow *aWindow)
virtual void onResetButton(wxCommandEvent &aEvent)
WX_TREEBOOK * m_treebook
Definition: paged_dialog.h:66
virtual void onPageChanged(wxBookCtrlEvent &aEvent)
wxBoxSizer * m_buttonsSizer
Definition: paged_dialog.h:75
void SetError(const wxString &aMessage, const wxString &aPageName, int aCtrlId, int aRow=-1, int aCol=-1)
wxButton * m_resetButton
Definition: paged_dialog.h:68
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:38
wxWindow * ResolvePage(size_t aPage)
Definition: wx_treebook.cpp:99
This file is part of the common library.
#define _(s)
wxFont GetControlFont(wxWindow *aWindow)
Definition: ui_common.cpp:162
std::map< wxString, wxString > g_lastPage
std::map< wxString, wxString > g_lastParentPage
#define wxRESETTABLE
#define ID_RESET_PANEL