1
/* Qlom is copyright Openismus GmbH, 2009
2
 *
3
 * This file is part of Qlom
4
 *
5
 * Qlom is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * Qlom is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with Qlom. If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
#include "main_window.h"
20
#include "document.h"
21
#include "error.h"
22
#include "tables_model.h"
23
#include "utils.h"
24
25
#include <memory>
26
#include <QSqlField>
27
#include <QSqlIndex>
28
#include <QSqlRecord>
29
#include <QtCore>
30
#include <QtGui>
31
32
#include "config.h"
33
34
QlomMainWindow::QlomMainWindow() :
35
    glomDocument(this),
36
    valid(true)
37
{
38
  setup();
39
}
40
41
QlomMainWindow::QlomMainWindow(const QString &filepath) :
42
    glomDocument(this),
43
    valid(true)
44
{
45
    setup();
46
47
    if(!glomDocument.loadDocument(filepath)) {
48
        valid = false;
49
        return;
50
    }
51
52
    QlomTablesModel *model = glomDocument.createTablesModel();
53
    centralTreeView->setModel(model);
54
55
    connect(centralTreeView, SIGNAL(doubleClicked(QModelIndex)),
56
        this, SLOT(treeviewDoubleclicked(QModelIndex)));
57
    show();
58
59
    // Open default table.
60
    showDefaultTable();
61
}
62
63
bool QlomMainWindow::isValid() const
64
{
65
    return valid;
66
}
67
68
void QlomMainWindow::receiveError(const QlomError &error)
69
{
70
    if(!error.what().isNull()) {
71
        QPointer<QMessageBox> dialog = new QMessageBox(this);
72
        dialog->setText(errorDomainLookup(error.domain()));
73
        dialog->setDetailedText(error.what());
74
75
        // Set icon style and dialog title according to error severity.
76
        switch (error.severity()) {
77
        case Qlom::CRITICAL_ERROR_SEVERITY:
78
            dialog->setWindowTitle(tr("Critical error"));
79
            dialog->setIcon(QMessageBox::Critical);
80
            break;
81
        case Qlom::WARNING_ERROR_SEVERITY:
82
            dialog->setWindowTitle(tr("Warning"));
83
            dialog->setIcon(QMessageBox::Warning);
84
            break;
85
        }
86
87
        dialog->exec();
88
        delete dialog;
89
    }
90
91
    // TODO: Whether to shut down the application should be for the caller to 
92
    // decide. murrayc.
93
    // QApplication::exit() does not work for us because libglom eats up the
94
    // important signals:
95
    // http://git.gnome.org/browse/glom/tree/glom/libglom/connectionpool.cc#n538
96
    /* If the error message was non-empty then the error message was shown to
97
     * the user too. All that remains is to shut down the application. */
98
    if(Qlom::CRITICAL_ERROR_SEVERITY == error.severity()) {
99
        exit(EXIT_FAILURE);
100
    }
