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  auto buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
66 
67  if( aUseReset )
68  {
69  m_resetButton = new wxButton( this, wxID_ANY, _( "Reset to Defaults" ) );
70  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  buttonsSizer->Add( m_auxiliaryButton, 0, wxRIGHT|wxLEFT, 5 );
77  }
78 
79  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  buttonsSizer->Add( sdbSizer, 1, 0, 5 );
89  mainSizer->Add( 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  // Call TransferDataFromWindow() only once:
237  // this is enough on wxWidgets 3.1
238  if( !DIALOG_SHIM::TransferDataFromWindow() )
239  return false;
240 
241  // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
242  // so we have to call it for each page
243 #if !wxCHECK_VERSION( 3, 1, 0 )
244  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
245  {
246  wxWindow* page = m_treebook->GetPage( i );
247 
248  if( !page->TransferDataFromWindow() )
249  return false;
250  }
251 #endif
252 
253  return true;
254 }
255 
256 
257 void PAGED_DIALOG::SetError( const wxString& aMessage, const wxString& aPageName, int aCtrlId,
258  int aRow, int aCol )
259 {
260  SetError( aMessage, FindWindow( aPageName ), FindWindow( aCtrlId ), aRow, aCol );
261 }
262 
263 
264 void PAGED_DIALOG::SetError( const wxString& aMessage, wxWindow* aPage, wxWindow* aCtrl,
265  int aRow, int aCol )
266 {
267  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
268  {
269  if( m_treebook->GetPage( i ) == aPage )
270  {
271  m_treebook->SetSelection( i );
272  break;
273  }
274  }
275 
276  // Once the page has been changed we want to wait for it to update before displaying
277  // the error dialog. So store the rest of the error info and wait for OnUpdateUI.
278  m_errorMessage = aMessage;
279  m_errorCtrl = aCtrl;
280  m_errorRow = aRow;
281  m_errorCol = aCol;
282 }
283 
284 
285 void PAGED_DIALOG::OnUpdateUI( wxUpdateUIEvent& event )
286 {
287  // Handle an error. This is delayed to OnUpdateUI so that we can change the focus
288  // even when the original validation was triggered from a killFocus event, and so
289  // that the corresponding notebook page can be shown in the background when triggered
290  // from an OK.
291  if( m_errorCtrl )
292  {
293  // We will re-enter this routine when the error dialog is displayed, so make
294  // sure we don't keep putting up more dialogs.
295  wxWindow* ctrl = m_errorCtrl;
296  m_errorCtrl = nullptr;
297 
299 
300  if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ctrl ) )
301  {
302  textCtrl->SetSelection( -1, -1 );
303  textCtrl->SetFocus();
304  return;
305  }
306 
307  if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( ctrl ) )
308  {
309  if( m_errorRow > 0 )
310  {
311  int pos = scintilla->PositionFromLine( m_errorRow - 1 ) + ( m_errorCol - 1 );
312  scintilla->GotoPos( pos );
313  }
314 
315  scintilla->SetFocus();
316  return;
317  }
318 
319  if( wxGrid* grid = dynamic_cast<wxGrid*>( ctrl ) )
320  {
321  grid->SetFocus();
322  grid->MakeCellVisible( m_errorRow, m_errorCol );
323  grid->SetGridCursor( m_errorRow, m_errorCol );
324 
325  grid->EnableCellEditControl( true );
326  grid->ShowCellEditControl();
327  return;
328  }
329  }
330 
331  if( m_treebook->GetCurrentPage()->GetChildren().IsEmpty() )
332  {
333  unsigned next = m_treebook->GetSelection() + 1;
334 
335  if( next < m_treebook->GetPageCount() )
336  m_treebook->SetSelection( next );
337  }
338 }
339 
340 
341 void PAGED_DIALOG::OnPageChange( wxBookCtrlEvent& event )
342 {
343  size_t page = event.GetSelection();
344 
345  // Enable the reset button only if the page is resettable
346  if( m_resetButton )
347  {
348  if( auto panel = dynamic_cast<RESETTABLE_PANEL*>( m_treebook->GetPage( page ) ) )
349  {
350  m_resetButton->SetToolTip( panel->GetResetTooltip() );
351  m_resetButton->Enable( true );
352  }
353  else
354  {
355  m_resetButton->SetToolTip( wxString() );
356  m_resetButton->Enable( false );
357  }
358 
359  }
360 
361  // Work around an OSX bug where the wxGrid children don't get placed correctly until
362  // the first resize event
363 #ifdef __WXMAC__
364  if( page + 1 <= m_macHack.size() && m_macHack[ page ] )
365  {
366  wxSize pageSize = m_treebook->GetPage( page )->GetSize();
367  pageSize.x -= 5;
368  pageSize.y += 2;
369 
370  m_treebook->GetPage( page )->SetSize( pageSize );
371  m_macHack[ page ] = false;
372  }
373 #endif
374 
375  Layout();
376 }
377 
378 
379 void PAGED_DIALOG::OnResetButton( wxCommandEvent& aEvent )
380 {
381  int sel = m_treebook->GetSelection();
382 
383  if( sel == wxNOT_FOUND )
384  return;
385 
386  RESETTABLE_PANEL* panel = dynamic_cast<RESETTABLE_PANEL*>( m_treebook->GetPage( sel ) );
387 
388  if( panel )
389  panel->ResetPanel();
390 }
CITER next(CITER it)
Definition: ptree.cpp:126
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:253
std::map< wxString, wxString > g_lastPage
This file is part of the common library.
std::vector< bool > m_macHack
Definition: paged_dialog.h:43
std::string m_hash_key
Definition: dialog_shim.h:191
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:76
#define NULL
bool TransferDataFromWindow() override
void OnUpdateUI(wxUpdateUIEvent &event)
WX_INFOBAR * m_infoBar
Definition: paged_dialog.h:79
wxTreebook * m_treebook
Definition: paged_dialog.h:75
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:70
virtual void OnAuxiliaryAction(wxCommandEvent &event)
Definition: paged_dialog.h:70
#define _(s)
Definition: 3d_actions.cpp:33
wxButton * m_resetButton
Definition: paged_dialog.h:77
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 manor.
PAGED_DIALOG(wxWindow *aParent, const wxString &aTitle, bool aUseReset=false, const wxString &aAuxiliaryAction=wxEmptyString)