1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2020 Jon Evans <jon@craftyjon.com>
5  * Copyright (C) 2020 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 
21 #include <bitmaps.h>
22 #include <dialogs/dialog_color_picker.h>
23 #include <launch_ext.h>
24 #include <layer_ids.h>
25 #include <menus_helpers.h>
26 #include <dialogs/panel_color_settings.h>
27 #include <pgm_base.h>
28 #include <settings/color_settings.h>
29 #include <settings/common_settings.h>
30 #include <settings/settings_manager.h>
31 #include <validators.h>
32 #include <widgets/color_swatch.h>
33 
34 #include <wx/msgdlg.h>
35 #include <wx/textdlg.h>
36 
37 // Button ID starting point
38 constexpr int FIRST_BUTTON_ID = 1800;
39 
40 
PANEL_COLOR_SETTINGS(wxWindow * aParent)41 PANEL_COLOR_SETTINGS::PANEL_COLOR_SETTINGS( wxWindow* aParent ) :
42         PANEL_COLOR_SETTINGS_BASE( aParent ),
43         m_currentSettings( nullptr ),
44         m_swatches(),
45         m_copied( COLOR4D::UNSPECIFIED ),
46         m_validLayers(),
47         m_backgroundLayer( LAYER_PCB_BACKGROUND ),
48         m_colorNamespace()
49 {
50 #ifdef __APPLE__
51     m_btnOpenFolder->SetLabel( _( "Reveal Themes in Finder" ) );
52 
53     // Simple border is too dark on OSX
54     m_colorsListWindow->SetWindowStyle( wxBORDER_SUNKEN|wxVSCROLL );
55 #endif
56 }
57 
58 
OnBtnOpenThemeFolderClicked(wxCommandEvent & event)59 void PANEL_COLOR_SETTINGS::OnBtnOpenThemeFolderClicked( wxCommandEvent& event )
60 {
61     wxString dir( SETTINGS_MANAGER::GetColorSettingsPath() );
62     LaunchExternal( dir );
63 }
64 
65 
ResetPanel()66 void PANEL_COLOR_SETTINGS::ResetPanel()
67 {
68     if( !m_currentSettings || m_currentSettings->IsReadOnly() )
69         return;
70 
71     for( const std::pair<const int, COLOR_SWATCH*>& pair : m_swatches )
72     {
73         int           layer  = pair.first;
74         COLOR_SWATCH* button = pair.second;
75 
76         COLOR4D defaultColor = m_currentSettings->GetDefaultColor( layer );
77 
78         m_currentSettings->SetColor( layer, defaultColor );
79         button->SetSwatchColor( defaultColor, false );
80     }
81 }
82 
83 
Show(bool show)84 bool PANEL_COLOR_SETTINGS::Show( bool show )
85 {
86     if( show )
87     {
88         // In case changes have been made to the current theme in another panel:
89         int idx = m_cbTheme->GetSelection();
90         COLOR_SETTINGS* settings = static_cast<COLOR_SETTINGS*>( m_cbTheme->GetClientData( idx ) );
91 
92         if( settings )
93             *m_currentSettings = *settings;
94 
95         onNewThemeSelected();
96         updateSwatches();
97     }
98 
99     return PANEL_COLOR_SETTINGS_BASE::Show( show );
100 }
101 
102 
OnLeftDownTheme(wxMouseEvent & event)103 void PANEL_COLOR_SETTINGS::OnLeftDownTheme( wxMouseEvent& event )
104 {
105     // Lazy rebuild of theme menu to catch any colour theme changes made in other panels
106     createThemeList( m_currentSettings->GetFilename() );
107 
108     event.Skip();
109 }
110 
111 
OnThemeChanged(wxCommandEvent & event)112 void PANEL_COLOR_SETTINGS::OnThemeChanged( wxCommandEvent& event )
113 {
114     int idx = m_cbTheme->GetSelection();
115 
116     if( idx == static_cast<int>( m_cbTheme->GetCount() ) - 2 )
117     {
118         // separator; re-select active theme
119         m_cbTheme->SetStringSelection( m_currentSettings->GetName() );
120         return;
121     }
122 
123     if( idx == (int)m_cbTheme->GetCount() - 1 )
124     {
125         // New Theme...
126 
127         if( !saveCurrentTheme( false ) )
128             return;
129 
130         FOOTPRINT_NAME_VALIDATOR themeNameValidator;
131         wxTextEntryDialog dlg( this, _( "New theme name:" ), _( "Add Color Theme" ) );
132         dlg.SetTextValidator( themeNameValidator );
133 
134         if( dlg.ShowModal() != wxID_OK )
135             return;
136 
137         wxString themeName = dlg.GetValue();
138         wxFileName fn( themeName + wxT( ".json" ) );
139         fn.SetPath( SETTINGS_MANAGER::GetColorSettingsPath() );
140 
141         if( fn.Exists() )
142         {
143             wxMessageBox( _( "Theme already exists!" ) );
144             return;
145         }
146 
147         SETTINGS_MANAGER& settingsMgr = Pgm().GetSettingsManager();
148         COLOR_SETTINGS*   newSettings = settingsMgr.AddNewColorSettings( themeName );
149         newSettings->SetName( themeName );
150         newSettings->SetReadOnly( false );
151 
152         for( auto layer : m_validLayers )
153             newSettings->SetColor( layer, m_currentSettings->GetColor( layer ) );
154 
155         newSettings->SaveToFile( settingsMgr.GetPathForSettingsFile( newSettings ) );
156 
157         idx = m_cbTheme->Insert( themeName, idx - 1, static_cast<void*>( newSettings ) );
158         m_cbTheme->SetSelection( idx );
159 
160         m_optOverrideColors->SetValue( newSettings->GetOverrideSchItemColors() );
161 
162         *m_currentSettings = *newSettings;
163         updateSwatches();
164         onNewThemeSelected();
165     }
166     else
167     {
168         COLOR_SETTINGS* selected = static_cast<COLOR_SETTINGS*>( m_cbTheme->GetClientData( idx ) );
169 
170         if( selected->GetFilename() != m_currentSettings->GetFilename() )
171         {
172             if( !saveCurrentTheme( false ) )
173                 return;
174 
175             m_optOverrideColors->SetValue( selected->GetOverrideSchItemColors() );
176 
177             *m_currentSettings = *selected;
178             onNewThemeSelected();
179             updateSwatches();
180         }
181     }
182 }
183 
184 
updateSwatches()185 void PANEL_COLOR_SETTINGS::updateSwatches()
186 {
187     bool    isReadOnly = m_currentSettings->IsReadOnly();
188     COLOR4D background = m_currentSettings->GetColor( m_backgroundLayer );
189 
190     for( std::pair<int, COLOR_SWATCH*> pair : m_swatches )
191     {
192         pair.second->SetSwatchBackground( background );
193         pair.second->SetSwatchColor( m_currentSettings->GetColor( pair.first ), false );
194         pair.second->SetReadOnly( isReadOnly );
195     }
196 }
197 
198 
createThemeList(const wxString & aCurrent)199 void PANEL_COLOR_SETTINGS::createThemeList( const wxString& aCurrent )
200 {
201     int width    = 0;
202     int height   = 0;
203 
204     m_cbTheme->GetTextExtent( _( "New Theme..." ), &width, &height );
205     int minwidth = width;
206 
207     m_cbTheme->Clear();
208 
209     for( COLOR_SETTINGS* settings : Pgm().GetSettingsManager().GetColorSettingsList() )
210     {
211         wxString name = settings->GetName();
212 
213         if( settings->IsReadOnly() )
214             name += wxS( " " ) + _( "(read-only)" );
215 
216         int pos = m_cbTheme->Append( name, static_cast<void*>( settings ) );
217 
218         if( settings->GetFilename() == aCurrent )
219             m_cbTheme->SetSelection( pos );
220 
221         m_cbTheme->GetTextExtent( name, &width, &height );
222         minwidth = std::max( minwidth, width );
223     }
224 
225     m_cbTheme->Append( wxT( "---" ) );
226     m_cbTheme->Append( _( "New Theme..." ) );
227 
228     m_cbTheme->SetMinSize( wxSize( minwidth + 50, -1 ) );
229 }
230 
231 
createSwatch(int aLayer,const wxString & aName)232 void PANEL_COLOR_SETTINGS::createSwatch( int aLayer, const wxString& aName )
233 {
234     wxStaticText* label = new wxStaticText( m_colorsListWindow, wxID_ANY, aName );
235 
236     // The previously selected theme can be deleted and cannot be selected.
237     // so select the default theme (first theme of the list)
238     if( m_cbTheme->GetSelection() < 0 )
239     {
240         m_cbTheme->SetSelection( 0 );
241         onNewThemeSelected();
242     }
243 
244     void*           clientData = m_cbTheme->GetClientData( m_cbTheme->GetSelection() );
245     COLOR_SETTINGS* selected = static_cast<COLOR_SETTINGS*>( clientData );
246 
247     int             id = FIRST_BUTTON_ID + aLayer;
248     COLOR4D         defaultColor    = selected->GetDefaultColor( aLayer );
249     COLOR4D         color           = m_currentSettings->GetColor( aLayer );
250     COLOR4D         backgroundColor = m_currentSettings->GetColor( m_backgroundLayer );
251 
252     COLOR_SWATCH* swatch = new COLOR_SWATCH( m_colorsListWindow, color, id, backgroundColor,
253                                              defaultColor, SWATCH_MEDIUM );
254     swatch->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
255 
256     m_colorsGridSizer->Add( label, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxLEFT, 5 );
257     m_colorsGridSizer->Add( swatch, 0, wxALIGN_CENTER_VERTICAL | wxALL, 3 );
258 
259     m_labels[aLayer]   = label;
260     m_swatches[aLayer] = swatch;
261 
262     swatch->Bind( wxEVT_RIGHT_DOWN,
263                   [&, aLayer]( wxMouseEvent& aEvent )
264                   {
265                       ShowColorContextMenu( aEvent, aLayer );
266                   } );
267 
268     swatch->Bind( COLOR_SWATCH_CHANGED, &PANEL_COLOR_SETTINGS::OnColorChanged, this );
269 }
270 
271 
ShowColorContextMenu(wxMouseEvent & aEvent,int aLayer)272 void PANEL_COLOR_SETTINGS::ShowColorContextMenu( wxMouseEvent& aEvent, int aLayer )
273 {
274     auto selected =
275             static_cast<COLOR_SETTINGS*>( m_cbTheme->GetClientData( m_cbTheme->GetSelection() ) );
276 
277     COLOR4D current  = m_currentSettings->GetColor( aLayer );
278     COLOR4D saved    = selected->GetColor( aLayer );
279     bool    readOnly = m_currentSettings->IsReadOnly();
280 
281     wxMenu menu;
282 
283     AddMenuItem( &menu, ID_COPY, _( "Copy color" ), KiBitmap( BITMAPS::copy ) );
284 
285     if( !readOnly && m_copied != COLOR4D::UNSPECIFIED )
286         AddMenuItem( &menu, ID_PASTE, _( "Paste color" ), KiBitmap( BITMAPS::paste ) );
287 
288     if( !readOnly && current != saved )
289         AddMenuItem( &menu, ID_REVERT, _( "Revert to saved color" ), KiBitmap( BITMAPS::undo ) );
290 
291     menu.Bind( wxEVT_COMMAND_MENU_SELECTED,
292             [&]( wxCommandEvent& aCmd )
293             {
294                 switch( aCmd.GetId() )
295                 {
296                 case ID_COPY:
297                     m_copied = current;
298                     break;
299 
300                 case ID_PASTE:
301                     updateColor( aLayer, m_copied );
302                     break;
303 
304                 case ID_REVERT:
305                     updateColor( aLayer, saved );
306                     break;
307 
308                 default:
309                     aCmd.Skip();
310                 }
311             } );
312 
313     PopupMenu( &menu );
314 }
315 
316 
OnColorChanged(wxCommandEvent & aEvent)317 void PANEL_COLOR_SETTINGS::OnColorChanged( wxCommandEvent& aEvent )
318 {
319     COLOR_SWATCH* swatch = static_cast<COLOR_SWATCH*>( aEvent.GetEventObject() );
320     COLOR4D       newColor = swatch->GetSwatchColor();
321     LAYER_NUM     layer = static_cast<SCH_LAYER_ID>( swatch->GetId() - FIRST_BUTTON_ID );
322 
323     updateColor( layer, newColor );
324 }
325 
326 
updateColor(int aLayer,const KIGFX::COLOR4D & aColor)327 void PANEL_COLOR_SETTINGS::updateColor( int aLayer, const KIGFX::COLOR4D& aColor )
328 {
329     if( m_currentSettings )
330         m_currentSettings->SetColor( aLayer, aColor );
331 
332     // Colors must be persisted when edited because multiple PANEL_COLOR_SETTINGS could be
333     // referring to the same theme.
334     saveCurrentTheme( false );
335 
336     m_swatches[aLayer]->SetSwatchColor( aColor, false );
337 
338     if( m_currentSettings && aLayer == m_backgroundLayer )
339     {
340         COLOR4D background = m_currentSettings->GetColor( m_backgroundLayer );
341 
342         for( std::pair<int, COLOR_SWATCH*> pair : m_swatches )
343             pair.second->SetSwatchBackground( background );
344     }
345 
346     onColorChanged();
347 }
348 
349 
saveCurrentTheme(bool aValidate)350 bool PANEL_COLOR_SETTINGS::saveCurrentTheme( bool aValidate )
351 {
352     if( m_currentSettings->IsReadOnly() )
353         return true;
354 
355     if( aValidate && !validateSave() )
356         return false;
357 
358     SETTINGS_MANAGER& settingsMgr = Pgm().GetSettingsManager();
359     COLOR_SETTINGS* selected = settingsMgr.GetColorSettings( m_currentSettings->GetFilename() );
360 
361     selected->SetOverrideSchItemColors( m_optOverrideColors->GetValue() );
362 
363     for( auto layer : m_validLayers )
364         selected->SetColor( layer, m_currentSettings->GetColor( layer ) );
365 
366     settingsMgr.SaveColorSettings( selected, m_colorNamespace );
367 
368     return true;
369 }
370