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 aShowReset,
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( aShowReset )
68  {
69  m_resetButton = new wxButton( this, wxID_ANY, _( "Reset to Defaults" ) );
70  m_buttonsSizer->Add( m_resetButton, 0, wxALIGN_CENTER_VERTICAL|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, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
77  }
78 
79  m_buttonsSizer->AddStretchSpacer();
80 
81  wxStdDialogButtonSizer* 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  {
100  m_auxiliaryButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
101  wxCommandEventHandler( PAGED_DIALOG::OnAuxiliaryAction ),
102  nullptr, this );
103  }
104 
105  if( m_resetButton )
106  {
107  m_resetButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED,
108  wxCommandEventHandler( PAGED_DIALOG::OnResetButton ), nullptr,
109  this );
110  }
111 
112  m_treebook->Connect( wxEVT_TREEBOOK_PAGE_CHANGED,
113  wxBookCtrlEventHandler( PAGED_DIALOG::OnPageChange ), NULL, this );
114  Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PAGED_DIALOG::OnUpdateUI ), nullptr, this );
115 }
116 
117 
118 // Finish initialization after the bookctrl pages have been added.
120 {
121  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
122  m_macHack.push_back( true );
123 
124  // For some reason adding page labels to the treeCtrl doesn't invalidate its bestSize
125  // cache so we have to do it by hand
126  m_treebook->GetTreeCtrl()->InvalidateBestSize();
127 
128  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
129  {
130  m_treebook->ExpandNode( i );
131  m_treebook->GetPage( i )->Layout();
132  }
133 
134  m_treebook->Fit();
135  m_treebook->Layout();
136 
138 }
139 
140 
141 void PAGED_DIALOG::SetInitialPage( const wxString& aPage, const wxString& aParentPage )
142 {
143  g_lastPage[ m_title ] = aPage;
144  g_lastParentPage[ m_title ] = aParentPage;
145 }
146 
147 
149 {
150  // Store the current parentPageTitle/pageTitle hierarchy so we can re-select it
151  // next time.
152  wxString lastPage = wxEmptyString;
153  wxString lastParentPage = wxEmptyString;
154 
155  int selected = m_treebook->GetSelection();
156 
157  if( selected != wxNOT_FOUND )
158  {
159  lastPage = m_treebook->GetPageText( (unsigned) selected );
160 
161  int parent = m_treebook->GetPageParent( (unsigned) selected );
162 
163  if( parent != wxNOT_FOUND )
164  lastParentPage = m_treebook->GetPageText( (unsigned) parent );
165  }
166 
167  g_lastPage[ m_title ] = lastPage;
168  g_lastParentPage[ m_title ] = lastParentPage;
169 
170  if( m_auxiliaryButton )
171  {
172  m_auxiliaryButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED,
173  wxCommandEventHandler( PAGED_DIALOG::OnAuxiliaryAction ),
174  nullptr, this );
175  }
176 
177  if( m_resetButton )
178  {
179  m_resetButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED,
180  wxCommandEventHandler( PAGED_DIALOG::OnResetButton ), nullptr,
181  this );
182  }
183 
184  m_treebook->Disconnect( wxEVT_TREEBOOK_PAGE_CHANGED,
185  wxBookCtrlEventHandler( PAGED_DIALOG::OnPageChange ), NULL, this );
186  Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PAGED_DIALOG::OnUpdateUI ),
187  nullptr, 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  m_treebook->SetSelection( (unsigned) std::max( 0, lastPageIndex ) );
237 
238  return true;
239 }
240 
241 
243 {
244  bool ret = true;
245 
246  // Call TransferDataFromWindow() only once:
247  // this is enough on wxWidgets 3.1
248  if( !DIALOG_SHIM::TransferDataFromWindow() )
249  ret = false;
250 
251  // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
252  // so we have to call it for each page
253 #if !wxCHECK_VERSION( 3, 1, 0 )
254  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
255  {
256  wxWindow* page = m_treebook->GetPage( i );
257 
258  if( !page->TransferDataFromWindow() )
259  {
260  ret = false;
261  break;
262  }
263  }
264 #endif
265 
266  if( !ret && !m_errorMessage.IsEmpty() )
267  m_infoBar->ShowMessage( m_errorMessage, wxICON_WARNING );
268 
269  return ret;
270 }
271 
272 
273 void PAGED_DIALOG::SetError( const wxString& aMessage, const wxString& aPageName, int aCtrlId,
274  int aRow, int aCol )
275 {
276  SetError( aMessage, FindWindow( aPageName ), FindWindow( aCtrlId ), aRow, aCol );
277 }
278 
279 
280 void PAGED_DIALOG::SetError( const wxString& aMessage, wxWindow* aPage, wxWindow* aCtrl,
281  int aRow, int aCol )
282 {
283  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
284  {
285  if( m_treebook->GetPage( i ) == aPage )
286  {
287  m_treebook->SetSelection( i );
288  break;
289  }
290  }
291 
292  // Once the page has been changed we want to wait for it to update before displaying
293  // the error dialog. So store the rest of the error info and wait for OnUpdateUI.
294  m_errorMessage = aMessage;
295  m_errorCtrl = aCtrl;
296  m_errorRow = aRow;
297  m_errorCol = aCol;
298 }
299 
300 
301 void PAGED_DIALOG::OnUpdateUI( wxUpdateUIEvent& event )
302 {
303  // Handle an error. This is delayed to OnUpdateUI so that we can change the focus
304  // even when the original validation was triggered from a killFocus event, and so
305  // that the corresponding notebook page can be shown in the background when triggered
306  // from an OK.
307  if( m_errorCtrl )
308  {
309  // We will re-enter this routine when the error dialog is displayed, so make
310  // sure we don't keep putting up more dialogs.
311  wxWindow* ctrl = m_errorCtrl;
312  m_errorCtrl = nullptr;
313 
314  m_infoBar->ShowMessageFor( m_errorMessage, 10000, wxICON_WARNING );
315 
316  if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ctrl ) )
317  {
318  textCtrl->SetSelection( -1, -1 );
319  textCtrl->SetFocus();
320  return;
321  }
322 
323  if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( ctrl ) )
324  {
325  if( m_errorRow > 0 )
326  {
327  int pos = scintilla->PositionFromLine( m_errorRow - 1 ) + ( m_errorCol - 1 );
328  scintilla->GotoPos( pos );
329  }
330 
331  scintilla->SetFocus();
332  return;
333  }
334 
335  if( wxGrid* grid = dynamic_cast<wxGrid*>( ctrl ) )
336  {
337  grid->SetFocus();
338  grid->MakeCellVisible( m_errorRow, m_errorCol );
339  grid->SetGridCursor( m_errorRow, m_errorCol );
340 
341  grid->EnableCellEditControl( true );
342  grid->ShowCellEditControl();
343  return;
344  }
345  }
346 
347  if( m_treebook->GetCurrentPage()->GetChildren().IsEmpty() )
348  {
349  unsigned next = m_treebook->GetSelection() + 1;
350 
351  if( next < m_treebook->GetPageCount() )
352  m_treebook->SetSelection( next );
353  }
354 }
355 
356 
357 void PAGED_DIALOG::OnPageChange( wxBookCtrlEvent& event )
358 {
359  size_t page = event.GetSelection();
360 
361  // Enable the reset button only if the page is resettable
362  if( m_resetButton )
363  {
364  if( auto panel = dynamic_cast<RESETTABLE_PANEL*>( m_treebook->GetPage( page ) ) )
365  {
366  m_resetButton->SetToolTip( panel->GetResetTooltip() );
367  m_resetButton->Enable( true );
368  }
369  else
370  {
371  m_resetButton->SetToolTip( wxString() );
372  m_resetButton->Enable( false );
373  }
374 
375  }
376 
377  // Work around an OSX bug where the wxGrid children don't get placed correctly until
378  // the first resize event
379 #ifdef __WXMAC__
380  if( page + 1 <= m_macHack.size() && m_macHack[ page ] )
381  {
382  wxSize pageSize = m_treebook->GetPage( page )->GetSize();
383  pageSize.x -= 5;
384  pageSize.y += 2;
385 
386  m_treebook->GetPage( page )->SetSize( pageSize );
387  m_macHack[ page ] = false;
388  }
389 #endif
390 
391  Layout();
392 }
393 
394 
395 void PAGED_DIALOG::OnResetButton( wxCommandEvent& aEvent )
396 {
397  int sel = m_treebook->GetSelection();
398 
399  if( sel == wxNOT_FOUND )
400  return;
401 
402  RESETTABLE_PANEL* panel = dynamic_cast<RESETTABLE_PANEL*>( m_treebook->GetPage( sel ) );
403 
404  if( panel )
405  panel->ResetPanel();
406 }
CITER next(CITER it)
Definition: ptree.cpp:126
void ShowMessageFor(const wxString &aMessage, int aTime, int aFlags=wxICON_INFORMATION)
Show the infobar with the provided message and icon for a specific period of time.
Definition: infobar.cpp:123
void ShowMessage(const wxString &aMessage, int aFlags=wxICON_INFORMATION) override
Show the info bar with the provided message and icon.
Definition: infobar.cpp:134
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:78
#define NULL
bool TransferDataFromWindow() override
void OnUpdateUI(wxUpdateUIEvent &event)
WX_INFOBAR * m_infoBar
Definition: paged_dialog.h:81
wxTreebook * m_treebook
Definition: paged_dialog.h:77
wxBoxSizer * m_buttonsSizer
Definition: paged_dialog.h:43
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: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:73
virtual void OnAuxiliaryAction(wxCommandEvent &event)
Definition: paged_dialog.h:72
#define _(s)
Definition: 3d_actions.cpp:33
wxButton * m_resetButton
Definition: paged_dialog.h:79
void OnResetButton(wxCommandEvent &aEvent)
void finishInitialization()
wxString m_title
Definition: paged_dialog.h:34
A wxPanel that is designed to be reset in a standard manner.