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.

436 lines
11 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 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 <bitmaps.h>
  20. #include <widgets/wx_collapsible_pane.h>
  21. #include <wx/collpane.h>
  22. #include <wx/dc.h>
  23. #include <wx/dcclient.h>
  24. #include <wx/panel.h>
  25. #include <wx/renderer.h>
  26. #include <wx/settings.h>
  27. #include <wx/sizer.h>
  28. #include <wx/toplevel.h>
  29. #include <wx/window.h>
  30. #ifdef _WIN32
  31. #include <windows.h>
  32. #endif
  33. #include <algorithm>
  34. wxDEFINE_EVENT( WX_COLLAPSIBLE_PANE_HEADER_CHANGED, wxCommandEvent );
  35. wxDEFINE_EVENT( WX_COLLAPSIBLE_PANE_CHANGED, wxCommandEvent );
  36. bool WX_COLLAPSIBLE_PANE:: Create( wxWindow* aParent, wxWindowID aId, const wxString& aLabel,
  37. const wxPoint& aPos, const wxSize& aSize, long aStyle,
  38. const wxValidator& aValidator, const wxString& aName )
  39. {
  40. if( !wxControl::Create( aParent, aId, aPos, aSize, aStyle, aValidator, aName ) )
  41. return false;
  42. m_sizer = new wxBoxSizer( wxVERTICAL );
  43. m_header = new WX_COLLAPSIBLE_PANE_HEADER( this, wxID_ANY, aLabel, wxPoint( 0, 0 ),
  44. wxDefaultSize );
  45. m_sizer->Add( m_header, wxSizerFlags().Border( wxBOTTOM, getBorder() ) );
  46. m_pane = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
  47. wxTAB_TRAVERSAL | wxNO_BORDER, wxT( "COLLAPSIBLE_PANE_PANE" ) );
  48. m_pane->Hide();
  49. Bind( wxEVT_SIZE, &WX_COLLAPSIBLE_PANE::onSize, this );
  50. Bind( WX_COLLAPSIBLE_PANE_HEADER_CHANGED, &WX_COLLAPSIBLE_PANE::onHeaderClicked, this );
  51. return true;
  52. }
  53. void WX_COLLAPSIBLE_PANE::init()
  54. {
  55. m_pane = nullptr;
  56. m_sizer = nullptr;
  57. m_header = nullptr;
  58. }
  59. WX_COLLAPSIBLE_PANE::~WX_COLLAPSIBLE_PANE()
  60. {
  61. if( m_sizer )
  62. m_sizer->SetContainingWindow( nullptr );
  63. // Not owned by wxWindow
  64. delete m_sizer;
  65. }
  66. void WX_COLLAPSIBLE_PANE::Collapse( bool aCollapse )
  67. {
  68. if( IsCollapsed() == aCollapse )
  69. return;
  70. InvalidateBestSize();
  71. m_pane->Show( !aCollapse );
  72. m_header->SetCollapsed( aCollapse );
  73. SetSize( GetBestSize() );
  74. }
  75. bool WX_COLLAPSIBLE_PANE::IsCollapsed() const
  76. {
  77. return !m_pane || !m_pane->IsShown();
  78. }
  79. void WX_COLLAPSIBLE_PANE::SetLabel( const wxString& aLabel )
  80. {
  81. m_header->SetLabel( aLabel );
  82. m_header->SetInitialSize();
  83. Layout();
  84. }
  85. bool WX_COLLAPSIBLE_PANE::SetBackgroundColour( const wxColour& aColor )
  86. {
  87. m_header->SetBackgroundColour( aColor );
  88. return wxWindow::SetBackgroundColour( aColor );
  89. }
  90. bool WX_COLLAPSIBLE_PANE::InformFirstDirection( int aDirection, int aSize, int aAvailableOtherDir )
  91. {
  92. wxWindow* const pane = GetPane();
  93. if( !pane )
  94. return false;
  95. if( !pane->InformFirstDirection( aDirection, aSize, aAvailableOtherDir ) )
  96. return false;
  97. InvalidateBestSize();
  98. return true;
  99. }
  100. wxSize WX_COLLAPSIBLE_PANE::DoGetBestClientSize() const
  101. {
  102. wxSize size = m_sizer->GetMinSize();
  103. if( IsExpanded() )
  104. {
  105. wxSize paneSize = m_pane->GetBestSize();
  106. size.SetWidth( std::max( size.GetWidth(), paneSize.x ) );
  107. size.SetHeight( size.y + getBorder() + paneSize.y );
  108. }
  109. return size;
  110. }
  111. bool WX_COLLAPSIBLE_PANE::Layout()
  112. {
  113. if( !m_sizer || !m_pane || !m_header )
  114. return false;
  115. wxSize size( GetSize() );
  116. m_sizer->SetDimension( 0, 0, size.x, m_sizer->GetMinSize().y );
  117. m_sizer->Layout();
  118. if( IsExpanded() )
  119. {
  120. int yoffset = m_sizer->GetSize().y + getBorder();
  121. m_pane->SetSize( 0, yoffset, size.x, size.y - yoffset );
  122. m_pane->Layout();
  123. }
  124. return true;
  125. }
  126. int WX_COLLAPSIBLE_PANE::getBorder() const
  127. {
  128. #if defined( __WXMSW__ )
  129. wxASSERT( m_header );
  130. return m_header->ConvertDialogToPixels( wxSize( 2, 0 ) ).x;
  131. #else
  132. return 3;
  133. #endif
  134. }
  135. void WX_COLLAPSIBLE_PANE::onSize( wxSizeEvent& aEvent )
  136. {
  137. Layout();
  138. aEvent.Skip();
  139. }
  140. void WX_COLLAPSIBLE_PANE::onHeaderClicked( wxCommandEvent& aEvent )
  141. {
  142. if( aEvent.GetEventObject() != m_header )
  143. {
  144. aEvent.Skip();
  145. return;
  146. }
  147. Collapse( !IsCollapsed() );
  148. wxCommandEvent evt( WX_COLLAPSIBLE_PANE_CHANGED, GetId() );
  149. evt.SetEventObject( this );
  150. ProcessEvent( evt );
  151. }
  152. // WX_COLLAPSIBLE_PANE_HEADER implementation
  153. void WX_COLLAPSIBLE_PANE_HEADER::init()
  154. {
  155. m_collapsed = true;
  156. m_inWindow = false;
  157. }
  158. bool WX_COLLAPSIBLE_PANE_HEADER::Create( wxWindow* aParent, wxWindowID aId, const wxString& aLabel,
  159. const wxPoint& aPos, const wxSize& aSize, long aStyle,
  160. const wxValidator& aValidator, const wxString& aName )
  161. {
  162. if ( !wxControl::Create( aParent, aId, aPos, aSize, aStyle, aValidator, aName ) )
  163. return false;
  164. SetLabel( aLabel );
  165. Bind( wxEVT_PAINT, &WX_COLLAPSIBLE_PANE_HEADER::onPaint, this );
  166. Bind( wxEVT_SET_FOCUS, &WX_COLLAPSIBLE_PANE_HEADER::onFocus, this );
  167. Bind( wxEVT_KILL_FOCUS, &WX_COLLAPSIBLE_PANE_HEADER::onFocus, this );
  168. Bind( wxEVT_ENTER_WINDOW, &WX_COLLAPSIBLE_PANE_HEADER::onEnterWindow, this);
  169. Bind( wxEVT_LEAVE_WINDOW, &WX_COLLAPSIBLE_PANE_HEADER::onLeaveWindow, this);
  170. Bind( wxEVT_LEFT_UP, &WX_COLLAPSIBLE_PANE_HEADER::onLeftUp, this );
  171. Bind( wxEVT_CHAR, &WX_COLLAPSIBLE_PANE_HEADER::onChar, this );
  172. return true;
  173. }
  174. void WX_COLLAPSIBLE_PANE_HEADER::SetCollapsed( bool aCollapsed )
  175. {
  176. m_collapsed = aCollapsed;
  177. Refresh();
  178. }
  179. void WX_COLLAPSIBLE_PANE_HEADER::doSetCollapsed( bool aCollapsed )
  180. {
  181. SetCollapsed( aCollapsed );
  182. wxCommandEvent evt( WX_COLLAPSIBLE_PANE_HEADER_CHANGED, GetId() );
  183. evt.SetEventObject( this );
  184. ProcessEvent( evt );
  185. }
  186. wxSize WX_COLLAPSIBLE_PANE_HEADER::DoGetBestClientSize() const
  187. {
  188. WX_COLLAPSIBLE_PANE_HEADER* self = const_cast<WX_COLLAPSIBLE_PANE_HEADER*>( this );
  189. // The code here parallels that of OnPaint() -- except without drawing.
  190. wxClientDC dc( self );
  191. wxString text;
  192. wxControl::FindAccelIndex( GetLabel(), &text );
  193. wxSize size = dc.GetTextExtent( text );
  194. // Reserve space for arrow (which is a square the height of the text)
  195. size.x += size.GetHeight();
  196. #ifdef __WXMSW__
  197. size.IncBy( GetSystemMetrics( SM_CXFOCUSBORDER ),
  198. GetSystemMetrics( SM_CYFOCUSBORDER ) );
  199. #endif // __WXMSW__
  200. return size;
  201. }
  202. void WX_COLLAPSIBLE_PANE_HEADER::onPaint( wxPaintEvent& aEvent )
  203. {
  204. wxPaintDC dc( this );
  205. wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
  206. #ifdef __WXMSW__
  207. wxBrush brush = dc.GetBrush();
  208. brush.SetColour( GetParent()->GetBackgroundColour() );
  209. dc.SetBrush( brush );
  210. dc.SetPen( *wxTRANSPARENT_PEN );
  211. dc.DrawRectangle( rect );
  212. #endif
  213. // Make the background look like a button when the pointer is over it
  214. if( m_inWindow )
  215. {
  216. dc.SetBrush( wxBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNHIGHLIGHT ) ) );
  217. dc.SetPen( *wxTRANSPARENT_PEN );
  218. dc.DrawRectangle( rect );
  219. }
  220. wxString text;
  221. int indexAccel = wxControl::FindAccelIndex( GetLabel(), &text );
  222. wxSize textSize = dc.GetTextExtent( text );
  223. // Compute all the sizes
  224. wxRect arrowRect( 0, 0, textSize.GetHeight(), textSize.GetHeight() );
  225. wxRect textRect( arrowRect.GetTopRight(), textSize );
  226. textRect = textRect.CenterIn( rect, wxVERTICAL );
  227. // Find out if the window we are in is active or not
  228. bool isActive = true;
  229. wxTopLevelWindow* tlw = dynamic_cast<wxTopLevelWindow*>( wxGetTopLevelParent( this ) );
  230. if( tlw && !tlw->IsActive() )
  231. isActive = false;
  232. // Draw the arrow
  233. drawArrow( dc, arrowRect, isActive );
  234. // We are responsible for showing the text as disabled when the window isn't active
  235. wxColour clr;
  236. if( isActive )
  237. clr = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
  238. else
  239. clr = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
  240. dc.SetTextForeground( clr );
  241. dc.DrawLabel( text, textRect, wxALIGN_CENTER_VERTICAL, indexAccel );
  242. #ifdef __WXMSW__
  243. int flags = 0;
  244. if( m_inWindow )
  245. flags |= wxCONTROL_CURRENT;
  246. int focusSize = GetSystemMetrics( SM_CXFOCUSBORDER );
  247. if( HasFocus() )
  248. wxRendererNative::Get().DrawFocusRect( this, dc, textRect.Inflate( focusSize ), flags );
  249. #endif
  250. }
  251. void WX_COLLAPSIBLE_PANE_HEADER::onFocus( wxFocusEvent& aEvent )
  252. {
  253. Refresh();
  254. aEvent.Skip();
  255. }
  256. void WX_COLLAPSIBLE_PANE_HEADER::onEnterWindow( wxMouseEvent& aEvent )
  257. {
  258. m_inWindow = true;
  259. Refresh();
  260. aEvent.Skip();
  261. }
  262. void WX_COLLAPSIBLE_PANE_HEADER::onLeaveWindow( wxMouseEvent& aEvent )
  263. {
  264. m_inWindow = false;
  265. Refresh();
  266. aEvent.Skip();
  267. }
  268. void WX_COLLAPSIBLE_PANE_HEADER::onLeftUp( wxMouseEvent& aEvent )
  269. {
  270. doSetCollapsed( !m_collapsed );
  271. aEvent.Skip();
  272. }
  273. void WX_COLLAPSIBLE_PANE_HEADER::onChar( wxKeyEvent& aEvent )
  274. {
  275. switch( aEvent.GetKeyCode() )
  276. {
  277. case WXK_SPACE:
  278. case WXK_RETURN:
  279. case WXK_NUMPAD_ENTER:
  280. doSetCollapsed( !m_collapsed );
  281. break;
  282. default:
  283. aEvent.Skip();
  284. break;
  285. }
  286. }
  287. void WX_COLLAPSIBLE_PANE_HEADER::drawArrow( wxDC& aDC, wxRect aRect, bool aIsActive )
  288. {
  289. // The bottom corner of the triangle is located halfway across the area and 3/4 down from the top
  290. wxPoint btmCorner( aRect.GetWidth() / 2, 3 * aRect.GetHeight() / 4 );
  291. // The right corner of the triangle is located halfway down from the top and 3/4 across the area
  292. wxPoint rtCorner( 3 * aRect.GetWidth() / 4, aRect.GetHeight() / 2 );
  293. // Choose the other corner depending on if the panel is expanded or collapsed
  294. wxPoint otherCorner( 0, 0 );
  295. if( m_collapsed )
  296. otherCorner = wxPoint( aRect.GetWidth() / 2, aRect.GetHeight() / 4 );
  297. else
  298. otherCorner = wxPoint( aRect.GetWidth() / 4, aRect.GetHeight() / 2 );
  299. // Choose the color to draw the triangle
  300. wxColour clr;
  301. // Highlight the arrow when the pointer is inside the header, otherwise use text color
  302. if( m_inWindow )
  303. clr = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
  304. else
  305. clr = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
  306. // If the window isn't active, then use the disabled text color
  307. if( !aIsActive )
  308. clr = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
  309. // Must set both the pen (for the outline) and the brush (for the polygon fill)
  310. aDC.SetPen( wxPen( clr ) );
  311. aDC.SetBrush( wxBrush( clr ) );
  312. // Draw the triangle
  313. wxPointList points;
  314. points.Append( &btmCorner );
  315. points.Append( &rtCorner );
  316. points.Append( &otherCorner );
  317. aDC.DrawPolygon( &points );
  318. }