101
}
102
103
void QlomMainWindow::setup()
104
{
105
    setWindowTitle(qApp->applicationName());
106
107
    // Create the menu.
108
    QAction *fileOpen = new QAction(tr("&Open"), this);
109
    fileOpen->setShortcut(tr("Ctrl+O", "Open file"));
110
    fileOpen->setStatusTip(tr("Open a Glom document"));
111
    QAction *fileClose = new QAction(tr("&Close"), this);
112
    fileClose->setShortcut(tr("Ctrl+W", "Close file"));
113
    fileClose->setStatusTip(tr("Close the current Glom document"));
114
    QAction *fileQuit = new QAction(tr("&Quit"), this);
115
    fileQuit->setShortcut(tr("Ctrl+Q", "Quit application"));
116
    fileQuit->setStatusTip(tr("Quit the application"));
117
    QAction *helpAbout = new QAction(tr("About"), this);
118
    helpAbout->setShortcut(tr("Ctrl+A", "About application"));
119
    helpAbout->setStatusTip(
120
        tr("Display credits and license information for Qlom"));
121
122
    QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
123
    fileMenu->addAction(fileOpen);
124
    fileMenu->addAction(fileClose);
125
    fileMenu->addAction(fileQuit);
126
    QMenu *aboutMenu = menuBar()->addMenu(tr("&Help"));
127
    aboutMenu->addAction(helpAbout);
128
129
    connect(fileOpen, SIGNAL(triggered(bool)),
130
        this, SLOT(fileOpenTriggered()));
131
    connect(fileClose, SIGNAL(triggered(bool)),
132
        this, SLOT(fileCloseTriggered()));
133
    connect(fileQuit, SIGNAL(triggered(bool)),
134
        this, SLOT(fileQuitTriggered()));
135
    connect(helpAbout, SIGNAL(triggered(bool)),
136
        this, SLOT(helpAboutTriggered()));
137
138
    connect(&glomDocument.errorReporter(), SIGNAL(errorRaised(QlomError)),
139
        this, SLOT(receiveError(QlomError)));
140
141
    centralTreeView = new QTreeView(this);
142
    centralTreeView->setEditTriggers(QAbstractItemView::NoEditTriggers);
143
    setCentralWidget(centralTreeView);
144
145
    readSettings();
146
}
147
148
QlomMainWindow::~QlomMainWindow()
149
{
150
    writeSettings();
151
    QSqlDatabase::database().close();
152
}
153
154
void QlomMainWindow::showAboutDialog()
155
{
156
    // About dialogs are window-modal in Qt, except on Mac OS X.
157
    QMessageBox::about(this, tr("About Qlom"), tr(PACKAGE_NAME "\n"
158
          "A Qt Glom database viewer\n"
159
          "Copyright 2009 Openismus GmbH"));
160
    /* lupdate does not recognise the above string, although if the string is
161
     * manually concatenated then it works fine. TODO: File bug. */
162
}
163
164
void QlomMainWindow::writeSettings()
165
{
166
    QSettings settings;
167
    settings.setValue("MainWindow/Size", size());
168
    settings.setValue("MainWindow/InternalProperties", saveState());
169
}
170
171
void QlomMainWindow::readSettings()
172
{
173
    QSettings settings;
174
    resize(settings.value("MainWindow/Size", sizeHint()).toSize());
175
    restoreState(settings.value("MainWindow/InternalProperties").toByteArray());
176
}
177
178
QString QlomMainWindow::errorDomainLookup(
179
    const Qlom::QlomErrorDomain errorDomain)
