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-2021 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/statline.h>
26 #include <wx/treebook.h>
27 #include <wx/treectrl.h>
28 
29 #include <widgets/infobar.h>
30 #include <widgets/paged_dialog.h>
31 #include <wx/stc/stc.h>
32 
33 #include <algorithm>
34 
35 // Maps from dialogTitle <-> pageTitle for keeping track of last-selected pages.
36 // This is not a simple page index because some dialogs have dynamic page sets.
37 std::map<wxString, wxString> g_lastPage;
38 std::map<wxString, wxString> g_lastParentPage;
39 
40 
41 PAGED_DIALOG::PAGED_DIALOG( wxWindow* aParent, const wxString& aTitle, bool aShowReset,
42  const wxString& aAuxiliaryAction ) :
43  DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxDefaultSize,
44  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
45  m_auxiliaryButton( nullptr ),
46  m_resetButton( nullptr ),
47  m_cancelButton( nullptr ),
48  m_title( aTitle ),
49  m_dirty( false ),
50  m_errorCtrl( nullptr ),
51  m_errorRow( 0 ),
52  m_errorCol( 0 )
53 {
54  auto mainSizer = new wxBoxSizer( wxVERTICAL );
55  SetSizer( mainSizer );
56 
57  m_infoBar = new WX_INFOBAR( this );
58  mainSizer->Add( m_infoBar, 0, wxEXPAND, 0 );
59 
60  m_treebook = new wxTreebook( this, wxID_ANY );
61  m_treebook->SetFont( KIUI::GetControlFont( this ) );
62  mainSizer->Add( m_treebook, 1, wxEXPAND|wxLEFT|wxTOP, 10 );
63 
64  auto line = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
65  wxLI_HORIZONTAL );
66  mainSizer->Add( line, 0, wxEXPAND|wxLEFT|wxTOP|wxRIGHT, 10 );
67 
68  m_buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
69 
70  if( aShowReset )
71  {
72  m_resetButton = new wxButton( this, wxID_ANY, _( "Reset to Defaults" ) );
73  m_buttonsSizer->Add( m_resetButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
74  }
75 
76  if( !aAuxiliaryAction.IsEmpty() )
77  {
78  m_auxiliaryButton = new wxButton( this, wxID_ANY, aAuxiliaryAction );
79  m_buttonsSizer->Add( m_auxiliaryButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
80  }
81 
82  m_buttonsSizer->AddStretchSpacer();
83 
84  wxStdDialogButtonSizer* sdbSizer = new wxStdDialogButtonSizer();
85  wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
86  sdbSizer->AddButton( sdbSizerOK );
87  wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
88  sdbSizer->AddButton( sdbSizerCancel );
89  sdbSizer->Realize();
90 
91  m_buttonsSizer->Add( sdbSizer, 1, 0, 5 );
92  mainSizer->Add( m_buttonsSizer, 0, wxALL|wxEXPAND, 5 );
93 
94  sdbSizerOK->SetDefault();
95 
96  // We normally save the dialog size and position based on its class-name. This class
97  // substitutes the title so that each distinctly-titled dialog can have its own saved
98  // size and position.
99  m_hash_key = aTitle;
100 
101  if( m_auxiliaryButton )
102  {
103  m_auxiliaryButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
104  wxCommandEventHandler( PAGED_DIALOG::OnAuxiliaryAction ),
105  nullptr, this );
106  }
107 
108  if( m_resetButton )
109  {
110  m_resetButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
111  wxCommandEventHandler( PAGED_DIALOG::OnResetButton ), nullptr,
112  this );
113  }
114 
115  m_treebook->Connect( wxEVT_TREEBOOK_PAGE_CHANGED,
116  wxBookCtrlEventHandler( PAGED_DIALOG::OnPageChange ), nullptr, this );
117  Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PAGED_DIALOG::OnUpdateUI ), nullptr, this );
118 }
119 
120 
122 {
123  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
124  m_macHack.push_back( true );
125 
126  // For some reason adding page labels to the treeCtrl doesn't invalidate its bestSize
127  // cache so we have to do it by hand
128  m_treebook->GetTreeCtrl()->InvalidateBestSize();
129 
130  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
131  {
132  m_treebook->ExpandNode( i );
133  m_treebook->GetPage( i )->Layout();
134  }
135 
136  m_treebook->Layout();
137  m_treebook->Fit();
138 
140 
141  Centre( wxBOTH );
142 }
143 
144 
145 void 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 
174  if( m_auxiliaryButton )
175  {
176  m_auxiliaryButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED,
177  wxCommandEventHandler( PAGED_DIALOG::OnAuxiliaryAction ),
178  nullptr, this );
179  }
180 
181  if( m_resetButton )
182  {
183  m_resetButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED,
184  wxCommandEventHandler( PAGED_DIALOG::OnResetButton ), nullptr,
185  this );
186  }
187 
188  m_treebook->Disconnect( wxEVT_TREEBOOK_PAGE_CHANGED,
189  wxBookCtrlEventHandler( PAGED_DIALOG::OnPageChange ), nullptr, this );
190  Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PAGED_DIALOG::OnUpdateUI ),
191  nullptr, this );
192 }
193 
194 
196 {
198 
199  // Call TransferDataToWindow() only once:
200  // this is enough on wxWidgets 3.1
201  if( !DIALOG_SHIM::TransferDataToWindow() )
202  return false;
203 
204  // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
205  // so we have to call it for each page
206 #if !wxCHECK_VERSION( 3, 1, 0 )
207  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
208  {
209  wxWindow* page = m_treebook->GetPage( i );
210 
211  if( !page->TransferDataToWindow() )
212  return false;
213  }
214 #endif
215 
216  // Search for a page matching the lastParentPageTitle/lastPageTitle hierarchy
217  wxString lastPage = g_lastPage[ m_title ];
218  wxString lastParentPage = g_lastParentPage[ m_title ];
219  int lastPageIndex = wxNOT_FOUND;
220 
221  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
222  {
223  if( m_treebook->GetPageText( i ) == lastPage )
224  {
225  if( lastParentPage.IsEmpty() )
226  {
227  lastPageIndex = i;
228  break;
229  }
230 
231  if( m_treebook->GetPageParent( i ) >= 0
232  && m_treebook->GetPageText( (unsigned) m_treebook->GetPageParent( i ) ) == lastParentPage )
233  {
234  lastPageIndex = i;
235  break;
236  }
237  }
238  }
239 
240  m_treebook->SetSelection( (unsigned) std::max( 0, lastPageIndex ) );
241 
242  return true;
243 }
244 
245 
247 {
248  bool ret = true;
249 
250  // Call TransferDataFromWindow() only once:
251  // this is enough on wxWidgets 3.1
252  if( !DIALOG_SHIM::TransferDataFromWindow() )
253  ret = false;
254 
255  // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
256  // so we have to call it for each page
257 #if !wxCHECK_VERSION( 3, 1, 0 )
258  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
259  {
260  wxWindow* page = m_treebook->GetPage( i );
261 
262  if( !page->TransferDataFromWindow() )
263  {
264  ret = false;
265  break;
266  }
267  }
268 #endif
269 
270  if( !ret && !m_errorMessage.IsEmpty() )
271  m_infoBar->ShowMessage( m_errorMessage, wxICON_WARNING );
272 
273  return ret;
274 }
275 
276 
277 void PAGED_DIALOG::SetError( const wxString& aMessage, const wxString& aPageName, int aCtrlId,
278  int aRow, int aCol )
279 {
280  SetError( aMessage, FindWindow( aPageName ), FindWindow( aCtrlId ), aRow, aCol );
281 }
282 
283 
284 void PAGED_DIALOG::SetError( const wxString& aMessage, wxWindow* aPage, wxWindow* aCtrl,
285  int aRow, int aCol )
286 {
287  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
288  {
289  if( m_treebook->GetPage( i ) == aPage )
290  {
291  m_treebook->SetSelection( i );
292  break;
293  }
294  }
295 
296  // Once the page has been changed we want to wait for it to update before displaying
297  // the error dialog. So store the rest of the error info and wait for OnUpdateUI.
298  m_errorMessage = aMessage;
299  m_errorCtrl = aCtrl;
300  m_errorRow = aRow;
301  m_errorCol = aCol;
302 }
303 
304 
305 void PAGED_DIALOG::OnUpdateUI( wxUpdateUIEvent& event )
306 {
307  // Handle an error. This is delayed to OnUpdateUI so that we can change the focus
308  // even when the original validation was triggered from a killFocus event, and so
309  // that the corresponding notebook page can be shown in the background when triggered
310  // from an OK.
311  if( m_errorCtrl )
312  {
313  // We will re-enter this routine when the error dialog is displayed, so make
314  // sure we don't keep putting up more dialogs.
315  wxWindow* ctrl = m_errorCtrl;
316  m_errorCtrl = nullptr;
317 
318  m_infoBar->ShowMessageFor( m_errorMessage, 10000, wxICON_WARNING );
319 
320  if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ctrl ) )
321  {
322  textCtrl->SetSelection( -1, -1 );
323  textCtrl->SetFocus();
324  return;
325  }
326 
327  if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( ctrl ) )
328  {
329  if( m_errorRow > 0 )
330  {
331  int pos = scintilla->PositionFromLine( m_errorRow - 1 ) + ( m_errorCol - 1 );
332  scintilla->GotoPos( pos );
333  }
334 
335  scintilla->SetFocus();
336  return;
337  }
338 
339  if( wxGrid* grid = dynamic_cast<wxGrid*>( ctrl ) )
340  {
341  grid->SetFocus();
342  grid->MakeCellVisible( m_errorRow, m_errorCol );
343  grid->SetGridCursor( m_errorRow, m_errorCol );
344 
345  grid->EnableCellEditControl( true );
346  grid->ShowCellEditControl();
347  return;
348  }
349  }
350 
351  if( m_treebook->GetCurrentPage()->GetChildren().IsEmpty() )
352  {
353  unsigned next = m_treebook->GetSelection() + 1;
354 
355  // Use ChangeSelection() here because SetSelection() generates page change events which
356  // creates an infinite wxUpdateUIEvent loop.
357  if( next < m_treebook->GetPageCount() )
358  m_treebook->ChangeSelection( next );
359  }
360 }
361 
362 
363 void PAGED_DIALOG::OnPageChange( wxBookCtrlEvent& event )
364 {
365  size_t page = event.GetSelection();
366 
367  // Enable the reset button only if the page is re-settable
368  if( m_resetButton )
369  {
370  if( auto panel = dynamic_cast<RESETTABLE_PANEL*>( m_treebook->GetPage( page ) ) )
371  {
372  m_resetButton->SetLabel( wxString::Format( _( "Reset %s to Defaults" ),
373  m_treebook->GetPageText( page ) ) );
374  m_resetButton->SetToolTip( panel->GetResetTooltip() );
375  m_resetButton->Enable( true );
376  }
377  else
378  {
379  m_resetButton->SetLabel( _( "Reset to Defaults" ) );
380  m_resetButton->SetToolTip( wxString() );
381  m_resetButton->Enable( false );
382  }
383 
384  }
385 
386  // Work around an OSX bug where the wxGrid children don't get placed correctly until
387  // the first resize event
388 #ifdef __WXMAC__
389  if( page + 1 <= m_macHack.size() && m_macHack[ page ] )
390  {
391  wxSize pageSize = m_treebook->GetPage( page )->GetSize();
392  pageSize.x -= 5;
393  pageSize.y += 2;
394 
395  m_treebook->GetPage( page )->SetSize( pageSize );
396  m_macHack[ page ] = false;
397  }
398 #endif
399 }
400 
401 
402 void PAGED_DIALOG::OnResetButton( wxCommandEvent& aEvent )
403 {
404  int sel = m_treebook->GetSelection();
405 
406  if( sel == wxNOT_FOUND )
407  return;
408 
409  RESETTABLE_PANEL* panel = dynamic_cast<RESETTABLE_PANEL*>( m_treebook->GetPage( sel ) );
410 
411  if( panel )
412  panel->ResetPanel();
413 }
CITER next(CITER it)
Definition: ptree.cpp:126
void ShowMessage(const wxString &aMessage, int aFlags=wxICON_INFORMATION) override
Show the info bar with the provided message and icon.
Definition: infobar.cpp:142
std::map< wxString, wxString > g_lastPage
This file is part of the common library.
std::vector< bool > m_macHack
Definition: paged_dialog.h:80
std::string m_hash_key
Definition: dialog_shim.h:201
void SetInitialPage(const wxString &aPage, const wxString &aParentPage=wxEmptyString)
~PAGED_DIALOG() override
std::map< wxString, wxString > g_lastParentPage
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition: dialog_shim.h:82
void SetError(const wxString &aMessage, const wxString &aPageName, int aCtrlId, int aRow=-1, int aCol=-1)
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: infobar.cpp:128
wxFont GetControlFont(wxWindow *aWindow)
Definition: ui_common.cpp:150
void OnPageChange(wxBookCtrlEvent &event)
bool TransferDataToWindow() override
wxWindow * m_errorCtrl
Definition: paged_dialog.h:74
wxButton * m_auxiliaryButton
Definition: paged_dialog.h:63
bool TransferDataFromWindow() override
void OnUpdateUI(wxUpdateUIEvent &event)
WX_INFOBAR * m_infoBar
Definition: paged_dialog.h:66
wxTreebook * m_treebook
Definition: paged_dialog.h:62
#define _(s)
wxBoxSizer * m_buttonsSizer
Definition: paged_dialog.h:78
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
virtual void ResetPanel()=0
Reset the contents of this panel.
PAGED_DIALOG(wxWindow *aParent, const wxString &aTitle, bool aShowReset, const wxString &aAuxiliaryAction=wxEmptyString)
wxString m_errorMessage
Definition: paged_dialog.h:73
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
A modified version of the wxInfoBar class that allows us to:
Definition: infobar.h:73
virtual void OnAuxiliaryAction(wxCommandEvent &event)
Definition: paged_dialog.h:57
wxButton * m_resetButton
Definition: paged_dialog.h:64
void OnResetButton(wxCommandEvent &aEvent)
void finishInitialization()
wxString m_title
Definition: paged_dialog.h:69
A wxPanel that is designed to be reset in a standard manner.