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.

517 lines
13 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 Ian McInerney <ian.s.mcinerney@ieee.org>
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <id.h>
  21. #include <kiplatform/ui.h>
  22. #include <widgets/wx_infobar.h>
  23. #include "wx/artprov.h"
  24. #include <wx/aui/framemanager.h>
  25. #include <wx/bmpbuttn.h>
  26. #include <wx/debug.h>
  27. #include <wx/hyperlink.h>
  28. #include <wx/infobar.h>
  29. #include <wx/sizer.h>
  30. #include <wx/stattext.h>
  31. #include <wx/timer.h>
  32. #include <eda_base_frame.h>
  33. #ifdef __WXMSW__
  34. #include <dpi_scaling_common.h>
  35. #endif
  36. wxDEFINE_EVENT( KIEVT_SHOW_INFOBAR, wxCommandEvent );
  37. wxDEFINE_EVENT( KIEVT_DISMISS_INFOBAR, wxCommandEvent );
  38. BEGIN_EVENT_TABLE( WX_INFOBAR, wxInfoBarGeneric )
  39. EVT_COMMAND( wxID_ANY, KIEVT_SHOW_INFOBAR, WX_INFOBAR::onShowInfoBar )
  40. EVT_COMMAND( wxID_ANY, KIEVT_DISMISS_INFOBAR, WX_INFOBAR::onDismissInfoBar )
  41. EVT_SYS_COLOUR_CHANGED( WX_INFOBAR::onThemeChange )
  42. EVT_BUTTON( ID_CLOSE_INFOBAR, WX_INFOBAR::onCloseButton )
  43. EVT_TIMER( ID_CLOSE_INFOBAR, WX_INFOBAR::onTimer )
  44. END_EVENT_TABLE()
  45. WX_INFOBAR::WX_INFOBAR( wxWindow* aParent, wxAuiManager* aMgr, wxWindowID aWinid )
  46. : wxInfoBarGeneric( aParent, aWinid ),
  47. m_showTime( 0 ),
  48. m_updateLock( false ),
  49. m_showTimer( nullptr ),
  50. m_auiManager( aMgr ),
  51. m_type( MESSAGE_TYPE::GENERIC )
  52. {
  53. m_showTimer = new wxTimer( this, ID_CLOSE_INFOBAR );
  54. wxColour fg, bg;
  55. KIPLATFORM::UI::GetInfoBarColours( fg, bg );
  56. SetBackgroundColour( bg );
  57. SetForegroundColour( fg );
  58. #ifdef __WXMAC__
  59. // Infobar is broken on Mac without the effects
  60. SetShowHideEffects( wxSHOW_EFFECT_ROLL_TO_BOTTOM, wxSHOW_EFFECT_ROLL_TO_TOP );
  61. SetEffectDuration( 200 );
  62. #else
  63. // Infobar freezes canvas on Windows with the effect, and GTK looks bad with it
  64. SetShowHideEffects( wxSHOW_EFFECT_NONE, wxSHOW_EFFECT_NONE );
  65. #endif
  66. // The infobar seems to start too small, so increase its height
  67. int sx, sy;
  68. GetSize( &sx, &sy );
  69. sy = 1.5 * sy;
  70. // The bitmap gets cutoff sometimes with the default size, so force it to be the same
  71. // height as the infobar.
  72. wxSizer* sizer = GetSizer();
  73. wxSize iconSize = wxArtProvider::GetSizeHint( wxART_BUTTON );
  74. #ifdef __WXMSW__
  75. DPI_SCALING_COMMON dpi( nullptr, aParent );
  76. iconSize.x *= dpi.GetContentScaleFactor();
  77. sx *= dpi.GetContentScaleFactor();
  78. sy *= dpi.GetContentScaleFactor();
  79. #endif
  80. SetSize( sx, sy );
  81. sizer->SetItemMinSize( (size_t) 0, iconSize.x, sy );
  82. // Forcefully remove all existing buttons added by the wx constructors.
  83. // The default close button doesn't work with the AUI manager update scheme, so this
  84. // ensures any close button displayed is ours.
  85. RemoveAllButtons();
  86. Layout();
  87. m_parent->Bind( wxEVT_SIZE, &WX_INFOBAR::onSize, this );
  88. }
  89. WX_INFOBAR::~WX_INFOBAR()
  90. {
  91. m_parent->Unbind( wxEVT_SIZE, &WX_INFOBAR::onSize, this );
  92. delete m_showTimer;
  93. }
  94. void WX_INFOBAR::SetShowTime( int aTime )
  95. {
  96. m_showTime = aTime;
  97. }
  98. void WX_INFOBAR::QueueShowMessage( const wxString& aMessage, int aFlags )
  99. {
  100. wxCommandEvent* evt = new wxCommandEvent( KIEVT_SHOW_INFOBAR );
  101. evt->SetString( aMessage.c_str() );
  102. evt->SetInt( aFlags );
  103. GetEventHandler()->QueueEvent( evt );
  104. }
  105. void WX_INFOBAR::QueueDismiss()
  106. {
  107. wxCommandEvent* evt = new wxCommandEvent( KIEVT_DISMISS_INFOBAR );
  108. GetEventHandler()->QueueEvent( evt );
  109. }
  110. void WX_INFOBAR::ShowMessageFor( const wxString& aMessage, int aTime, int aFlags,
  111. MESSAGE_TYPE aType )
  112. {
  113. // Don't do anything if we requested the UI update
  114. if( m_updateLock )
  115. return;
  116. m_showTime = aTime;
  117. ShowMessage( aMessage, aFlags );
  118. m_type = aType;
  119. }
  120. void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags )
  121. {
  122. // Don't do anything if we requested the UI update
  123. if( m_updateLock )
  124. return;
  125. m_updateLock = true;
  126. m_message = aMessage;
  127. m_message.Trim();
  128. wxInfoBarGeneric::ShowMessage( m_message, aFlags );
  129. if( m_auiManager )
  130. updateAuiLayout( true );
  131. if( m_showTime > 0 )
  132. m_showTimer->StartOnce( m_showTime );
  133. m_type = MESSAGE_TYPE::GENERIC;
  134. m_updateLock = false;
  135. }
  136. void WX_INFOBAR::ShowMessage( const wxString& aMessage, int aFlags, MESSAGE_TYPE aType )
  137. {
  138. // Don't do anything if we requested the UI update
  139. if( m_updateLock )
  140. return;
  141. ShowMessage( aMessage, aFlags );
  142. m_type = aType;
  143. }
  144. void WX_INFOBAR::Dismiss()
  145. {
  146. if( !IsShownOnScreen() )
  147. return;
  148. // Don't do anything if we requested the UI update
  149. if( m_updateLock )
  150. return;
  151. m_updateLock = true;
  152. wxInfoBarGeneric::Dismiss();
  153. if( m_auiManager )
  154. updateAuiLayout( false );
  155. if( m_callback )
  156. (*m_callback)();
  157. m_updateLock = false;
  158. }
  159. void WX_INFOBAR::onThemeChange( wxSysColourChangedEvent& aEvent )
  160. {
  161. wxColour fg, bg;
  162. KIPLATFORM::UI::GetInfoBarColours( fg, bg );
  163. SetBackgroundColour( bg );
  164. SetForegroundColour( fg );
  165. if( wxBitmapButton* btn = GetCloseButton() )
  166. {
  167. wxString tooltip = btn->GetToolTipText();
  168. RemoveAllButtons();
  169. AddCloseButton( tooltip );
  170. }
  171. }
  172. void WX_INFOBAR::onSize( wxSizeEvent& aEvent )
  173. {
  174. int barWidth = GetSize().GetWidth();
  175. wxSizer* sizer = GetSizer();
  176. if( !sizer )
  177. return;
  178. wxSizerItem* text = sizer->GetItem( 1 );
  179. if( text )
  180. {
  181. if( auto textCtrl = dynamic_cast<wxStaticText*>( text->GetWindow() ) )
  182. textCtrl->SetLabelText( m_message );
  183. }
  184. // Calculate the horizontal size: because the infobar is shown on top of the draw canvas
  185. // it is adjusted to the canvas width.
  186. // On Mac, the canvas is the parent
  187. // On other OS the parent is EDA_BASE_FRAME that contains the canvas
  188. int parentWidth = m_parent->GetClientSize().GetWidth();
  189. EDA_BASE_FRAME* frame = dynamic_cast<EDA_BASE_FRAME*>( m_parent );
  190. if( frame && frame->GetToolCanvas() )
  191. parentWidth = frame->GetToolCanvas()->GetSize().GetWidth();
  192. if( barWidth != parentWidth )
  193. SetSize( parentWidth, GetSize().GetHeight() );
  194. if( text )
  195. {
  196. if( auto textCtrl = dynamic_cast<wxStaticText*>( text->GetWindow() ) )
  197. {
  198. // Re-wrap the text (this is done automatically later but we need it now)
  199. // And count how many lines we need. If we have embedded newlines, then
  200. // multiply the number of lines by the text min height to find the correct
  201. // min height for the control. The min height of the text control will be the size
  202. // of a single line of text. This assumes that two lines of text are larger
  203. // than the height of the icon for the bar.
  204. textCtrl->Wrap( text->GetSize().GetWidth() );
  205. wxString new_text = textCtrl->GetLabel();
  206. int height = ( new_text.Freq( '\n' ) + 1 ) * text->GetMinSize().GetHeight();
  207. SetMinSize( wxSize( GetSize().GetWidth(), height ) );
  208. }
  209. }
  210. aEvent.Skip();
  211. }
  212. void WX_INFOBAR::updateAuiLayout( bool aShow )
  213. {
  214. wxASSERT( m_auiManager );
  215. wxAuiPaneInfo& pane = m_auiManager->GetPane( this );
  216. // If the infobar is in a pane, then show/hide the pane
  217. if( pane.IsOk() )
  218. {
  219. if( aShow )
  220. pane.Show();
  221. else
  222. pane.Hide();
  223. }
  224. // Update the AUI manager regardless
  225. m_auiManager->Update();
  226. }
  227. void WX_INFOBAR::AddButton( wxWindowID aId, const wxString& aLabel )
  228. {
  229. wxButton* button = new wxButton( this, aId, aLabel );
  230. AddButton( button );
  231. }
  232. void WX_INFOBAR::AddButton( wxButton* aButton )
  233. {
  234. wxSizer* sizer = GetSizer();
  235. wxASSERT( aButton );
  236. #ifdef __WXMAC__
  237. // Based on the code in the original class:
  238. // smaller buttons look better in the (narrow) info bar under OS X
  239. aButton->SetWindowVariant( wxWINDOW_VARIANT_SMALL );
  240. #endif // __WXMAC__
  241. auto element = sizer->Add( aButton, wxSizerFlags( 0 ).Centre().Border( wxRIGHT ) );
  242. element->SetFlag( wxSTRETCH_MASK );
  243. if( IsShownOnScreen() )
  244. sizer->Layout();
  245. }
  246. void WX_INFOBAR::AddButton( wxHyperlinkCtrl* aHypertextButton )
  247. {
  248. wxSizer* sizer = GetSizer();
  249. wxASSERT( aHypertextButton );
  250. sizer->Add( aHypertextButton, wxSizerFlags().Centre().Border( wxRIGHT ).Shaped() );
  251. if( IsShownOnScreen() )
  252. sizer->Layout();
  253. }
  254. void WX_INFOBAR::AddCloseButton( const wxString& aTooltip )
  255. {
  256. wxBitmapButton* button = wxBitmapButton::NewCloseButton( this, ID_CLOSE_INFOBAR );
  257. button->SetToolTip( aTooltip );
  258. AddButton( button );
  259. }
  260. void WX_INFOBAR::RemoveAllButtons()
  261. {
  262. wxSizer* sizer = GetSizer();
  263. if( sizer->GetItemCount() == 0 )
  264. return;
  265. // The last item is already the spacer
  266. if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
  267. return;
  268. for( int i = sizer->GetItemCount() - 1; i >= 0; i-- )
  269. {
  270. wxSizerItem* sItem = sizer->GetItem( i );
  271. // The spacer is the end of the custom buttons
  272. if( sItem->IsSpacer() )
  273. break;
  274. delete sItem->GetWindow();
  275. }
  276. }
  277. bool WX_INFOBAR::HasCloseButton() const
  278. {
  279. return GetCloseButton();
  280. }
  281. wxBitmapButton* WX_INFOBAR::GetCloseButton() const
  282. {
  283. wxSizer* sizer = GetSizer();
  284. if( !sizer )
  285. return nullptr;
  286. if( sizer->GetItemCount() == 0 )
  287. return nullptr;
  288. if( sizer->GetItem( sizer->GetItemCount() - 1 )->IsSpacer() )
  289. return nullptr;
  290. wxSizerItem* item = sizer->GetItem( sizer->GetItemCount() - 1 );
  291. if( item && item->GetWindow() && item->GetWindow()->GetId() == ID_CLOSE_INFOBAR )
  292. return static_cast<wxBitmapButton*>( item->GetWindow() );
  293. return nullptr;
  294. }
  295. void WX_INFOBAR::onShowInfoBar( wxCommandEvent& aEvent )
  296. {
  297. RemoveAllButtons();
  298. AddCloseButton();
  299. ShowMessage( aEvent.GetString(), aEvent.GetInt() );
  300. }
  301. void WX_INFOBAR::onDismissInfoBar( wxCommandEvent& aEvent )
  302. {
  303. Dismiss();
  304. }
  305. void WX_INFOBAR::onCloseButton( wxCommandEvent& aEvent )
  306. {
  307. Dismiss();
  308. }
  309. void WX_INFOBAR::onTimer( wxTimerEvent& aEvent )
  310. {
  311. // Reset and clear the timer
  312. m_showTimer->Stop();
  313. m_showTime = 0;
  314. Dismiss();
  315. }
  316. EDA_INFOBAR_PANEL::EDA_INFOBAR_PANEL( wxWindow* aParent, wxWindowID aId, const wxPoint& aPos,
  317. const wxSize& aSize, long aStyle, const wxString& aName )
  318. : wxPanel( aParent, aId, aPos, aSize, aStyle, aName )
  319. {
  320. m_mainSizer = new wxFlexGridSizer( 1, 0, 0 );
  321. m_mainSizer->SetFlexibleDirection( wxBOTH );
  322. m_mainSizer->AddGrowableCol( 0, 1 );
  323. SetSizer( m_mainSizer );
  324. }
  325. void EDA_INFOBAR_PANEL::AddInfoBar( WX_INFOBAR* aInfoBar )
  326. {
  327. wxASSERT( aInfoBar );
  328. aInfoBar->Reparent( this );
  329. m_mainSizer->Add( aInfoBar, 1, wxEXPAND, 0 );
  330. m_mainSizer->Layout();
  331. }
  332. void EDA_INFOBAR_PANEL::AddOtherItem( wxWindow* aOtherItem )
  333. {
  334. wxASSERT( aOtherItem );
  335. aOtherItem->Reparent( this );
  336. m_mainSizer->Add( aOtherItem, 1, wxEXPAND, 0 );
  337. m_mainSizer->AddGrowableRow( 1, 1 );
  338. m_mainSizer->Layout();
  339. }
  340. REPORTER& INFOBAR_REPORTER::Report( const wxString& aText, SEVERITY aSeverity )
  341. {
  342. m_message.reset( new wxString( aText ) );
  343. m_severity = aSeverity;
  344. m_messageSet = true;
  345. return *this;
  346. }
  347. bool INFOBAR_REPORTER::HasMessage() const
  348. {
  349. return m_message && !m_message->IsEmpty();
  350. }
  351. void INFOBAR_REPORTER::Finalize()
  352. {
  353. // Don't do anything if no message was ever given
  354. if( !m_infoBar || !m_messageSet )
  355. return;
  356. // Short circuit if the message is empty and it is already hidden
  357. if( !HasMessage() && !m_infoBar->IsShownOnScreen() )
  358. return;
  359. int icon = wxICON_NONE;
  360. switch( m_severity )
  361. {
  362. case RPT_SEVERITY_UNDEFINED: icon = wxICON_INFORMATION; break;
  363. case RPT_SEVERITY_INFO: icon = wxICON_INFORMATION; break;
  364. case RPT_SEVERITY_EXCLUSION: icon = wxICON_WARNING; break;
  365. case RPT_SEVERITY_ACTION: icon = wxICON_WARNING; break;
  366. case RPT_SEVERITY_WARNING: icon = wxICON_WARNING; break;
  367. case RPT_SEVERITY_ERROR: icon = wxICON_ERROR; break;
  368. case RPT_SEVERITY_IGNORE: icon = wxICON_INFORMATION; break;
  369. case RPT_SEVERITY_DEBUG: icon = wxICON_INFORMATION; break;
  370. }
  371. if( m_message->EndsWith( wxS( "\n" ) ) )
  372. *m_message = m_message->Left( m_message->Length() - 1 );
  373. if( HasMessage() )
  374. m_infoBar->QueueShowMessage( *m_message, icon );
  375. else
  376. m_infoBar->QueueDismiss();
  377. }