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