KiCad PCB EDA Suite
collapsible_pane.cpp
Go to the documentation of this file.
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 
20 #include <bitmaps.h>
22 
23 #include <wx/collpane.h>
24 #include <wx/dc.h>
25 #include <wx/dcclient.h>
26 #include <wx/panel.h>
27 #include <wx/renderer.h>
28 #include <wx/settings.h>
29 #include <wx/sizer.h>
30 #include <wx/toplevel.h>
31 #include <wx/window.h>
32 
33 #ifdef _WIN32
34 #include <windows.h>
35 #endif
36 
37 #include <algorithm>
38 
39 
40 wxDEFINE_EVENT( WX_COLLAPSIBLE_PANE_HEADER_CHANGED, wxCommandEvent );
41 wxDEFINE_EVENT( WX_COLLAPSIBLE_PANE_CHANGED, wxCommandEvent );
42 
43 
44 bool WX_COLLAPSIBLE_PANE:: Create( wxWindow* aParent, wxWindowID aId, const wxString& aLabel,
45  const wxPoint& aPos, const wxSize& aSize, long aStyle,
46  const wxValidator& aValidator, const wxString& aName )
47 {
48  if( !wxControl::Create( aParent, aId, aPos, aSize, aStyle, aValidator, aName ) )
49  return false;
50 
51  m_sizer = new wxBoxSizer( wxVERTICAL );
52 
53  m_header = new WX_COLLAPSIBLE_PANE_HEADER( this, wxID_ANY, aLabel, wxPoint( 0, 0 ),
54  wxDefaultSize );
55 
56  m_sizer->Add( m_header, wxSizerFlags().Border( wxBOTTOM, getBorder() ) );
57 
58  m_pane = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
59  wxTAB_TRAVERSAL | wxNO_BORDER, wxT( "COLLAPSIBLE_PANE_PANE" ) );
60 
61  m_pane->Hide();
62 
63  Bind( wxEVT_SIZE, &WX_COLLAPSIBLE_PANE::onSize, this );
64  Bind( WX_COLLAPSIBLE_PANE_HEADER_CHANGED, &WX_COLLAPSIBLE_PANE::onHeaderClicked, this );
65 
66  return true;
67 }
68 
69 
71 {
72  m_pane = nullptr;
73  m_sizer = nullptr;
74  m_header = nullptr;
75 }
76 
77 
79 {
80  if( m_sizer )
81  m_sizer->SetContainingWindow( nullptr );
82 
83  // Not owned by wxWindow
84  delete m_sizer;
85 }
86 
87 
88 void WX_COLLAPSIBLE_PANE::Collapse( bool aCollapse )
89 {
90  if( IsCollapsed() == aCollapse )
91  return;
92 
93  InvalidateBestSize();
94 
95  m_pane->Show( !aCollapse );
96  m_header->SetCollapsed( aCollapse );
97 
98  SetSize( GetBestSize() );
99 }
100 
101 
103 {
104  return !m_pane || !m_pane->IsShown();
105 }
106 
107 
108 void WX_COLLAPSIBLE_PANE::SetLabel( const wxString& aLabel )
109 {
110  m_header->SetLabel( aLabel );
111  m_header->SetInitialSize();
112 
113  Layout();
114 }
115 
116 
117 bool WX_COLLAPSIBLE_PANE::SetBackgroundColour( const wxColour& aColor )
118 {
119  m_header->SetBackgroundColour( aColor );
120  return wxWindow::SetBackgroundColour( aColor );
121 }
122 
123 
124 bool WX_COLLAPSIBLE_PANE::InformFirstDirection( int aDirection, int aSize, int aAvailableOtherDir )
125 {
126  wxWindow* const pane = GetPane();
127 
128  if( !pane )
129  return false;
130 
131  if( !pane->InformFirstDirection( aDirection, aSize, aAvailableOtherDir ) )
132  return false;
133 
134  InvalidateBestSize();
135 
136  return true;
137 }
138 
139 
141 {
142  wxSize size = m_sizer->GetMinSize();
143 
144  if( IsExpanded() )
145  {
146  wxSize paneSize = m_pane->GetBestSize();
147 
148  size.SetWidth( std::max( size.GetWidth(), paneSize.x ) );
149  size.SetHeight( size.y + getBorder() + paneSize.y );
150  }
151 
152  return size;
153 }
154 
155 
157 {
158  if( !m_sizer || !m_pane || !m_header )
159  return false;
160 
161  wxSize size( GetSize() );
162 
163  m_sizer->SetDimension( 0, 0, size.x, m_sizer->GetMinSize().y );
164  m_sizer->Layout();
165 
166  if( IsExpanded() )
167  {
168  int yoffset = m_sizer->GetSize().y + getBorder();
169  m_pane->SetSize( 0, yoffset, size.x, size.y - yoffset );
170  m_pane->Layout();
171  }
172 
173  return true;
174 }
175 
176 
178 {
179 #if defined( __WXMSW__ )
180  wxASSERT( m_header );
181  return m_header->ConvertDialogToPixels( wxSize( 2, 0 ) ).x;
182 #else
183  return 3;
184 #endif
185 }
186 
187 
188 void WX_COLLAPSIBLE_PANE::onSize( wxSizeEvent& aEvent )
189 {
190  Layout();
191  aEvent.Skip();
192 }
193 
194 
195 void WX_COLLAPSIBLE_PANE::onHeaderClicked( wxCommandEvent& aEvent )
196 {
197  if( aEvent.GetEventObject() != m_header )
198  {
199  aEvent.Skip();
200  return;
201  }
202 
203  Collapse( !IsCollapsed() );
204 
205  wxCommandEvent evt( WX_COLLAPSIBLE_PANE_CHANGED, GetId() );
206  evt.SetEventObject( this );
207  ProcessEvent( evt );
208 }
209 
210 
211 // WX_COLLAPSIBLE_PANE_HEADER implementation
212 
213 
215 {
216  m_collapsed = true;
217  m_inWindow = false;
218 }
219 
220 
221 bool WX_COLLAPSIBLE_PANE_HEADER::Create( wxWindow* aParent, wxWindowID aId, const wxString& aLabel,
222  const wxPoint& aPos, const wxSize& aSize, long aStyle,
223  const wxValidator& aValidator, const wxString& aName )
224 {
225  if ( !wxControl::Create( aParent, aId, aPos, aSize, aStyle, aValidator, aName ) )
226  return false;
227 
228  SetLabel( aLabel );
229 
230  Bind( wxEVT_PAINT, &WX_COLLAPSIBLE_PANE_HEADER::onPaint, this );
231  Bind( wxEVT_SET_FOCUS, &WX_COLLAPSIBLE_PANE_HEADER::onFocus, this );
232  Bind( wxEVT_KILL_FOCUS, &WX_COLLAPSIBLE_PANE_HEADER::onFocus, this );
233  Bind( wxEVT_ENTER_WINDOW, &WX_COLLAPSIBLE_PANE_HEADER::onEnterWindow, this);
234  Bind( wxEVT_LEAVE_WINDOW, &WX_COLLAPSIBLE_PANE_HEADER::onLeaveWindow, this);
235  Bind( wxEVT_LEFT_UP, &WX_COLLAPSIBLE_PANE_HEADER::onLeftUp, this );
236  Bind( wxEVT_CHAR, &WX_COLLAPSIBLE_PANE_HEADER::onChar, this );
237 
238  return true;
239 }
240 
241 
243 {
244  m_collapsed = aCollapsed;
245  Refresh();
246 }
247 
248 
250 {
251  SetCollapsed( aCollapsed );
252 
253  wxCommandEvent evt( WX_COLLAPSIBLE_PANE_HEADER_CHANGED, GetId() );
254  evt.SetEventObject( this );
255  ProcessEvent( evt );
256 }
257 
258 
260 {
261  WX_COLLAPSIBLE_PANE_HEADER* self = const_cast<WX_COLLAPSIBLE_PANE_HEADER*>( this );
262 
263  // The code here parallels that of OnPaint() -- except without drawing.
264  wxClientDC dc( self );
265  wxString text;
266 
267  wxControl::FindAccelIndex( GetLabel(), &text );
268 
269  wxSize size = dc.GetTextExtent( text );
270 
271  // Reserve space for arrow (which is a square the height of the text)
272  size.x += size.GetHeight();
273 
274 #ifdef __WXMSW__
275  size.IncBy( GetSystemMetrics( SM_CXFOCUSBORDER ),
276  GetSystemMetrics( SM_CYFOCUSBORDER ) );
277 #endif // __WXMSW__
278 
279  return size;
280 }
281 
282 
283 void WX_COLLAPSIBLE_PANE_HEADER::onPaint( wxPaintEvent& aEvent )
284 {
285  wxPaintDC dc( this );
286  wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
287 
288 #ifdef __WXMSW__
289  wxBrush brush = dc.GetBrush();
290  brush.SetColour( GetParent()->GetBackgroundColour() );
291  dc.SetBrush( brush );
292  dc.SetPen( *wxTRANSPARENT_PEN );
293  dc.DrawRectangle( rect );
294 #endif
295 
296  // Make the background look like a button when the pointer is over it
297  if( m_inWindow )
298  {
299  dc.SetBrush( wxBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNHIGHLIGHT ) ) );
300  dc.SetPen( *wxTRANSPARENT_PEN );
301  dc.DrawRectangle( rect );
302  }
303 
304  wxString text;
305  int indexAccel = wxControl::FindAccelIndex( GetLabel(), &text );
306 
307  wxSize textSize = dc.GetTextExtent( text );
308 
309  // Compute all the sizes
310  wxRect arrowRect( 0, 0, textSize.GetHeight(), textSize.GetHeight() );
311  wxRect textRect( arrowRect.GetTopRight(), textSize );
312  textRect = textRect.CenterIn( rect, wxVERTICAL );
313 
314  // Find out if the window we are in is active or not
315  bool isActive = true;
316  wxTopLevelWindow* tlw = dynamic_cast<wxTopLevelWindow*>( wxGetTopLevelParent( this ) );
317 
318  if( tlw && !tlw->IsActive() )
319  isActive = false;
320 
321  // Draw the arrow
322  drawArrow( dc, arrowRect, isActive );
323 
324  // We are responsible for showing the text as disabled when the window isn't active
325  wxColour clr;
326 
327  if( isActive )
328  clr = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
329  else
330  clr = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
331 
332  dc.SetTextForeground( clr );
333  dc.DrawLabel( text, textRect, wxALIGN_CENTER_VERTICAL, indexAccel );
334 
335 #ifdef __WXMSW__
336  int flags = 0;
337 
338  if( m_inWindow )
339  flags |= wxCONTROL_CURRENT;
340 
341  int focusSize = GetSystemMetrics( SM_CXFOCUSBORDER );
342 
343  if( HasFocus() )
344  wxRendererNative::Get().DrawFocusRect( this, dc, textRect.Inflate( focusSize ), flags );
345 #endif
346 }
347 
348 
349 void WX_COLLAPSIBLE_PANE_HEADER::onFocus( wxFocusEvent& aEvent )
350 {
351  Refresh();
352  aEvent.Skip();
353 }
354 
355 
356 void WX_COLLAPSIBLE_PANE_HEADER::onEnterWindow( wxMouseEvent& aEvent )
357 {
358  m_inWindow = true;
359  Refresh();
360  aEvent.Skip();
361 }
362 
363 
364 void WX_COLLAPSIBLE_PANE_HEADER::onLeaveWindow( wxMouseEvent& aEvent )
365 {
366  m_inWindow = false;
367  Refresh();
368  aEvent.Skip();
369 }
370 
371 
372 void WX_COLLAPSIBLE_PANE_HEADER::onLeftUp( wxMouseEvent& aEvent )
373 {
375  aEvent.Skip();
376 }
377 
378 
379 void WX_COLLAPSIBLE_PANE_HEADER::onChar( wxKeyEvent& aEvent )
380 {
381  switch( aEvent.GetKeyCode() )
382  {
383  case WXK_SPACE:
384  case WXK_RETURN:
386  break;
387 
388  default:
389  aEvent.Skip();
390  break;
391  }
392 }
393 
394 
395 void WX_COLLAPSIBLE_PANE_HEADER::drawArrow( wxDC& aDC, wxRect aRect, bool aIsActive )
396 {
397  // The bottom corner of the triangle is located halfway across the area and 3/4 down from the top
398  wxPoint btmCorner( aRect.GetWidth() / 2, 3 * aRect.GetHeight() / 4 );
399 
400  // The right corner of the triangle is located halfway down from the top and 3/4 across the area
401  wxPoint rtCorner( 3 * aRect.GetWidth() / 4, aRect.GetHeight() / 2 );
402 
403  // Choose the other corner depending on if the panel is expanded or collapsed
404  wxPoint otherCorner( 0, 0 );
405 
406  if( m_collapsed )
407  otherCorner = wxPoint( aRect.GetWidth() / 2, aRect.GetHeight() / 4 );
408  else
409  otherCorner = wxPoint( aRect.GetWidth() / 4, aRect.GetHeight() / 2 );
410 
411  // Choose the color to draw the triangle
412  wxColour clr;
413 
414  // Highlight the arrow when the pointer is inside the header, otherwise use text color
415  if( m_inWindow )
416  clr = wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT );
417  else
418  clr = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
419 
420  // If the window isn't active, then use the disabled text color
421  if( !aIsActive )
422  clr = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
423 
424  // Must set both the pen (for the outline) and the brush (for the polygon fill)
425  aDC.SetPen( wxPen( clr ) );
426  aDC.SetBrush( wxBrush( clr ) );
427 
428  // Draw the triangle
429  wxPointList points;
430  points.Append( &btmCorner );
431  points.Append( &rtCorner );
432  points.Append( &otherCorner );
433 
434  aDC.DrawPolygon( &points );
435 }
bool SetBackgroundColour(const wxColour &aColor) override
bool Create(wxWindow *aParent, wxWindowID aId, const wxString &aLabel, const wxPoint &aPos=wxDefaultPosition, const wxSize &aSize=wxDefaultSize, long aStyle=wxBORDER_NONE, const wxValidator &aValidator=wxDefaultValidator, const wxString &aName=wxT("COLLAPSIBLE_PANE_HEADER"))
void onFocus(wxFocusEvent &aEvent)
A header control for WX_COLLAPSIBLE_PANE Looks like a static text with a unicode arrow prepended to s...
bool Create(wxWindow *aParent, wxWindowID aId, const wxString &aLabel, const wxPoint &aPos=wxDefaultPosition, const wxSize &aSize=wxDefaultSize, long aStyle=wxBORDER_NONE, const wxValidator &aValidator=wxDefaultValidator, const wxString &aName=wxT("COLLAPSIBLE_PANE_HEADER"))
void doSetCollapsed(bool aCollapsed)
bool InformFirstDirection(int aDirection, int aSize, int aAvailableOtherDir) override
void onEnterWindow(wxMouseEvent &aEvent)
void drawArrow(wxDC &aDC, wxRect aRect, bool aIsActive)
void onHeaderClicked(wxCommandEvent &aEvent)
void SetCollapsed(bool aCollapsed=true)
WX_COLLAPSIBLE_PANE_HEADER * m_header
void Refresh()
Update the board display after modifying it by a python script (note: it is automatically called by a...
void onPaint(wxPaintEvent &aEvent)
wxSize DoGetBestClientSize() const override
wxDEFINE_EVENT(WX_COLLAPSIBLE_PANE_HEADER_CHANGED, wxCommandEvent)
bool Layout() override
void onLeaveWindow(wxMouseEvent &aEvent)
void onChar(wxKeyEvent &aEvent)
void onSize(wxSizeEvent &aEvent)
void SetLabel(const wxString &aLabel) override
void Collapse(bool aCollapse=true)
void onLeftUp(wxMouseEvent &aEvent)
wxSize DoGetBestClientSize() const override