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>
21 #include <wx/treebook.h>
22 #include <wx/treectrl.h>
23 #include <wx/grid.h>
24 #include <wx/statline.h>
25 
26 #include <widgets/paged_dialog.h>
27 #include <wx/stc/stc.h>
28 
29 // Maps from dialogTitle <-> pageTitle for keeping track of last-selected pages.
30 // This is not a simple page index because some dialogs have dynamic page sets.
31 std::map<wxString, wxString> g_lastPage;
32 std::map<wxString, wxString> g_lastParentPage;
33 
34 
35 PAGED_DIALOG::PAGED_DIALOG( wxWindow* aParent, const wxString& aTitle,
36  const wxString& aAuxiliaryAction ) :
37  DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxDefaultSize,
38  wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
39  m_title( aTitle ),
40  m_errorCtrl( nullptr ),
41  m_errorRow( 0 ),
42  m_errorCol( 0 ),
43  m_auxiliaryButton( nullptr )
44 {
45  auto mainSizer = new wxBoxSizer( wxVERTICAL );
46  SetSizer( mainSizer );
47 
48  m_treebook = new wxTreebook( this, wxID_ANY );
49  mainSizer->Add( m_treebook, 1, wxEXPAND|wxLEFT|wxTOP, 10 );
50 
51  auto line = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
52  mainSizer->Add( line, 0, wxEXPAND|wxLEFT|wxTOP|wxRIGHT, 10 );
53 
54  auto buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
55 
56  if( !aAuxiliaryAction.IsEmpty() )
57  {
58  m_auxiliaryButton = new wxButton( this, wxID_ANY, aAuxiliaryAction );
59  buttonsSizer->Add( m_auxiliaryButton, 0, wxEXPAND|wxRIGHT|wxLEFT, 10 );
60  }
61 
62  auto sdbSizer = new wxStdDialogButtonSizer();
63  auto sdbSizerOK = new wxButton( this, wxID_OK );
64  sdbSizer->AddButton( sdbSizerOK );
65  auto sdbSizerCancel = new wxButton( this, wxID_CANCEL );
66  sdbSizer->AddButton( sdbSizerCancel );
67  sdbSizer->Realize();
68 
69  buttonsSizer->Add( sdbSizer, 1, wxEXPAND, 5 );
70  mainSizer->Add( buttonsSizer, 0, wxALL|wxEXPAND, 5 );
71 
72  sdbSizerOK->SetDefault();
73 
74  // We normally save the dialog size and position based on its class-name. This class
75  // substitutes the title so that each distinctly-titled dialog can have its own saved
76  // size and position.
77  m_hash_key = aTitle;
78 
79  if( m_auxiliaryButton )
80  m_auxiliaryButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PAGED_DIALOG::OnAuxiliaryAction ), nullptr, this );
81 
82  m_treebook->Connect( wxEVT_TREEBOOK_PAGE_CHANGED, wxBookCtrlEventHandler( PAGED_DIALOG::OnPageChange ), NULL, this );
83  Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PAGED_DIALOG::OnUpdateUI ), nullptr, this );
84 }
85 
86 
87 // Finish initialization after the bookctrl pages have been added.
89 {
90  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
91  m_macHack.push_back( true );
92 
93  // For some reason adding page labels to the treeCtrl doesn't invalidate its bestSize
94  // cache so we have to do it by hand
95  m_treebook->GetTreeCtrl()->InvalidateBestSize();
96 
97  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
98  {
99  m_treebook->ExpandNode( i );
100  m_treebook->GetPage( i )->Layout();
101  }
102 
103  m_treebook->Fit();
104  m_treebook->Layout();
105 
107 }
108 
109 
110 void PAGED_DIALOG::SetInitialPage( const wxString& aPage, const wxString& aParentPage )
111 {
112  g_lastPage[ m_title ] = aPage;
113  g_lastParentPage[ m_title ] = aParentPage;
114 }
115 
116 
118 {
119  // Store the current parentPageTitle/pageTitle hierarchy so we can re-select it
120  // next time.
121  wxString lastPage = wxEmptyString;
122  wxString lastParentPage = wxEmptyString;
123 
124  int selected = m_treebook->GetSelection();
125 
126  if( selected != wxNOT_FOUND )
127  {
128  lastPage = m_treebook->GetPageText( (unsigned) selected );
129 
130  int parent = m_treebook->GetPageParent( (unsigned) selected );
131 
132  if( parent != wxNOT_FOUND )
133  lastParentPage = m_treebook->GetPageText( (unsigned) parent );
134  }
135 
136  g_lastPage[ m_title ] = lastPage;
137  g_lastParentPage[ m_title ] = lastParentPage;
138 
139  if( m_auxiliaryButton )
140  m_auxiliaryButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PAGED_DIALOG::OnAuxiliaryAction ), nullptr, this );
141 
142  m_treebook->Disconnect( wxEVT_TREEBOOK_PAGE_CHANGED, wxBookCtrlEventHandler( PAGED_DIALOG::OnPageChange ), NULL, this );
143 
144  Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PAGED_DIALOG::OnUpdateUI ), nullptr, this );
145 }
146 
147 
149 {
151 
152  // Call TransferDataToWindow() only once:
153  // this is enough on wxWidgets 3.1
154  if( !DIALOG_SHIM::TransferDataToWindow() )
155  return false;
156 
157  // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
158  // so we have to call it for each page
159 #if !wxCHECK_VERSION( 3, 1, 0 )
160  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
161  {
162  wxWindow* page = m_treebook->GetPage( i );
163 
164  if( !page->TransferDataToWindow() )
165  return false;
166  }
167 #endif
168 
169  // Search for a page matching the lastParentPageTitle/lastPageTitle hierarchy
170  wxString lastPage = g_lastPage[ m_title ];
171  wxString lastParentPage = g_lastParentPage[ m_title ];
172  int lastPageIndex = wxNOT_FOUND;
173 
174  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
175  {
176  if( m_treebook->GetPageText( i ) == lastPage )
177  {
178  if( lastParentPage.IsEmpty() )
179  {
180  lastPageIndex = i;
181  break;
182  }
183 
184  if( m_treebook->GetPageParent( i ) >= 0
185  && m_treebook->GetPageText( (unsigned) m_treebook->GetPageParent( i ) ) == lastParentPage )
186  {
187  lastPageIndex = i;
188  break;
189  }
190  }
191  }
192 
193  m_treebook->SetSelection( (unsigned) std::max( 0, lastPageIndex ) );
194 
195  return true;
196 }
197 
198 
200 {
201  // Call TransferDataFromWindow() only once:
202  // this is enough on wxWidgets 3.1
203  if( !DIALOG_SHIM::TransferDataFromWindow() )
204  return false;
205 
206  // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
207  // so we have to call it for each page
208 #if !wxCHECK_VERSION( 3, 1, 0 )
209  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
210  {
211  wxWindow* page = m_treebook->GetPage( i );
212 
213  if( !page->TransferDataFromWindow() )
214  return false;
215  }
216 #endif
217 
218  return true;
219 }
220 
221 
222 void PAGED_DIALOG::SetError( const wxString& aMessage, const wxString& aPageName, int aCtrlId,
223  int aRow, int aCol )
224 {
225  SetError( aMessage, FindWindow( aPageName ), FindWindow( aCtrlId ), aRow, aCol );
226 }
227 
228 
229 void PAGED_DIALOG::SetError( const wxString& aMessage, wxWindow* aPage, wxWindow* aCtrl,
230  int aRow, int aCol )
231 {
232  for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
233  {
234  if( m_treebook->GetPage( i ) == aPage )
235  {
236  m_treebook->SetSelection( i );
237  break;
238  }
239  }
240 
241  // Once the page has been changed we want to wait for it to update before displaying
242  // the error dialog. So store the rest of the error info and wait for OnUpdateUI.
243  m_errorMessage = aMessage;
244  m_errorCtrl = aCtrl;
245  m_errorRow = aRow;
246  m_errorCol = aCol;
247 }
248 
249 
250 void PAGED_DIALOG::OnUpdateUI( wxUpdateUIEvent& event )
251 {
252  // Handle an error. This is delayed to OnUpdateUI so that we can change the focus
253  // even when the original validation was triggered from a killFocus event, and so
254  // that the corresponding notebook page can be shown in the background when triggered
255  // from an OK.
256  if( m_errorCtrl )
257  {
258  // We will re-enter this routine when the error dialog is displayed, so make
259  // sure we don't keep putting up more dialogs.
260  wxWindow* ctrl = m_errorCtrl;
261  m_errorCtrl = nullptr;
262 
264 
265  if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( ctrl ) )
266  {
267  textCtrl->SetSelection( -1, -1 );
268  textCtrl->SetFocus();
269  return;
270  }
271 
272  if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( ctrl ) )
273  {
274  if( m_errorRow > 0 )
275  {
276  int pos = scintilla->PositionFromLine( m_errorRow - 1 ) + ( m_errorCol - 1 );
277  scintilla->GotoPos( pos );
278  }
279 
280  scintilla->SetFocus();
281  return;
282  }
283 
284  if( wxGrid* grid = dynamic_cast<wxGrid*>( ctrl ) )
285  {
286  grid->SetFocus();
287  grid->MakeCellVisible( m_errorRow, m_errorCol );
288  grid->SetGridCursor( m_errorRow, m_errorCol );
289 
290  grid->EnableCellEditControl( true );
291  grid->ShowCellEditControl();
292  return;
293  }
294  }
295 }
296 
297 
298 void PAGED_DIALOG::OnPageChange( wxBookCtrlEvent& event )
299 {
300 #ifdef __WXMAC__
301  // Work around an OSX bug where the wxGrid children don't get placed correctly until
302  // the first resize event
303  int page = event.GetSelection();
304 
305  if( page + 1 <= m_macHack.size() && m_macHack[ page ] )
306  {
307  wxSize pageSize = m_treebook->GetPage( page )->GetSize();
308  pageSize.x -= 3;
309  pageSize.y += 2;
310 
311  m_treebook->GetPage( page )->SetSize( pageSize );
312  m_macHack[ page ] = false;
313  }
314 #endif
315 }
316 
317 
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:38
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:34
wxButton * m_auxiliaryButton
Definition: paged_dialog.h:66
#define NULL
bool TransferDataFromWindow() override
PAGED_DIALOG(wxWindow *aParent, const wxString &aTitle, const wxString &aAuxiliaryAction=wxEmptyString)
void OnUpdateUI(wxUpdateUIEvent &event)
wxTreebook * m_treebook
Definition: paged_dialog.h:65
wxString m_errorMessage
Definition: paged_dialog.h:33
virtual void OnAuxiliaryAction(wxCommandEvent &event)
Definition: paged_dialog.h:61
void finishInitialization()
wxString m_title
Definition: paged_dialog.h:31