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