summaryrefslogtreecommitdiffstats
path: root/src/errordialoghandler.cpp
blob: 4b67de7d9870920e0279ed7dd383ea81574e1e3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include "errordialoghandler.h"

#include <QCoreApplication>
#include <QScopedPointer>
#include <QThread>
#include <QtDebug>

#include "moc_errordialoghandler.cpp"
#include "util/assert.h"
#include "util/compatibility/qmutex.h"
#include "util/versionstore.h"

ErrorDialogProperties::ErrorDialogProperties()
        : m_title(VersionStore::applicationName()),
          m_detailsUseMonospaceFont(false),
          m_modal(true),
          m_shouldQuit(false),
          m_type(DLG_NONE),
          m_icon(QMessageBox::NoIcon),
          m_defaultButton(QMessageBox::NoButton),
          m_escapeButton(QMessageBox::NoButton) {
}

void ErrorDialogProperties::setTitle(const QString& title) {
    m_title.append(" - ").append(title);
}

void ErrorDialogProperties::setText(const QString& text) {
    // If no key is set, use this window text since it is likely to be unique
    if (m_key.isEmpty()) {
        m_key = text;
    }
    m_text = text;
}

void ErrorDialogProperties::setType(DialogType typeToSet) {
    m_type = typeToSet;
    switch (m_type) {
        case DLG_FATAL:     // Fatal uses critical icon
        case DLG_CRITICAL:  m_icon = QMessageBox::Critical; break;
        case DLG_WARNING:   m_icon = QMessageBox::Warning; break;
        case DLG_INFO:      m_icon = QMessageBox::Information; break;
        case DLG_QUESTION:  m_icon = QMessageBox::Question; break;
        case DLG_NONE:
        default:
            // default is NoIcon
            break;
    }
}

void ErrorDialogProperties::addButton(QMessageBox::StandardButton button) {
    m_buttons.append(button);
}

// ----------------------------------------------------
// ---------- ErrorDialogHandler begins here ----------

ErrorDialogHandler* ErrorDialogHandler::s_pInstance = nullptr;
bool ErrorDialogHandler::s_bEnabled = true;

// static
void ErrorDialogHandler::setEnabled(bool enabled) {
    s_bEnabled = enabled;
}

ErrorDialogHandler::ErrorDialogHandler() {
    m_errorCondition = false;
    connect(this, &ErrorDialogHandler::showErrorDialog, this, &ErrorDialogHandler::errorDialog);
}

ErrorDialogHandler::~ErrorDialogHandler() {
    s_pInstance = nullptr;
}

ErrorDialogProperties* ErrorDialogHandler::newDialogProperties() {
    return new ErrorDialogProperties();
}

bool ErrorDialogHandler::requestErrorDialog(
        DialogType type, const QString& message, bool shouldQuit) {
    if (!s_bEnabled) {
        return false;
    }
    ErrorDialogProperties* props = newDialogProperties();
    props->setType(type);
    props->setText(message);
    if (shouldQuit) {
        props->setShouldQuit(shouldQuit);
    }
    switch (type) {
        case DLG_FATAL:     props->setTitle(tr("Fatal error")); break;
        case DLG_CRITICAL:  props->setTitle(tr("Critical error")); break;
        case DLG_WARNING:   props->setTitle(tr("Warning")); break;
        case DLG_INFO:      props->setTitle(tr("Information")); break;
        case DLG_QUESTION:  props->setTitle(tr("Question")); break;
        case DLG_NONE:
        default:
            // Default title & (lack of) icon is fine
            break;
    }
    return requestErrorDialog(props);
}

bool ErrorDialogHandler::requestErrorDialog(ErrorDialogProperties* props) {
    if (!s_bEnabled) {
        delete props;
        return false;
    }

    // Make sure the minimum items are set
    QString text = props->getText();
    VERIFY_OR_DEBUG_ASSERT(!text.isEmpty()) {
        delete props;
        return false;
    }

    // Skip if a dialog with the same key is already displayed
    auto locker = lockMutex(&m_mutex);
    bool keyExists = m_dialogKeys.contains(props->getKey());
    locker.unlock();
    if (keyExists) {
        delete props;
        return false;
    }

    emit showErrorDialog(props);
    return true;
}

