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.

479 lines
15 KiB

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