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