1 
2 /*
3  * This program source code file is part of KiCad, a free EDA CAD application.
4  *
5  * Copyright (C) 2010 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6  * Copyright (C) 2010-2021 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 
26 
27 
28 /*  This source module implements the layer visibility and selection widget
29     @todo make bitmap size dependent on the point size.
30 */
31 
32 
33 //#define STAND_ALONE     1   // define to enable test program for LAYER_WIDGET
34 
35 
36 #include "layer_widget.h"
37 
38 #include <bitmaps.h>
39 #include <macros.h>
40 #include <menus_helpers.h>
41 #include <widgets/indicator_icon.h>
42 #include <widgets/wx_ellipsized_static_text.h>
43 #include <wx/checkbox.h>
44 
45 #include <algorithm>
46 
47 
48 const wxEventType LAYER_WIDGET::EVT_LAYER_COLOR_CHANGE = wxNewEventType();
49 
50 
51 /**
52  * Reduce the size of the wxFont associated with \a aControl.
53  */
shrinkFont(wxWindow * aControl,int aPointSize)54 static void shrinkFont( wxWindow* aControl, int aPointSize )
55 {
56     wxFont font = aControl->GetFont();
57     font.SetPointSize( aPointSize );
58     aControl->SetFont( font );              // need this?
59 }
60 
61 
encodeId(int aColumn,int aId)62 int LAYER_WIDGET::encodeId( int aColumn, int aId )
63 {
64     int id = aId * LYR_COLUMN_COUNT + aColumn;
65     return id;
66 }
67 
68 
getDecodedId(int aControlId)69 LAYER_NUM LAYER_WIDGET::getDecodedId( int aControlId )
70 {
71     int id = aControlId / LYR_COLUMN_COUNT;    // rounding is OK.
72     return id;
73 }
74 
75 
OnLeftDownLayers(wxMouseEvent & event)76 void LAYER_WIDGET::OnLeftDownLayers( wxMouseEvent& event )
77 {
78     int row;
79     LAYER_NUM layer;
80 
81     wxWindow* eventSource = (wxWindow*) event.GetEventObject();
82 
83     // if mouse event is coming from the m_LayerScrolledWindow and not one
84     // of its children, we have to find the row manually based on y coord.
85     if( eventSource == m_LayerScrolledWindow )
86     {
87         int y = event.GetY();
88 
89         wxArrayInt heights = m_LayersFlexGridSizer->GetRowHeights();
90 
91         int height = 0;
92 
93         int rowCount = GetLayerRowCount();
94 
95         for( row = 0;  row<rowCount;  ++row )
96         {
97             if( y < height + heights[row] )
98                 break;
99 
100             height += heights[row];
101         }
102 
103         if( row >= rowCount )
104             row = rowCount - 1;
105 
106         layer = getDecodedId( getLayerComp( row, 0 )->GetId() );
107     }
108     else
109     {
110         // all nested controls on a given row will have their ID encoded with
111         // encodeId(), and the corresponding decoding is getDecodedId()
112         int id = eventSource->GetId();
113         layer  = getDecodedId( id );
114         row    = findLayerRow( layer );
115     }
116 
117     if( OnLayerSelect( layer ) )    // if client allows this change.
118         SelectLayerRow( row );
119 
120     passOnFocus();
121 }
122 
123 
OnRightDownLayer(wxMouseEvent & aEvent,COLOR_SWATCH * aColorSwatch,const wxString & aLayerName)124 void LAYER_WIDGET::OnRightDownLayer( wxMouseEvent& aEvent, COLOR_SWATCH* aColorSwatch,
125                                      const wxString& aLayerName )
126 {
127     wxMenu menu;
128 
129     AddMenuItem( &menu, ID_CHANGE_LAYER_COLOR,
130                  _( "Change Layer Color for" ) + wxS( " " ) + aLayerName,
131                  KiBitmap( BITMAPS::color_materials ) );
132     menu.AppendSeparator();
133 
134     OnLayerRightClick( menu );
135 
136     menu.Bind( wxEVT_COMMAND_MENU_SELECTED, [aColorSwatch]( wxCommandEvent& event )
137                                             {
138                                                 if( event.GetId() == ID_CHANGE_LAYER_COLOR )
139                                                 {
140                                                     aColorSwatch->GetNewSwatchColor();
141                                                 }
142                                                 else
143                                                 {
144                                                     event.Skip();
145                                                 }
146                                             } );
147 
148     PopupMenu( &menu );
149     passOnFocus();
150 }
151 
152 
OnLayerSwatchChanged(wxCommandEvent & aEvent)153 void LAYER_WIDGET::OnLayerSwatchChanged( wxCommandEvent& aEvent )
154 {
155     COLOR_SWATCH* eventSource = static_cast<COLOR_SWATCH*>( aEvent.GetEventObject() );
156     COLOR4D       newColor = eventSource->GetSwatchColor();
157     LAYER_NUM     layer = getDecodedId( eventSource->GetId() );
158 
159     // tell the client code.
160     OnLayerColorChange( layer, newColor );
161 
162     // notify others
163     wxCommandEvent event( EVT_LAYER_COLOR_CHANGE );
164     wxPostEvent( this, event );
165 
166     passOnFocus();
167 }
168 
169 
OnLayerCheckBox(wxCommandEvent & event)170 void LAYER_WIDGET::OnLayerCheckBox( wxCommandEvent& event )
171 {
172     wxCheckBox* eventSource = (wxCheckBox*) event.GetEventObject();
173     LAYER_NUM layer = getDecodedId( eventSource->GetId() );
174     OnLayerVisible( layer, eventSource->IsChecked() );
175     passOnFocus();
176 }
177 
178 
OnRightDownRender(wxMouseEvent & aEvent,COLOR_SWATCH * aColorSwatch,const wxString & aRenderName)179 void LAYER_WIDGET::OnRightDownRender( wxMouseEvent& aEvent, COLOR_SWATCH* aColorSwatch,
180                                       const wxString& aRenderName )
181 {
182     wxMenu menu;
183 
184     AddMenuItem( &menu, ID_CHANGE_RENDER_COLOR,
185                  _( "Change Render Color for" ) + wxS( " " )+ aRenderName,
186                  KiBitmap( BITMAPS::color_materials ) );
187 
188     menu.Bind( wxEVT_COMMAND_MENU_SELECTED,
189                [aColorSwatch]( wxCommandEvent& event )
190                {
191                    if( event.GetId() == ID_CHANGE_RENDER_COLOR )
192                        aColorSwatch->GetNewSwatchColor();
193                    else
194                        event.Skip();
195                } );
196 
197     PopupMenu( &menu );
198     passOnFocus();
199 }
200 
201 
OnRenderSwatchChanged(wxCommandEvent & aEvent)202 void LAYER_WIDGET::OnRenderSwatchChanged( wxCommandEvent& aEvent )
203 {
204     auto eventSource = static_cast<COLOR_SWATCH*>( aEvent.GetEventObject() );
205 
206     COLOR4D newColor = eventSource->GetSwatchColor();
207 
208     LAYER_NUM id = getDecodedId( eventSource->GetId() );
209 
210     if( id == LAYER_PCB_BACKGROUND )
211     {
212         // Update all swatch backgrounds
213         int count = GetLayerRowCount();
214         int row;
215         int col = 1;    // bitmap button is column 1 in layers tab
216 
217         for( row = 0; row < count; ++row )
218         {
219             COLOR_SWATCH* swatch = dynamic_cast<COLOR_SWATCH*>( getLayerComp( row, col ) );
220 
221             if( swatch )
222                 swatch->SetSwatchBackground( newColor );
223         }
224 
225         count = GetRenderRowCount();
226         col = 0;    // bitmap button is column 0 in render tab
227 
228         for( row = 0; row < count; ++row )
229         {
230             COLOR_SWATCH* swatch = dynamic_cast<COLOR_SWATCH*>( getRenderComp( row, col ) );
231 
232             if( swatch )
233                 swatch->SetSwatchBackground( newColor );
234         }
235     }
236 
237     // tell the client code.
238     OnRenderColorChange( id, newColor );
239 
240     passOnFocus();
241 }
242 
243 
OnRenderCheckBox(wxCommandEvent & event)244 void LAYER_WIDGET::OnRenderCheckBox( wxCommandEvent& event )
245 {
246     wxCheckBox* eventSource = (wxCheckBox*) event.GetEventObject();
247     LAYER_NUM id = getDecodedId( eventSource->GetId() );
248     OnRenderEnable( id, eventSource->IsChecked() );
249     passOnFocus();
250 }
251 
252 
OnTabChange(wxNotebookEvent & event)253 void LAYER_WIDGET::OnTabChange( wxNotebookEvent& event )
254 {
255 //    wxFocusEvent    event( wxEVT_SET_FOCUS );
256 //    m_FocusOwner->AddPendingEvent( event );
257 
258     // Does not work in this context, probably because we have receive control here too early.
259     passOnFocus();
260 }
261 
262 
getLayerComp(int aRow,int aColumn) const263 wxWindow* LAYER_WIDGET::getLayerComp( int aRow, int aColumn ) const
264 {
265     unsigned ndx = aRow * LYR_COLUMN_COUNT + aColumn;
266 
267     if( ndx < m_LayersFlexGridSizer->GetChildren().GetCount() )
268         return m_LayersFlexGridSizer->GetChildren()[ndx]->GetWindow();
269 
270     return nullptr;
271 }
272 
273 
findLayerRow(LAYER_NUM aLayer) const274 int LAYER_WIDGET::findLayerRow( LAYER_NUM aLayer ) const
275 {
276     int count = GetLayerRowCount();
277 
278     for( int row = 0; row < count; ++row )
279     {
280         // column 0 in the layer scroll window has a wxStaticBitmap, get its ID.
281         wxWindow* w = getLayerComp( row, 0 );
282         wxASSERT( w );
283 
284         if( aLayer == getDecodedId( w->GetId() ) )
285             return row;
286     }
287 
288     return -1;
289 }
290 
291 
getRenderComp(int aRow,int aColumn) const292 wxWindow* LAYER_WIDGET::getRenderComp( int aRow, int aColumn ) const
293 {
294     int ndx = aRow * RND_COLUMN_COUNT + aColumn;
295 
296     if( (unsigned) ndx < m_RenderFlexGridSizer->GetChildren().GetCount() )
297         return m_RenderFlexGridSizer->GetChildren()[ndx]->GetWindow();
298 
299     return nullptr;
300 }
301 
302 
findRenderRow(int aId) const303 int LAYER_WIDGET::findRenderRow( int aId ) const
304 {
305     int count = GetRenderRowCount();
306 
307     for( int row = 0; row < count; ++row )
308     {
309         // column 0 in the layer scroll window has a wxStaticBitmap, get its ID.
310         wxWindow* w = getRenderComp( row, 0 );
311         wxASSERT( w );
312 
313         if( aId == getDecodedId( w->GetId() ) )
314             return row;
315     }
316 
317     return -1;
318 }
319 
320 
insertLayerRow(int aRow,const ROW & aSpec)321 void LAYER_WIDGET::insertLayerRow( int aRow, const ROW& aSpec )
322 {
323     wxASSERT( aRow >= 0 );
324 
325     int         col;
326     int         index = aRow * LYR_COLUMN_COUNT;
327     const int   flags = wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT;
328 
329     // column 0
330     col = COLUMN_ICON_ACTIVE;
331     auto sbm = new INDICATOR_ICON( m_LayerScrolledWindow, *m_IconProvider,
332                                    ROW_ICON_PROVIDER::STATE::OFF, encodeId( col, aSpec.id ) );
333     sbm->Bind( wxEVT_LEFT_DOWN, &LAYER_WIDGET::OnLeftDownLayers, this );
334     m_LayersFlexGridSizer->wxSizer::Insert( index+col, sbm, 0, flags );
335 
336     // column 1 (COLUMN_COLORBM)
337     col = COLUMN_COLORBM;
338 
339     auto bmb = new COLOR_SWATCH( m_LayerScrolledWindow, aSpec.color, encodeId( col, aSpec.id ),
340                                  getBackgroundLayerColor(), aSpec.defaultColor, SWATCH_SMALL );
341     bmb->Bind( wxEVT_LEFT_DOWN, &LAYER_WIDGET::OnLeftDownLayers, this );
342     bmb->Bind( COLOR_SWATCH_CHANGED, &LAYER_WIDGET::OnLayerSwatchChanged, this );
343     bmb->SetToolTip( _("Left double click or middle click for color change, right click for "
344                        "menu" ) );
345     m_LayersFlexGridSizer->wxSizer::Insert( index+col, bmb, 0, flags );
346 
347     // column 2 (COLUMN_COLOR_LYR_CB)
348     col = COLUMN_COLOR_LYR_CB;
349     wxCheckBox* cb = new wxCheckBox( m_LayerScrolledWindow, encodeId( col, aSpec.id ),
350                                      wxEmptyString );
351     cb->SetValue( aSpec.state );
352     cb->Bind( wxEVT_COMMAND_CHECKBOX_CLICKED, &LAYER_WIDGET::OnLayerCheckBox, this );
353     cb->SetToolTip( _( "Enable this for visibility" ) );
354     m_LayersFlexGridSizer->wxSizer::Insert( index+col, cb, 0, flags );
355 
356     // column 3 (COLUMN_COLOR_LYRNAME)
357     col = COLUMN_COLOR_LYRNAME;
358     WX_ELLIPSIZED_STATIC_TEXT* st = new WX_ELLIPSIZED_STATIC_TEXT( m_LayerScrolledWindow,
359                                                                    encodeId( col, aSpec.id ),
360                                                                    aSpec.rowName, wxDefaultPosition,
361                                                                    wxDefaultSize,
362                                                                    wxST_ELLIPSIZE_MIDDLE );
363     shrinkFont( st, m_PointSize );
364     st->Bind( wxEVT_LEFT_DOWN, &LAYER_WIDGET::OnLeftDownLayers, this );
365     st->SetToolTip( aSpec.tooltip );
366     st->SetMinimumStringLength( m_smallestLayerString );
367     m_LayersFlexGridSizer->wxSizer::Insert( index+col, st, 0, flags | wxEXPAND );
368 
369     // column 4 (COLUMN_ALPHA_INDICATOR)
370     col = COLUMN_ALPHA_INDICATOR;
371     sbm = new INDICATOR_ICON( m_LayerScrolledWindow, *m_IconProvider,
372                               ROW_ICON_PROVIDER::STATE::OFF, wxID_ANY );
373     m_LayersFlexGridSizer->wxSizer::Insert( index+col, sbm, 0, flags );
374 
375     // Bind right click eventhandler to all columns
376     wxString layerName( aSpec.rowName );
377 
378     sbm->Bind( wxEVT_RIGHT_DOWN, [this, bmb, layerName] ( wxMouseEvent& aEvt )
379                                  {
380                                      OnRightDownLayer( aEvt, bmb, layerName );
381                                  } );
382     bmb->Bind( wxEVT_RIGHT_DOWN, [this, bmb, layerName] ( wxMouseEvent& aEvt )
383                                  {
384                                      OnRightDownLayer( aEvt, bmb, layerName );
385                                  } );
386     cb->Bind( wxEVT_RIGHT_DOWN, [this, bmb, layerName] ( wxMouseEvent& aEvt )
387                                 {
388                                     OnRightDownLayer( aEvt, bmb, layerName );
389                                 } );
390     st->Bind( wxEVT_RIGHT_DOWN, [this, bmb, layerName] ( wxMouseEvent& aEvt )
391                                 {
392                                     OnRightDownLayer( aEvt, bmb, layerName );
393                                 } );
394 }
395 
396 
updateLayerRow(int aRow,const wxString & aName)397 void LAYER_WIDGET::updateLayerRow( int aRow, const wxString& aName )
398 {
399     wxStaticText* label = dynamic_cast<wxStaticText*>( getLayerComp( aRow, COLUMN_COLOR_LYRNAME ) );
400 
401     if( label )
402         label->SetLabel( aName );
403 
404     INDICATOR_ICON* indicator = (INDICATOR_ICON*) getLayerComp( aRow, 0 );
405 
406     if( indicator )
407     {
408         if( aRow == m_CurrentRow )
409             indicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::ON );
410         if( useAlternateBitmap( aRow ) )
411             indicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::DIMMED );
412         else
413             indicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::OFF );
414     }
415 }
416 
417 
insertRenderRow(int aRow,const ROW & aSpec)418 void LAYER_WIDGET::insertRenderRow( int aRow, const ROW& aSpec )
419 {
420     wxASSERT( aRow >= 0 );
421 
422     int         col;
423     int         index = aRow * RND_COLUMN_COUNT;
424     const int   flags = wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT;
425 
426     wxString renderName( aSpec.rowName );
427     wxCheckBox* cb = nullptr;
428 
429     // column 1
430     if( !aSpec.spacer )
431     {
432         col = 1;
433         cb = new wxCheckBox( m_RenderScrolledWindow, encodeId( col, aSpec.id ),
434                              aSpec.rowName, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT );
435         shrinkFont( cb, m_PointSize );
436         cb->SetValue( aSpec.state );
437         cb->Enable( aSpec.changeable );
438         cb->Bind( wxEVT_COMMAND_CHECKBOX_CLICKED, &LAYER_WIDGET::OnRenderCheckBox, this );
439         cb->SetToolTip( aSpec.tooltip );
440     }
441 
442     // column 0
443     col = 0;
444 
445     if( aSpec.color != COLOR4D::UNSPECIFIED )
446     {
447         auto bmb = new COLOR_SWATCH( m_RenderScrolledWindow, aSpec.color, encodeId( col, aSpec.id ),
448                                      getBackgroundLayerColor(), aSpec.defaultColor, SWATCH_SMALL );
449         bmb->Bind( COLOR_SWATCH_CHANGED, &LAYER_WIDGET::OnRenderSwatchChanged, this );
450         bmb->SetToolTip( _( "Left double click or middle click for color change" ) );
451         m_RenderFlexGridSizer->wxSizer::Insert( index+col, bmb, 0, flags );
452 
453         bmb->Bind( wxEVT_RIGHT_DOWN, [this, bmb, renderName] ( wxMouseEvent& aEvt ) {
454             OnRightDownRender( aEvt, bmb, renderName );
455         } );
456         cb->Bind( wxEVT_RIGHT_DOWN, [this, bmb, renderName] ( wxMouseEvent& aEvt ) {
457             OnRightDownRender( aEvt, bmb, renderName );
458         } );
459 
460         // could add a left click handler on the color button that toggles checkbox.
461     }
462     else    // == -1, no color selection wanted
463     {
464         // need a place holder within the sizer to keep grid full.
465         wxPanel* invisible = new wxPanel( m_RenderScrolledWindow, encodeId( col, aSpec.id ) );
466         m_RenderFlexGridSizer->wxSizer::Insert( index+col, invisible, 0, flags );
467     }
468 
469     // Items have to be inserted in order
470     col = 1;
471 
472     if( aSpec.spacer )
473     {
474         wxPanel* invisible = new wxPanel( m_RenderScrolledWindow, wxID_ANY );
475         m_RenderFlexGridSizer->wxSizer::Insert( index+col, invisible, 0, flags );
476     }
477     else
478     {
479         m_RenderFlexGridSizer->wxSizer::Insert( index+col, cb, 0, flags );
480     }
481 }
482 
483 
passOnFocus()484 void LAYER_WIDGET::passOnFocus()
485 {
486     m_FocusOwner->SetFocus();
487 }
488 
489 
LAYER_WIDGET(wxWindow * aParent,wxWindow * aFocusOwner,wxWindowID id,const wxPoint & pos,const wxSize & size,long style)490 LAYER_WIDGET::LAYER_WIDGET( wxWindow* aParent, wxWindow* aFocusOwner, wxWindowID id,
491                             const wxPoint& pos, const wxSize& size, long style ) :
492     wxPanel( aParent, id, pos, size, style ),
493     m_smallestLayerString( "M...M" )
494 {
495     int indicatorSize = ConvertDialogToPixels( wxSize( 6, 6 ) ).x;
496     m_IconProvider = new ROW_ICON_PROVIDER( indicatorSize );
497 
498     int pointSize = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ).GetPointSize();
499     int screenHeight = wxSystemSettings::GetMetric( wxSYS_SCREEN_Y );
500 
501     if( screenHeight <= 900 && pointSize >= indicatorSize )
502         pointSize = pointSize * 8 / 10;
503 
504     m_PointSize = pointSize;
505 
506     wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
507 
508     m_notebook = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP );
509 
510     wxFont font = m_notebook->GetFont();
511 
512     // change the font size on the notebook's tabs to match aPointSize
513     font.SetPointSize( pointSize );
514     m_notebook->SetFont( font );
515 
516     m_LayerPanel = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize,
517                                 wxTAB_TRAVERSAL );
518 
519     wxBoxSizer* layerPanelSizer;
520     layerPanelSizer = new wxBoxSizer( wxVERTICAL );
521 
522     m_LayerScrolledWindow = new wxScrolledWindow( m_LayerPanel, wxID_ANY, wxDefaultPosition,
523                                                   wxDefaultSize, wxNO_BORDER );
524     m_LayerScrolledWindow->SetScrollRate( 5, 5 );
525     m_LayersFlexGridSizer = new wxFlexGridSizer( 0, LYR_COLUMN_COUNT, 0, 1 );
526     m_LayersFlexGridSizer->SetFlexibleDirection( wxHORIZONTAL );
527     m_LayersFlexGridSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_NONE );
528 
529     // Make column 3 growable/stretchable
530     m_LayersFlexGridSizer->AddGrowableCol( 3, 1 );
531 
532     m_LayerScrolledWindow->SetSizer( m_LayersFlexGridSizer );
533     m_LayerScrolledWindow->Layout();
534     m_LayersFlexGridSizer->Fit( m_LayerScrolledWindow );
535     layerPanelSizer->Add( m_LayerScrolledWindow, 1, wxBOTTOM | wxEXPAND | wxLEFT | wxTOP, 2 );
536 
537     m_LayerPanel->SetSizer( layerPanelSizer );
538     m_LayerPanel->Layout();
539     layerPanelSizer->Fit( m_LayerPanel );
540     m_notebook->AddPage( m_LayerPanel, _( "Layers" ), true );
541     m_RenderingPanel = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize,
542                                     wxTAB_TRAVERSAL );
543 
544     wxBoxSizer* renderPanelSizer;
545     renderPanelSizer = new wxBoxSizer( wxVERTICAL );
546 
547     m_RenderScrolledWindow = new wxScrolledWindow( m_RenderingPanel, wxID_ANY, wxDefaultPosition,
548                                                    wxDefaultSize, wxNO_BORDER );
549     m_RenderScrolledWindow->SetScrollRate( 5, 5 );
550     m_RenderFlexGridSizer = new wxFlexGridSizer( 0, RND_COLUMN_COUNT, 0, 1 );
551     m_RenderFlexGridSizer->SetFlexibleDirection( wxHORIZONTAL );
552     m_RenderFlexGridSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_NONE );
553 
554     m_RenderScrolledWindow->SetSizer( m_RenderFlexGridSizer );
555     m_RenderScrolledWindow->Layout();
556     m_RenderFlexGridSizer->Fit( m_RenderScrolledWindow );
557     renderPanelSizer->Add( m_RenderScrolledWindow, 1, wxALL | wxEXPAND, 5 );
558 
559     m_RenderingPanel->SetSizer( renderPanelSizer );
560     m_RenderingPanel->Layout();
561     renderPanelSizer->Fit( m_RenderingPanel );
562     m_notebook->AddPage( m_RenderingPanel, _( "Items" ), false );
563 
564     mainSizer->Add( m_notebook, 1, wxEXPAND, 5 );
565 
566     SetSizer( mainSizer );
567 
568     m_FocusOwner = aFocusOwner;
569 
570     m_CurrentRow = -1;  // hide the arrow initially
571 
572     // trap the tab changes so that we can call passOnFocus().
573     m_notebook->Bind( wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED, &LAYER_WIDGET::OnTabChange, this );
574 
575     Layout();
576 }
577 
578 
~LAYER_WIDGET()579 LAYER_WIDGET::~LAYER_WIDGET()
580 {
581     delete m_IconProvider;
582 }
583 
584 
GetBestSize() const585 wxSize LAYER_WIDGET::GetBestSize() const
586 {
587     // size of m_LayerScrolledWindow --------------
588     wxArrayInt widths = m_LayersFlexGridSizer->GetColWidths();
589     int totWidth = 0;
590 
591     if( widths.GetCount() )
592     {
593         for( int i = 0; i < LYR_COLUMN_COUNT; ++i )
594         {
595             totWidth += widths[i] + m_LayersFlexGridSizer->GetHGap();
596         }
597     }
598 
599     // Account for the parent's frame:
600     totWidth += 15;
601 
602     /* The minimum height is a small size to properly force computation
603      * of the panel's scrollbars (otherwise it will assume it *has* all
604      * this space) */
605     unsigned totHeight = 32;
606 
607     wxSize layerz( totWidth, totHeight );
608 
609     layerz += m_LayerPanel->GetWindowBorderSize();
610 
611     // size of m_RenderScrolledWindow --------------
612     widths = m_RenderFlexGridSizer->GetColWidths();
613     totWidth = 0;
614 
615     if( widths.GetCount() )
616     {
617         for( int i = 0; i < RND_COLUMN_COUNT; ++i )
618         {
619             totWidth += widths[i] + m_RenderFlexGridSizer->GetHGap();
620         }
621     }
622 
623     // account for the parent's frame, this one has void space of 10 PLUS a border:
624     totWidth += 15;
625 
626     // For totHeight re-use the previous small one
627     wxSize renderz( totWidth, totHeight );
628 
629     renderz += m_RenderingPanel->GetWindowBorderSize();
630 
631     wxSize clientz( std::max(renderz.x,layerz.x), std::max(renderz.y,layerz.y) );
632 
633     return clientz;
634 }
635 
636 
GetLayerRowCount() const637 int LAYER_WIDGET::GetLayerRowCount() const
638 {
639     int controlCount = m_LayersFlexGridSizer->GetChildren().GetCount();
640     return controlCount / LYR_COLUMN_COUNT;
641 }
642 
643 
GetRenderRowCount() const644 int LAYER_WIDGET::GetRenderRowCount() const
645 {
646     int controlCount = m_RenderFlexGridSizer->GetChildren().GetCount();
647     return controlCount / RND_COLUMN_COUNT;
648 }
649 
650 
AppendLayerRow(const ROW & aRow)651 void LAYER_WIDGET::AppendLayerRow( const ROW& aRow )
652 {
653     int nextRow = GetLayerRowCount();
654     insertLayerRow( nextRow, aRow );
655 }
656 
657 
ClearLayerRows()658 void LAYER_WIDGET::ClearLayerRows()
659 {
660     m_LayersFlexGridSizer->Clear( true );
661 }
662 
663 
AppendRenderRow(const ROW & aRow)664 void LAYER_WIDGET::AppendRenderRow( const ROW& aRow )
665 {
666     int nextRow = GetRenderRowCount();
667     insertRenderRow( nextRow, aRow );
668 }
669 
670 
ClearRenderRows()671 void LAYER_WIDGET::ClearRenderRows()
672 {
673     m_RenderFlexGridSizer->Clear( true );
674 }
675 
676 
SelectLayerRow(int aRow)677 void LAYER_WIDGET::SelectLayerRow( int aRow )
678 {
679     // enable the layer tab at index 0
680     m_notebook->SetSelection( 0 );
681 
682     INDICATOR_ICON* oldIndicator = (INDICATOR_ICON*) getLayerComp( m_CurrentRow, 0 );
683 
684     if( oldIndicator )
685     {
686         if( useAlternateBitmap( m_CurrentRow ) )
687             oldIndicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::DIMMED );
688         else
689             oldIndicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::OFF );
690     }
691 
692     INDICATOR_ICON* newIndicator = (INDICATOR_ICON*) getLayerComp( aRow, 0 );
693 
694     if( newIndicator )
695     {
696         newIndicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::ON );
697 
698         // Make sure the desired layer row is visible.
699         // It seems that as of 2.8.2, setting the focus does this.
700         // I don't expect the scrolling to be needed at all because
701         // the minimum window size may end up being established so that the
702         // scroll bars will not be visible.
703         getLayerComp( aRow, 1 )->SetFocus();
704     }
705 
706     m_CurrentRow = aRow;
707 
708     // give the focus back to the app.
709     passOnFocus();
710 }
711 
712 
SelectLayer(LAYER_NUM aLayer)713 void LAYER_WIDGET::SelectLayer( LAYER_NUM aLayer )
714 {
715     int row = findLayerRow( aLayer );
716     SelectLayerRow( row );
717 }
718 
719 
GetSelectedLayer()720 LAYER_NUM LAYER_WIDGET::GetSelectedLayer()
721 {
722     wxWindow* w = getLayerComp( m_CurrentRow, 0 );
723 
724     if( w )
725         return getDecodedId( w->GetId() );
726 
727     return UNDEFINED_LAYER;
728 }
729 
730 
SetLayerVisible(LAYER_NUM aLayer,bool isVisible)731 void LAYER_WIDGET::SetLayerVisible( LAYER_NUM aLayer, bool isVisible )
732 {
733     setLayerCheckbox( aLayer, isVisible );
734     OnLayerVisible( aLayer, isVisible );
735 }
736 
737 
setLayerCheckbox(LAYER_NUM aLayer,bool isVisible)738 void LAYER_WIDGET::setLayerCheckbox( LAYER_NUM aLayer, bool isVisible )
739 {
740     int row = findLayerRow( aLayer );
741 
742     if( row >= 0 )
743     {
744         wxCheckBox* cb = (wxCheckBox*) getLayerComp( row, COLUMN_COLOR_LYR_CB );
745         wxASSERT( cb );
746         cb->SetValue( isVisible );      // does not fire an event
747     }
748 }
749 
750 
IsLayerVisible(LAYER_NUM aLayer)751 bool LAYER_WIDGET::IsLayerVisible( LAYER_NUM aLayer )
752 {
753     int row = findLayerRow( aLayer );
754 
755     if( row >= 0 )
756     {
757         wxCheckBox* cb = (wxCheckBox*) getLayerComp( row, COLUMN_COLOR_LYR_CB );
758         wxASSERT( cb );
759         return cb->GetValue();
760     }
761 
762     return false;
763 }
764 
765 
SetLayerColor(LAYER_NUM aLayer,const COLOR4D & aColor)766 void LAYER_WIDGET::SetLayerColor( LAYER_NUM aLayer, const COLOR4D& aColor )
767 {
768     int row = findLayerRow( aLayer );
769 
770     if( row >= 0 )
771     {
772         int col = 1;    // bitmap button is column 1
773         auto swatch = static_cast<COLOR_SWATCH*>( getLayerComp( row, col ) );
774         wxASSERT( swatch );
775 
776         swatch->SetSwatchColor( aColor, false );
777     }
778 }
779 
780 
GetLayerColor(LAYER_NUM aLayer) const781 COLOR4D LAYER_WIDGET::GetLayerColor( LAYER_NUM aLayer ) const
782 {
783     int row = findLayerRow( aLayer );
784 
785     if( row >= 0 )
786     {
787         int col = 1;    // bitmap button is column 1
788         auto swatch = static_cast<COLOR_SWATCH*>( getLayerComp( row, col ) );
789         wxASSERT( swatch );
790 
791         return swatch->GetSwatchColor();
792     }
793 
794     return COLOR4D::UNSPECIFIED;   // it's caller fault, gave me a bad layer
795 }
796 
797 
SetRenderState(int aId,bool isSet)798 void LAYER_WIDGET::SetRenderState( int aId, bool isSet )
799 {
800     int row = findRenderRow( aId );
801 
802     if( row >= 0 )
803     {
804         int col = 1;    // checkbox is column 1
805         wxCheckBox* cb = (wxCheckBox*) getRenderComp( row, col );
806         wxASSERT( cb );
807         cb->SetValue( isSet );  // does not fire an event
808     }
809 }
810 
811 
GetRenderState(int aId)812 bool LAYER_WIDGET::GetRenderState( int aId )
813 {
814     int row = findRenderRow( aId );
815 
816     if( row >= 0 )
817     {
818         int col = 1;    // checkbox is column 1
819         wxCheckBox* cb = (wxCheckBox*) getRenderComp( row, col );
820         wxASSERT( cb );
821         return cb->GetValue();
822     }
823 
824     return false;   // the value of a non-existent row
825 }
826 
827 
UpdateLayouts()828 void LAYER_WIDGET::UpdateLayouts()
829 {
830     m_LayersFlexGridSizer->Layout();
831     m_RenderFlexGridSizer->Layout();
832     m_LayerPanel->Layout();
833     m_RenderingPanel->Layout();
834     FitInside();
835 }
836 
837 
UpdateLayerIcons()838 void LAYER_WIDGET::UpdateLayerIcons()
839 {
840     int rowCount = GetLayerRowCount();
841 
842     for( int row = 0; row < rowCount ; row++ )
843     {
844         INDICATOR_ICON* indicator = (INDICATOR_ICON*) getLayerComp( row, COLUMN_ICON_ACTIVE );
845 
846         if( indicator )
847         {
848             ROW_ICON_PROVIDER::STATE state;
849 
850             if( row == m_CurrentRow )
851                 state = ROW_ICON_PROVIDER::STATE::ON;
852             else if( useAlternateBitmap( row ) )
853                 state = ROW_ICON_PROVIDER::STATE::DIMMED;
854             else
855                 state = ROW_ICON_PROVIDER::STATE::OFF;
856 
857             indicator->SetIndicatorState( state );
858         }
859     }
860 }
861