void ErrorDialogHandler::errorDialog(ErrorDialogProperties* pProps) {
    QScopedPointer<ErrorDialogProperties> props(pProps);
    if (!props) {
        return;
    }

    // Check we are in the main thread.
    if (QThread::currentThread()->objectName() != "Main") {
        qWarning() << "WARNING: errorDialog not called in the main thread. Not showing error dialog.";
        return;
    }

    QMessageBox* pMsgBox = new QMessageBox();
    pMsgBox->setIcon(props->m_icon);
    pMsgBox->setWindowTitle(props->m_title);
    pMsgBox->setText(props->m_text);
    if (!props->m_infoText.isEmpty()) {
        pMsgBox->setInformativeText(props->m_infoText);
    }
    if (!props->m_details.isEmpty()) {
        pMsgBox->setDetailedText(props->m_details);
        if (props->m_detailsUseMonospaceFont) {
            pMsgBox->setStyleSheet("QTextEdit { font-family: monospace; }");
        }
    }

    while (!props->m_buttons.isEmpty()) {
        pMsgBox->addButton(props->m_buttons.takeFirst());
    }
    pMsgBox->setDefaultButton(props->m_defaultButton);
    pMsgBox->setEscapeButton(props->m_escapeButton);
    pMsgBox->setModal(props->m_modal);

    // This deletes the msgBox automatically, avoiding a memory leak
    pMsgBox->setAttribute(Qt::WA_DeleteOnClose, true);
    // Without this, QApplication would close Mixxx when closing this
    // dialog and using the QML GUI because QApplication only takes
    // into account QWidget windows.
    pMsgBox->setAttribute(Qt::WA_QuitOnClose, false);

    auto locker = lockMutex(&m_mutex);
    // To avoid duplicate dialogs on the same error
    m_dialogKeys.append(props->m_key);

    // Signal mapper calls our slot with the key parameter so it knows which to
    // remove from the list
    QString key = props->m_key;
    connect(pMsgBox,
            &QMessageBox::finished,
            this,
            [this, key, pMsgBox] { boxClosed(key, pMsgBox); });

    locker.unlock();

    if (props->m_modal) {
        // Blocks so the user has a chance to read it before application exit
        pMsgBox->exec();
    } else {
        pMsgBox->show();
    }

    // If critical/fatal, gracefully exit application if possible
    if (props->m_shouldQuit) {
        m_errorCondition = true;
        if (QCoreApplication::instance()) {
            QCoreApplication::instance()->exit(-1);
        } else {
            qDebug() << "QCoreApplication::instance() is NULL! Abruptly quitting...";
            if (props->m_type==DLG_FATAL) {
                abort();
            } else {
                exit(-1);
            }
        }
    }
}

void ErrorDialogHandler::boxClosed(const QString& key, QMessageBox* msgBox) {
    auto locker = lockMutex(&m_mutex);
    locker.unlock();

    QMessageBox::StandardButton whichStdButton = msgBox->standardButton(msgBox->clickedButton());
    emit stdButtonClicked(key, whichStdButton);

    // If the user clicks "Ignore," we leave the key in the list so the same
    // error is not displayed again for the duration of the session
    if (whichStdButton == QMessageBox::Ignore) {
        qWarning() << "Suppressing this" << msgBox->windowTitle()
                   << "error box for the duration of the application.";
        return;
    }

    const auto locker2 = lockMutex(&m_mutex);
    if (m_dialogKeys.contains(key)) {
        if (!m_dialogKeys.removeOne(key)) {
            qWarning() << "Error dialog key removal from list failed!";
        }
    } else {
        qWarning() << "Error dialog key is missing from key list!";
    }
}

bool ErrorDialogHandler::checkError() {
    return m_errorCondition;
}