Merge lp:~mixxxdevelopers/mixxx/features_messageBox into lp:~mixxxdevelopers/mixxx/trunk
- features_messageBox
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 2414 |
Proposed branch: | lp:~mixxxdevelopers/mixxx/features_messageBox |
Merge into: | lp:~mixxxdevelopers/mixxx/trunk |
Diff against target: |
1305 lines (+570/-287) 15 files modified
mixxx/src/SConscript (+1/-1) mixxx/src/errordialog.cpp (+0/-76) mixxx/src/errordialog.h (+0/-51) mixxx/src/errordialoghandler.cpp (+208/-0) mixxx/src/errordialoghandler.h (+138/-0) mixxx/src/main.cpp (+28/-88) mixxx/src/midi/midimapping.cpp (+60/-12) mixxx/src/midi/midimapping.h (+3/-0) mixxx/src/midi/midiscriptengine.cpp (+114/-48) mixxx/src/midi/midiscriptengine.h (+9/-4) mixxx/src/mixxxcontrol.cpp (+2/-2) mixxx/src/recording/writeaudiofile.cpp (+1/-1) mixxx/src/soundsourcemp3.cpp (+1/-1) mixxx/src/upgrade.cpp (+4/-2) mixxx/src/widget/wpushbutton.cpp (+1/-1) |
To merge this branch: | bzr merge lp:~mixxxdevelopers/mixxx/features_messageBox |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Albert Santoni | Approve | ||
Sean M. Pappalardo | Needs Resubmitting | ||
Review via email: mp+23707@code.launchpad.net |
Commit message
Description of the change
This branch is now ready to be merged. It successfully provides a more flexible (and two-way) replacement for our flawed use of qWarning to create user-facing dialogs in many places. I have made this replacement in MidiMapping and MidiScriptEngine, of which the latter is a good example of usage and addresses Albert's initial desire to offer the user some actions (ignore or reload) when a MIDI script goes awry. I've tested these replacements extensively and feel that they're ready for code review and wider testing.
I respectfully ask that this branch be considered for merge to trunk in time for the 1.8 release for the following reasons:
1) It provides useful user feedback and actions when there are problems with MIDI scripts, especially now that qWarning has been disabled
2) It doesn't affect the core functionality of any classes except ErrorDialog, and the previous use of that has been accounted for and tests good. (qCritical boxes.)
I ask that reviewers pay special attention to threading intricacies in the new code as I am still very new at multi-threaded programming. I have tried to use signals for cross-thread communication but would not be surprised if I messed something up. (There is also still a thread problem on shutdown with multiple MIDI devices activated that has carried over from trunk.)
- 2380. By Sean M. Pappalardo
-
- Renamed class to a more accurate "ErrorDialogHan
dler"
- Renamed class to "ErrorDialogProperties"
- Protected all ErrorDialogProperties variables and made access functions
- Made ErrorDialogHandler a singleton and removed the previous global variable
- Moved ErrorDialogProperties instantiation into ErrorDialogHandler
- Fixed MidiMapping and MidiScriptEngine to work with the changes, tests good
- Mutex-protected the m_dialogKeys QList in ErrorDialogHandler, the only reentrant shared data item that is used by multiple threads
- Changed qDebugs to qWarnings in a few files where it made sense, since qWarning now just logs and they'll still show up if qDebugs are disabled
Sean M. Pappalardo (pegasus-renegadetech) wrote : | # |
Addressed all of Albert's concerns, resubmitting for review.
- 2381. By Sean M. Pappalardo
-
Idiot...forgot to add the renamed files.
Albert Santoni (gamegod) wrote : | # |
You should probably lock around your use of m_pSignalMapper, but other than that, I think this is a huge improvement since the last review! I'm impressed! Your code's starting to look like it was written by a C++ programmer. :)
Preview Diff
1 | === modified file 'mixxx/src/SConscript' |
2 | --- mixxx/src/SConscript 2010-05-04 17:21:17 +0000 |
3 | +++ mixxx/src/SConscript 2010-05-07 07:32:30 +0000 |
4 | @@ -342,7 +342,7 @@ |
5 | mixxxcontrol.cpp |
6 | mixxx.cpp |
7 | mixxxview.cpp |
8 | - errordialog.cpp |
9 | + errordialoghandler.cpp |
10 | upgrade.cpp |
11 | |
12 | soundsource.cpp |
13 | |
14 | === removed file 'mixxx/src/errordialog.cpp' |
15 | --- mixxx/src/errordialog.cpp 2010-01-05 11:18:14 +0000 |
16 | +++ mixxx/src/errordialog.cpp 1970-01-01 00:00:00 +0000 |
17 | @@ -1,76 +0,0 @@ |
18 | -/*************************************************************************** |
19 | - errordialog.cpp - description |
20 | - ------------------- |
21 | - begin : Sun Feb 22 2009 |
22 | - copyright : (C) 2009 by Sean M. Pappalardo |
23 | - email : pegasus@c64.org |
24 | -***************************************************************************/ |
25 | - |
26 | -/*************************************************************************** |
27 | -* * |
28 | -* This program is free software; you can redistribute it and/or modify * |
29 | -* it under the terms of the GNU General Public License as published by * |
30 | -* the Free Software Foundation; either version 2 of the License, or * |
31 | -* (at your option) any later version. * |
32 | -* * |
33 | -***************************************************************************/ |
34 | - |
35 | -#include "errordialog.h" |
36 | -#include <QMessageBox> |
37 | -#include <QtDebug> |
38 | -#include <QtCore> |
39 | - |
40 | -ErrorDialog::ErrorDialog() { |
41 | - m_errorCondition=false; |
42 | - connect(this, SIGNAL(showErrorDialog(int, QString, QString)), this, SLOT(errorDialog(int, QString, QString))); |
43 | -} |
44 | - |
45 | -ErrorDialog::~ErrorDialog() |
46 | -{ |
47 | -} |
48 | - |
49 | -void ErrorDialog::requestErrorDialog(int type, QString message) { |
50 | - switch (type) { |
51 | - case 1: requestErrorDialog(type,"Mixxx - Critical error",message); break; |
52 | - case 0: |
53 | - default: |
54 | - requestErrorDialog(type,"Mixxx - Warning",message); |
55 | - break; |
56 | - } |
57 | -} |
58 | - |
59 | -void ErrorDialog::requestErrorDialog(int type, QString title, QString message) { |
60 | - emit (showErrorDialog(type, title, message)); |
61 | - disconnect(this, SIGNAL(showErrorDialog(int, QString, QString)), this, SLOT(errorDialog(int, QString, QString))); // Avoid multiple dialogs until this one is acknowledged |
62 | -} |
63 | - |
64 | -void ErrorDialog::errorDialog(int type, QString title, QString message) { |
65 | - QMessageBox msgBox; |
66 | - msgBox.setText(message); |
67 | - msgBox.setWindowTitle(title); |
68 | - switch (type) { |
69 | - case 1: msgBox.setIcon(QMessageBox::Critical); break; |
70 | - case 0: |
71 | - default: |
72 | - msgBox.setIcon(QMessageBox::Warning); |
73 | - break; |
74 | - } |
75 | - msgBox.exec(); // Block so the user can read it before exiting |
76 | - |
77 | - if (type==1) { // If critical/fatal, gracefully exit application if possible |
78 | - m_errorCondition=true; |
79 | - if (QCoreApplication::instance()) { |
80 | - QCoreApplication::instance()->exit(-1); |
81 | - } |
82 | - else { |
83 | - qDebug() << "QCoreApplication::instance() is NULL! Abruptly quitting..."; |
84 | - exit(-1); |
85 | - } |
86 | - } |
87 | - |
88 | - connect(this, SIGNAL(showErrorDialog(int, QString, QString)), this, SLOT(errorDialog(int, QString, QString))); // reconnect the signal |
89 | -} |
90 | - |
91 | -bool ErrorDialog::checkError() { |
92 | - return m_errorCondition; |
93 | -} |
94 | \ No newline at end of file |
95 | |
96 | === removed file 'mixxx/src/errordialog.h' |
97 | --- mixxx/src/errordialog.h 2010-01-05 11:18:14 +0000 |
98 | +++ mixxx/src/errordialog.h 1970-01-01 00:00:00 +0000 |
99 | @@ -1,51 +0,0 @@ |
100 | -/*************************************************************************** |
101 | - errordialog.h - description |
102 | - ------------------- |
103 | - begin : Fri Feb 20 2009 |
104 | - copyright : (C) 2009 by Sean M. Pappalardo |
105 | - email : pegasus@c64.org |
106 | - ***************************************************************************/ |
107 | - |
108 | -/*************************************************************************** |
109 | - * * |
110 | - * This program is free software; you can redistribute it and/or modify * |
111 | - * it under the terms of the GNU General Public License as published by * |
112 | - * the Free Software Foundation; either version 2 of the License, or * |
113 | - * (at your option) any later version. * |
114 | - * * |
115 | - ***************************************************************************/ |
116 | - |
117 | -#ifndef ERRORDIALOG_H |
118 | -#define ERRORDIALOG_H |
119 | - |
120 | -#include <QObject> |
121 | - |
122 | -/** |
123 | - * Class used to allow all threads to display message boxes on error conditions |
124 | - * |
125 | - *@author Sean M. Pappalardo |
126 | - */ |
127 | - |
128 | -class ErrorDialog : public QObject { |
129 | - Q_OBJECT |
130 | -public: |
131 | - ErrorDialog(); |
132 | - ~ErrorDialog(); |
133 | - /** A qMessageHandler calls either of these to emit a signal to display the requested message box */ |
134 | - void requestErrorDialog(int type, QString message); |
135 | - void requestErrorDialog(int type, QString title, QString message); |
136 | - /** Allows a means for main() to skip exec() if there was a critical or fatal error dialog displayed on app initialization */ |
137 | - bool checkError(); |
138 | - |
139 | -signals: |
140 | - void showErrorDialog(int type, QString title, QString message); |
141 | - |
142 | -private: |
143 | - bool m_errorCondition; |
144 | - |
145 | -private slots: |
146 | - /** Actually displays the box */ |
147 | - void errorDialog(int type, QString title, QString message); |
148 | -}; |
149 | - |
150 | -#endif |
151 | |
152 | === added file 'mixxx/src/errordialoghandler.cpp' |
153 | --- mixxx/src/errordialoghandler.cpp 1970-01-01 00:00:00 +0000 |
154 | +++ mixxx/src/errordialoghandler.cpp 2010-05-07 07:32:30 +0000 |
155 | @@ -0,0 +1,208 @@ |
156 | +/*************************************************************************** |
157 | + errordialoghandler.cpp - description |
158 | + ------------------- |
159 | + begin : Sun Feb 22 2009 |
160 | + copyright : (C) 2009 by Sean M. Pappalardo |
161 | + email : pegasus@c64.org |
162 | +***************************************************************************/ |
163 | + |
164 | +/*************************************************************************** |
165 | +* * |
166 | +* This program is free software; you can redistribute it and/or modify * |
167 | +* it under the terms of the GNU General Public License as published by * |
168 | +* the Free Software Foundation; either version 2 of the License, or * |
169 | +* (at your option) any later version. * |
170 | +* * |
171 | +***************************************************************************/ |
172 | + |
173 | +#include "errordialoghandler.h" |
174 | +#include <QMessageBox> |
175 | +#include <QtDebug> |
176 | +#include <QtCore> |
177 | + |
178 | +ErrorDialogProperties::ErrorDialogProperties() { |
179 | + m_type = DLG_NONE; |
180 | + m_icon = QMessageBox::NoIcon; |
181 | + m_title = "Mixxx"; |
182 | + m_modal = true; |
183 | +} |
184 | + |
185 | +void ErrorDialogProperties::setTitle(QString title) { |
186 | + m_title.append(" - ").append(title); |
187 | +} |
188 | + |
189 | +void ErrorDialogProperties::setText(QString text) { |
190 | + // If no key is set, use this window text since it is likely to be unique |
191 | + if (m_key.isEmpty()) m_key = text; |
192 | + m_text = text; |
193 | +} |
194 | + |
195 | +void ErrorDialogProperties::setType(DialogType typeToSet) { |
196 | + m_type = typeToSet; |
197 | + switch (m_type) { |
198 | + case DLG_FATAL: // Fatal uses critical icon |
199 | + case DLG_CRITICAL: m_icon = QMessageBox::Critical; break; |
200 | + case DLG_WARNING: m_icon = QMessageBox::Warning; break; |
201 | + case DLG_INFO: m_icon = QMessageBox::Information; break; |
202 | + case DLG_QUESTION: m_icon = QMessageBox::Question; break; |
203 | + case DLG_NONE: |
204 | + default: |
205 | + // default is NoIcon |
206 | + break; |
207 | + } |
208 | +} |
209 | + |
210 | +void ErrorDialogProperties::addButton(QMessageBox::StandardButton button) { |
211 | + m_buttons.append(button); |
212 | +} |
213 | + |
214 | + |
215 | +// ---------------------------------------------------- |
216 | +// ---------- ErrorDialogHandler begins here ---------- |
217 | + |
218 | +ErrorDialogHandler::ErrorDialogHandler() { |
219 | + m_pSignalMapper = new QSignalMapper(this); |
220 | + connect(m_pSignalMapper, SIGNAL(mapped(QString)), this, SLOT(boxClosed(QString))); |
221 | + |
222 | + m_errorCondition=false; |
223 | + connect(this, SIGNAL(showErrorDialog(ErrorDialogProperties*)), |
224 | + this, SLOT(errorDialog(ErrorDialogProperties*))); |
225 | +} |
226 | + |
227 | +ErrorDialogHandler::~ErrorDialogHandler() |
228 | +{ |
229 | + delete m_pSignalMapper; |
230 | + delete s_pInstance; |
231 | +} |
232 | + |
233 | +ErrorDialogProperties* ErrorDialogHandler::newDialogProperties() { |
234 | + ErrorDialogProperties* props = new ErrorDialogProperties(); |
235 | + return props; |
236 | +} |
237 | + |
238 | +bool ErrorDialogHandler::requestErrorDialog(DialogType type, QString message) { |
239 | + ErrorDialogProperties* props = newDialogProperties(); |
240 | + props->setType(type); |
241 | + props->setText(message); |
242 | + switch (type) { |
243 | + case DLG_FATAL: props->setTitle("Fatal error"); break; |
244 | + case DLG_CRITICAL: props->setTitle("Critical error"); break; |
245 | + case DLG_WARNING: props->setTitle("Warning"); break; |
246 | + case DLG_INFO: props->setTitle("Information"); break; |
247 | + case DLG_QUESTION: props->setTitle("Question"); break; |
248 | + case DLG_NONE: |
249 | + default: |
250 | + // Default title & (lack of) icon is fine |
251 | + break; |
252 | + } |
253 | + return requestErrorDialog(props); |
254 | +} |
255 | + |
256 | +bool ErrorDialogHandler::requestErrorDialog(ErrorDialogProperties* props) { |
257 | + |
258 | + // Make sure the minimum items are set |
259 | + QString text = props->getText(); |
260 | + Q_ASSERT(!text.isEmpty()); |
261 | + |
262 | + // Skip if a dialog with the same key is already displayed |
263 | + m_mutex.lock(); |
264 | + bool keyExists = m_dialogKeys.contains(props->getKey()); |
265 | + m_mutex.unlock(); |
266 | + if (keyExists) { |
267 | + ErrorDialogProperties* dlgPropsTemp = props; |
268 | + props = NULL; |
269 | + delete dlgPropsTemp; |
270 | + return false; |
271 | + } |
272 | + |
273 | + emit (showErrorDialog(props)); |
274 | + return true; |
275 | +} |
276 | + |
277 | +void ErrorDialogHandler::errorDialog(ErrorDialogProperties* props) { |
278 | + |
279 | + // Jest makin' sho' this is only run in the main (GUI) thread |
280 | + Q_ASSERT(QThread::currentThread()->objectName() == "Main"); |
281 | + |
282 | + QMessageBox* msgBox = new QMessageBox(); |
283 | + |
284 | + msgBox->setIcon(props->m_icon); |
285 | + msgBox->setWindowTitle(props->m_title); |
286 | + msgBox->setText(props->m_text); |
287 | + if (!props->m_infoText.isEmpty()) msgBox->setInformativeText(props->m_infoText); |
288 | + if (!props->m_details.isEmpty()) msgBox->setDetailedText(props->m_details); |
289 | + |
290 | + QPushButton* buttonToSet; |
291 | + bool setDefault; |
292 | + while(!props->m_buttons.isEmpty()) { |
293 | + setDefault = false; |
294 | + if (props->m_buttons.first() == props->m_defaultButton) setDefault = true; |
295 | + |
296 | + buttonToSet = msgBox->addButton(props->m_buttons.takeFirst()); |
297 | + |
298 | + if (setDefault) msgBox->setDefaultButton(buttonToSet); |
299 | + } |
300 | + |
301 | + if (props->m_escapeButton) msgBox->setEscapeButton(msgBox->button(props->m_escapeButton)); |
302 | + |
303 | + msgBox->setModal(props->m_modal); |
304 | + |
305 | + // This deletes the msgBox automatically, avoiding a memory leak |
306 | + msgBox->setAttribute(Qt::WA_DeleteOnClose, true); |
307 | + |
308 | + m_mutex.lock(); |
309 | + m_dialogKeys.append(props->m_key); // To avoid duplicate dialogs on the same error |
310 | + m_mutex.unlock(); |
311 | + |
312 | + // Signal mapper calls our slot with the key parameter so it knows which to remove from the list |
313 | + connect(msgBox, SIGNAL(finished(int)), m_pSignalMapper, SLOT(map())); |
314 | + m_pSignalMapper->setMapping(msgBox, props->m_key); |
315 | + |
316 | + if (props->m_modal) msgBox->exec(); // Blocks so the user has a chance to read it before application exit |
317 | + else msgBox->show(); |
318 | + |
319 | + // If fatal, should we just abort here and not try to exit gracefully? |
320 | + |
321 | + if (props->m_type>=DLG_CRITICAL) { // If critical/fatal, gracefully exit application if possible |
322 | + m_errorCondition=true; |
323 | + if (QCoreApplication::instance()) { |
324 | + QCoreApplication::instance()->exit(-1); |
325 | + } |
326 | + else { |
327 | + qDebug() << "QCoreApplication::instance() is NULL! Abruptly quitting..."; |
328 | + if (props->m_type==DLG_FATAL) abort(); |
329 | + else exit(-1); |
330 | + } |
331 | + } |
332 | + |
333 | + ErrorDialogProperties* dlgPropsTemp = props; |
334 | + props = NULL; |
335 | + delete dlgPropsTemp; |
336 | +} |
337 | + |
338 | +void ErrorDialogHandler::boxClosed(QString key) |
339 | +{ |
340 | + QMessageBox* msgBox = (QMessageBox*)m_pSignalMapper->mapping(key); |
341 | + |
342 | + QMessageBox::StandardButton whichStdButton = msgBox->standardButton(msgBox->clickedButton()); |
343 | + |
344 | + emit stdButtonClicked(key, whichStdButton); |
345 | + |
346 | + // If the user clicks "Ignore," we leave the key in the list so the same |
347 | + // error is not displayed again for the duration of the session |
348 | + if (whichStdButton == QMessageBox::Ignore) { |
349 | + qWarning() << "Suppressing this" << msgBox->windowTitle() << "error box for the duration of the application."; |
350 | + return; |
351 | + } |
352 | + |
353 | + m_mutex.lock(); |
354 | + if (m_dialogKeys.contains(key)) { |
355 | + if (!m_dialogKeys.removeOne(key)) qWarning() << "Error dialog key removal from list failed!"; |
356 | + } |
357 | + else qWarning() << "Error dialog key is missing from key list!"; |
358 | + m_mutex.unlock(); |
359 | +} |
360 | + |
361 | +bool ErrorDialogHandler::checkError() { |
362 | + return m_errorCondition; |
363 | +} |
364 | \ No newline at end of file |
365 | |
366 | === added file 'mixxx/src/errordialoghandler.h' |
367 | --- mixxx/src/errordialoghandler.h 1970-01-01 00:00:00 +0000 |
368 | +++ mixxx/src/errordialoghandler.h 2010-05-07 07:32:30 +0000 |
369 | @@ -0,0 +1,138 @@ |
370 | +/*************************************************************************** |
371 | + errordialoghandler.h - description |
372 | + ------------------- |
373 | + begin : Fri Feb 20 2009 |
374 | + copyright : (C) 2009 by Sean M. Pappalardo |
375 | + email : pegasus@c64.org |
376 | + ***************************************************************************/ |
377 | + |
378 | +/*************************************************************************** |
379 | + * * |
380 | + * This program is free software; you can redistribute it and/or modify * |
381 | + * it under the terms of the GNU General Public License as published by * |
382 | + * the Free Software Foundation; either version 2 of the License, or * |
383 | + * (at your option) any later version. * |
384 | + * * |
385 | + ***************************************************************************/ |
386 | + |
387 | +#ifndef ERRORDIALOGHANDLER_H |
388 | +#define ERRORDIALOGHANDLER_H |
389 | + |
390 | +#include <QObject> |
391 | +#include <QMessageBox> |
392 | +#include <QSignalMapper> |
393 | +#include <QMutex> |
394 | + |
395 | +/** |
396 | + * Class used to allow all threads to display message boxes on error conditions |
397 | + * with a custom list of standard buttons and to be able to react to them |
398 | + * |
399 | + *@author Sean M. Pappalardo |
400 | + */ |
401 | + |
402 | +typedef enum { |
403 | + DLG_FATAL = 5, |
404 | + DLG_CRITICAL = 4, |
405 | + DLG_WARNING = 3, |
406 | + DLG_INFO = 2, |
407 | + DLG_QUESTION = 1, |
408 | + DLG_NONE = 0 // No icon (default) |
409 | +} DialogType; |
410 | + |
411 | +class ErrorDialogProperties { |
412 | + public: |
413 | + // Befriending ErrorDialogHandler allows it to have cleaner code |
414 | + // since the two are closely related anyway |
415 | + friend class ErrorDialogHandler; |
416 | + |
417 | + /** Set the window title. ("Mixxx" is always prepended.) */ |
418 | + void setTitle(QString title); |
419 | + /** Set a key to prevent multiple dialogs until the first is closed */ |
420 | + void setKey(QString key) { m_key = key; } |
421 | + QString getKey() { return m_key; } |
422 | + /** Set the primary window text */ |
423 | + void setText(QString text); |
424 | + QString getText() { return m_text; } |
425 | + /** Set additional window text */ |
426 | + void setInfoText(QString text) { m_infoText = text; } |
427 | + /** Set detailed text (causes "Show Details" button to appear.) */ |
428 | + void setDetails(QString text) { m_details = text; } |
429 | + /** Set whether the box is modal (blocks the GUI) or not */ |
430 | + void setModal(bool modal) { m_modal = modal; } |
431 | + /** Automatically sets the icon to match the type for convenience. */ |
432 | + void setType(DialogType typeToSet); |
433 | + /** Set the box's icon to one of the standard ones */ |
434 | + void setIcon(QMessageBox::Icon icon) { m_icon = icon; } |
435 | + |
436 | + /** Add a standard button to the box */ |
437 | + void addButton(QMessageBox::StandardButton); |
438 | + /** Set the default button to highlight */ |
439 | + void setDefaultButton(QMessageBox::StandardButton button) { m_defaultButton = button; } |
440 | + /** Set the button to click if the Escape key is pressed */ |
441 | + void setEscapeButton(QMessageBox::StandardButton button) { m_escapeButton = button; } |
442 | + |
443 | + protected: |
444 | + // Protected since only ErrorDialogHandler should instantiate this |
445 | + ErrorDialogProperties(); |
446 | + |
447 | + QString m_title; |
448 | + QString m_key; |
449 | + QString m_text; |
450 | + QString m_infoText; |
451 | + QString m_details; |
452 | + bool m_modal; |
453 | + DialogType m_type; |
454 | + QMessageBox::Icon m_icon; |
455 | + /** List of standard buttons to add to the box, in order |
456 | + Note that a QMessageBox::Ignore button, if clicked, will suppress |
457 | + error boxes with the same key for the duration of the application. */ |
458 | + QList<QMessageBox::StandardButton> m_buttons; |
459 | + /** The default button to highlight, if any. */ |
460 | + QMessageBox::StandardButton m_defaultButton; |
461 | + /** The button that's clicked if the Escape key is pressed. */ |
462 | + QMessageBox::StandardButton m_escapeButton; |
463 | +}; |
464 | + |
465 | +/** Singleton class because we only need one Handler to manage all error dialogs */ |
466 | +class ErrorDialogHandler : public QObject { |
467 | + Q_OBJECT |
468 | +public: |
469 | + static ErrorDialogHandler* instance() { |
470 | + if (!s_pInstance) |
471 | + s_pInstance = new ErrorDialogHandler; |
472 | + return s_pInstance; |
473 | + } |
474 | + |
475 | + ~ErrorDialogHandler(); |
476 | + /** Call this to get a new instance of ErrorDialogProperties to populate with data */ |
477 | + ErrorDialogProperties* newDialogProperties(); |
478 | + /** A qMessageHandler or any thread calls either of these to emit a signal to display the requested message box */ |
479 | + /** They return false if a dialog with the same key (or title if no key) is already displayed */ |
480 | + bool requestErrorDialog(DialogType type, QString message); |
481 | + bool requestErrorDialog(ErrorDialogProperties* props); |
482 | + /** Allows a means for main() to skip exec() if there was a critical or fatal error dialog displayed on app initialization */ |
483 | + bool checkError(); |
484 | + |
485 | +signals: |
486 | + void showErrorDialog(ErrorDialogProperties* props); |
487 | + void stdButtonClicked(QString key, QMessageBox::StandardButton whichStdButton); |
488 | + |
489 | +private: |
490 | + ErrorDialogHandler(); // Private constructor |
491 | + ErrorDialogHandler(const ErrorDialogHandler&); // Prevent copy-construction |
492 | + ErrorDialogHandler& operator=(const ErrorDialogHandler&); // Prevent assignment |
493 | + |
494 | + static ErrorDialogHandler *s_pInstance; |
495 | + |
496 | + bool m_errorCondition; |
497 | + QList<QString> m_dialogKeys; |
498 | + QSignalMapper* m_pSignalMapper; |
499 | + QMutex m_mutex; |
500 | + |
501 | +private slots: |
502 | + /** Actually displays the box */ |
503 | + void errorDialog(ErrorDialogProperties* props); |
504 | + void boxClosed(QString key); |
505 | +}; |
506 | + |
507 | +#endif |
508 | |
509 | === modified file 'mixxx/src/main.cpp' |
510 | --- mixxx/src/main.cpp 2010-04-12 07:42:45 +0000 |
511 | +++ mixxx/src/main.cpp 2010-05-07 07:32:30 +0000 |
512 | @@ -31,8 +31,9 @@ |
513 | #include "mixxx.h" |
514 | #include "qpixmap.h" |
515 | #include "qsplashscreen.h" |
516 | -#include "errordialog.h" |
517 | +#include "errordialoghandler.h" |
518 | #include "defs_audiofiles.h" |
519 | +#include "defs_version.h" |
520 | |
521 | #ifdef __LADSPA__ |
522 | #include <ladspa/ladspaloader.h> |
523 | @@ -70,72 +71,12 @@ |
524 | QApplication * a; |
525 | |
526 | QStringList plugin_paths; //yes this is global. sometimes global is good. |
527 | -ErrorDialog *dialogHelper; //= new ErrorDialog(); // allows threads to show error dialogs |
528 | +ErrorDialogHandler* ErrorDialogHandler::s_pInstance = 0; // Resolves "undefined reference" linker errors |
529 | |
530 | void qInitImages_mixxx(); |
531 | |
532 | -void MessageOutput( QtMsgType type, const char * msg ) |
533 | -{ |
534 | - static QMutex mutex; |
535 | - QMutexLocker locker(&mutex); |
536 | - |
537 | - switch ( type ) { |
538 | - case QtDebugMsg: |
539 | -#ifdef __WINDOWS__ |
540 | - if (strstr(msg, "doneCurrent")) { |
541 | - break; |
542 | - } |
543 | -#endif |
544 | - fprintf( stderr, "Debug: %s\n", msg ); |
545 | - break; |
546 | - case QtWarningMsg: |
547 | - fprintf( stderr, "Warning: %s\n", msg); |
548 | - break; |
549 | - case QtCriticalMsg: |
550 | - fprintf( stderr, "Critical: %s\n", msg ); |
551 | - QMessageBox::warning(0, "Mixxx", msg); |
552 | - exit(-1); |
553 | - break; |
554 | - case QtFatalMsg: |
555 | - fprintf( stderr, "Fatal: %s\n", msg ); |
556 | - QMessageBox::warning(0, "Mixxx", msg); |
557 | - abort(); |
558 | - } |
559 | -} |
560 | - |
561 | QFile Logfile; // global logfile variable |
562 | |
563 | - |
564 | -void MessageToLogfile( QtMsgType type, const char * msg ) |
565 | -{ |
566 | - static QMutex mutex; |
567 | - QMutexLocker locker(&mutex); |
568 | - |
569 | - Q3TextStream Log( &Logfile ); |
570 | - switch ( type ) { |
571 | - case QtDebugMsg: |
572 | - Log << "Debug: " << msg << "\n"; |
573 | - break; |
574 | - case QtWarningMsg: |
575 | - Log << "Warning: " << msg << "\n"; |
576 | - //a->lock(); //this doesn't do anything in Qt4 |
577 | - QMessageBox::warning(0, "Mixxx", msg); |
578 | - //a->unlock(); |
579 | - break; |
580 | - case QtCriticalMsg: |
581 | - fprintf( stderr, "Critical: %s\n", msg ); |
582 | - QMessageBox::warning(0, "Mixxx", msg); |
583 | - exit(-1); |
584 | - break; |
585 | - case QtFatalMsg: |
586 | - fprintf( stderr, "Fatal: %s\n", msg ); |
587 | - QMessageBox::warning(0, "Mixxx", msg); |
588 | - abort(); |
589 | - } |
590 | - Logfile.flush(); |
591 | -} |
592 | - |
593 | - |
594 | /* Debug message handler which outputs to both a logfile and a |
595 | * and prepends the thread the message came from too. |
596 | * |
597 | @@ -161,7 +102,9 @@ |
598 | #endif |
599 | } |
600 | |
601 | - Q3TextStream Log( &Logfile ); |
602 | + Q3TextStream Log( &Logfile ); |
603 | + |
604 | + ErrorDialogHandler* dialogHandler = ErrorDialogHandler::instance(); |
605 | |
606 | switch ( type ) { |
607 | case QtDebugMsg: |
608 | @@ -176,26 +119,22 @@ |
609 | case QtWarningMsg: |
610 | fprintf( stderr, "Warning: %s\n", s); |
611 | Log << "Warning: " << s << "\n"; |
612 | - //QMessageBox::warning(0, "Mixxx", input); |
613 | - //dialogHelper->requestErrorDialog(0,input); |
614 | - //I will break your legs if you re-enable the above lines of code. |
615 | - //You shouldn't be using qWarning for reporting user-facing errors. |
616 | - //Implement your own error message box... |
617 | - // - Albert (March 11, 2010) |
618 | + // Don't use qWarning for reporting user-facing errors. |
619 | + //dialogHandler->requestErrorDialog(DLG_WARNING,input); |
620 | break; |
621 | case QtCriticalMsg: |
622 | fprintf( stderr, "Critical: %s\n", s ); |
623 | Log << "Critical: " << s << "\n"; |
624 | - //QMessageBox::critical(0, "Mixxx", input); |
625 | - dialogHelper->requestErrorDialog(1,input); |
626 | -// exit(-1); |
627 | - break; //NOTREACHED(?) |
628 | + Logfile.flush(); // Ensure the error is written to the log before exiting |
629 | + dialogHandler->requestErrorDialog(DLG_CRITICAL,input); |
630 | +// exit(-1); // Done in ErrorDialogHandler |
631 | + break; //NOTREACHED |
632 | case QtFatalMsg: |
633 | fprintf( stderr, "Fatal: %s\n", s ); |
634 | Log << "Fatal: " << s << "\n"; |
635 | - //QMessageBox::critical(0, "Mixxx", input); |
636 | - dialogHelper->requestErrorDialog(1,input); |
637 | - abort(); |
638 | + Logfile.flush(); // Ensure the error is written to the log before aborting |
639 | + dialogHandler->requestErrorDialog(DLG_FATAL,input); |
640 | +// abort(); // Done in ErrorDialogHandler |
641 | break; //NOTREACHED |
642 | } |
643 | Logfile.flush(); |
644 | @@ -208,22 +147,21 @@ |
645 | |
646 | |
647 | //it seems like this code should be inline in MessageHandler() but for some reason having it there corrupts the messages sometimes -kousu 2/2009 |
648 | - |
649 | + |
650 | |
651 | #ifdef __WINDOWS__ |
652 | #ifdef DEBUGCONSOLE |
653 | InitDebugConsole(); |
654 | #endif |
655 | #endif |
656 | - dialogHelper = new ErrorDialog(); |
657 | - |
658 | qInstallMsgHandler( MessageHandler ); |
659 | - |
660 | + |
661 | + // Other things depend on this name to enforce thread exclusivity, |
662 | + // so if you change it here, change it also in: |
663 | + // * ErrorDialogHandler::errorDialog() |
664 | QThread::currentThread()->setObjectName("Main"); |
665 | a = new QApplication(argc, argv); |
666 | |
667 | - |
668 | - |
669 | #ifdef __LADSPA__ |
670 | //LADSPALoader ladspaloader; |
671 | #endif |
672 | @@ -251,7 +189,9 @@ |
673 | for (int i=0; i<argc; ++i) |
674 | { |
675 | if (argv[i]==QString("-h") || argv[i]==QString("--h") || argv[i]==QString("--help")) { |
676 | - printf("Mixxx digital DJ software - command line options"); |
677 | + printf("Mixxx digital DJ software v"); |
678 | + printf(VERSION); |
679 | + printf(" - Command line options"); |
680 | printf("\n(These are case-sensitive.)\n\n\ |
681 | [FILE] Load the specified music file(s) at start-up.\n\ |
682 | Each must be one of the following file types:\n\ |
683 | @@ -345,7 +285,7 @@ |
684 | |
685 | int result = -1; |
686 | |
687 | - if (!dialogHelper->checkError()) { |
688 | + if (!(ErrorDialogHandler::instance()->checkError())) { |
689 | qDebug() << "Displaying mixxx"; |
690 | mixxx->show(); |
691 | |
692 | @@ -355,12 +295,12 @@ |
693 | |
694 | delete mixxx; |
695 | |
696 | - qDebug() << "Mixxx shutdown complete."; |
697 | + qDebug() << "Mixxx shutdown complete with code" << result; |
698 | |
699 | - // Don't make any more output after this |
700 | - // or mixxx.log will get clobbered! |
701 | + // Don't make any more output after this |
702 | + // or mixxx.log will get clobbered! |
703 | if(Logfile.isOpen()) |
704 | - Logfile.close(); |
705 | + Logfile.close(); |
706 | |
707 | //delete plugin_paths; |
708 | return result; |
709 | |
710 | === modified file 'mixxx/src/midi/midimapping.cpp' |
711 | --- mixxx/src/midi/midimapping.cpp 2010-02-02 19:33:39 +0000 |
712 | +++ mixxx/src/midi/midimapping.cpp 2010-05-07 07:32:30 +0000 |
713 | @@ -30,14 +30,15 @@ |
714 | #include "mididevicedummy.h" |
715 | #include "midiledhandler.h" |
716 | #include "configobject.h" |
717 | +#include "errordialoghandler.h" |
718 | |
719 | #define REQUIRED_SCRIPT_FILE "midi-mappings-scripts.js" |
720 | #define XML_SCHEMA_VERSION "1" |
721 | #define DEFAULT_DEVICE_PRESET BINDINGS_PATH.append(m_deviceName.right(m_deviceName.size()-m_deviceName.indexOf(" ")-1).replace(" ", "_") + MIDI_MAPPING_EXTENSION) |
722 | |
723 | -static QString toHex(QString numberStr) { |
724 | - return "0x" + QString("0" + QString::number(numberStr.toUShort(), 16).toUpper()).right(2); |
725 | -} |
726 | +// static QString toHex(QString numberStr) { |
727 | +// return "0x" + QString("0" + QString::number(numberStr.toUShort(), 16).toUpper()).right(2); |
728 | +// } |
729 | |
730 | MidiMapping::MidiMapping(MidiDevice* outputMidiDevice) |
731 | : QObject(), |
732 | @@ -75,9 +76,12 @@ |
733 | |
734 | if(m_pScriptEngine) return; |
735 | |
736 | - //XXX Deadly hack attack: |
737 | + //XXX FIXME: Deadly hack attack: |
738 | if (m_pOutputMidiDevice == NULL) { |
739 | m_pOutputMidiDevice = new MidiDeviceDummy(this); //Just make some dummy device :( |
740 | + /* Why can't this be the same as the input MIDI device? Most of the time, |
741 | + the script engine thinks of it as the input device. Scripting is useful |
742 | + even if the MIDI device has no outputs - Sean 4/19/10 */ |
743 | } |
744 | //XXX Memory leak :( |
745 | |
746 | @@ -110,6 +114,9 @@ |
747 | m_pScriptEngine, SLOT(execute(QString))); |
748 | connect(this, SIGNAL(callMidiScriptFunction(QString, QString)), |
749 | m_pScriptEngine, SLOT(execute(QString, QString))); |
750 | + |
751 | + // Allow the MidiScriptEngine to tell us if it needs to reset the controller (on errors) |
752 | + connect(m_pScriptEngine, SIGNAL(resetController()), this, SLOT(reset())); |
753 | } |
754 | |
755 | void MidiMapping::loadScriptCode() { |
756 | @@ -598,13 +605,46 @@ |
757 | if (mixxxControl.getMidiOption()==MIDI_OPT_SCRIPT && |
758 | scriptFunctions.indexOf(mixxxControl.getControlObjectValue())==-1) { |
759 | |
760 | - QString statusText = QString(midiMessage.getMidiStatusByte()); |
761 | - qWarning() << "Error: Function" << mixxxControl.getControlObjectValue() |
762 | - << "was not found in loaded scripts." |
763 | - << "The MIDI Message with status byte" |
764 | - << statusText << midiMessage.getMidiNo() |
765 | - << "will not be bound. Please check the" |
766 | - << "mapping and script files."; |
767 | + QString status = QString("%1").arg(midiMessage.getMidiStatusByte(), 0, 16).toUpper(); |
768 | + status = "0x"+status; |
769 | + QString byte2 = QString("%1").arg(midiMessage.getMidiNo(), 0, 16).toUpper(); |
770 | + byte2 = "0x"+byte2; |
771 | + |
772 | + // If status is MIDI pitch, the 2nd byte is part of the payload so don't display it |
773 | + if (midiMessage.getMidiStatusByte() == 0xE0) byte2 = ""; |
774 | + |
775 | + QString errorLog = QString("MIDI script function \"%1\" not found. " |
776 | + "(Mapped to MIDI message %2 %3)") |
777 | + .arg(mixxxControl.getControlObjectValue()) |
778 | + .arg(status) |
779 | + .arg(byte2); |
780 | + |
781 | + if (m_pOutputMidiDevice != NULL |
782 | + && m_pOutputMidiDevice->midiDebugging()) { |
783 | + qCritical() << errorLog; |
784 | + } |
785 | + else { |
786 | + qWarning() << errorLog; |
787 | + ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); |
788 | + props->setType(DLG_WARNING); |
789 | + props->setTitle(tr("MIDI script function not found")); |
790 | + props->setText(QString(tr("The MIDI script function '%1' was not " |
791 | + "found in loaded scripts.")) |
792 | + .arg(mixxxControl.getControlObjectValue())); |
793 | + props->setInfoText(QString(tr("The MIDI message %1 %2 will not be bound." |
794 | + "\n(Click Show Details for hints.)")) |
795 | + .arg(status) |
796 | + .arg(byte2)); |
797 | + QString detailsText = QString(tr("* Check to see that the " |
798 | + "function name is spelled correctly in the mapping " |
799 | + "file (.xml) and script file (.js)\n")); |
800 | + detailsText += QString(tr("* Check to see that the script " |
801 | + "file name (.js) is spelled correctly in the mapping " |
802 | + "file (.xml)")); |
803 | + props->setDetails(detailsText); |
804 | + |
805 | + ErrorDialogHandler::instance()->requestErrorDialog(props); |
806 | + } |
807 | } else { |
808 | #endif |
809 | //Add to the input mapping. |
810 | @@ -936,7 +976,7 @@ |
811 | /*MixxxControl* MidiMapping::getInputMixxxControl(MidiMessage command) |
812 | { |
813 | if (!m_inputMapping.contains(command)) { |
814 | - qDebug() << "Warning: unbound MIDI command"; |
815 | + qWarning() << "Unbound MIDI command"; |
816 | qDebug() << "Midi Status:" << command.getMidiStatusByte(); |
817 | qDebug() << "Midi No:" << command.getMidiNo(); |
818 | qDebug() << "Midi Channel:" << command.getMidiChannel(); |
819 | @@ -1109,6 +1149,14 @@ |
820 | } |
821 | #endif |
822 | |
823 | +// Reset the MIDI controller |
824 | +void MidiMapping::reset() { |
825 | +#ifdef __MIDISCRIPT__ // Can't ifdef slots in the .h file, so we just do the body. |
826 | + restartScriptEngine(); |
827 | + applyPreset(); |
828 | +#endif |
829 | +} |
830 | + |
831 | void MidiMapping::slotScriptEngineReady() { |
832 | #ifdef __MIDISCRIPT__ // Can't ifdef slots in the .h file, so we just do the body. |
833 | |
834 | |
835 | === modified file 'mixxx/src/midi/midimapping.h' |
836 | --- mixxx/src/midi/midimapping.h 2010-02-02 19:33:39 +0000 |
837 | +++ mixxx/src/midi/midimapping.h 2010-05-07 07:32:30 +0000 |
838 | @@ -102,6 +102,9 @@ |
839 | void beginMidiLearn(MixxxControl control); |
840 | void cancelMidiLearn(); |
841 | void slotScriptEngineReady(); |
842 | + /** Restarts the script engine and re-applies the mapping |
843 | + to effectively reset the controller */ |
844 | + void reset(); |
845 | |
846 | signals: |
847 | void inputMappingChanged(); |
848 | |
849 | === modified file 'mixxx/src/midi/midiscriptengine.cpp' |
850 | --- mixxx/src/midi/midiscriptengine.cpp 2010-03-24 07:13:38 +0000 |
851 | +++ mixxx/src/midi/midiscriptengine.cpp 2010-05-07 07:32:30 +0000 |
852 | @@ -20,13 +20,15 @@ |
853 | #include "controlobjectthread.h" |
854 | #include "mididevice.h" |
855 | #include "midiscriptengine.h" |
856 | +#include "errordialoghandler.h" |
857 | + |
858 | // #include <QScriptSyntaxCheckResult> |
859 | |
860 | #ifdef _MSC_VER |
861 | -#include <float.h> // for _isnan() on VC++ |
862 | -#define isnan(x) _isnan(x) // VC++ uses _isnan() instead of isnan() |
863 | + #include <float.h> // for _isnan() on VC++ |
864 | + #define isnan(x) _isnan(x) // VC++ uses _isnan() instead of isnan() |
865 | #else |
866 | -#include <math.h> // for isnan() everywhere else |
867 | + #include <math.h> // for isnan() everywhere else |
868 | #endif |
869 | |
870 | |
871 | @@ -34,6 +36,8 @@ |
872 | m_pEngine(NULL), |
873 | m_pMidiDevice(midiDevice) |
874 | { |
875 | + // Handle error dialog buttons |
876 | + qRegisterMetaType<QMessageBox::StandardButton>("QMessageBox::StandardButton"); |
877 | } |
878 | |
879 | MidiScriptEngine::~MidiScriptEngine() { |
880 | @@ -56,6 +60,7 @@ |
881 | -------- ------------------------------------------------------ */ |
882 | void MidiScriptEngine::gracefulShutdown(QList<QString> scriptFunctionPrefixes) { |
883 | qDebug() << "MidiScriptEngine shutting down..."; |
884 | + |
885 | m_scriptEngineLock.lock(); |
886 | // Clear the m_connectedControls hash so we stop responding |
887 | // to signals. |
888 | @@ -65,7 +70,7 @@ |
889 | disconnect(m_pMidiDevice, SIGNAL(callMidiScriptFunction(QString, char, char, |
890 | char, MidiStatusByte, QString)), |
891 | this, SLOT(execute(QString, char, char, char, MidiStatusByte, QString))); |
892 | - |
893 | + |
894 | // Stop all timers |
895 | stopAllTimers(); |
896 | |
897 | @@ -201,6 +206,9 @@ |
898 | QThread::currentThread()->setObjectName(QString("MidiScriptEngine %1").arg(++id)); |
899 | |
900 | // Prevent the script engine from strangling other parts of Mixxx |
901 | + // incase of a misbehaving script |
902 | + // - Should we perhaps not do this when running with --midiDebug so it's more |
903 | + // obvious if a script is taking too much CPU time? - Sean 4/19/10 |
904 | QThread::currentThread()->setPriority(QThread::LowPriority); |
905 | |
906 | m_scriptEngineLock.lock(); |
907 | @@ -233,7 +241,7 @@ |
908 | bool MidiScriptEngine::execute(QString function) { |
909 | m_scriptEngineLock.lock(); |
910 | bool ret = safeExecute(function); |
911 | - if (!ret) qDebug() << "MidiScriptEngine: Invalid script function" << function; |
912 | + if (!ret) qWarning() << "MidiScriptEngine: Invalid script function" << function; |
913 | m_scriptEngineLock.unlock(); |
914 | return ret; |
915 | } |
916 | @@ -246,7 +254,7 @@ |
917 | bool MidiScriptEngine::execute(QString function, QString data) { |
918 | m_scriptEngineLock.lock(); |
919 | bool ret = safeExecute(function, data); |
920 | - if (!ret) qDebug() << "MidiScriptEngine: Invalid script function" << function; |
921 | + if (!ret) qWarning() << "MidiScriptEngine: Invalid script function" << function; |
922 | m_scriptEngineLock.unlock(); |
923 | return ret; |
924 | } |
925 | @@ -262,7 +270,7 @@ |
926 | QString group) { |
927 | m_scriptEngineLock.lock(); |
928 | bool ret = safeExecute(function, channel, control, value, status, group); |
929 | - if (!ret) qDebug() << "MidiScriptEngine: Invalid script function" << function; |
930 | + if (!ret) qWarning() << "MidiScriptEngine: Invalid script function" << function; |
931 | m_scriptEngineLock.unlock(); |
932 | return ret; |
933 | } |
934 | @@ -326,12 +334,7 @@ |
935 | .arg(scriptCode); |
936 | |
937 | if (m_midiDebug) qCritical() << "MidiScriptEngine:" << error; |
938 | - else { |
939 | - qDebug() << "MidiScriptEngine:" << error; |
940 | - qWarning() << "There was an error in a MIDI script." |
941 | - "\nA control you just used is not working properly and you may experience erratic behavior." |
942 | - "\nCheck the console or mixxx.log file for details."; |
943 | - } |
944 | + else scriptErrorDialog(error); |
945 | return false; |
946 | } |
947 | |
948 | @@ -433,30 +436,76 @@ |
949 | QString filename = exception.property("fileName").toString(); |
950 | |
951 | QStringList error; |
952 | - error << filename << errorMessage << QString(line); |
953 | - m_scriptErrors.insert(filename, error); |
954 | - |
955 | + error << (filename.isEmpty() ? "" : filename) << errorMessage << QString(line); |
956 | + m_scriptErrors.insert((filename.isEmpty() ? "passed code" : filename), error); |
957 | + |
958 | + QString errorText = QString(tr("Uncaught exception at line %1 in file %2: %3")) |
959 | + .arg(line) |
960 | + .arg((filename.isEmpty() ? "" : filename)) |
961 | + .arg(errorMessage); |
962 | + |
963 | + if (filename.isEmpty()) |
964 | + errorText = QString(tr("Uncaught exception at line %1 in passed code: %2")) |
965 | + .arg(line) |
966 | + .arg(errorMessage); |
967 | + |
968 | if (m_midiDebug) |
969 | - qCritical() << "MidiScriptEngine: uncaught exception:" |
970 | - << errorMessage |
971 | - << "in" << filename << "at line" |
972 | - << line |
973 | + qCritical() << "MidiScriptEngine:" << errorText |
974 | << "\nBacktrace:\n" |
975 | << backtrace; |
976 | - else { |
977 | - qDebug() << "MidiScriptEngine WARNING: uncaught exception:" |
978 | - << errorMessage |
979 | - << "in" << filename << "at line" |
980 | - << line; |
981 | - qWarning() << "There was a problem with a MIDI script." |
982 | - "\nA control you just used is not working properly and you may experience erratic behavior." |
983 | - "\nCheck the console or mixxx.log file for details."; |
984 | - } |
985 | + else scriptErrorDialog(errorText); |
986 | return true; |
987 | } |
988 | return false; |
989 | } |
990 | |
991 | +/* -------- ------------------------------------------------------ |
992 | +Purpose: Common error dialog creation code for run-time exceptions |
993 | + Allows users to ignore the error or reload the mappings |
994 | +Input: Detailed error string |
995 | +Output: - |
996 | +-------- ------------------------------------------------------ */ |
997 | +void MidiScriptEngine::scriptErrorDialog(QString detailedError) { |
998 | + qWarning() << "MidiScriptEngine:" << detailedError; |
999 | + ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); |
1000 | + props->setType(DLG_WARNING); |
1001 | + props->setTitle(tr("MIDI script error")); |
1002 | + props->setText(tr("A MIDI control you just used is not working properly.")); |
1003 | + props->setInfoText(tr("<html>(The MIDI script code needs to be fixed.)" |
1004 | + "<br>For now, you can:<ul><li>Ignore this error for this session but you may experience erratic behavior</li>" |
1005 | + "<li>Try to recover by resetting your controller</li></ul></html>")); |
1006 | + props->setDetails(detailedError); |
1007 | + props->setKey(detailedError); // To prevent multiple windows for the same error |
1008 | + |
1009 | + // Allow user to suppress further notifications about this particular error |
1010 | + props->addButton(QMessageBox::Ignore); |
1011 | + |
1012 | + props->addButton(QMessageBox::Retry); |
1013 | + props->addButton(QMessageBox::Close); |
1014 | + props->setDefaultButton(QMessageBox::Close); |
1015 | + props->setEscapeButton(QMessageBox::Close); |
1016 | + props->setModal(false); |
1017 | + |
1018 | + if (ErrorDialogHandler::instance()->requestErrorDialog(props)) { |
1019 | + // Enable custom handling of the dialog buttons |
1020 | + connect(ErrorDialogHandler::instance(), SIGNAL(stdButtonClicked(QString, QMessageBox::StandardButton)), |
1021 | + this, SLOT(errorDialogButton(QString, QMessageBox::StandardButton))); |
1022 | + } |
1023 | +} |
1024 | + |
1025 | +/* -------- ------------------------------------------------------ |
1026 | +Purpose: Slot to handle custom button clicks in error dialogs |
1027 | +Input: Key of dialog, StandardButton that was clicked |
1028 | +Output: - |
1029 | +-------- ------------------------------------------------------ */ |
1030 | +void MidiScriptEngine::errorDialogButton(QString key, QMessageBox::StandardButton button) { |
1031 | + |
1032 | + // Something was clicked, so disable this signal now |
1033 | + disconnect(ErrorDialogHandler::instance(), SIGNAL(stdButtonClicked(QString, QMessageBox::StandardButton)), |
1034 | + this, SLOT(errorDialogButton(QString, QMessageBox::StandardButton))); |
1035 | + |
1036 | + if (button == QMessageBox::Retry) emit(resetController()); |
1037 | +} |
1038 | |
1039 | /* -------- ------------------------------------------------------ |
1040 | Purpose: Returns a list of functions available in the QtScript |
1041 | @@ -541,7 +590,7 @@ |
1042 | |
1043 | ControlObjectThread *cot = getControlObjectThread(group, name); |
1044 | if (cot == NULL) { |
1045 | - qDebug() << "MidiScriptEngine: Unknown control" << group << name; |
1046 | + qWarning() << "MidiScriptEngine: Unknown control" << group << name; |
1047 | return 0.0; |
1048 | } |
1049 | |
1050 | @@ -564,7 +613,7 @@ |
1051 | } |
1052 | |
1053 | if(isnan(newValue)) { |
1054 | - qDebug() << "Warning: script setting [" << group << "," << name |
1055 | + qWarning() << "MidiScriptEngine: script setting [" << group << "," << name |
1056 | << "] to NotANumber, ignoring."; |
1057 | return; |
1058 | } |
1059 | @@ -658,7 +707,7 @@ |
1060 | |
1061 | ControlObject* sender = (ControlObject*)this->sender(); |
1062 | if(sender == NULL) { |
1063 | - qDebug() << "MidiScriptEngine::slotValueChanged() Shouldn't happen -- sender == NULL"; |
1064 | + qWarning() << "MidiScriptEngine::slotValueChanged() Shouldn't happen -- sender == NULL"; |
1065 | m_scriptEngineLock.unlock(); |
1066 | return; |
1067 | } |
1068 | @@ -674,16 +723,15 @@ |
1069 | QScriptValue function_value = m_pEngine->evaluate(function); |
1070 | QScriptValueList args; |
1071 | args << QScriptValue(m_pEngine, value); |
1072 | -// function_value.call(QScriptValue(), args); |
1073 | args << QScriptValue(m_pEngine, key.group); // Added by Math` |
1074 | args << QScriptValue(m_pEngine, key.item); // Added by Math` |
1075 | QScriptValue result = function_value.call(QScriptValue(), args); |
1076 | if (result.isError()) { |
1077 | - qDebug()<< "MidiScriptEngine: Call to " << function << " resulted in an error: " << result.toString(); |
1078 | + qWarning()<< "MidiScriptEngine: Call to " << function << " resulted in an error: " << result.toString(); |
1079 | } |
1080 | |
1081 | } else { |
1082 | - qDebug() << "MidiScriptEngine::slotValueChanged() Received signal from ControlObject that is not connected to a script function."; |
1083 | + qWarning() << "MidiScriptEngine::slotValueChanged() Received signal from ControlObject that is not connected to a script function."; |
1084 | } |
1085 | |
1086 | m_scriptEngineLock.unlock(); |
1087 | @@ -705,11 +753,24 @@ |
1088 | // Read in the script file |
1089 | QFile input(filename); |
1090 | if (!input.open(QIODevice::ReadOnly)) { |
1091 | - qCritical() << "MidiScriptEngine: Problem opening the script file: " |
1092 | - << filename |
1093 | - << ", error #" |
1094 | - << input.error(); |
1095 | - return false; |
1096 | + QString errorLog = |
1097 | + QString("MidiScriptEngine: Problem opening the script file: %1, error # %2, %3") |
1098 | + .arg(filename) |
1099 | + .arg(input.error()) |
1100 | + .arg(input.errorString()); |
1101 | + |
1102 | + if (m_midiDebug) qCritical() << errorLog; |
1103 | + else { |
1104 | + qWarning() << errorLog; |
1105 | + ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); |
1106 | + props->setType(DLG_WARNING); |
1107 | + props->setTitle("MIDI script file problem"); |
1108 | + props->setText(QString("There was a problem opening the MIDI script file %1.").arg(filename)); |
1109 | + props->setInfoText(input.errorString()); |
1110 | + |
1111 | + ErrorDialogHandler::instance()->requestErrorDialog(props); |
1112 | + return false; |
1113 | + } |
1114 | } |
1115 | QString scriptCode = ""; |
1116 | scriptCode.append(input.readAll()); |
1117 | @@ -738,10 +799,15 @@ |
1118 | |
1119 | if (m_midiDebug) qCritical() << "MidiScriptEngine:" << error; |
1120 | else { |
1121 | - qDebug() << "MidiScriptEngine:" << error; |
1122 | - qWarning() << "There was an error in the MIDI script file" << filename |
1123 | - << "\nThe functionality provided by this script file will be disabled." |
1124 | - "\nCheck the console or mixxx.log file for details."; |
1125 | + qWarning() << "MidiScriptEngine:" << error; |
1126 | + ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); |
1127 | + props->setType(DLG_WARNING); |
1128 | + props->setTitle("MIDI script file error"); |
1129 | + props->setText(QString("There was an error in the MIDI script file %1.").arg(filename)); |
1130 | + props->setInfoText("The functionality provided by this script file will be disabled."); |
1131 | + props->setDetails(error); |
1132 | + |
1133 | + ErrorDialogHandler::instance()->requestErrorDialog(props); |
1134 | } |
1135 | return false; |
1136 | } |
1137 | @@ -799,7 +865,7 @@ |
1138 | } |
1139 | |
1140 | if (interval<20) { |
1141 | - qDebug() << "Timer request for" << interval << "ms is too short. Setting to the minimum of 20ms."; |
1142 | + qWarning() << "Timer request for" << interval << "ms is too short. Setting to the minimum of 20ms."; |
1143 | interval=20; |
1144 | } |
1145 | // This makes use of every QObject's internal timer mechanism. Nice, clean, and simple. |
1146 | @@ -809,7 +875,7 @@ |
1147 | timerTarget.first = scriptCode; |
1148 | timerTarget.second = oneShot; |
1149 | m_timers[timerId]=timerTarget; |
1150 | - if (timerId==0) qDebug() << "MIDI Script timer could not be created"; |
1151 | + if (timerId==0) qWarning() << "MIDI Script timer could not be created"; |
1152 | else if (m_midiDebug) { |
1153 | if (oneShot) qDebug() << "Starting one-shot timer:" << timerId; |
1154 | else qDebug() << "Starting timer:" << timerId; |
1155 | @@ -830,7 +896,7 @@ |
1156 | if(lock) m_scriptEngineLock.unlock(); |
1157 | |
1158 | if (!m_timers.contains(timerId)) { |
1159 | - qDebug() << "Killing timer" << timerId << ": That timer does not exist!"; |
1160 | + qWarning() << "Killing timer" << timerId << ": That timer does not exist!"; |
1161 | return; |
1162 | } |
1163 | if (m_midiDebug) qDebug() << "Killing timer:" << timerId; |
1164 | @@ -868,7 +934,7 @@ |
1165 | |
1166 | m_scriptEngineLock.lock(); |
1167 | if (!m_timers.contains(timerId)) { |
1168 | - qDebug() << "Timer" << timerId << "fired but there's no function mapped to it!"; |
1169 | + qWarning() << "Timer" << timerId << "fired but there's no function mapped to it!"; |
1170 | m_scriptEngineLock.unlock(); |
1171 | return; |
1172 | } |
1173 | |
1174 | === modified file 'mixxx/src/midi/midiscriptengine.h' |
1175 | --- mixxx/src/midi/midiscriptengine.h 2010-02-03 14:13:29 +0000 |
1176 | +++ mixxx/src/midi/midiscriptengine.h 2010-05-07 07:32:30 +0000 |
1177 | @@ -19,6 +19,7 @@ |
1178 | |
1179 | #include <QEvent> |
1180 | #include <QtScript> |
1181 | +#include <QMessageBox> |
1182 | #include "configobject.h" |
1183 | #include "midimessage.h" |
1184 | class MidiDevice; |
1185 | @@ -68,11 +69,15 @@ |
1186 | |
1187 | signals: |
1188 | void initialized(); |
1189 | + void resetController(); |
1190 | |
1191 | protected: |
1192 | void run(); |
1193 | void timerEvent(QTimerEvent *event); |
1194 | |
1195 | +private slots: |
1196 | + void errorDialogButton(QString key, QMessageBox::StandardButton button); |
1197 | + |
1198 | private: |
1199 | // Only call these with the scriptEngineLock |
1200 | bool safeEvaluate(QString filepath); |
1201 | @@ -82,14 +87,14 @@ |
1202 | bool safeExecute(QString function, char channel, |
1203 | char control, char value, MidiStatusByte status, QString group); |
1204 | void initializeScriptEngine(); |
1205 | - |
1206 | + |
1207 | + void scriptErrorDialog(QString detailedError); |
1208 | void generateScriptFunctions(QString code); |
1209 | bool checkException(); |
1210 | void stopAllTimers(); |
1211 | |
1212 | ControlObjectThread* getControlObjectThread(QString group, QString name); |
1213 | - |
1214 | - |
1215 | + |
1216 | MidiDevice* m_pMidiDevice; |
1217 | bool m_midiDebug; |
1218 | QHash<ConfigKey, QString> m_connectedControls; |
1219 | @@ -98,7 +103,7 @@ |
1220 | QMap<QString,QStringList> m_scriptErrors; |
1221 | QMutex m_scriptEngineLock; |
1222 | QHash<ConfigKey, ControlObjectThread*> m_controlCache; |
1223 | - QHash<int, QPair<QString, bool> > m_timers; |
1224 | + QHash<int, QPair<QString, bool> > m_timers; |
1225 | }; |
1226 | |
1227 | #endif |
1228 | |
1229 | === modified file 'mixxx/src/mixxxcontrol.cpp' |
1230 | --- mixxx/src/mixxxcontrol.cpp 2009-04-26 17:25:30 +0000 |
1231 | +++ mixxx/src/mixxxcontrol.cpp 2010-05-07 07:32:30 +0000 |
1232 | @@ -61,7 +61,7 @@ |
1233 | m_midiOption = MIDI_OPT_SCRIPT; |
1234 | else { |
1235 | m_midiOption = MIDI_OPT_NORMAL; |
1236 | - qDebug() << "Warning: Unknown midioption" << strMidiOption << "in" << __FILE__; |
1237 | + qWarning() << "Unknown midioption" << strMidiOption << "in" << __FILE__; |
1238 | } |
1239 | |
1240 | //Parse threshold stuff, only used for output. |
1241 | @@ -140,7 +140,7 @@ |
1242 | strMidiOption = "script-binding"; |
1243 | else { |
1244 | strMidiOption = "Unknown"; |
1245 | - qDebug() << "Warning: Unknown midioption in" << __FILE__; |
1246 | + qWarning() << "Unknown midioption in" << __FILE__; |
1247 | } |
1248 | |
1249 | QDomElement singleOption = nodeMaker.createElement(strMidiOption); |
1250 | |
1251 | === modified file 'mixxx/src/recording/writeaudiofile.cpp' |
1252 | --- mixxx/src/recording/writeaudiofile.cpp 2008-04-29 05:21:46 +0000 |
1253 | +++ mixxx/src/recording/writeaudiofile.cpp 2010-05-07 07:32:30 +0000 |
1254 | @@ -90,7 +90,7 @@ |
1255 | } |
1256 | } |
1257 | else |
1258 | - qDebug() << "Warning: Tried to open WriteAudioFile before recording control was set to RECORD_ON"; |
1259 | + qWarning() << "Tried to open WriteAudioFile before recording control was set to RECORD_ON"; |
1260 | } |
1261 | |
1262 | void WriteAudioFile::write(const CSAMPLE * pIn, int iBufferSize) |
1263 | |
1264 | === modified file 'mixxx/src/soundsourcemp3.cpp' |
1265 | --- mixxx/src/soundsourcemp3.cpp 2010-02-25 10:14:57 +0000 |
1266 | +++ mixxx/src/soundsourcemp3.cpp 2010-05-07 07:32:30 +0000 |
1267 | @@ -318,7 +318,7 @@ |
1268 | break; |
1269 | default: //By the MP3 specs, an MP3 _has_ to have one of the above samplerates... |
1270 | units = MAD_UNITS_44100_HZ; |
1271 | - qDebug() << "Warning: MP3 with corrupt samplerate (" << SRATE << "), defaulting to 44100"; |
1272 | + qWarning() << "MP3 with corrupt samplerate (" << SRATE << "), defaulting to 44100"; |
1273 | |
1274 | SRATE = 44100; //Prevents division by zero errors. |
1275 | } |
1276 | |
1277 | === modified file 'mixxx/src/upgrade.cpp' |
1278 | --- mixxx/src/upgrade.cpp 2010-03-06 21:46:14 +0000 |
1279 | +++ mixxx/src/upgrade.cpp 2010-05-07 07:32:30 +0000 |
1280 | @@ -190,8 +190,10 @@ |
1281 | |
1282 | if (configVersion == VERSION) qDebug() << "Configuration file is at the current version" << VERSION; |
1283 | else { |
1284 | - qDebug() << "Warning: Configuration file is at version" << configVersion << "and I don't know how to upgrade it to the current" << VERSION; |
1285 | - qDebug() << " (That means a function to do this needs to be added to upgrade.cpp.)"; |
1286 | + qWarning() << "Configuration file is at version" << configVersion |
1287 | + << "and I don't know how to upgrade it to the current" << VERSION |
1288 | + << "\n (That means a function to do this needs to be added to upgrade.cpp.)" |
1289 | + << "\n-> Leaving the configuration file version as-is."; |
1290 | } |
1291 | |
1292 | return config; |
1293 | |
1294 | === modified file 'mixxx/src/widget/wpushbutton.cpp' |
1295 | --- mixxx/src/widget/wpushbutton.cpp 2009-07-07 06:26:30 +0000 |
1296 | +++ mixxx/src/widget/wpushbutton.cpp 2010-05-07 07:32:30 +0000 |
1297 | @@ -90,7 +90,7 @@ |
1298 | //If we have 2 states, tell my controlpushbutton object that we're a toggle button. |
1299 | if (iNumStates == 2) { |
1300 | if (p == 0) |
1301 | - qDebug() << "Warning: wpushbutton p is null\n"; |
1302 | + qWarning() << "wpushbutton p is null\n"; |
1303 | else |
1304 | p->setToggleButton(true); |
1305 | } |
- Change DialogProperties class's name to ErrorDialogProp erties. www.codeguru. com/forum/ showthread. php?t=344782
- The DialogProperties class should have no public variables. Make public get/set methods for all the variables you want to change, and make those variables private.
- You should not have any global variables. Eg. g_pDialogHelper. If you really want to use ErrorDialog the way you are currently, you can get away with it by using the Singleton design pattern: http://
(The wikipedia article on it has a super overcomplicated C++ example.)
- You must make ErrorDialog thread safe. Use QMutexLocker and a single QMutex.