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 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, wxLI_HORIZONTAL );
62  mainSizer->Add( line, 0, wxEXPAND|wxLEFT|wxTOP|wxRIGHT, 10 );
63 
64  auto buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
65 
66  if( aUseReset )
67  {
68  m_resetButton = new wxButton( this, wxID_ANY, _( "Reset to Defaults" ) );
69  buttonsSizer->Add( m_resetButton, 0, wxALL, 5 );
70  }
71 
72  if( !aAuxiliaryAction.IsEmpty() )
73  {
74  m_auxiliaryButton = new wxButton( this, wxID_ANY, aAuxiliaryAction );
75  buttonsSizer->Add( m_auxiliaryButton, 0, wxALL, 5 );
76  }
77 
78  buttonsSizer->AddStretchSpacer();
79 
80  auto sdbSizer = new wxStdDialogButtonSizer();
81  wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
82  sdbSizer->AddButton( sdbSizerOK );
83  wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
84  sdbSizer->AddButton( sdbSizerCancel );
85  sdbSizer->Realize();
86 
87  buttonsSizer->Add( sdbSizer, 1, 0, 5 );
88  mainSizer->Add( buttonsSizer, 0, wxALL|wxEXPAND, 5 );
89 
90  sdbSizerOK->SetDefault();
91 
92  // We normally save the dialog size and position based on its class-name. This class
93  // substitutes the title so that each distinctly-titled dialog can have its own saved
94  // size and position.
95  m_hash_key = aTitle;
96 
97  if( m_auxiliaryButton )
98  m_auxiliaryButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PAGED_DIALOG::OnAuxiliaryAction ), nullptr, this );
99 
100  if( m_resetButton )
101  m_resetButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PAGED_DIALOG::OnResetButton ), nullptr, this );
102 
103  m_treebook->Connect( wxEVT_TREEBOOK_PAGE_CHANGED, wxBookCtrlEventHandler( PAGED_DIALOG::OnPageChange ), NULL, this );
104  Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PAGED_DIALOG::OnUpdateUI ), nullptr, this );
105 }
106 
107 
108 // Finish initialization after the bookctrl pages have been added.
110 {
111  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
112  m_macHack.push_back( true );
113 
114  // For some reason adding page labels to the treeCtrl doesn't invalidate its bestSize
115  // cache so we have to do it by hand
116  m_treebook->GetTreeCtrl()->InvalidateBestSize();
117 
118  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
119  {
120  m_treebook->ExpandNode( i );
121  m_treebook->GetPage( i )->Layout();
122  }
123 
124  m_treebook->Fit();
125  m_treebook->Layout();
126 
128 }
129 
130 
131 void PAGED_DIALOG::SetInitialPage( const wxString& aPage, const wxString& aParentPage )
132 {
133  g_lastPage[ m_title ] = aPage;
134  g_lastParentPage[ m_title ] = aParentPage;
135 }
136 
137 
139 {
140  // Store the current parentPageTitle/pageTitle hierarchy so we can re-select it
141  // next time.
142  wxString lastPage = wxEmptyString;
143  wxString lastParentPage = wxEmptyString;
144 
145  int selected = m_treebook->GetSelection();
146 
147  if( selected != wxNOT_FOUND )
148  {
149  lastPage = m_treebook->GetPageText( (unsigned) selected );
150 
151  int parent = m_treebook->GetPageParent( (unsigned) selected );
152 
153  if( parent != wxNOT_FOUND )
154  lastParentPage = m_treebook->GetPageText( (unsigned) parent );
155  }
156 
157  g_lastPage[ m_title ] = lastPage;
158  g_lastParentPage[ m_title ] = lastParentPage;
159 
160  if( m_auxiliaryButton )
161  m_auxiliaryButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PAGED_DIALOG::OnAuxiliaryAction ), nullptr, this );
162 
163  if( m_resetButton )
164  m_resetButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PAGED_DIALOG::OnResetButton ), nullptr, this );
165 
166  m_treebook->Disconnect( wxEVT_TREEBOOK_PAGE_CHANGED, wxBookCtrlEventHandler( PAGED_DIALOG::OnPageChange ), NULL, this );
167  Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PAGED_DIALOG::OnUpdateUI ), nullptr, this );
168 }
169 
170 
172 {
174 
175  // Call TransferDataToWindow() only once:
176  // this is enough on wxWidgets 3.1
177  if( !DIALOG_SHIM::TransferDataToWindow() )
178  return false;
179 
180  // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
181  // so we have to call it for each page
182 #if !wxCHECK_VERSION( 3, 1, 0 )
183  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
184  {
185  wxWindow* page = m_treebook->GetPage( i );
186 
187  if( !page->TransferDataToWindow() )
188  return false;
189  }
190 #endif
191 
192  // Search for a page matching the lastParentPageTitle/lastPageTitle hierarchy
193  wxString lastPage = g_lastPage[ m_title ];
194  wxString lastParentPage = g_lastParentPage[ m_title ];
195  int lastPageIndex = wxNOT_FOUND;
196 
197  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
198  {
199  if( m_treebook->GetPageText( i ) == lastPage )
200  {
201  if( lastParentPage.IsEmpty() )
202  {
203  lastPageIndex = i;
204  break;
205  }
206 
207  if( m_treebook->GetPageParent( i ) >= 0
208  && m_treebook->GetPageText( (unsigned) m_treebook->GetPageParent( i ) ) == lastParentPage )
209  {
210  lastPageIndex = i;
211  break;
212  }
213  }
214  }
215 
216  m_treebook->SetSelection( (unsigned) std::max( 0, lastPageIndex ) );
217 
218  return true;
219 }
220 
221 
223 {
224  // Call TransferDataFromWindow() only once:
225  // this is enough on wxWidgets 3.1
226  if( !DIALOG_SHIM::TransferDataFromWindow() )
227  return false;
228 
229  // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
230  // so we have to call it for each page
231 #if !wxCHECK_VERSION( 3, 1, 0 )
232  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
233  {
234  wxWindow* page = m_treebook->GetPage( i );
235 
236  if( !page->TransferDataFromWindow() )
237  return false;
238  }
239 #endif
240 
241  return true;
242 }
243 
244 
245 void PAGED_DIALOG::SetError( const wxString& aMessage, const wxString& aPageName, int aCtrlId,
246  int aRow, int aCol )
247 {
248  SetError( aMessage, FindWindow( aPageName ), FindWindow( aCtrlId ), aRow, aCol );
249 }
250 
251 
252 void PAGED_DIALOG::SetError( const wxString& aMessage, wxWindow* aPage, wxWindow* aCtrl,
253  int aRow, int aCol )
254 {
255  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
256  {
257  if( m_treebook->GetPage( i ) == aPage )
258  {
259  m_treebook->SetSelection( i );
260  break;
261  }
262  }
263 
264  // Once the page has been changed we want to wait for it to update before displaying
265  // the error dialog. So store the rest of the error info and wait for OnUpdateUI.
266  m_errorMessage = aMessage;
267  m_errorCtrl = aCtrl;
268  m_errorRow = aRow;
269  m_errorCol = aCol;
270 }
271 
272 
273 void PAGED_DIALOG::OnUpdateUI( wxUpdateUIEvent& event )
274 {
275  // Handle an error. This is delayed to OnUpdateUI so that we can change the focus
276  // even when the original validation was triggered from a killFocus event, and so
277  // that the corresponding notebook page can be shown in the background when triggered
278  // from an OK.
279  if( m_errorCtrl )
280  {
281  // We will re-enter this routine when the error dialog is displayed, so make
282  // sure we don't keep putting up more dialogs.
283  wxWindow* ctrl = m_errorCtrl;
284  m_errorCtrl = nullptr;
285 
287 
288  if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ctrl ) )
289  {
290  textCtrl->SetSelection( -1, -1 );
291  textCtrl->SetFocus();
292  return;
293  }
294 
295  if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( ctrl ) )
296  {
297  if( m_errorRow > 0 )
298  {
299  int pos = scintilla->PositionFromLine( m_errorRow - 1 ) + ( m_errorCol - 1 );
300  scintilla->GotoPos( pos );
301  }
302 
303  scintilla->SetFocus();
304  return;
305  }
306 
307  if( wxGrid* grid = dynamic_cast<wxGrid*>( ctrl ) )
308  {
309  grid->SetFocus();
310  grid->MakeCellVisible( m_errorRow, m_errorCol );
311  grid->SetGridCursor( m_errorRow, m_errorCol );
312 
313  grid->EnableCellEditControl( true );
314  grid->ShowCellEditControl();
315  return;
316  }
317  }
318 
319  if( m_treebook->GetCurrentPage()->GetChildren().IsEmpty() )
320  {
321  unsigned next = m_treebook->GetSelection() + 1;
322 
323  if( next < m_treebook->GetPageCount() )
324  m_treebook->SetSelection( next );
325  }
326 }
327 
328 
329 void PAGED_DIALOG::OnPageChange( wxBookCtrlEvent& event )
330 {
331  size_t page = event.GetSelection();
332 
333  // Enable the reset button only if the page is resettable
334  if( m_resetButton )
335  {
336  if( auto panel = dynamic_cast<RESETTABLE_PANEL*>( m_treebook->GetPage( page ) ) )
337  {
338  m_resetButton->SetToolTip( panel->GetResetTooltip() );
339  m_resetButton->Enable( true );
340  }
341  else
342  {
343  m_resetButton->SetToolTip( wxString() );
344  m_resetButton->Enable( false );
345  }
346 
347  }
348 
349  // Work around an OSX bug where the wxGrid children don't get placed correctly until
350  // the first resize event
351 #ifdef __WXMAC__
352  if( page + 1 <= m_macHack.size() && m_macHack[ page ] )
353  {
354  wxSize pageSize = m_treebook->GetPage( page )->GetSize();
355  pageSize.x -= 3;
356  pageSize.y += 2;
357 
358  m_treebook->GetPage( page )->SetSize( pageSize );
359  m_macHack[ page ] = false;
360  }
361 #endif
362 
363  Layout();
364 }
365 
366 
367 void PAGED_DIALOG::OnResetButton( wxCommandEvent& aEvent )
368 {
369  int sel = m_treebook->GetSelection();
370 
371  if( sel == wxNOT_FOUND )
372  return;
373 
374  RESETTABLE_PANEL* panel = dynamic_cast<RESETTABLE_PANEL*>( m_treebook->GetPage( sel ) );
375 
376  if( panel )
377  panel->ResetPanel();
378 }
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:252
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:198
void SetInitialPage(const wxString &aPage, const wxString &aParentPage=wxEmptyString)
void FinishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
~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
A modified version of the wxInfoBar class that allows us to:
Definition: infobar.h:68
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)