You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

430 lines
13 KiB

3 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019-2022 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. #include <confirm.h>
  20. #include <widgets/resettable_panel.h>
  21. #include <wx/button.h>
  22. #include <wx/grid.h>
  23. #include <wx/sizer.h>
  24. #include <wx/statline.h>
  25. #include <wx/treebook.h>
  26. #include <wx/treectrl.h>
  27. #include <widgets/infobar.h>
  28. #include <widgets/paged_dialog.h>
  29. #include <wx/stc/stc.h>
  30. #include <algorithm>
  31. #include "wx/listctrl.h"
  32. // Maps from dialogTitle <-> pageTitle for keeping track of last-selected pages.
  33. // This is not a simple page index because some dialogs have dynamic page sets.
  34. std::map<wxString, wxString> g_lastPage;
  35. std::map<wxString, wxString> g_lastParentPage;
  36. PAGED_DIALOG::PAGED_DIALOG( wxWindow* aParent, const wxString& aTitle, bool aShowReset,
  37. const wxString& aAuxiliaryAction ) :
  38. DIALOG_SHIM( aParent, wxID_ANY, aTitle, wxDefaultPosition, wxDefaultSize,
  39. wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
  40. m_auxiliaryButton( nullptr ),
  41. m_resetButton( nullptr ),
  42. m_cancelButton( nullptr ),
  43. m_title( aTitle )
  44. {
  45. auto mainSizer = new wxBoxSizer( wxVERTICAL );
  46. SetSizer( mainSizer );
  47. m_infoBar = new WX_INFOBAR( this );
  48. mainSizer->Add( m_infoBar, 0, wxEXPAND, 0 );
  49. m_treebook = new wxTreebook( this, wxID_ANY );
  50. m_treebook->SetFont( KIUI::GetControlFont( this ) );
  51. mainSizer->Add( m_treebook, 1, wxEXPAND|wxLEFT|wxTOP, 10 );
  52. auto line = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
  53. wxLI_HORIZONTAL );
  54. mainSizer->Add( line, 0, wxEXPAND|wxLEFT|wxTOP|wxRIGHT, 10 );
  55. m_buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
  56. if( aShowReset )
  57. {
  58. m_resetButton = new wxButton( this, wxID_ANY, _( "Reset to Defaults" ) );
  59. m_buttonsSizer->Add( m_resetButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
  60. }
  61. if( !aAuxiliaryAction.IsEmpty() )
  62. {
  63. m_auxiliaryButton = new wxButton( this, wxID_ANY, aAuxiliaryAction );
  64. m_buttonsSizer->Add( m_auxiliaryButton, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT|wxLEFT, 5 );
  65. }
  66. m_buttonsSizer->AddStretchSpacer();
  67. wxStdDialogButtonSizer* sdbSizer = new wxStdDialogButtonSizer();
  68. wxButton* sdbSizerOK = new wxButton( this, wxID_OK );
  69. sdbSizer->AddButton( sdbSizerOK );
  70. wxButton* sdbSizerCancel = new wxButton( this, wxID_CANCEL );
  71. sdbSizer->AddButton( sdbSizerCancel );
  72. sdbSizer->Realize();
  73. m_buttonsSizer->Add( sdbSizer, 1, 0, 5 );
  74. mainSizer->Add( m_buttonsSizer, 0, wxALL|wxEXPAND, 5 );
  75. SetupStandardButtons();
  76. // We normally save the dialog size and position based on its class-name. This class
  77. // substitutes the title so that each distinctly-titled dialog can have its own saved
  78. // size and position.
  79. m_hash_key = aTitle;
  80. if( m_auxiliaryButton )
  81. {
  82. m_auxiliaryButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onAuxiliaryAction,
  83. this );
  84. }
  85. if( m_resetButton )
  86. {
  87. m_resetButton->Bind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onResetButton, this );
  88. }
  89. m_treebook->Bind( wxEVT_CHAR_HOOK, &PAGED_DIALOG::onCharHook, this );
  90. m_treebook->Bind( wxEVT_TREEBOOK_PAGE_CHANGED, &PAGED_DIALOG::onPageChanged, this );
  91. m_treebook->Bind( wxEVT_TREEBOOK_PAGE_CHANGING, &PAGED_DIALOG::onPageChanging, this );
  92. }
  93. void PAGED_DIALOG::finishInitialization()
  94. {
  95. // For some reason adding page labels to the treeCtrl doesn't invalidate its bestSize
  96. // cache so we have to do it by hand
  97. m_treebook->GetTreeCtrl()->InvalidateBestSize();
  98. for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
  99. m_treebook->GetPage( i )->Layout();
  100. m_treebook->Layout();
  101. m_treebook->Fit();
  102. finishDialogSettings();
  103. Centre( wxBOTH );
  104. }
  105. void PAGED_DIALOG::SetInitialPage( const wxString& aPage, const wxString& aParentPage )
  106. {
  107. g_lastPage[ m_title ] = aPage;
  108. g_lastParentPage[ m_title ] = aParentPage;
  109. }
  110. PAGED_DIALOG::~PAGED_DIALOG()
  111. {
  112. // Store the current parentPageTitle/pageTitle hierarchy so we can re-select it
  113. // next time.
  114. wxString lastPage = wxEmptyString;
  115. wxString lastParentPage = wxEmptyString;
  116. int selected = m_treebook->GetSelection();
  117. if( selected != wxNOT_FOUND )
  118. {
  119. lastPage = m_treebook->GetPageText( (unsigned) selected );
  120. int parent = m_treebook->GetPageParent( (unsigned) selected );
  121. if( parent != wxNOT_FOUND )
  122. lastParentPage = m_treebook->GetPageText( (unsigned) parent );
  123. }
  124. g_lastPage[ m_title ] = lastPage;
  125. g_lastParentPage[ m_title ] = lastParentPage;
  126. if( m_auxiliaryButton )
  127. {
  128. m_auxiliaryButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onAuxiliaryAction,
  129. this );
  130. }
  131. if( m_resetButton )
  132. {
  133. m_resetButton->Unbind( wxEVT_COMMAND_BUTTON_CLICKED, &PAGED_DIALOG::onResetButton, this );
  134. }
  135. m_treebook->Unbind( wxEVT_CHAR_HOOK, &PAGED_DIALOG::onCharHook, this );
  136. m_treebook->Unbind( wxEVT_TREEBOOK_PAGE_CHANGED, &PAGED_DIALOG::onPageChanged, this );
  137. m_treebook->Unbind( wxEVT_TREEBOOK_PAGE_CHANGING, &PAGED_DIALOG::onPageChanging, this );
  138. }
  139. bool PAGED_DIALOG::TransferDataToWindow()
  140. {
  141. finishInitialization();
  142. // Call TransferDataToWindow() only once:
  143. // this is enough on wxWidgets 3.1
  144. if( !DIALOG_SHIM::TransferDataToWindow() )
  145. return false;
  146. // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
  147. // so we have to call it for each page
  148. #if !wxCHECK_VERSION( 3, 1, 0 )
  149. for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
  150. {
  151. wxWindow* page = m_treebook->GetPage( i );
  152. if( !page->TransferDataToWindow() )
  153. return false;
  154. }
  155. #endif
  156. // Search for a page matching the lastParentPageTitle/lastPageTitle hierarchy
  157. wxString lastPage = g_lastPage[ m_title ];
  158. wxString lastParentPage = g_lastParentPage[ m_title ];
  159. int lastPageIndex = wxNOT_FOUND;
  160. for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
  161. {
  162. if( m_treebook->GetPageText( i ) == lastPage )
  163. {
  164. if( lastParentPage.IsEmpty() )
  165. {
  166. lastPageIndex = i;
  167. break;
  168. }
  169. if( m_treebook->GetPageParent( i ) >= 0
  170. && m_treebook->GetPageText( (unsigned) m_treebook->GetPageParent( i ) ) == lastParentPage )
  171. {
  172. lastPageIndex = i;
  173. break;
  174. }
  175. }
  176. }
  177. lastPageIndex = std::max( 0, lastPageIndex );
  178. m_treebook->ChangeSelection( lastPageIndex );
  179. UpdateResetButton( lastPageIndex );
  180. return true;
  181. }
  182. bool PAGED_DIALOG::TransferDataFromWindow()
  183. {
  184. bool ret = true;
  185. // Call TransferDataFromWindow() only once:
  186. // this is enough on wxWidgets 3.1
  187. if( !DIALOG_SHIM::TransferDataFromWindow() )
  188. ret = false;
  189. // On wxWidgets 3.0, TransferDataFromWindow() is not called recursively
  190. // so we have to call it for each page
  191. #if !wxCHECK_VERSION( 3, 1, 0 )
  192. for( size_t i = 0; i < m_treebook->GetPageCount(); ++i )
  193. {
  194. wxWindow* page = m_treebook->GetPage( i );
  195. if( !page->TransferDataFromWindow() )
  196. {
  197. m_treebook->ChangeSelection( i );
  198. ret = false;
  199. break;
  200. }
  201. }
  202. #endif
  203. return ret;
  204. }
  205. void PAGED_DIALOG::SetError( const wxString& aMessage, const wxString& aPageName, int aCtrlId,
  206. int aRow, int aCol )
  207. {
  208. SetError( aMessage, FindWindow( aPageName ), FindWindow( aCtrlId ), aRow, aCol );
  209. }
  210. void PAGED_DIALOG::SetError( const wxString& aMessage, wxWindow* aPage, wxWindow* aCtrl,
  211. int aRow, int aCol )
  212. {
  213. if( aCtrl )
  214. {
  215. m_infoBar->ShowMessageFor( aMessage, 10000, wxICON_WARNING );
  216. if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( aCtrl ) )
  217. {
  218. textCtrl->SetSelection( -1, -1 );
  219. textCtrl->SetFocus();
  220. return;
  221. }
  222. if( wxStyledTextCtrl* scintilla = dynamic_cast<wxStyledTextCtrl*>( aCtrl ) )
  223. {
  224. if( aRow > 0 )
  225. {
  226. int pos = scintilla->PositionFromLine( aRow - 1 ) + ( aCol - 1 );
  227. scintilla->GotoPos( pos );
  228. }
  229. scintilla->SetFocus();
  230. return;
  231. }
  232. if( wxGrid* grid = dynamic_cast<wxGrid*>( aCtrl ) )
  233. {
  234. grid->SetFocus();
  235. grid->MakeCellVisible( aRow, aCol );
  236. grid->SetGridCursor( aRow, aCol );
  237. grid->EnableCellEditControl( true );
  238. grid->ShowCellEditControl();
  239. return;
  240. }
  241. }
  242. }
  243. void PAGED_DIALOG::UpdateResetButton( int aPage )
  244. {
  245. wxWindow* panel = m_treebook->GetPage( aPage );
  246. // Enable the reset button only if the page is re-settable
  247. if( m_resetButton )
  248. {
  249. if( panel && ( panel->GetWindowStyle() & wxRESETTABLE ) )
  250. {
  251. m_resetButton->SetLabel( wxString::Format( _( "Reset %s to Defaults" ),
  252. m_treebook->GetPageText( aPage ) ) );
  253. m_resetButton->SetToolTip( panel->GetHelpTextAtPoint( wxPoint( -INT_MAX, INT_MAX ),
  254. wxHelpEvent::Origin_Unknown ) );
  255. m_resetButton->Enable( true );
  256. }
  257. else
  258. {
  259. m_resetButton->SetLabel( _( "Reset to Defaults" ) );
  260. m_resetButton->SetToolTip( wxString() );
  261. m_resetButton->Enable( false );
  262. }
  263. m_resetButton->GetParent()->Layout();
  264. }
  265. }
  266. void PAGED_DIALOG::onCharHook( wxKeyEvent& aEvent )
  267. {
  268. if( dynamic_cast<wxTextEntry*>( aEvent.GetEventObject() )
  269. || dynamic_cast<wxStyledTextCtrl*>( aEvent.GetEventObject() )
  270. || dynamic_cast<wxListView*>( aEvent.GetEventObject() ) )
  271. {
  272. aEvent.Skip();
  273. return;
  274. }
  275. if( aEvent.GetKeyCode() == WXK_UP )
  276. {
  277. int page = m_treebook->GetSelection();
  278. if( page >= 1 )
  279. {
  280. if( m_treebook->GetPage( page - 1 )->GetChildren().IsEmpty() )
  281. m_treebook->SetSelection( std::max( page - 2, 0 ) );
  282. else
  283. m_treebook->SetSelection( page - 1 );
  284. }
  285. m_treebook->GetTreeCtrl()->SetFocus(); // Don't allow preview canvas to steal focus
  286. }
  287. else if( aEvent.GetKeyCode() == WXK_DOWN )
  288. {
  289. int page = m_treebook->GetSelection();
  290. m_treebook->SetSelection( std::min<int>( page + 1, m_treebook->GetPageCount() - 1 ) );
  291. m_treebook->GetTreeCtrl()->SetFocus(); // Don't allow preview canvas to steal focus
  292. }
  293. else
  294. {
  295. aEvent.Skip();
  296. }
  297. }
  298. void PAGED_DIALOG::onPageChanged( wxBookCtrlEvent& event )
  299. {
  300. int page = event.GetSelection();
  301. // Use the first sub-page when a tree level node is selected.
  302. if( m_treebook->GetCurrentPage()->GetChildren().IsEmpty() )
  303. {
  304. unsigned next = page + 1;
  305. if( next < m_treebook->GetPageCount() )
  306. m_treebook->ChangeSelection( next );
  307. }
  308. UpdateResetButton( page );
  309. wxSizeEvent evt( wxDefaultSize );
  310. wxQueueEvent( m_treebook, evt.Clone() );
  311. }
  312. void PAGED_DIALOG::onPageChanging( wxBookCtrlEvent& aEvent )
  313. {
  314. int currentPage = aEvent.GetOldSelection();
  315. if( currentPage == wxNOT_FOUND )
  316. return;
  317. wxWindow* page = m_treebook->GetPage( currentPage );
  318. wxCHECK( page, /* void */ );
  319. // If there is a validation error on the current page, don't allow the page change.
  320. if( !page->Validate() || !page->TransferDataFromWindow() )
  321. {
  322. aEvent.Veto();
  323. return;
  324. }
  325. }
  326. void PAGED_DIALOG::onResetButton( wxCommandEvent& aEvent )
  327. {
  328. int sel = m_treebook->GetSelection();
  329. if( sel == wxNOT_FOUND )
  330. return;
  331. // NB: dynamic_cast doesn't work over Kiway
  332. wxWindow* panel = m_treebook->GetPage( sel );
  333. if( panel )
  334. {
  335. wxCommandEvent resetCommand( wxEVT_COMMAND_BUTTON_CLICKED, ID_RESET_PANEL );
  336. panel->ProcessWindowEvent( resetCommand );
  337. }
  338. }