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.

345 lines
9.0 KiB

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 (C) 2016 Anil8735(https://stackoverflow.com/users/3659387/anil8753)
  5. * from https://stackoverflow.com/a/37274011
  6. * Copyright (C) 2020-2022 Kicad Developers, see AUTHORS.txt for contributors.
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <widgets/split_button.h>
  26. #include <wx/button.h>
  27. #include <wx/dcclient.h>
  28. #include <wx/dcmemory.h>
  29. #include <wx/menu.h>
  30. #include <wx/renderer.h>
  31. #include <wx/settings.h>
  32. #include <wx/version.h>
  33. #include <kiplatform/ui.h>
  34. SPLIT_BUTTON::SPLIT_BUTTON( wxWindow* aParent, wxWindowID aId, const wxString& aLabel,
  35. const wxPoint& aPos, const wxSize& aSize ) :
  36. wxPanel( aParent, aId, aPos, aSize, wxBORDER_NONE | wxTAB_TRAVERSAL, wxS( "DropDownButton" ) ),
  37. m_label( aLabel )
  38. {
  39. if( aSize == wxDefaultSize )
  40. {
  41. #if wxCHECK_VERSION( 3, 1, 3 )
  42. wxSize defaultSize = wxButton::GetDefaultSize( aParent );
  43. #else
  44. wxSize defaultSize = wxButton::GetDefaultSize();
  45. #endif
  46. wxSize textSize = GetTextExtent( m_label );
  47. SetMinSize( wxSize( std::max( textSize.GetWidth(), defaultSize.GetWidth() + 1 ),
  48. defaultSize.GetHeight() + 1 ) );
  49. }
  50. Bind( wxEVT_PAINT, &SPLIT_BUTTON::OnPaint, this );
  51. Bind( wxEVT_LEFT_UP, &SPLIT_BUTTON::OnLeftButtonUp, this );
  52. Bind( wxEVT_LEFT_DOWN, &SPLIT_BUTTON::OnLeftButtonDown, this );
  53. Bind( wxEVT_KILL_FOCUS, &SPLIT_BUTTON::OnKillFocus, this );
  54. Bind( wxEVT_LEAVE_WINDOW, &SPLIT_BUTTON::OnMouseLeave, this );
  55. Bind( wxEVT_ENTER_WINDOW, &SPLIT_BUTTON::OnMouseEnter, this );
  56. Bind( wxEVT_SYS_COLOUR_CHANGED, wxSysColourChangedEventHandler( SPLIT_BUTTON::onThemeChanged ),
  57. this );
  58. m_pMenu = new wxMenu();
  59. }
  60. SPLIT_BUTTON::~SPLIT_BUTTON()
  61. {
  62. delete m_pMenu;
  63. m_pMenu = nullptr;
  64. }
  65. void SPLIT_BUTTON::onThemeChanged( wxSysColourChangedEvent &aEvent )
  66. {
  67. Refresh();
  68. }
  69. void SPLIT_BUTTON::SetMinSize( const wxSize& aSize )
  70. {
  71. m_unadjustedMinSize = aSize;
  72. wxPanel::SetMinSize( wxSize( aSize.GetWidth() + m_arrowButtonWidth + m_widthPadding,
  73. aSize.GetHeight() ) );
  74. }
  75. void SPLIT_BUTTON::SetWidthPadding( int aPadding )
  76. {
  77. m_widthPadding = aPadding;
  78. SetMinSize( m_unadjustedMinSize );
  79. }
  80. void SPLIT_BUTTON::SetBitmap( const wxBitmap& aBmp )
  81. {
  82. m_bitmap = aBmp;
  83. SetMinSize( wxSize( m_bitmap.GetWidth(), m_bitmap.GetHeight() ) );
  84. }
  85. void SPLIT_BUTTON::SetLabel( const wxString& aLabel )
  86. {
  87. if( m_label != aLabel )
  88. {
  89. m_label = aLabel;
  90. Refresh();
  91. }
  92. }
  93. wxMenu* SPLIT_BUTTON::GetSplitButtonMenu()
  94. {
  95. return m_pMenu;
  96. }
  97. void SPLIT_BUTTON::OnKillFocus( wxFocusEvent& aEvent )
  98. {
  99. if( m_stateButton != 0 || m_stateMenu != 0 )
  100. {
  101. m_stateButton = 0;
  102. m_stateMenu = 0;
  103. Refresh();
  104. }
  105. aEvent.Skip();
  106. }
  107. void SPLIT_BUTTON::OnMouseLeave( wxMouseEvent& aEvent )
  108. {
  109. if( m_stateButton != 0 || m_stateMenu != 0 )
  110. {
  111. m_stateButton = 0;
  112. m_stateMenu = 0;
  113. Refresh();
  114. }
  115. aEvent.Skip();
  116. }
  117. void SPLIT_BUTTON::OnMouseEnter( wxMouseEvent& aEvent )
  118. {
  119. if( m_stateButton != wxCONTROL_CURRENT || m_stateMenu != wxCONTROL_CURRENT )
  120. {
  121. m_stateButton = wxCONTROL_CURRENT;
  122. m_stateMenu = wxCONTROL_CURRENT;
  123. Refresh();
  124. }
  125. aEvent.Skip();
  126. }
  127. void SPLIT_BUTTON::OnLeftButtonUp( wxMouseEvent& aEvent )
  128. {
  129. m_stateButton = 0;
  130. m_stateMenu = 0;
  131. Refresh();
  132. int x = -1;
  133. int y = -1;
  134. aEvent.GetPosition( &x, &y );
  135. if( x < ( GetSize().GetWidth() - m_arrowButtonWidth ) )
  136. {
  137. wxEvtHandler* pEventHandler = GetEventHandler();
  138. wxASSERT( pEventHandler );
  139. pEventHandler->CallAfter(
  140. [=]()
  141. {
  142. wxCommandEvent evt( wxEVT_BUTTON, GetId() );
  143. evt.SetEventObject( this );
  144. GetEventHandler()->ProcessEvent( evt );
  145. } );
  146. }
  147. m_bLButtonDown = false;
  148. aEvent.Skip();
  149. }
  150. void SPLIT_BUTTON::OnLeftButtonDown( wxMouseEvent& aEvent )
  151. {
  152. m_bLButtonDown = true;
  153. int x = -1;
  154. int y = -1;
  155. aEvent.GetPosition( &x, &y );
  156. if( x >= ( GetSize().GetWidth() - m_arrowButtonWidth ) )
  157. {
  158. m_stateButton = 0;
  159. m_stateMenu = wxCONTROL_PRESSED;
  160. Refresh();
  161. wxSize size = GetSize();
  162. wxPoint position;
  163. position.x = 0;
  164. position.y = size.GetHeight();
  165. PopupMenu( m_pMenu, position );
  166. m_stateMenu = 0;
  167. Refresh();
  168. }
  169. else
  170. {
  171. m_stateButton = wxCONTROL_PRESSED;
  172. m_stateMenu = wxCONTROL_PRESSED;
  173. Refresh();
  174. }
  175. aEvent.Skip();
  176. }
  177. void SPLIT_BUTTON::OnPaint( wxPaintEvent& WXUNUSED( aEvent ) )
  178. {
  179. wxPaintDC dc( this );
  180. wxSize size = GetSize();
  181. const int width = size.GetWidth() - m_arrowButtonWidth;
  182. #ifdef __WXMAC__
  183. auto drawBackground =
  184. [&]( wxRect aRect )
  185. {
  186. // wxWidgets doesn't have much support for dark mode on OSX; none of the
  187. // system colours return the right values, nor does wxRendererNative draw
  188. // the borders correctly. So we add some empirically chosen hacks here.
  189. // NOTE: KEEP THESE HACKS IN SYNC WITH STD_BITMAP_BUTTON
  190. wxColor fg = wxSystemSettings::GetColour( wxSYS_COLOUR_BTNTEXT );
  191. wxColor bg = wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE );
  192. aRect.width += 1;
  193. aRect.height += 1;
  194. if( KIPLATFORM::UI::IsDarkTheme() )
  195. {
  196. bg = bg.ChangeLightness( m_bIsEnable ? 130 : 120 );
  197. dc.SetBrush( bg );
  198. dc.SetPen( bg );
  199. }
  200. else
  201. {
  202. bg = bg.ChangeLightness( m_bIsEnable ? 200 : 160 );
  203. dc.SetBrush( bg );
  204. fg = fg.ChangeLightness( 180 );
  205. dc.SetPen( fg );
  206. }
  207. dc.DrawRoundedRectangle( aRect, aRect.height / 4.0 );
  208. };
  209. #endif
  210. // Draw first part of button
  211. wxRect r1;
  212. r1.x = 0;
  213. r1.y = 0;
  214. r1.width = width;
  215. r1.height = size.GetHeight();
  216. #ifdef __WXMAC__
  217. // wxRendereNative doesn't handle dark mode on OSX.
  218. drawBackground( r1 );
  219. #else
  220. #ifdef _WXMSW_
  221. r1.width += 2;
  222. #endif
  223. wxRendererNative::Get().DrawPushButton( this, dc, r1, m_stateButton );
  224. #endif
  225. SetForegroundColour( m_bIsEnable ? wxSystemSettings::GetColour( wxSYS_COLOUR_BTNTEXT )
  226. : wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
  227. if( m_bitmap.IsOk() )
  228. {
  229. wxMemoryDC mdc( m_bitmap );
  230. r1.x = ( width - m_bitmap.GetWidth() ) / 2;
  231. if( r1.x < 0 )
  232. r1.x = 0;
  233. r1.y += ( size.GetHeight() - m_bitmap.GetHeight() ) / 2;
  234. dc.Blit( wxPoint( r1.x, r1.y ), m_bitmap.GetSize(), &mdc, wxPoint( 0, 0 ), wxCOPY, true );
  235. }
  236. else
  237. {
  238. r1.y += ( ( size.GetHeight() - GetCharHeight() ) / 2 ) - 1;
  239. dc.DrawLabel( m_label, r1, wxALIGN_CENTER_HORIZONTAL );
  240. }
  241. // Draw second part of button
  242. wxRect r2;
  243. r2.x = width;
  244. r2.y = 0;
  245. r2.width = m_arrowButtonWidth;
  246. r2.height = size.GetHeight();
  247. #ifdef __WXMAC__
  248. // wxRendereNative doesn't handle dark mode on OSX.
  249. drawBackground( r2 );
  250. #else
  251. r2.x -= 2;
  252. wxRendererNative::Get().DrawPushButton( this, dc, r2, m_stateMenu );
  253. #endif
  254. wxRendererNative::Get().DrawDropArrow( this, dc, r2, m_stateMenu );
  255. }
  256. bool SPLIT_BUTTON::Enable( bool aEnable )
  257. {
  258. m_bIsEnable = aEnable;
  259. wxPanel::Enable( m_bIsEnable );
  260. if( m_bIsEnable
  261. && ( m_stateButton == wxCONTROL_DISABLED || m_stateMenu == wxCONTROL_DISABLED ) )
  262. {
  263. m_stateButton = 0;
  264. m_stateMenu = 0;
  265. Refresh();
  266. }
  267. if( !m_bIsEnable
  268. && ( m_stateButton != wxCONTROL_DISABLED || m_stateMenu != wxCONTROL_DISABLED ) )
  269. {
  270. m_stateButton = wxCONTROL_DISABLED;
  271. m_stateMenu = wxCONTROL_DISABLED;
  272. Refresh();
  273. }
  274. return aEnable;
  275. }