1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "mainwindow.h"
52#include "dialog.h"
53
54#include <QtWidgets>
55#include <QtSql>
56#include <QtXml>
57
58extern int uniqueAlbumId;
59extern int uniqueArtistId;
60
61MainWindow::MainWindow(const QString &artistTable, const QString &albumTable,
62 QFile *albumDetails, QWidget *parent)
63 : QMainWindow(parent)
64{
65 file = albumDetails;
66 readAlbumData();
67
68 model = new QSqlRelationalTableModel(this);
69 model->setTable(albumTable);
70 model->setRelation(column: 2, relation: QSqlRelation(artistTable, "id", "artist"));
71 model->select();
72
73 QGroupBox *artists = createArtistGroupBox();
74 QGroupBox *albums = createAlbumGroupBox();
75 QGroupBox *details = createDetailsGroupBox();
76
77 artistView->setCurrentIndex(0);
78 uniqueAlbumId = model->rowCount();
79 uniqueArtistId = artistView->count();
80
81 connect(sender: model, signal: &QSqlRelationalTableModel::rowsInserted,
82 receiver: this, slot: &MainWindow::updateHeader);
83 connect(sender: model, signal: &QSqlRelationalTableModel::rowsRemoved,
84 receiver: this, slot: &MainWindow::updateHeader);
85
86 QGridLayout *layout = new QGridLayout;
87 layout->addWidget(artists, row: 0, column: 0);
88 layout->addWidget(albums, row: 1, column: 0);
89 layout->addWidget(details, row: 0, column: 1, rowSpan: 2, columnSpan: 1);
90 layout->setColumnStretch(column: 1, stretch: 1);
91 layout->setColumnMinimumWidth(column: 0, minSize: 500);
92
93 QWidget *widget = new QWidget;
94 widget->setLayout(layout);
95 setCentralWidget(widget);
96 createMenuBar();
97
98 showImageLabel();
99 resize(w: 850, h: 400);
100 setWindowTitle(tr(s: "Music Archive"));
101}
102
103void MainWindow::changeArtist(int row)
104{
105 if (row > 0) {
106 QModelIndex index = model->relationModel(column: 2)->index(row, column: 1);
107 model->setFilter("artist = '" + index.data().toString() + '\'') ;
108 showArtistProfile(index);
109 } else if (row == 0) {
110 model->setFilter(QString());
111 showImageLabel();
112 } else {
113 return;
114 }
115}
116
117void MainWindow::showArtistProfile(QModelIndex index)
118{
119 QSqlRecord record = model->relationModel(column: 2)->record(row: index.row());
120
121 QString name = record.value(name: "artist").toString();
122 QString count = record.value(name: "albumcount").toString();
123 profileLabel->setText(tr(s: "Artist : %1 \n" \
124 "Number of Albums: %2").arg(a: name).arg(a: count));
125
126 profileLabel->show();
127 iconLabel->show();
128
129 titleLabel->hide();
130 trackList->hide();
131 imageLabel->hide();
132}
133
134void MainWindow::showAlbumDetails(QModelIndex index)
135{
136 QSqlRecord record = model->record(row: index.row());
137
138 QString artist = record.value(name: "artist").toString();
139 QString title = record.value(name: "title").toString();
140 QString year = record.value(name: "year").toString();
141 QString albumId = record.value(name: "albumid").toString();
142
143 showArtistProfile(index: indexOfArtist(artist));
144 titleLabel->setText(tr(s: "Title: %1 (%2)").arg(a: title).arg(a: year));
145 titleLabel->show();
146
147 QDomNodeList albums = albumData.elementsByTagName(tagname: "album");
148 for (int i = 0; i < albums.count(); ++i) {
149 QDomNode album = albums.item(index: i);
150 if (album.toElement().attribute(name: "id") == albumId) {
151 getTrackList(album: album.toElement());
152 break;
153 }
154 }
155 if (trackList->count() != 0)
156 trackList->show();
157}
158
159void MainWindow::getTrackList(QDomNode album)
160{
161 trackList->clear();
162
163 QDomNodeList tracks = album.childNodes();
164 QDomNode track;
165 QString trackNumber;
166
167 for (int i = 0; i < tracks.count(); ++i) {
168
169 track = tracks.item(index: i);
170 trackNumber = track.toElement().attribute(name: "number");
171
172 QListWidgetItem *item = new QListWidgetItem(trackList);
173 item->setText(trackNumber + ": " + track.toElement().text());
174 }
175}
176
177void MainWindow::addAlbum()
178{
179 Dialog *dialog = new Dialog(model, albumData, file, this);
180 int accepted = dialog->exec();
181
182 if (accepted == 1) {
183 int lastRow = model->rowCount() - 1;
184 albumView->selectRow(row: lastRow);
185 albumView->scrollToBottom();
186 showAlbumDetails(index: model->index(row: lastRow, column: 0));
187 }
188}
189
190void MainWindow::deleteAlbum()
191{
192 QModelIndexList selection = albumView->selectionModel()->selectedRows(column: 0);
193
194 if (!selection.empty()) {
195 QModelIndex idIndex = selection.at(i: 0);
196 int id = idIndex.data().toInt();
197 QString title = idIndex.sibling(arow: idIndex.row(), acolumn: 1).data().toString();
198 QString artist = idIndex.sibling(arow: idIndex.row(), acolumn: 2).data().toString();
199
200 QMessageBox::StandardButton button;
201 button = QMessageBox::question(parent: this, title: tr(s: "Delete Album"),
202 text: tr(s: "Are you sure you want to "
203 "delete '%1' by '%2'?")
204 .arg(args&: title, args&: artist),
205 buttons: QMessageBox::Yes | QMessageBox::No);
206
207 if (button == QMessageBox::Yes) {
208 removeAlbumFromFile(id);
209 removeAlbumFromDatabase(album: idIndex);
210 decreaseAlbumCount(artistIndex: indexOfArtist(artist));
211
212 showImageLabel();
213 }
214 } else {
215 QMessageBox::information(parent: this, title: tr(s: "Delete Album"),
216 text: tr(s: "Select the album you want to delete."));
217 }
218}
219
220void MainWindow::removeAlbumFromFile(int id)
221{
222
223 QDomNodeList albums = albumData.elementsByTagName(tagname: "album");
224
225 for (int i = 0; i < albums.count(); ++i) {
226 QDomNode node = albums.item(index: i);
227 if (node.toElement().attribute(name: "id").toInt() == id) {
228 albumData.elementsByTagName(tagname: "archive").item(index: 0).removeChild(oldChild: node);
229 break;
230 }
231 }
232/*
233 The following code is commented out since the example uses an in
234 memory database, i.e., altering the XML file will bring the data
235 out of sync.
236
237 if (!file->open(QIODevice::WriteOnly)) {
238 return;
239 } else {
240 QTextStream stream(file);
241 albumData.elementsByTagName("archive").item(0).save(stream, 4);
242 file->close();
243 }
244*/
245}
246
247void MainWindow::removeAlbumFromDatabase(QModelIndex index)
248{
249 model->removeRow(arow: index.row());
250}
251
252void MainWindow::decreaseAlbumCount(QModelIndex artistIndex)
253{
254 int row = artistIndex.row();
255 QModelIndex albumCountIndex = artistIndex.sibling(arow: row, acolumn: 2);
256 int albumCount = albumCountIndex.data().toInt();
257
258 QSqlTableModel *artists = model->relationModel(column: 2);
259
260 if (albumCount == 1) {
261 artists->removeRow(arow: row);
262 showImageLabel();
263 } else {
264 artists->setData(index: albumCountIndex, value: QVariant(albumCount - 1));
265 }
266}
267
268void MainWindow::readAlbumData()
269{
270 if (!file->open(flags: QIODevice::ReadOnly))
271 return;
272
273 if (!albumData.setContent(dev: file)) {
274 file->close();
275 return;
276 }
277 file->close();
278}
279
280QGroupBox* MainWindow::createArtistGroupBox()
281{
282 artistView = new QComboBox;
283 artistView->setModel(model->relationModel(column: 2));
284 artistView->setModelColumn(1);
285
286 connect(sender: artistView, signal: QOverload<int>::of(ptr: &QComboBox::currentIndexChanged),
287 receiver: this, slot: &MainWindow::changeArtist);
288
289 QGroupBox *box = new QGroupBox(tr(s: "Artist"));
290
291 QGridLayout *layout = new QGridLayout;
292 layout->addWidget(artistView, row: 0, column: 0);
293 box->setLayout(layout);
294
295 return box;
296}
297
298QGroupBox* MainWindow::createAlbumGroupBox()
299{
300 QGroupBox *box = new QGroupBox(tr(s: "Album"));
301
302 albumView = new QTableView;
303 albumView->setEditTriggers(QAbstractItemView::NoEditTriggers);
304 albumView->setSortingEnabled(true);
305 albumView->setSelectionBehavior(QAbstractItemView::SelectRows);
306 albumView->setSelectionMode(QAbstractItemView::SingleSelection);
307 albumView->setShowGrid(false);
308 albumView->verticalHeader()->hide();
309 albumView->setAlternatingRowColors(true);
310 albumView->setModel(model);
311 adjustHeader();
312
313 QLocale locale = albumView->locale();
314 locale.setNumberOptions(QLocale::OmitGroupSeparator);
315 albumView->setLocale(locale);
316
317 connect(sender: albumView, signal: &QTableView::clicked,
318 receiver: this, slot: &MainWindow::showAlbumDetails);
319 connect(sender: albumView, signal: &QTableView::activated,
320 receiver: this, slot: &MainWindow::showAlbumDetails);
321
322 QVBoxLayout *layout = new QVBoxLayout;
323 layout->addWidget(albumView, stretch: 0, alignment: { });
324 box->setLayout(layout);
325
326 return box;
327}
328
329QGroupBox* MainWindow::createDetailsGroupBox()
330{
331 QGroupBox *box = new QGroupBox(tr(s: "Details"));
332
333 profileLabel = new QLabel;
334 profileLabel->setWordWrap(true);
335 profileLabel->setAlignment(Qt::AlignBottom);
336
337 titleLabel = new QLabel;
338 titleLabel->setWordWrap(true);
339 titleLabel->setAlignment(Qt::AlignBottom);
340
341 iconLabel = new QLabel();
342 iconLabel->setAlignment(Qt::AlignBottom | Qt::AlignRight);
343 iconLabel->setPixmap(QPixmap(":/images/icon.png"));
344
345 imageLabel = new QLabel;
346 imageLabel->setWordWrap(true);
347 imageLabel->setAlignment(Qt::AlignCenter);
348 imageLabel->setPixmap(QPixmap(":/images/image.png"));
349
350 trackList = new QListWidget;
351
352 QGridLayout *layout = new QGridLayout;
353 layout->addWidget(imageLabel, row: 0, column: 0, rowSpan: 3, columnSpan: 2);
354 layout->addWidget(profileLabel, row: 0, column: 0);
355 layout->addWidget(iconLabel, row: 0, column: 1);
356 layout->addWidget(titleLabel, row: 1, column: 0, rowSpan: 1, columnSpan: 2);
357 layout->addWidget(trackList, row: 2, column: 0, rowSpan: 1, columnSpan: 2);
358 layout->setRowStretch(row: 2, stretch: 1);
359 box->setLayout(layout);
360
361 return box;
362}
363
364void MainWindow::createMenuBar()
365{
366 QAction *addAction = new QAction(tr(s: "&Add album..."), this);
367 QAction *deleteAction = new QAction(tr(s: "&Delete album..."), this);
368 QAction *quitAction = new QAction(tr(s: "&Quit"), this);
369 QAction *aboutAction = new QAction(tr(s: "&About"), this);
370 QAction *aboutQtAction = new QAction(tr(s: "About &Qt"), this);
371
372 addAction->setShortcut(tr(s: "Ctrl+A"));
373 deleteAction->setShortcut(tr(s: "Ctrl+D"));
374 quitAction->setShortcuts(QKeySequence::Quit);
375
376 QMenu *fileMenu = menuBar()->addMenu(title: tr(s: "&File"));
377 fileMenu->addAction(action: addAction);
378 fileMenu->addAction(action: deleteAction);
379 fileMenu->addSeparator();
380 fileMenu->addAction(action: quitAction);
381
382 QMenu *helpMenu = menuBar()->addMenu(title: tr(s: "&Help"));
383 helpMenu->addAction(action: aboutAction);
384 helpMenu->addAction(action: aboutQtAction);
385
386 connect(sender: addAction, signal: &QAction::triggered,
387 receiver: this, slot: &MainWindow::addAlbum);
388 connect(sender: deleteAction, signal: &QAction::triggered,
389 receiver: this, slot: &MainWindow::deleteAlbum);
390 connect(sender: quitAction, signal: &QAction::triggered,
391 receiver: this, slot: &MainWindow::close);
392 connect(sender: aboutAction, signal: &QAction::triggered,
393 receiver: this, slot: &MainWindow::about);
394 connect(sender: aboutQtAction, signal: &QAction::triggered,
395 qApp, slot: &QApplication::aboutQt);
396}
397
398void MainWindow::showImageLabel()
399{
400 profileLabel->hide();
401 titleLabel->hide();
402 iconLabel->hide();
403 trackList->hide();
404
405 imageLabel->show();
406}
407
408QModelIndex MainWindow::indexOfArtist(const QString &artist)
409{
410 QSqlTableModel *artistModel = model->relationModel(column: 2);
411
412 for (int i = 0; i < artistModel->rowCount(); i++) {
413 QSqlRecord record = artistModel->record(row: i);
414 if (record.value(name: "artist") == artist)
415 return artistModel->index(row: i, column: 1);
416 }
417 return QModelIndex();
418}
419
420void MainWindow::updateHeader(QModelIndex, int, int)
421{
422 adjustHeader();
423}
424
425void MainWindow::adjustHeader()
426{
427 albumView->hideColumn(column: 0);
428 albumView->horizontalHeader()->setSectionResizeMode(logicalIndex: 1, mode: QHeaderView::Stretch);
429 albumView->resizeColumnToContents(column: 2);
430 albumView->resizeColumnToContents(column: 3);
431}
432
433void MainWindow::about()
434{
435 QMessageBox::about(parent: this, title: tr(s: "About Music Archive"),
436 text: tr(s: "<p>The <b>Music Archive</b> example shows how to present "
437 "data from different data sources in the same application. "
438 "The album titles, and the corresponding artists and release dates, "
439 "are kept in a database, while each album's tracks are stored "
440 "in an XML file. </p><p>The example also shows how to add as "
441 "well as remove data from both the database and the "
442 "associated XML file using the API provided by the Qt SQL and "
443 "Qt XML modules, respectively.</p>"));
444}
445

source code of qtbase/examples/sql/masterdetail/mainwindow.cpp