KiCad PCB EDA Suite
confirm.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) 2007 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <wx/app.h>
26#include <wx/stockitem.h>
27#include <wx/richmsgdlg.h>
28#include <wx/choicdlg.h>
29#include <wx/crt.h>
30#include <confirm.h>
32#include <functional>
33#include <unordered_map>
34#include "cli/cli_names.h"
35
36// Set of dialogs that have been chosen not to be shown again
37static std::unordered_map<unsigned long, int> doNotShowAgainDlgs;
38
39
40bool IsGUI()
41{
42 if( !wxTheApp )
43 return false;
44
45#if wxCHECK_VERSION( 3, 1, 6 )
46 return wxTheApp->IsGUI();
47#else
48 // wxWidgets older than version 3.1.6 do not have a way to know if the app
49 // has a GUI or is a console application.
50 // So the trick is to set the App class name when starting kicad-cli, and when
51 // the app class name is the kicad-cli class name the app is a console app
52 bool run_gui = wxTheApp->GetClassName() != KICAD_CLI_APP_NAME;
53 return run_gui;
54#endif
55}
56
57
58KIDIALOG::KIDIALOG( wxWindow* aParent, const wxString& aMessage, const wxString& aCaption,
59 long aStyle )
60 : wxRichMessageDialog( aParent, aMessage, aCaption, aStyle | wxCENTRE | wxSTAY_ON_TOP ),
61 m_hash( 0 ),
62 m_cancelMeansCancel( true )
63{
64}
65
66
67KIDIALOG::KIDIALOG( wxWindow* aParent, const wxString& aMessage, KD_TYPE aType,
68 const wxString& aCaption )
69 : wxRichMessageDialog( aParent, aMessage, getCaption( aType, aCaption ), getStyle( aType ) ),
70 m_hash( 0 ),
71 m_cancelMeansCancel( true )
72{
73}
74
75
76void KIDIALOG::DoNotShowCheckbox( wxString aUniqueId, int line )
77{
78 ShowCheckBox( _( "Do not show again" ), false );
79
80 m_hash = std::hash<wxString>{}( aUniqueId ) + line;
81}
82
83
85{
86 return doNotShowAgainDlgs.count( m_hash ) > 0;
87}
88
89
91{
93}
94
95
96bool KIDIALOG::Show( bool aShow )
97{
98 // We should check the do-not-show-again setting only when the dialog is displayed
99 if( aShow )
100 {
101 // Check if this dialog should be shown to the user
102 auto it = doNotShowAgainDlgs.find( m_hash );
103
104 if( it != doNotShowAgainDlgs.end() )
105 return it->second;
106 }
107
108 int ret = wxRichMessageDialog::Show( aShow );
109
110 // Has the user asked not to show the dialog again?
111 // Note that we don't save a Cancel value unless the Cancel button is being used for some
112 // other function (which is actually more common than it being used for Cancel).
113 if( IsCheckBoxChecked() && (!m_cancelMeansCancel || ret != wxID_CANCEL ) )
115
116 return ret;
117}
118
119
121{
122 // Check if this dialog should be shown to the user
123 auto it = doNotShowAgainDlgs.find( m_hash );
124
125 if( it != doNotShowAgainDlgs.end() )
126 return it->second;
127
128 int ret = wxRichMessageDialog::ShowModal();
129
130 // Has the user asked not to show the dialog again?
131 // Note that we don't save a Cancel value unless the Cancel button is being used for some
132 // other function (which is actually more common than it being used for Cancel).
133 if( IsCheckBoxChecked() && (!m_cancelMeansCancel || ret != wxID_CANCEL ) )
135
136 return ret;
137}
138
139
140wxString KIDIALOG::getCaption( KD_TYPE aType, const wxString& aCaption )
141{
142 if( !aCaption.IsEmpty() )
143 return aCaption;
144
145 switch( aType )
146 {
147 case KD_NONE: /* fall through */
148 case KD_INFO: return _( "Message" );
149 case KD_QUESTION: return _( "Question" );
150 case KD_WARNING: return _( "Warning" );
151 case KD_ERROR: return _( "Error" );
152 }
153
154 return wxEmptyString;
155}
156
157
159{
160 long style = wxOK | wxCENTRE | wxSTAY_ON_TOP;
161
162 switch( aType )
163 {
164 case KD_NONE: break;
165 case KD_INFO: style |= wxICON_INFORMATION; break;
166 case KD_QUESTION: style |= wxICON_QUESTION; break;
167 case KD_WARNING: style |= wxICON_WARNING; break;
168 case KD_ERROR: style |= wxICON_ERROR; break;
169 }
170
171 return style;
172}
173
174
175bool OverrideLock( wxWindow* aParent, const wxString& aMessage )
176{
177#ifdef __APPLE__
178 // wxMessageDialog gets the button spacing wrong on Mac so we have to use wxRichMessageDialog.
179 // Note that its warning icon is more like wxMessageDialog's error icon, so we use it instead
180 // of wxICON_ERROR.
181 wxRichMessageDialog dlg( aParent, aMessage, _( "File Open Warning" ),
182 wxYES_NO | wxICON_WARNING | wxCENTER );
183 dlg.SetExtendedMessage( _( "Interleaved saves may produce very unexpected results." )
184 + wxS( "\n" ) );
185 dlg.SetYesNoLabels( _( "Cancel" ), _( "Open Anyway" ) );
186#else
187 wxMessageDialog dlg( aParent, aMessage, _( "File Open Warning" ),
188 wxYES_NO | wxICON_ERROR | wxCENTER );
189 dlg.SetExtendedMessage( _( "Interleaved saves may produce very unexpected results." ) );
190 dlg.SetYesNoLabels( _( "Cancel" ), _( "Open Anyway" ) );
191#endif
192
193 return dlg.ShowModal() == wxID_NO;
194}
195
196
197int UnsavedChangesDialog( wxWindow* parent, const wxString& aMessage, bool* aApplyToAll )
198{
199 static bool s_apply_to_all = false;
200
201 wxRichMessageDialog dlg( parent, aMessage, _( "Save Changes?" ),
202 wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_WARNING | wxCENTER );
203 dlg.SetExtendedMessage( _( "If you don't save, all your changes will be permanently lost." )
204 + wxS( "\n" ) );
205 dlg.SetYesNoLabels( _( "Save" ), _( "Discard Changes" ) );
206
207 if( aApplyToAll )
208 dlg.ShowCheckBox( _( "Apply to all" ), s_apply_to_all );
209
210 int ret = dlg.ShowModal();
211
212 if( aApplyToAll )
213 {
214 *aApplyToAll = dlg.IsCheckBoxChecked();
215 s_apply_to_all = dlg.IsCheckBoxChecked();
216 }
217
218 // Returns wxID_YES, wxID_NO, or wxID_CANCEL
219 return ret;
220}
221
222
223int UnsavedChangesDialog( wxWindow* parent, const wxString& aMessage )
224{
225#ifdef __APPLE__
226 // wxMessageDialog gets the button order (and spacing) wrong on Mac so we have to use
227 // wxRichMessageDialog.
228 return UnsavedChangesDialog( parent, aMessage, nullptr );
229#else
230 #ifdef _WIN32
231 // wxMessageDialog on windows invokes TaskDialogIndirect which is a native function for a dialog
232 // As a result it skips wxWidgets for modal management...and we don't parent frames properly
233 // among other things for Windows to do the right thing by default
234 // Disable all the windows manually to avoid being able to hit this dialog from the tool frame and kicad frame at the same time
235 wxWindowDisabler disable( true );
236 #endif
237
238 wxMessageDialog dlg( parent, aMessage, _( "Save Changes?" ),
239 wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_WARNING | wxCENTER );
240 dlg.SetExtendedMessage( _( "If you don't save, all your changes will be permanently lost." ) );
241 dlg.SetYesNoLabels( _( "Save" ), _( "Discard Changes" ) );
242
243 // Returns wxID_YES, wxID_NO, or wxID_CANCEL
244 return dlg.ShowModal();
245#endif
246}
247
248
249bool ConfirmRevertDialog( wxWindow* parent, const wxString& aMessage )
250{
251 wxMessageDialog dlg( parent, aMessage, wxEmptyString,
252 wxOK | wxCANCEL | wxOK_DEFAULT | wxICON_WARNING | wxCENTER );
253 dlg.SetExtendedMessage( _( "Your current changes will be permanently lost." ) );
254 dlg.SetOKCancelLabels( _( "Revert" ), _( "Cancel" ) );
255
256 return dlg.ShowModal() == wxID_OK;
257}
258
259
260bool HandleUnsavedChanges( wxWindow* aParent, const wxString& aMessage,
261 const std::function<bool()>& aSaveFunction )
262{
263 switch( UnsavedChangesDialog( aParent, aMessage ) )
264 {
265 case wxID_YES: return aSaveFunction();
266 case wxID_NO: return true;
267 default:
268 case wxID_CANCEL: return false;
269 }
270}
271
272
273int OKOrCancelDialog( wxWindow* aParent, const wxString& aWarning, const wxString& aMessage,
274 const wxString& aDetailedMessage, const wxString& aOKLabel,
275 const wxString& aCancelLabel, bool* aApplyToAll )
276{
277 wxRichMessageDialog dlg( aParent, aMessage, aWarning,
278 wxOK | wxCANCEL | wxOK_DEFAULT | wxICON_WARNING | wxCENTER );
279
280 dlg.SetOKCancelLabels( ( aOKLabel.IsEmpty() ) ? _( "OK" ) : aOKLabel,
281 ( aCancelLabel.IsEmpty() ) ? _( "Cancel" ) : aCancelLabel );
282
283 if( !aDetailedMessage.IsEmpty() )
284 dlg.SetExtendedMessage( aDetailedMessage );
285
286 if( aApplyToAll )
287 dlg.ShowCheckBox( _( "Apply to all" ), true );
288
289 int ret = dlg.ShowModal();
290
291 if( aApplyToAll )
292 *aApplyToAll = dlg.IsCheckBoxChecked();
293
294 // Returns wxID_OK or wxID_CANCEL
295 return ret;
296}
297
298
299// DisplayError should be deprecated, use DisplayErrorMessage instead
300void DisplayError( wxWindow* aParent, const wxString& aText, int aDisplayTime )
301{
302 if( !wxTheApp || !wxTheApp->IsMainLoopRunning() )
303 {
304 wxLogError( "%s", aText );
305 return;
306 }
307
308 if( !IsGUI() )
309 {
310 wxFprintf( stderr, aText );
311 return;
312 }
313
314 wxMessageDialog* dlg;
315 int icon = aDisplayTime > 0 ? wxICON_INFORMATION : wxICON_ERROR;
316
317 dlg = new wxMessageDialog( aParent, aText, _( "Warning" ),
318 wxOK | wxCENTRE | wxRESIZE_BORDER | icon | wxSTAY_ON_TOP );
319
320 dlg->ShowModal();
321 dlg->Destroy();
322}
323
324
325void DisplayErrorMessage( wxWindow* aParent, const wxString& aText, const wxString& aExtraInfo )
326{
327 if( !wxTheApp || !wxTheApp->IsMainLoopRunning() )
328 {
329 wxLogError( "%s %s", aText, aExtraInfo );
330 return;
331 }
332
333 if( !IsGUI() )
334 {
335 wxFprintf( stderr, aText );
336 return;
337 }
338
339 wxMessageDialog* dlg;
340
341 dlg = new wxMessageDialog( aParent, aText, _( "Error" ),
342 wxOK | wxCENTRE | wxRESIZE_BORDER | wxICON_ERROR | wxSTAY_ON_TOP );
343
344 if( !aExtraInfo.IsEmpty() )
345 dlg->SetExtendedMessage( aExtraInfo );
346
347 dlg->ShowModal();
348 dlg->Destroy();
349}
350
351
352void DisplayInfoMessage( wxWindow* aParent, const wxString& aMessage, const wxString& aExtraInfo )
353{
354 if( !wxTheApp || !wxTheApp->GetTopWindow() )
355 {
356 wxLogDebug( "%s %s", aMessage, aExtraInfo );
357 return;
358 }
359
360 if( !IsGUI() )
361 {
362 wxFprintf( stdout, "%s %s", aMessage, aExtraInfo );
363 return;
364 }
365
366 wxMessageDialog* dlg;
367 int icon = wxICON_INFORMATION;
368
369 dlg = new wxMessageDialog( aParent, aMessage, _( "Information" ),
370 wxOK | wxCENTRE | wxRESIZE_BORDER | icon | wxSTAY_ON_TOP );
371
372 if( !aExtraInfo.IsEmpty() )
373 dlg->SetExtendedMessage( aExtraInfo );
374
375 dlg->ShowModal();
376 dlg->Destroy();
377}
378
379
380bool IsOK( wxWindow* aParent, const wxString& aMessage )
381{
382 // wxMessageDialog no longer responds correctly to the <ESC> key (on at least OSX and MSW)
383 // so we're now using wxRichMessageDialog.
384 //
385 // Note also that we have to repurpose an OK/Cancel version of it because otherwise wxWidgets
386 // uses "destructive" spacing for the "No" button.
387
388#ifdef __APPLE__
389 // Why is wxICON_QUESTION a light-bulb on Mac? That has more of a hint or info connotation.
390 int icon = wxICON_WARNING;
391#else
392 int icon = wxICON_QUESTION;
393#endif
394
395#if !defined( __WXGTK__ )
396 wxRichMessageDialog dlg( aParent, aMessage, _( "Confirmation" ),
397 wxOK | wxCANCEL | wxOK_DEFAULT | wxCENTRE | icon | wxSTAY_ON_TOP );
398#else
399 wxMessageDialog dlg( aParent, aMessage, _( "Confirmation" ),
400 wxOK | wxCANCEL | wxOK_DEFAULT | wxCENTRE | icon | wxSTAY_ON_TOP );
401#endif
402
403 dlg.SetOKCancelLabels( _( "Yes" ), _( "No" ) );
404
405 return dlg.ShowModal() == wxID_OK;
406}
407
408
409int SelectSingleOption( wxWindow* aParent, const wxString& aTitle,
410 const wxString& aMessage, const wxArrayString& aOptions )
411{
412 wxSingleChoiceDialog dlg( aParent, aMessage, aTitle, aOptions );
413
414 if( dlg.ShowModal() != wxID_OK )
415 return -1;
416
417 return dlg.GetSelection();
418}
419
KD_TYPE
< Dialog type. Selects appropriate icon and default dialog title
Definition: confirm.h:49
@ KD_INFO
Definition: confirm.h:49
@ KD_QUESTION
Definition: confirm.h:49
@ KD_ERROR
Definition: confirm.h:49
@ KD_NONE
Definition: confirm.h:49
@ KD_WARNING
Definition: confirm.h:49
void ForceShowAgain()
Definition: confirm.cpp:90
bool DoNotShowAgain() const
Definition: confirm.cpp:84
bool m_cancelMeansCancel
Definition: confirm.h:79
static long getStyle(KD_TYPE aType)
Definition: confirm.cpp:158
unsigned long m_hash
Definition: confirm.h:78
KIDIALOG(wxWindow *aParent, const wxString &aMessage, const wxString &aCaption, long aStyle=wxOK)
Definition: confirm.cpp:58
void DoNotShowCheckbox(wxString file, int line)
Checks the 'do not show again' setting for the dialog.
Definition: confirm.cpp:76
int ShowModal() override
Definition: confirm.cpp:120
static wxString getCaption(KD_TYPE aType, const wxString &aCaption)
Definition: confirm.cpp:140
bool Show(bool aShow=true) override
Definition: confirm.cpp:96
#define KICAD_CLI_APP_NAME
Definition: cli_names.h:23
int SelectSingleOption(wxWindow *aParent, const wxString &aTitle, const wxString &aMessage, const wxArrayString &aOptions)
Display a dialog with radioboxes asking the user to select an option.
Definition: confirm.cpp:409
int OKOrCancelDialog(wxWindow *aParent, const wxString &aWarning, const wxString &aMessage, const wxString &aDetailedMessage, const wxString &aOKLabel, const wxString &aCancelLabel, bool *aApplyToAll)
Display a warning dialog with aMessage and returns the user response.
Definition: confirm.cpp:273
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition: confirm.cpp:380
static std::unordered_map< unsigned long, int > doNotShowAgainDlgs
Definition: confirm.cpp:37
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:300
bool OverrideLock(wxWindow *aParent, const wxString &aMessage)
Display a dialog indicating the file is already open, with an option to reset the lock.
Definition: confirm.cpp:175
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition: confirm.cpp:352
bool HandleUnsavedChanges(wxWindow *aParent, const wxString &aMessage, const std::function< bool()> &aSaveFunction)
Display a dialog with Save, Cancel and Discard Changes buttons.
Definition: confirm.cpp:260
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:325
int UnsavedChangesDialog(wxWindow *parent, const wxString &aMessage, bool *aApplyToAll)
A specialized version of HandleUnsavedChanges which handles an apply-to-all checkbox.
Definition: confirm.cpp:197
bool ConfirmRevertDialog(wxWindow *parent, const wxString &aMessage)
Display a confirmation dialog for a revert action.
Definition: confirm.cpp:249
bool IsGUI()
Determine if the application is running with a GUI.
Definition: confirm.cpp:40
This file is part of the common library.
#define _(s)