180
{
181
    switch (errorDomain) {
182
    case Qlom::DOCUMENT_ERROR_DOMAIN:
183
        return tr("An error occurred while reading the Glom Document");
184
        break;
185
    case Qlom::DATABASE_ERROR_DOMAIN:
186
        return tr("An error occurred with the database");
187
        break;
188
    case Qlom::LOGIC_ERROR_DOMAIN:
189
        return tr("The programmmer had a logic error.");
190
        break;
191
    default:
192
        qCritical("Unhandled error domain: %i", errorDomain);
193
        return tr("Unhandled error domain");
194
        break;
195
    }
196
}
197
198
void QlomMainWindow::fileOpenTriggered()
199
{
200
    QPointer<QFileDialog> dialog = new QFileDialog(this);
201
    dialog->setFileMode(QFileDialog::ExistingFile);
202
    dialog->setNameFilter("Glom document (*.glom)");
203
    if (dialog->exec()) {
204
        // Close the document before opening a document.
205
        fileCloseTriggered();
206
207
        QStringList files = dialog->selectedFiles();
208
        if(!glomDocument.loadDocument(files.first()))
209
        {
210
            delete dialog;
211
            return;
212
        }
213
214
        QlomTablesModel *model = glomDocument.createTablesModel();
215
        centralTreeView->setModel(model);
216
        // Open default table.
217
        showDefaultTable();
218
    }
219
    delete dialog;
220
}
221
222
void QlomMainWindow::fileCloseTriggered()
223
{
224
    centralTreeView->deleteLater();
225
    centralTreeView = new QTreeView(this);
226
    setCentralWidget(centralTreeView);
227
    connect(centralTreeView, SIGNAL(doubleClicked(QModelIndex)),
228
        this, SLOT(treeviewDoubleclicked(QModelIndex)));
229
}
230
231
void QlomMainWindow::fileQuitTriggered()
232
{
233
    qApp->quit();
234
}
235
236
void QlomMainWindow::helpAboutTriggered()
237
{
238
    showAboutDialog();
239
}
240
241
void QlomMainWindow::treeviewDoubleclicked(const QModelIndex& index)
242
{
243
    const QString &tableName = index.data(Qlom::TableNameRole).toString();
244
    QlomListLayoutModel *model = glomDocument.createListLayoutModel(tableName);
245
    showTable(model);
246
247
    const QString tableDisplayName = index.data().toString();
248
}
249
250
void QlomMainWindow::showDefaultTable()
251
{
252
    // Show the default table, or the first non-hidden table, if there is one.
253
    QlomListLayoutModel *model =
254
      glomDocument.createDefaultTableListLayoutModel();
255
256
    if (model) {
257
        showTable(model);
258
    }
259
}
260
261
void QlomMainWindow::showTable(QlomListLayoutModel *model)
262
{
263
    Q_ASSERT(0 != model);
264
265
    QMainWindow *tableModelWindow = new QMainWindow;
266
    QlomListView *view = new QlomListView(tableModelWindow);
267
268
    tableModelWindow->setAttribute(Qt::WA_DeleteOnClose);
269
    tableModelWindow->setCentralWidget(view);
270
    tableModelWindow->setWindowTitle(model->tableDisplayName());
271
    tableModelWindow->show();
272
273
    model->setParent(view);
274
    view->setModel(model);
275
276
    // Marks model as "read-only" here, because the view has no way to edit it.
277
    view->setEditTriggers(QAbstractItemView::NoEditTriggers);
278
279
    // Setup delegates for all columns, if available.
280
    for(int idx = 0; idx < model->columnCount(); ++idx) {
281
        view->setupDelegateForColumn(idx);
282
    }
283
284
    // Setup edit button for last column.
285
    const int colIdx = model->columnCount();
286
    model->insertColumnAt(colIdx);
287
    model->setHeaderData(colIdx, Qt::Horizontal, QVariant(tr("Actions")));
288
    QlomButtonDelegate *buttonDelegate = new QlomButtonDelegate(tr("Details"), view);
289
    connect(buttonDelegate, SIGNAL(buttonPressed(QObject *)),
290
            this, SLOT(onDetailsPressed(QObject *)));
291
    view->setItemDelegateForColumn(colIdx, buttonDelegate);
292
293
    view->resizeColumnsToContents();
294
}
295
296
void QlomMainWindow::onDetailsPressed(QObject *obj)
297
{
298
    QlomModelIndexObject *indexObj = qobject_cast<QlomModelIndexObject *>(obj);
299
    if(indexObj)
300
        QMessageBox::critical(this, tr("Details button pressed"),
301
                                    tr("Cell index: (%1, %2)").arg(indexObj->index().column())
302
                                                              .arg(indexObj->index().row()));
303
}
304
305
QlomListView::QlomListView(QWidget *parent)
306
: QTableView(parent)
307
{}
308
309
QlomListView::~QlomListView()
310
{}
311
312
void QlomListView::setupDelegateForColumn(int column)
313
{
314
    QlomListLayoutModel *model = qobject_cast<QlomListLayoutModel *>(this->model());
315
316
    if (model)
317
    {
318
        QStyledItemDelegate *delegate = QlomListView::createDelegateFromColumn(model, column);
319
        setItemDelegateForColumn(column, delegate);
320
    }
321
}
322
323
QStyledItemDelegate * QlomListView::createDelegateFromColumn(QlomListLayoutModel *model,
324
                                                             int column)
325
{
326
    /* Need to respect the following constraint: The layout item in
327
     * theLayoutGroup that can be found at the position column points to has to
328
     * be a LayoutItem_Text or a LayoutItem_Field.
329
     * However, this method is not used efficiently, considering how most items
330
     * in a list view are field items. If LayoutItem_Text and LayoutItem_Field
331
     * had a common base clase featuring the get_formatting_used() API we could
332
     * get rid of the most annoying part at least: the dynamic casts. */
333
    const QlomListLayoutModel::GlomSharedLayoutItems items = model->getLayoutItems();
334
    for (Glom::LayoutGroup::type_list_const_items::const_iterator iter =
335
        items.begin(); iter != items.end(); ++iter) {
336
        if (column == std::distance(items.begin(), iter)) {
337
            Glom::sharedptr<const Glom::LayoutItem_Text> textItem =
338
                Glom::sharedptr<const Glom::LayoutItem_Text>::cast_dynamic(*iter);
339
            if(textItem)
340
                return new QlomLayoutItemTextDelegate(
341
                    textItem->get_formatting_used(),
342
                    QlomLayoutItemTextDelegate::GlomSharedField(),
343
                    ustringToQstring(textItem->get_text()));
344
345
            Glom::sharedptr<const Glom::LayoutItem_Field> fieldItem =
346
                Glom::sharedptr<const Glom::LayoutItem_Field>::cast_dynamic(*iter);
347
            if(fieldItem)
348
                return new QlomLayoutItemFieldDelegate(
349
                    fieldItem->get_formatting_used(),
350
                    fieldItem->get_full_field_details());
351
        }
352
    }
353
354
    return 0;
355
}