qt 6.5.1 original
20
examples/sql/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if(NOT TARGET Qt6::Widgets)
|
||||
return()
|
||||
endif()
|
||||
qt_internal_add_example(books)
|
||||
qt_internal_add_example(drilldown)
|
||||
qt_internal_add_example(cachedtable)
|
||||
qt_internal_add_example(querymodel)
|
||||
qt_internal_add_example(relationaltablemodel)
|
||||
qt_internal_add_example(sqlwidgetmapper)
|
||||
qt_internal_add_example(tablemodel)
|
||||
if(TARGET Qt6::Xml)
|
||||
qt_internal_add_example(masterdetail)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
qt_internal_add_example(sqlbrowser)
|
||||
endif()
|
9
examples/sql/README
Normal file
@ -0,0 +1,9 @@
|
||||
Qt provides extensive database interoperability, with support for products
|
||||
from both open source and proprietary vendors.
|
||||
|
||||
SQL support is integrated with Qt's model/view architecture, making it easier
|
||||
to provide GUI integration for your database applications.
|
||||
|
||||
|
||||
Documentation for these examples can be found via the Examples
|
||||
link in the main Qt documentation.
|
52
examples/sql/books/CMakeLists.txt
Normal file
@ -0,0 +1,52 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(books LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/sql/books")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Sql Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(books
|
||||
bookdelegate.cpp bookdelegate.h
|
||||
bookwindow.cpp bookwindow.h bookwindow.ui
|
||||
initdb.h
|
||||
main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(books PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(books PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Sql
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
# Resources:
|
||||
set(books_resource_files
|
||||
"images/star.png"
|
||||
)
|
||||
|
||||
qt_add_resources(books "books"
|
||||
PREFIX
|
||||
"/"
|
||||
FILES
|
||||
${books_resource_files}
|
||||
)
|
||||
|
||||
install(TARGETS books
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
91
examples/sql/books/bookdelegate.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "bookdelegate.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
BookDelegate::BookDelegate(QObject *parent)
|
||||
: QSqlRelationalDelegate(parent), star(QPixmap(":images/star.png"))
|
||||
{
|
||||
}
|
||||
|
||||
void BookDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
if (index.column() != 5) {
|
||||
QSqlRelationalDelegate::paint(painter, option, index);
|
||||
} else {
|
||||
const QAbstractItemModel *model = index.model();
|
||||
QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ?
|
||||
(option.state & QStyle::State_Active) ?
|
||||
QPalette::Normal :
|
||||
QPalette::Inactive :
|
||||
QPalette::Disabled;
|
||||
|
||||
if (option.state & QStyle::State_Selected)
|
||||
painter->fillRect(
|
||||
option.rect,
|
||||
option.palette.color(cg, QPalette::Highlight));
|
||||
|
||||
int rating = model->data(index, Qt::DisplayRole).toInt();
|
||||
int width = star.width();
|
||||
int height = star.height();
|
||||
int x = option.rect.x();
|
||||
int y = option.rect.y() + (option.rect.height() / 2) - (height / 2);
|
||||
for (int i = 0; i < rating; ++i) {
|
||||
painter->drawPixmap(x, y, star);
|
||||
x += width;
|
||||
}
|
||||
}
|
||||
|
||||
QPen pen = painter->pen();
|
||||
painter->setPen(option.palette.color(QPalette::Mid));
|
||||
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
|
||||
painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
|
||||
painter->setPen(pen);
|
||||
}
|
||||
|
||||
QSize BookDelegate::sizeHint(const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
if (index.column() == 5)
|
||||
return QSize(5 * star.width(), star.height()) + QSize(1, 1);
|
||||
// Since we draw the grid ourselves:
|
||||
return QSqlRelationalDelegate::sizeHint(option, index) + QSize(1, 1);
|
||||
}
|
||||
|
||||
bool BookDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index)
|
||||
{
|
||||
if (index.column() != 5)
|
||||
return QSqlRelationalDelegate::editorEvent(event, model, option, index);
|
||||
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
int stars = qBound(0, int(0.7 + qreal(mouseEvent->position().toPoint().x()
|
||||
- option.rect.x()) / star.width()), 5);
|
||||
model->setData(index, QVariant(stars));
|
||||
// So that the selection can change:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QWidget *BookDelegate::createEditor(QWidget *parent,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
if (index.column() != 4)
|
||||
return QSqlRelationalDelegate::createEditor(parent, option, index);
|
||||
|
||||
// For editing the year, return a spinbox with a range from -1000 to 2100.
|
||||
QSpinBox *sb = new QSpinBox(parent);
|
||||
sb->setFrame(false);
|
||||
sb->setMaximum(2100);
|
||||
sb->setMinimum(-1000);
|
||||
|
||||
return sb;
|
||||
}
|
36
examples/sql/books/bookdelegate.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef BOOKDELEGATE_H
|
||||
#define BOOKDELEGATE_H
|
||||
|
||||
#include <QModelIndex>
|
||||
#include <QPixmap>
|
||||
#include <QSize>
|
||||
#include <QSqlRelationalDelegate>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QPainter)
|
||||
|
||||
class BookDelegate : public QSqlRelationalDelegate
|
||||
{
|
||||
public:
|
||||
BookDelegate(QObject *parent);
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
QSize sizeHint(const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
bool editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) override;
|
||||
|
||||
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
private:
|
||||
QPixmap star;
|
||||
};
|
||||
|
||||
#endif
|
13
examples/sql/books/books.pro
Normal file
@ -0,0 +1,13 @@
|
||||
TEMPLATE = app
|
||||
INCLUDEPATH += .
|
||||
|
||||
HEADERS = bookdelegate.h bookwindow.h initdb.h
|
||||
RESOURCES = books.qrc
|
||||
SOURCES = bookdelegate.cpp main.cpp bookwindow.cpp
|
||||
FORMS = bookwindow.ui
|
||||
|
||||
QT += sql widgets widgets
|
||||
requires(qtConfig(tableview))
|
||||
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/sql/books
|
||||
INSTALLS += target
|
5
examples/sql/books/books.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource prefix="/">
|
||||
<file>images/star.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
124
examples/sql/books/bookwindow.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "bookwindow.h"
|
||||
#include "bookdelegate.h"
|
||||
#include "initdb.h"
|
||||
|
||||
#include <QtSql>
|
||||
|
||||
BookWindow::BookWindow()
|
||||
{
|
||||
ui.setupUi(this);
|
||||
|
||||
if (!QSqlDatabase::drivers().contains("QSQLITE"))
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Unable to load database",
|
||||
"This demo needs the SQLITE driver"
|
||||
);
|
||||
|
||||
// Initialize the database:
|
||||
QSqlError err = initDb();
|
||||
if (err.type() != QSqlError::NoError) {
|
||||
showError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the data model:
|
||||
model = new QSqlRelationalTableModel(ui.bookTable);
|
||||
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
|
||||
model->setTable("books");
|
||||
|
||||
// Remember the indexes of the columns:
|
||||
authorIdx = model->fieldIndex("author");
|
||||
genreIdx = model->fieldIndex("genre");
|
||||
|
||||
// Set the relations to the other database tables:
|
||||
model->setRelation(authorIdx, QSqlRelation("authors", "id", "name"));
|
||||
model->setRelation(genreIdx, QSqlRelation("genres", "id", "name"));
|
||||
|
||||
// Set the localized header captions:
|
||||
model->setHeaderData(authorIdx, Qt::Horizontal, tr("Author Name"));
|
||||
model->setHeaderData(genreIdx, Qt::Horizontal, tr("Genre"));
|
||||
model->setHeaderData(model->fieldIndex("title"),
|
||||
Qt::Horizontal, tr("Title"));
|
||||
model->setHeaderData(model->fieldIndex("year"), Qt::Horizontal, tr("Year"));
|
||||
model->setHeaderData(model->fieldIndex("rating"),
|
||||
Qt::Horizontal, tr("Rating"));
|
||||
|
||||
// Populate the model:
|
||||
if (!model->select()) {
|
||||
showError(model->lastError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the model and hide the ID column:
|
||||
ui.bookTable->setModel(model);
|
||||
ui.bookTable->setItemDelegate(new BookDelegate(ui.bookTable));
|
||||
ui.bookTable->setColumnHidden(model->fieldIndex("id"), true);
|
||||
ui.bookTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
|
||||
// Initialize the Author combo box:
|
||||
ui.authorEdit->setModel(model->relationModel(authorIdx));
|
||||
ui.authorEdit->setModelColumn(
|
||||
model->relationModel(authorIdx)->fieldIndex("name"));
|
||||
|
||||
ui.genreEdit->setModel(model->relationModel(genreIdx));
|
||||
ui.genreEdit->setModelColumn(
|
||||
model->relationModel(genreIdx)->fieldIndex("name"));
|
||||
|
||||
// Lock and prohibit resizing of the width of the rating column:
|
||||
ui.bookTable->horizontalHeader()->setSectionResizeMode(
|
||||
model->fieldIndex("rating"),
|
||||
QHeaderView::ResizeToContents);
|
||||
|
||||
QDataWidgetMapper *mapper = new QDataWidgetMapper(this);
|
||||
mapper->setModel(model);
|
||||
mapper->setItemDelegate(new BookDelegate(this));
|
||||
mapper->addMapping(ui.titleEdit, model->fieldIndex("title"));
|
||||
mapper->addMapping(ui.yearEdit, model->fieldIndex("year"));
|
||||
mapper->addMapping(ui.authorEdit, authorIdx);
|
||||
mapper->addMapping(ui.genreEdit, genreIdx);
|
||||
mapper->addMapping(ui.ratingEdit, model->fieldIndex("rating"));
|
||||
|
||||
connect(ui.bookTable->selectionModel(),
|
||||
&QItemSelectionModel::currentRowChanged,
|
||||
mapper,
|
||||
&QDataWidgetMapper::setCurrentModelIndex
|
||||
);
|
||||
|
||||
ui.bookTable->setCurrentIndex(model->index(0, 0));
|
||||
createMenuBar();
|
||||
}
|
||||
|
||||
void BookWindow::showError(const QSqlError &err)
|
||||
{
|
||||
QMessageBox::critical(this, "Unable to initialize Database",
|
||||
"Error initializing database: " + err.text());
|
||||
}
|
||||
|
||||
void BookWindow::createMenuBar()
|
||||
{
|
||||
QAction *quitAction = new QAction(tr("&Quit"), this);
|
||||
QAction *aboutAction = new QAction(tr("&About"), this);
|
||||
QAction *aboutQtAction = new QAction(tr("&About Qt"), this);
|
||||
|
||||
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
|
||||
fileMenu->addAction(quitAction);
|
||||
|
||||
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
|
||||
helpMenu->addAction(aboutAction);
|
||||
helpMenu->addAction(aboutQtAction);
|
||||
|
||||
connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit);
|
||||
connect(aboutAction, &QAction::triggered, this, &BookWindow::about);
|
||||
connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt);
|
||||
}
|
||||
|
||||
void BookWindow::about()
|
||||
{
|
||||
QMessageBox::about(this, tr("About Books"),
|
||||
tr("<p>The <b>Books</b> example shows how to use Qt SQL classes "
|
||||
"with a model/view framework."));
|
||||
}
|
31
examples/sql/books/bookwindow.h
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef BOOKWINDOW_H
|
||||
#define BOOKWINDOW_H
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
|
||||
#include "ui_bookwindow.h"
|
||||
|
||||
|
||||
class BookWindow: public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BookWindow();
|
||||
|
||||
private slots:
|
||||
void about();
|
||||
|
||||
private:
|
||||
void showError(const QSqlError &err);
|
||||
Ui::BookWindow ui;
|
||||
QSqlRelationalTableModel *model;
|
||||
int authorIdx, genreIdx;
|
||||
|
||||
void createMenuBar();
|
||||
};
|
||||
|
||||
#endif
|
164
examples/sql/books/bookwindow.ui
Normal file
@ -0,0 +1,164 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>BookWindow</class>
|
||||
<widget class="QMainWindow" name="BookWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>601</width>
|
||||
<height>420</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Books</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<layout class="QVBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="bookTable">
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Details</string>
|
||||
</property>
|
||||
<layout class="QFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string><b>Title:</b></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="titleEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string><b>Author: </b></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="authorEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string><b>Genre:</b></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="genreEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string><b>Year:</b></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="yearEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-1000</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><b>Rating:</b></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="ratingEdit">
|
||||
<property name="maximum">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>bookTable</tabstop>
|
||||
<tabstop>titleEdit</tabstop>
|
||||
<tabstop>authorEdit</tabstop>
|
||||
<tabstop>genreEdit</tabstop>
|
||||
<tabstop>yearEdit</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
BIN
examples/sql/books/images/star.png
Normal file
After Width: | Height: | Size: 782 B |
113
examples/sql/books/initdb.h
Normal file
@ -0,0 +1,113 @@
|
||||
// Copyright (C) 2020 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef INITDB_H
|
||||
#define INITDB_H
|
||||
|
||||
#include <QtSql>
|
||||
|
||||
void addBook(QSqlQuery &q, const QString &title, int year, const QVariant &authorId,
|
||||
const QVariant &genreId, int rating)
|
||||
{
|
||||
q.addBindValue(title);
|
||||
q.addBindValue(year);
|
||||
q.addBindValue(authorId);
|
||||
q.addBindValue(genreId);
|
||||
q.addBindValue(rating);
|
||||
q.exec();
|
||||
}
|
||||
|
||||
QVariant addGenre(QSqlQuery &q, const QString &name)
|
||||
{
|
||||
q.addBindValue(name);
|
||||
q.exec();
|
||||
return q.lastInsertId();
|
||||
}
|
||||
|
||||
QVariant addAuthor(QSqlQuery &q, const QString &name, QDate birthdate)
|
||||
{
|
||||
q.addBindValue(name);
|
||||
q.addBindValue(birthdate);
|
||||
q.exec();
|
||||
return q.lastInsertId();
|
||||
}
|
||||
|
||||
const auto BOOKS_SQL = QLatin1String(R"(
|
||||
create table books(id integer primary key, title varchar, author integer,
|
||||
genre integer, year integer, rating integer)
|
||||
)");
|
||||
|
||||
const auto AUTHORS_SQL = QLatin1String(R"(
|
||||
create table authors(id integer primary key, name varchar, birthdate date)
|
||||
)");
|
||||
|
||||
const auto GENRES_SQL = QLatin1String(R"(
|
||||
create table genres(id integer primary key, name varchar)
|
||||
)");
|
||||
|
||||
const auto INSERT_AUTHOR_SQL = QLatin1String(R"(
|
||||
insert into authors(name, birthdate) values(?, ?)
|
||||
)");
|
||||
|
||||
const auto INSERT_BOOK_SQL = QLatin1String(R"(
|
||||
insert into books(title, year, author, genre, rating)
|
||||
values(?, ?, ?, ?, ?)
|
||||
)");
|
||||
|
||||
const auto INSERT_GENRE_SQL = QLatin1String(R"(
|
||||
insert into genres(name) values(?)
|
||||
)");
|
||||
|
||||
QSqlError initDb()
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
|
||||
db.setDatabaseName(":memory:");
|
||||
|
||||
if (!db.open())
|
||||
return db.lastError();
|
||||
|
||||
QStringList tables = db.tables();
|
||||
if (tables.contains("books", Qt::CaseInsensitive)
|
||||
&& tables.contains("authors", Qt::CaseInsensitive))
|
||||
return QSqlError();
|
||||
|
||||
QSqlQuery q;
|
||||
if (!q.exec(BOOKS_SQL))
|
||||
return q.lastError();
|
||||
if (!q.exec(AUTHORS_SQL))
|
||||
return q.lastError();
|
||||
if (!q.exec(GENRES_SQL))
|
||||
return q.lastError();
|
||||
|
||||
if (!q.prepare(INSERT_AUTHOR_SQL))
|
||||
return q.lastError();
|
||||
QVariant asimovId = addAuthor(q, QLatin1String("Isaac Asimov"), QDate(1920, 2, 1));
|
||||
QVariant greeneId = addAuthor(q, QLatin1String("Graham Greene"), QDate(1904, 10, 2));
|
||||
QVariant pratchettId = addAuthor(q, QLatin1String("Terry Pratchett"), QDate(1948, 4, 28));
|
||||
|
||||
if (!q.prepare(INSERT_GENRE_SQL))
|
||||
return q.lastError();
|
||||
QVariant sfiction = addGenre(q, QLatin1String("Science Fiction"));
|
||||
QVariant fiction = addGenre(q, QLatin1String("Fiction"));
|
||||
QVariant fantasy = addGenre(q, QLatin1String("Fantasy"));
|
||||
|
||||
if (!q.prepare(INSERT_BOOK_SQL))
|
||||
return q.lastError();
|
||||
addBook(q, QLatin1String("Foundation"), 1951, asimovId, sfiction, 3);
|
||||
addBook(q, QLatin1String("Foundation and Empire"), 1952, asimovId, sfiction, 4);
|
||||
addBook(q, QLatin1String("Second Foundation"), 1953, asimovId, sfiction, 3);
|
||||
addBook(q, QLatin1String("Foundation's Edge"), 1982, asimovId, sfiction, 3);
|
||||
addBook(q, QLatin1String("Foundation and Earth"), 1986, asimovId, sfiction, 4);
|
||||
addBook(q, QLatin1String("Prelude to Foundation"), 1988, asimovId, sfiction, 3);
|
||||
addBook(q, QLatin1String("Forward the Foundation"), 1993, asimovId, sfiction, 3);
|
||||
addBook(q, QLatin1String("The Power and the Glory"), 1940, greeneId, fiction, 4);
|
||||
addBook(q, QLatin1String("The Third Man"), 1950, greeneId, fiction, 5);
|
||||
addBook(q, QLatin1String("Our Man in Havana"), 1958, greeneId, fiction, 4);
|
||||
addBook(q, QLatin1String("Guards! Guards!"), 1989, pratchettId, fantasy, 3);
|
||||
addBook(q, QLatin1String("Night Watch"), 2002, pratchettId, fantasy, 3);
|
||||
addBook(q, QLatin1String("Going Postal"), 2004, pratchettId, fantasy, 3);
|
||||
|
||||
return QSqlError();
|
||||
}
|
||||
|
||||
#endif
|
18
examples/sql/books/main.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "bookwindow.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
Q_INIT_RESOURCE(books);
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
BookWindow win;
|
||||
win.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
39
examples/sql/cachedtable/CMakeLists.txt
Normal file
@ -0,0 +1,39 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(cachedtable LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/sql/cachedtable")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Sql Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(cachedtable
|
||||
../connection.h
|
||||
main.cpp
|
||||
tableeditor.cpp tableeditor.h
|
||||
)
|
||||
|
||||
set_target_properties(cachedtable PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(cachedtable PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Sql
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS cachedtable
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
11
examples/sql/cachedtable/cachedtable.pro
Normal file
@ -0,0 +1,11 @@
|
||||
HEADERS = ../connection.h \
|
||||
tableeditor.h
|
||||
SOURCES = main.cpp \
|
||||
tableeditor.cpp
|
||||
QT += sql widgets
|
||||
requires(qtConfig(tableview))
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/sql/cachedtable
|
||||
INSTALLS += target
|
||||
|
20
examples/sql/cachedtable/main.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "../connection.h"
|
||||
#include "tableeditor.h"
|
||||
|
||||
//! [0]
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
if (!createConnection())
|
||||
return 1;
|
||||
|
||||
TableEditor editor("person");
|
||||
editor.show();
|
||||
return app.exec();
|
||||
}
|
||||
//! [0]
|
69
examples/sql/cachedtable/tableeditor.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
|
||||
#include "tableeditor.h"
|
||||
|
||||
//! [0]
|
||||
TableEditor::TableEditor(const QString &tableName, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
model = new QSqlTableModel(this);
|
||||
model->setTable(tableName);
|
||||
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
|
||||
model->select();
|
||||
|
||||
model->setHeaderData(0, Qt::Horizontal, tr("ID"));
|
||||
model->setHeaderData(1, Qt::Horizontal, tr("First name"));
|
||||
model->setHeaderData(2, Qt::Horizontal, tr("Last name"));
|
||||
|
||||
//! [0] //! [1]
|
||||
QTableView *view = new QTableView;
|
||||
view->setModel(model);
|
||||
view->resizeColumnsToContents();
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
submitButton = new QPushButton(tr("Submit"));
|
||||
submitButton->setDefault(true);
|
||||
revertButton = new QPushButton(tr("&Revert"));
|
||||
quitButton = new QPushButton(tr("Quit"));
|
||||
|
||||
buttonBox = new QDialogButtonBox(Qt::Vertical);
|
||||
buttonBox->addButton(submitButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(revertButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
connect(submitButton, &QPushButton::clicked, this, &TableEditor::submit);
|
||||
connect(revertButton, &QPushButton::clicked, model, &QSqlTableModel::revertAll);
|
||||
connect(quitButton, &QPushButton::clicked, this, &TableEditor::close);
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
mainLayout->addWidget(view);
|
||||
mainLayout->addWidget(buttonBox);
|
||||
setLayout(mainLayout);
|
||||
|
||||
setWindowTitle(tr("Cached Table"));
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
void TableEditor::submit()
|
||||
{
|
||||
model->database().transaction();
|
||||
if (model->submitAll()) {
|
||||
model->database().commit();
|
||||
} else {
|
||||
model->database().rollback();
|
||||
QMessageBox::warning(this, tr("Cached Table"),
|
||||
tr("The database reported an error: %1")
|
||||
.arg(model->lastError().text()));
|
||||
}
|
||||
}
|
||||
//! [5]
|
35
examples/sql/cachedtable/tableeditor.h
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef TABLEEDITOR_H
|
||||
#define TABLEEDITOR_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QDialogButtonBox;
|
||||
class QPushButton;
|
||||
class QSqlTableModel;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
//! [0]
|
||||
class TableEditor : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TableEditor(const QString &tableName, QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void submit();
|
||||
|
||||
private:
|
||||
QPushButton *submitButton;
|
||||
QPushButton *revertButton;
|
||||
QPushButton *quitButton;
|
||||
QDialogButtonBox *buttonBox;
|
||||
QSqlTableModel *model;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
81
examples/sql/connection.h
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QSqlQuery>
|
||||
|
||||
/*
|
||||
This file defines a helper function to open a connection to an
|
||||
in-memory SQLITE database and to create a test table.
|
||||
|
||||
If you want to use another database, simply modify the code
|
||||
below. All the examples in this directory use this function to
|
||||
connect to a database.
|
||||
*/
|
||||
//! [0]
|
||||
static bool createConnection()
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
|
||||
db.setDatabaseName(":memory:");
|
||||
if (!db.open()) {
|
||||
QMessageBox::critical(nullptr, QObject::tr("Cannot open database"),
|
||||
QObject::tr("Unable to establish a database connection.\n"
|
||||
"This example needs SQLite support. Please read "
|
||||
"the Qt SQL driver documentation for information how "
|
||||
"to build it.\n\n"
|
||||
"Click Cancel to exit."), QMessageBox::Cancel);
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery query;
|
||||
query.exec("create table person (id int primary key, "
|
||||
"firstname varchar(20), lastname varchar(20))");
|
||||
query.exec("insert into person values(101, 'Danny', 'Young')");
|
||||
query.exec("insert into person values(102, 'Christine', 'Holand')");
|
||||
query.exec("insert into person values(103, 'Lars', 'Gordon')");
|
||||
query.exec("insert into person values(104, 'Roberto', 'Robitaille')");
|
||||
query.exec("insert into person values(105, 'Maria', 'Papadopoulos')");
|
||||
|
||||
query.exec("create table items (id int primary key,"
|
||||
"imagefile int,"
|
||||
"itemtype varchar(20),"
|
||||
"description varchar(100))");
|
||||
query.exec("insert into items "
|
||||
"values(0, 0, 'Qt',"
|
||||
"'Qt is a full development framework with tools designed to "
|
||||
"streamline the creation of stunning applications and "
|
||||
"amazing user interfaces for desktop, embedded and mobile "
|
||||
"platforms.')");
|
||||
query.exec("insert into items "
|
||||
"values(1, 1, 'Qt Quick',"
|
||||
"'Qt Quick is a collection of techniques designed to help "
|
||||
"developers create intuitive, modern-looking, and fluid "
|
||||
"user interfaces using a CSS & JavaScript like language.')");
|
||||
query.exec("insert into items "
|
||||
"values(2, 2, 'Qt Creator',"
|
||||
"'Qt Creator is a powerful cross-platform integrated "
|
||||
"development environment (IDE), including UI design tools "
|
||||
"and on-device debugging.')");
|
||||
query.exec("insert into items "
|
||||
"values(3, 3, 'Qt Project',"
|
||||
"'The Qt Project governs the open source development of Qt, "
|
||||
"allowing anyone wanting to contribute to join the effort "
|
||||
"through a meritocratic structure of approvers and "
|
||||
"maintainers.')");
|
||||
|
||||
query.exec("create table images (itemid int, file varchar(20))");
|
||||
query.exec("insert into images values(0, 'images/qt-logo.png')");
|
||||
query.exec("insert into images values(1, 'images/qt-quick.png')");
|
||||
query.exec("insert into images values(2, 'images/qt-creator.png')");
|
||||
query.exec("insert into images values(3, 'images/qt-project.png')");
|
||||
|
||||
return true;
|
||||
}
|
||||
//! [0]
|
||||
|
||||
#endif
|
BIN
examples/sql/doc/images/books-demo.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
examples/sql/doc/images/cachedtable-example.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
examples/sql/doc/images/drilldown-example.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
examples/sql/doc/images/masterdetail-example.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
examples/sql/doc/images/querymodel-example.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
examples/sql/doc/images/relationaltablemodel-example.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
examples/sql/doc/images/sql-widget-mapper.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
examples/sql/doc/images/sqlbrowser-demo.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
examples/sql/doc/images/tablemodel-example.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
examples/sql/doc/images/widgetmapper-sql-mapping-table.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
examples/sql/doc/images/widgetmapper-sql-mapping.png
Normal file
After Width: | Height: | Size: 17 KiB |
24
examples/sql/doc/src/books.qdoc
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example books
|
||||
\title Books
|
||||
\ingroup sql_examples
|
||||
\brief Shows how to use Qt SQL classes with a model/view framework.
|
||||
|
||||
The Books example shows how Qt's SQL classes can be used with the model/view
|
||||
framework to create rich user interfaces for information stored in a database.
|
||||
|
||||
\borderedimage books-demo.png
|
||||
|
||||
Information about a collection of books is held in a database. The books are
|
||||
catalogued by author, title, genre, and year of publication. Although each of
|
||||
these fields can be displayed and edited using standard widgets, an additional
|
||||
field describing an arbitrary rating for the book needs something extra.
|
||||
|
||||
Books are rated using a system where each is allocated a number of stars; the
|
||||
more a book has, the better it is supposed to be. By clicking on a cell
|
||||
containing the rating, the number of stars can be modified, and the rating in
|
||||
the database is updated.
|
||||
*/
|
174
examples/sql/doc/src/cachedtable.qdoc
Normal file
@ -0,0 +1,174 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example cachedtable
|
||||
\title Cached Table Example
|
||||
\ingroup sql_examples
|
||||
|
||||
\brief The Cached Table example shows how a table view can be used to access a database,
|
||||
caching any changes to the data until the user explicitly submits them using a
|
||||
push button.
|
||||
|
||||
\borderedimage cachedtable-example.png
|
||||
|
||||
The example consists of a single class, \c TableEditor, which is a
|
||||
custom dialog widget that allows the user to modify data stored in
|
||||
a database. We will first review the class definition and how to
|
||||
use the class, then we will take a look at the implementation.
|
||||
|
||||
\section1 TableEditor Class Definition
|
||||
|
||||
The \c TableEditor class inherits QWidget making the table editor
|
||||
widget a top-level dialog window.
|
||||
|
||||
\snippet cachedtable/tableeditor.h 0
|
||||
|
||||
The \c TableEditor constructor takes two arguments: The first is a
|
||||
reference to the database table the \c TableEditor object will operate
|
||||
on. The other is a pointer to the parent widget and is passed on to the
|
||||
base class constructor.
|
||||
|
||||
Note the QSqlTableModel variable declaration: As we will see in
|
||||
this example, the QSqlTableModel class can be used to provide data
|
||||
to view classes such as QTableView. The QSqlTableModel class
|
||||
provides an editable data model making it possible to read and
|
||||
write database records from a single table. It is build on top of
|
||||
the lower-level QSqlQuery class which provides means of executing
|
||||
and manipulating SQL statements.
|
||||
|
||||
We are also going to show how a table view can be used to cache
|
||||
any changes to the data until the user explicitly requests to
|
||||
submit them. For that reason we need to declare a \c submit() slot
|
||||
in addition to the model and the editor's buttons.
|
||||
|
||||
\table 100%
|
||||
\header \li Connecting to a Database
|
||||
\row
|
||||
\li
|
||||
|
||||
Before we can use the \c TableEditor class, we must create a
|
||||
connection to the database containing the table we want to edit:
|
||||
|
||||
\snippet cachedtable/main.cpp 0
|
||||
|
||||
The \c createConnection() function is a helper function provided
|
||||
for convenience. It is defined in the \c connection.h file which
|
||||
is located in the \c sql example directory (all the examples in
|
||||
the \c sql directory use this function to connect to a database).
|
||||
|
||||
\snippet connection.h 0
|
||||
|
||||
The \c createConnection function opens a connection to an
|
||||
in-memory SQLITE database and creates a test table. If you want
|
||||
to use another database, simply modify this function's code.
|
||||
\endtable
|
||||
|
||||
\section1 TableEditor Class Implementation
|
||||
|
||||
The class implementation consists of only two functions, the
|
||||
constructor and the \c submit() slot. In the constructor we create
|
||||
and customize the data model and the various window elements:
|
||||
|
||||
\snippet cachedtable/tableeditor.cpp 0
|
||||
|
||||
First we create the data model and set the SQL database table we
|
||||
want the model to operate on. Note that the
|
||||
QSqlTableModel::setTable() function does not select data from the
|
||||
table; it only fetches its field information. For that reason we
|
||||
call the QSqlTableModel::select() function later on, populating
|
||||
the model with data from the table. The selection can be
|
||||
customized by specifying filters and sort conditions (see the
|
||||
QSqlTableModel class documentation for more details).
|
||||
|
||||
We also set the model's edit strategy. The edit strategy dictates
|
||||
when the changes done by the user in the view, are actually
|
||||
applied to the database. Since we want to cache the changes in the
|
||||
table view (i.e. in the model) until the user explicitly submits
|
||||
them, we choose the QSqlTableModel::OnManualSubmit strategy. The
|
||||
alternatives are QSqlTableModel::OnFieldChange and
|
||||
QSqlTableModel::OnRowChange.
|
||||
|
||||
Finally, we set up the labels displayed in the view header using
|
||||
the \l {QSqlQueryModel::setHeaderData()}{setHeaderData()} function
|
||||
that the model inherits from the QSqlQueryModel class.
|
||||
|
||||
\snippet cachedtable/tableeditor.cpp 1
|
||||
|
||||
Then we create a table view. The QTableView class provides a
|
||||
default model/view implementation of a table view, i.e. it
|
||||
implements a table view that displays items from a model. It also
|
||||
allows the user to edit the items, storing the changes in the
|
||||
model. To create a read only view, set the proper flag using the
|
||||
\l {QAbstractItemView::editTriggers}{editTriggers} property the
|
||||
view inherits from the QAbstractItemView class.
|
||||
|
||||
To make the view present our data, we pass our model to the view
|
||||
using the \l {QAbstractItemView::setModel()}{setModel()} function.
|
||||
|
||||
\snippet cachedtable/tableeditor.cpp 2
|
||||
|
||||
The \c {TableEditor}'s buttons are regular QPushButton objects. We
|
||||
add them to a button box to ensure that the buttons are presented
|
||||
in a layout that is appropriate to the current widget style. The
|
||||
rationale for this is that dialogs and message boxes typically
|
||||
present buttons in a layout that conforms to the interface
|
||||
guidelines for that platform. Invariably, different platforms have
|
||||
different layouts for their dialogs. QDialogButtonBox allows a
|
||||
developer to add buttons to it and will automatically use the
|
||||
appropriate layout for the user's desktop environment.
|
||||
|
||||
Most buttons for a dialog follow certain roles. When adding a
|
||||
button to a button box using the \l
|
||||
{QDialogButtonBox}{addButton()} function, the button's role must
|
||||
be specified using the QDialogButtonBox::ButtonRole
|
||||
enum. Alternatively, QDialogButtonBox provides several standard
|
||||
buttons (e.g. \uicontrol OK, \uicontrol Cancel, \uicontrol Save) that you can
|
||||
use. They exist as flags so you can OR them together in the
|
||||
constructor.
|
||||
|
||||
\snippet cachedtable/tableeditor.cpp 3
|
||||
|
||||
We connect the \uicontrol Quit button to the table editor's \l
|
||||
{QWidget::close()}{close()} slot, and the \uicontrol Submit button to
|
||||
our private \c submit() slot. The latter slot will take care of
|
||||
the data transactions. Finally, we connect the \uicontrol Revert button
|
||||
to our model's \l {QSqlTableModel::revertAll()}{revertAll()} slot,
|
||||
reverting all pending changes (i.e., restoring the original data).
|
||||
|
||||
\snippet cachedtable/tableeditor.cpp 4
|
||||
|
||||
In the end we add the button box and the table view to a layout,
|
||||
install the layout on the table editor widget, and set the
|
||||
editor's window title.
|
||||
|
||||
\snippet cachedtable/tableeditor.cpp 5
|
||||
|
||||
The \c submit() slot is called whenever the users hit the \uicontrol
|
||||
Submit button to save their changes.
|
||||
|
||||
First, we begin a transaction on the database using the
|
||||
QSqlDatabase::transaction() function. A database transaction is a
|
||||
unit of interaction with a database management system or similar
|
||||
system that is treated in a coherent and reliable way independent
|
||||
of other transactions. A pointer to the used database can be
|
||||
obtained using the QSqlTableModel::database() function.
|
||||
|
||||
Then, we try to submit all the pending changes, i.e. the model's
|
||||
modified items. If no error occurs, we commit the transaction to
|
||||
the database using the QSqlDatabase::commit() function (note that
|
||||
on some databases, this function will not work if there is an
|
||||
active QSqlQuery on the database). Otherwise we perform a rollback
|
||||
of the transaction using the QSqlDatabase::rollback() function and
|
||||
post a warning to the user.
|
||||
|
||||
\table 100%
|
||||
\row
|
||||
\li
|
||||
\b {See also:}
|
||||
|
||||
A complete list of Qt's SQL \l {Database Classes}, and the \l
|
||||
{Model/View Programming} documentation.
|
||||
|
||||
\endtable
|
||||
*/
|
522
examples/sql/doc/src/drilldown.qdoc
Normal file
@ -0,0 +1,522 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example drilldown
|
||||
\title Drill Down Example
|
||||
\ingroup sql_examples
|
||||
|
||||
\brief The Drill Down example shows how to read data from a database as
|
||||
well as submit changes, using the QSqlRelationalTableModel and
|
||||
QDataWidgetMapper classes.
|
||||
|
||||
\borderedimage drilldown-example.png Screenshot of the Drill Down Example
|
||||
|
||||
When running the example application, a user can retrieve
|
||||
information about each item by clicking the corresponding image.
|
||||
The application pops up an information window displaying the data,
|
||||
and allows the users to alter the description as well as the image.
|
||||
The main view will be updated when the users submit their changes.
|
||||
|
||||
The example consists of three classes:
|
||||
|
||||
\list
|
||||
\li \c ImageItem is a custom graphics item class used to
|
||||
display the images.
|
||||
|
||||
\li \c View is the main application widget allowing the user to
|
||||
browse through the various items.
|
||||
|
||||
\li \c InformationWindow displays the requested information,
|
||||
allowing the users to alter it and submit their changes to the
|
||||
database.
|
||||
\endlist
|
||||
|
||||
We will first take a look at the \c InformationWindow class to see
|
||||
how you can read and modify data from a database. Then we will
|
||||
review the main application widget, i.e., the \c View class, and
|
||||
the associated \c ImageItem class.
|
||||
|
||||
\section1 InformationWindow Class Definition
|
||||
|
||||
The \c InformationWindow class is a custom widget inheriting
|
||||
QWidget:
|
||||
|
||||
\snippet drilldown/informationwindow.h 0
|
||||
|
||||
When we create an information window, we pass the associated
|
||||
item ID, a parent, and a pointer to the database, to the
|
||||
constructor. We will use the database pointer to populate our
|
||||
window with data, while passing the parent parameter on to the
|
||||
base class. The ID is stored for future reference.
|
||||
|
||||
Once a window is created, we will use the public \c id() function
|
||||
to locate it whenever information for the given location is
|
||||
requested. We will also use the ID to update the main application
|
||||
widget when the users submit their changes to the database, i.e.,
|
||||
we will emit a signal carrying the ID and file name as parameters
|
||||
whenever the users changes the associated image.
|
||||
|
||||
\snippet drilldown/informationwindow.h 1
|
||||
|
||||
Since we allow the users to alter some of the data, we
|
||||
must provide functionality for reverting and submitting their
|
||||
changes. The \c enableButtons() slot is provided for convenience
|
||||
to enable and disable the various buttons when required.
|
||||
|
||||
\snippet drilldown/informationwindow.h 2
|
||||
|
||||
The \c createButtons() function is also a convenience function,
|
||||
provided to simplify the constructor. As mentioned above we store
|
||||
the item ID for future reference. We also store the name of
|
||||
the currently displayed image file to be able to determine when to
|
||||
emit the \c imageChanged() signal.
|
||||
|
||||
The information window uses the QLabel class to display the name of
|
||||
an item. The associated image file is displayed using a QComboBox
|
||||
instance while the description is displayed using QTextEdit. In
|
||||
addition, the window has three buttons to control the data flow and
|
||||
whether the window is shown or not.
|
||||
|
||||
Finally, we declare a \e mapper. The QDataWidgetMapper class
|
||||
provides mapping between a section of a data model to widgets. We
|
||||
will use the mapper to extract data from the given database,
|
||||
updating the database whenever the user modifies the data.
|
||||
|
||||
\section1 InformationWindow Class Implementation
|
||||
|
||||
The constructor takes three arguments: an item ID, a database
|
||||
pointer and a parent widget. The database pointer is actually a
|
||||
pointer to a QSqlRelationalTableModel object providing an editable
|
||||
data model (with foreign key support) for our database table.
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 0
|
||||
\snippet drilldown/informationwindow.cpp 1
|
||||
|
||||
First we create the various widgets required to display the data
|
||||
contained in the database. Most of the widgets are created in a
|
||||
straight forward manner. But note the combobox displaying the
|
||||
name of the image file:
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 2
|
||||
|
||||
In this example, information about the items are stored in a
|
||||
database table called "items". When creating the model,
|
||||
we will use a foreign key to establish a relation between this
|
||||
table and a second data base table, "images", containing the names
|
||||
of the available image files. We will get back to how this is done
|
||||
when reviewing the \c View class. The rationale for creating such
|
||||
a relation though, is that we want to ensure that the user only
|
||||
can choose between predefined image files.
|
||||
|
||||
The model corresponding to the "images" database table, is
|
||||
available through the QSqlRelationalTableModel's \l
|
||||
{QSqlRelationalTableModel::}{relationModel()} function, requiring
|
||||
the foreign key (in this case the "imagefile" column number) as
|
||||
argument. We use QComboBox's \l {QComboBox::}{setModel()} function
|
||||
to make the combobox use the "images" model. And, since this model
|
||||
has two columns ("itemid" and "file"), we also specify which
|
||||
column we want to be visible using the QComboBox::setModelColumn()
|
||||
function.
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 3
|
||||
|
||||
Then we create the mapper. The QDataWidgetMapper class allows us
|
||||
to create data-aware widgets by mapping them to sections of an
|
||||
item model.
|
||||
|
||||
The \l {QDataWidgetMapper::}{addMapping()} function adds a mapping
|
||||
between the given widget and the specified section of the
|
||||
model. If the mapper's orientation is horizontal (the default) the
|
||||
section is a column in the model, otherwise it is a row. We call
|
||||
the \l {QDataWidgetMapper::}{setCurrentIndex()} function to
|
||||
initialize the widgets with the data associated with the given
|
||||
item ID. Every time the current index changes, all the widgets
|
||||
are updated with the contents from the model.
|
||||
|
||||
We also set the mapper's submit policy to
|
||||
QDataWidgetMapper::ManualSubmit. This means that no data is
|
||||
submitted to the database until the user expliclity requests a
|
||||
submit (the alternative is QDataWidgetMapper::AutoSubmit,
|
||||
automatically submitting changes when the corresponding widget
|
||||
loses focus). Finally, we specify the item delegate the mapper
|
||||
view should use for its items. The QSqlRelationalDelegate class
|
||||
represents a delegate that unlike the default delegate, enables
|
||||
combobox functionality for fields that are foreign keys into other
|
||||
tables (like "imagefile" in our "items" table).
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 4
|
||||
|
||||
Finally, we connect the "something's changed" signals in the
|
||||
editors to our custom \c enableButtons slot, enabling the users
|
||||
to either submit or revert their changes.
|
||||
We need to use lambdas for connecting the \c enableButtons slot
|
||||
because its signature does not match \c QTextEdit::textChanged
|
||||
and \c QComboBox::currentIndexChanged.
|
||||
Since the latter has another overload with the signature
|
||||
\c {const QString &} and the selected signal would be ambiguous,
|
||||
we need to use \c QOverload<int>::of to select a specific overload
|
||||
for \c currentIndexChanged.
|
||||
|
||||
We add all the widgets into a layout, store the item ID and the
|
||||
name of the displayed image file for future reference, and set
|
||||
the window title and initial size.
|
||||
|
||||
|
||||
Note that we also set the Qt::Window window flag to indicate that
|
||||
our widget is in fact a window, with a window system frame and a
|
||||
title bar.
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 5
|
||||
|
||||
When a window is created, it is not deleted until the main
|
||||
application exits (i.e., if the user closes the information
|
||||
window, it is only hidden). For this reason we do not want to
|
||||
create more than one \c InformationWindow object for each
|
||||
item, and we provide the public \c id() function to be able to
|
||||
determine whether a window already exists for a given location
|
||||
when the user requests information about it.
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 6
|
||||
|
||||
The \c revert() slot is triggered whenever the user hits the \uicontrol
|
||||
Revert button.
|
||||
|
||||
Since we set the QDataWidgetMapper::ManualSubmit submit policy,
|
||||
none of the user's changes are written back to the model unless
|
||||
the user expliclity choose to submit all of them. Nevertheless, we
|
||||
can use the QDataWidgetMapper's \l {QDataWidgetMapper::}{revert()}
|
||||
slot to reset the editor widgets, repopulating all widgets with
|
||||
the current data of the model.
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 7
|
||||
|
||||
Likewise, the \c submit() slot is triggered whenever the users
|
||||
decide to submit their changes by pressing the \uicontrol Submit button.
|
||||
|
||||
We use QDataWidgetMapper's \l {QDataWidgetMapper::}{submit()} slot
|
||||
to submit all changes from the mapped widgets to the model,
|
||||
i.e. to the database. For every mapped section, the item delegate
|
||||
will then read the current value from the widget and set it in the
|
||||
model. Finally, the \e model's \l {QAbstractItemModel::}{submit()}
|
||||
function is invoked to let the model know that it should submit
|
||||
whatever it has cached to the permanent storage.
|
||||
|
||||
Note that before any data is submitted, we check if the user has
|
||||
chosen another image file using the previously stored \c
|
||||
displayedImage variable as reference. If the current and stored
|
||||
file names differ, we store the new file name and emit the \c
|
||||
imageChanged() signal.
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 8
|
||||
|
||||
The \c createButtons() function is provided for convenience, i.e.,
|
||||
to simplify the constructor.
|
||||
|
||||
We make the \uicontrol Close button the default button, i.e., the button
|
||||
that is pressed when the user presses \uicontrol Enter, and connect its
|
||||
\l {QPushButton::}{clicked()} signal to the widget's \l
|
||||
{QWidget::}{close()} slot. As mentioned above closing the window
|
||||
only hides the widget; it is not deleted. We also connect the \uicontrol
|
||||
Submit and \uicontrol Revert buttons to the corresponding \c submit()
|
||||
and \c revert() slots.
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 9
|
||||
|
||||
The QDialogButtonBox class is a widget that presents buttons in a
|
||||
layout that is appropriate to the current widget style. Dialogs
|
||||
like our information window, typically present buttons in a layout
|
||||
that conforms to the interface guidelines for that
|
||||
platform. Invariably, different platforms have different layouts
|
||||
for their dialogs. QDialogButtonBox allows us to add buttons,
|
||||
automatically using the appropriate layout for the user's desktop
|
||||
environment.
|
||||
|
||||
Most buttons for a dialog follow certain roles. We give the \uicontrol
|
||||
Submit and \uicontrol Revert buttons the \l
|
||||
{QDialogButtonBox::ButtonRole}{reset} role, i.e., indicating that
|
||||
pressing the button resets the fields to the default values (in
|
||||
our case the information contained in the database). The \l
|
||||
{QDialogButtonBox::ButtonRole}{reject} role indicates that
|
||||
clicking the button causes the dialog to be rejected. On the other
|
||||
hand, since we only hide the information window, any changes that
|
||||
the user has made will be preserved until the user explicitly
|
||||
reverts or submits them.
|
||||
|
||||
\snippet drilldown/informationwindow.cpp 10
|
||||
|
||||
The \c enableButtons() slot is called to enable the buttons
|
||||
whenever the user changes the presented data. Likewise, when the
|
||||
user chooses to submit the changes, the buttons are disabled to
|
||||
indicate that the current data is stored in the database.
|
||||
|
||||
This completes the \c InformationWindow class. Let's take a look
|
||||
at how we have used it in our example application.
|
||||
|
||||
\section1 View Class Definition
|
||||
|
||||
The \c View class represents the main application window and
|
||||
inherits QGraphicsView:
|
||||
|
||||
\snippet drilldown/view.h 0
|
||||
\codeline
|
||||
\snippet drilldown/view.h 1
|
||||
|
||||
The QGraphicsView class is part of the \l {Graphics View
|
||||
Framework} which we will use to display the images. To be able to
|
||||
respond to user interaction by displaying the appropriate
|
||||
information window when the image is clicked, we reimplement
|
||||
QGraphicsView's \l{QGraphicsView::}{mouseReleaseEvent()} function.
|
||||
|
||||
Note that the constructor expects the names of two database
|
||||
tables: One containing the detailed information about the items,
|
||||
and another containing the names of the available image files. We
|
||||
also provide a private \c updateImage() slot to catch \c
|
||||
{InformationWindow}'s \c imageChanged() signal that is emitted
|
||||
whenever the user changes an image associated with the item.
|
||||
|
||||
\snippet drilldown/view.h 2
|
||||
|
||||
The \c addItems() function is a convenience function provided to
|
||||
simplify the constructor. It is called only once, creating the
|
||||
various items and adding them to the view.
|
||||
|
||||
The \c findWindow() function, on the other hand, is frequently
|
||||
used. It is called from the \c showInformation() function to
|
||||
determine whether a window is already created for the given
|
||||
item (whenever we create an \c InformationWindow object, we
|
||||
store a reference to it in the \c informationWindows list). The
|
||||
latter function is in turn called from our custom \c
|
||||
mouseReleaseEvent() implementation.
|
||||
|
||||
\snippet drilldown/view.h 3
|
||||
|
||||
Finally, we declare a QSqlRelationalTableModel pointer. As
|
||||
previously mentioned, the QSqlRelationalTableModel class provides
|
||||
an editable data model with foreign key support. There are a
|
||||
couple of things you should keep in mind when using the
|
||||
QSqlRelationalTableModel class: The table must have a primary key
|
||||
declared and this key cannot contain a relation to another table,
|
||||
that is, it cannot be a foreign key. Also note that if a relational
|
||||
table contains keys that refer to non-existent rows in the
|
||||
referenced table, the rows containing the invalid keys will not be
|
||||
exposed through the model. It is the user's or the database's
|
||||
responsibility to maintain referential integrity.
|
||||
|
||||
\section1 View Class Implementation
|
||||
|
||||
Although the constructor requests the names of both the table
|
||||
containing office details as well as the table containing the
|
||||
names of the available image files, we only have to create a
|
||||
QSqlRelationalTableModel object for the "items" table:
|
||||
|
||||
\snippet drilldown/view.cpp 0
|
||||
|
||||
The reason is that once we have a model with the item details,
|
||||
we can create a relation to the available image files using
|
||||
QSqlRelationalTableModel's \l
|
||||
{QSqlRelationalTableModel::}{setRelation()} function. This
|
||||
function creates a foreign key for the given model column. The key
|
||||
is specified by the provided QSqlRelation object constructed by
|
||||
the name of the table the key refers to, the field the key is
|
||||
mapping to and the field that should be presented to the user.
|
||||
|
||||
Note that setting the table only specifies which table the model
|
||||
operates on, i.e., we must explicitly call the model's \l
|
||||
{QSqlRelationalTableModel::}{select()} function to populate our
|
||||
model.
|
||||
|
||||
\snippet drilldown/view.cpp 1
|
||||
|
||||
Then we create the contents of our view, i.e., the scene and its
|
||||
items. The labels are regular QGraphicsTextItem objects, whereas
|
||||
the images are instances of the \c ImageItem class, derived from
|
||||
QGraphicsPixmapItem. We will get back to this shortly when reviewing
|
||||
the \c addItems() function.
|
||||
|
||||
Finally, we set the main application widget's size constraints and
|
||||
window title.
|
||||
|
||||
\snippet drilldown/view.cpp 3
|
||||
|
||||
The \c addItems() function is called only once when creating the main
|
||||
application window. For each row in the database table, we first
|
||||
extract the corresponding record using the model's
|
||||
\l {QSqlRelationalTableModel::}{record()} function. The QSqlRecord
|
||||
class encapsulates both the functionality and characteristics of a
|
||||
database record, and supports adding and removing fields as well
|
||||
as setting and retrieving field values. The QSqlRecord::value()
|
||||
function returns the value of the field with the given name or
|
||||
index as a QVariant object.
|
||||
|
||||
For each record, we create a label item as well as an image item,
|
||||
calculate their position and add them to the scene. The image
|
||||
items are represented by instances of the \c ImageItem class. The
|
||||
reason we must create a custom item class is that we want to catch
|
||||
the item's hover events, animating the item when the mouse cursor
|
||||
is hovering over the image (by default, no items accept hover
|
||||
events). Please see the \l{Graphics View Framework} documentation
|
||||
and the \l{Graphics View Examples} for more details.
|
||||
|
||||
\snippet drilldown/view.cpp 5
|
||||
|
||||
We reimplement QGraphicsView's \l
|
||||
{QGraphicsView::}{mouseReleaseEvent()} event handler to respond to
|
||||
user interaction. If the user clicks any of the image items, this
|
||||
function calls the private \c showInformation() function to pop up
|
||||
the associated information window.
|
||||
|
||||
The \l {Graphics View Framework} provides the qgraphicsitem_cast()
|
||||
function to determine whether the given QGraphicsItem instance is
|
||||
of a given type. Note that if the event is not related to any of
|
||||
our image items, we pass it on to the base class implementation.
|
||||
|
||||
\snippet drilldown/view.cpp 6
|
||||
|
||||
The \c showInformation() function is given an \c ImageItem object
|
||||
as argument, and starts off by extracting the item's item ID.
|
||||
|
||||
Then it determines if there already is created an information
|
||||
window for this location.
|
||||
If no window for the given location exists, we create one by
|
||||
passing the item ID, a pointer to the model, and our view as a
|
||||
parent, to the \c InformationWindow constructor. Note that we
|
||||
connect the information window's \c imageChanged() signal to \e
|
||||
this widget's \c updateImage() slot, before we give it a suitable
|
||||
position and add it to the list of existing windows.
|
||||
If there is a window for the given location, and that window is
|
||||
visible, it ensures that the window is raised to the top of the
|
||||
widget stack and activated. If it is hidden, calling its \l
|
||||
{QWidget::}{show()} slot gives the same result.
|
||||
|
||||
|
||||
\snippet drilldown/view.cpp 7
|
||||
|
||||
The \c updateImage() slot takes an item ID and the name of an
|
||||
image file as arguments. It filters out the image items, and
|
||||
updates the one that correspond to the given item ID, with the
|
||||
provided image file.
|
||||
|
||||
\snippet drilldown/view.cpp 8
|
||||
|
||||
The \c findWindow() function simply searches through the list of
|
||||
existing windows, returning a pointer to the window that matches
|
||||
the given item ID, or \nullptr if the window doesn't exists.
|
||||
|
||||
Finally, let's take a quick look at our custom \c ImageItem class:
|
||||
|
||||
\section1 ImageItem Class Definition
|
||||
|
||||
The \c ImageItem class is provided to facilitate animation of the
|
||||
image items. It inherits QGraphicsPixmapItem and reimplements its
|
||||
hover event handlers:
|
||||
|
||||
\snippet drilldown/imageitem.h 0
|
||||
|
||||
We declare a \c Type enum value for our custom item and reimplement
|
||||
\l{QGraphicsItem::}{type()}. This is done so we can safely use
|
||||
qgraphicsitem_cast().
|
||||
In addition, we implement a public \c id() function to be able to
|
||||
identify the associated location and a public \c adjust() function
|
||||
that can be called to ensure that the image item is given the
|
||||
preferred size regardless of the original image file.
|
||||
|
||||
The animation is implemented using the QTimeLine class together
|
||||
with the event handlers and the private \c setFrame() slot: The
|
||||
image item will expand when the mouse cursor hovers over it,
|
||||
returning back to its original size when the cursor leaves its
|
||||
borders.
|
||||
|
||||
Finally, we store the item ID that this particular record is
|
||||
associated with as well as a z-value. In the \l {Graphics View
|
||||
Framework}, an item's z-value determines its position in the item
|
||||
stack. An item of high z-value will be drawn on top of an item
|
||||
with a lower z-value if they share the same parent item. We also
|
||||
provide an \c updateItemPosition() function to refresh the view
|
||||
when required.
|
||||
|
||||
\section1 ImageItem Class Implementation
|
||||
|
||||
The \c ImageItem class is really only a QGraphicsPixmapItem with
|
||||
some additional features, i.e., we can pass most of the
|
||||
constructor's arguments (the pixmap, parent and scene) on to the
|
||||
base class constructor:
|
||||
|
||||
\snippet drilldown/imageitem.cpp 0
|
||||
|
||||
Then we store the ID for future reference, and ensure that our
|
||||
image item will accept hover events. Hover events are delivered
|
||||
when there is no current mouse grabber item. They are sent when the
|
||||
mouse cursor enters an item, when it moves around inside the item,
|
||||
and when the cursor leaves an item. As we mentioned earlier, none
|
||||
of the \l {Graphics View Framework}'s items accept hover
|
||||
event's by default.
|
||||
|
||||
The QTimeLine class provides a timeline for controlling
|
||||
animations. Its \l {QTimeLine::}{duration} property holds the
|
||||
total duration of the timeline in milliseconds. By default, the
|
||||
time line runs once from the beginning and towards the end. The
|
||||
QTimeLine::setFrameRange() function sets the timeline's frame
|
||||
counter; when the timeline is running, the \l
|
||||
{QTimeLine::}{frameChanged()} signal is emitted each time the
|
||||
frame changes. We set the duration and frame range for our
|
||||
animation, and connect the time line's \l
|
||||
{QTimeLine::}{frameChanged()} and \l {QTimeLine::}{finished()}
|
||||
signals to our private \c setFrame() and \c updateItemPosition()
|
||||
slots.
|
||||
|
||||
Finally, we call \c adjust() to ensure that the item is given the
|
||||
preferred size.
|
||||
|
||||
\snippet drilldown/imageitem.cpp 1
|
||||
\codeline
|
||||
\snippet drilldown/imageitem.cpp 2
|
||||
|
||||
Whenever the mouse cursor enters or leaves the image item, the
|
||||
corresponding event handlers are triggered: We first set the time
|
||||
line's direction, making the item expand or shrink,
|
||||
respectively. Then we alter the item's z-value if it is not already
|
||||
set to the expected value.
|
||||
|
||||
In the case of hover \e enter events, we immediately update the
|
||||
item's position since we want the item to appear on top of all
|
||||
other items as soon as it starts expanding. In the case of hover
|
||||
\e leave events, on the other hand, we postpone the actual update
|
||||
to achieve the same result. But remember that when we constructed
|
||||
our item, we connected the time line's \l
|
||||
{QTimeLine::}{finished()} signal to the \c updateItemPosition()
|
||||
slot. In this way the item is given the correct position in the
|
||||
item stack once the animation is completed. Finally, if the time
|
||||
line is not already running, we start it.
|
||||
|
||||
\snippet drilldown/imageitem.cpp 3
|
||||
|
||||
When the time line is running, it triggers the \c setFrame() slot
|
||||
whenever the current frame changes due to the connection we
|
||||
created in the item constructor. It is this slot that controls the
|
||||
animation, expanding or shrinking the image item step by step.
|
||||
|
||||
We first call the \c adjust() function to ensure that we start off
|
||||
with the item's original size. Then we scale the item with a
|
||||
factor depending on the animation's progress (using the \c frame
|
||||
parameter). Note that by default, the transformation will be
|
||||
relative to the item's top-left corner. Since we want the item to
|
||||
be transformed relative to its center, we must translate the
|
||||
coordinate system before we scale the item.
|
||||
|
||||
In the end, only the following convenience functions remain:
|
||||
|
||||
\snippet drilldown/imageitem.cpp 4
|
||||
\codeline
|
||||
\snippet drilldown/imageitem.cpp 5
|
||||
\codeline
|
||||
\snippet drilldown/imageitem.cpp 6
|
||||
|
||||
The \c adjust() function defines and applies a transformation
|
||||
matrix, ensuring that our image item appears with the preferred
|
||||
size regardless of the size of the source image. The \c id()
|
||||
function is trivial, and is simply provided to be able to identify
|
||||
the item. In the \c updateItemPosition() slot we call the
|
||||
QGraphicsItem::setZValue() function, setting the elevation of the
|
||||
item.
|
||||
*/
|
20
examples/sql/doc/src/masterdetail.qdoc
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example masterdetail
|
||||
\title Master Detail Example
|
||||
\ingroup sql_examples
|
||||
|
||||
\brief The Master Detail Example shows how to present data from different
|
||||
data sources in the same application. The album titles, and the
|
||||
corresponding artists and release dates, are kept in a
|
||||
database, while each album's tracks are stored in an XML
|
||||
file.
|
||||
|
||||
The example also shows how to add as well as remove data from both
|
||||
the database and the associated XML file using the API provided by
|
||||
the Qt SQL and Qt XML modules, respectively.
|
||||
|
||||
\borderedimage masterdetail-example.png
|
||||
*/
|
14
examples/sql/doc/src/querymodel.qdoc
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example querymodel
|
||||
\title Query Model Example
|
||||
\ingroup sql_examples
|
||||
|
||||
\brief The Query Model example shows how to make customized versions of
|
||||
data obtained from a SQL query, using a model that encapsulates
|
||||
the query and table views to display the results.
|
||||
|
||||
\borderedimage querymodel-example.png
|
||||
*/
|
13
examples/sql/doc/src/relationaltablemodel.qdoc
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example relationaltablemodel
|
||||
\title Relational Table Model Example
|
||||
\ingroup sql_examples
|
||||
|
||||
\brief The Relational Table Model example shows how to use table views with a relational
|
||||
model to visualize the relations between items in a database.
|
||||
|
||||
\borderedimage relationaltablemodel-example.png
|
||||
*/
|
13
examples/sql/doc/src/sqlbrowser.qdoc
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example sqlbrowser
|
||||
\title SQL Browser
|
||||
\ingroup sql_examples
|
||||
|
||||
\brief The SQL Browser example shows how a data browser can be used to visualize
|
||||
the results of SQL statements on a live database.
|
||||
|
||||
\borderedimage sqlbrowser-demo.png
|
||||
*/
|
162
examples/sql/doc/src/sqlwidgetmapper.qdoc
Normal file
@ -0,0 +1,162 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example sqlwidgetmapper
|
||||
\title SQL Widget Mapper Example
|
||||
\ingroup sql_examples
|
||||
|
||||
\brief The SQL Widget Mapper example shows how to use a map information from a
|
||||
database to widgets on a form.
|
||||
|
||||
\borderedimage sql-widget-mapper.png
|
||||
|
||||
In the \l{Combo Widget Mapper Example}, we showed how to use a named
|
||||
mapping between a widget mapper and a QComboBox widget with a special
|
||||
purpose model to relate values in the model to a list of choices.
|
||||
|
||||
Again, we create a \c Window class with an almost identical user interface,
|
||||
providing a combo box to allow their addresses to be classified as "Home",
|
||||
"Work" or "Other". However, instead of using a separate model to hold these
|
||||
address types, we use one database table to hold the example data and
|
||||
another to hold the address types. In this way, we store all the
|
||||
information in the same place.
|
||||
|
||||
\section1 Window Class Definition
|
||||
|
||||
The class provides a constructor, a slot to keep the buttons up to date,
|
||||
and a private function to set up the model:
|
||||
|
||||
\snippet sqlwidgetmapper/window.h Window definition
|
||||
|
||||
In addition to the QDataWidgetMapper object and the controls used to make
|
||||
up the user interface, we use a QStandardItemModel to hold our data and
|
||||
a QStringListModel to hold information about the types of address that
|
||||
can be applied to each person's data.
|
||||
|
||||
\section1 Window Class Implementation
|
||||
|
||||
The first act performed by the \c Window class constructor is to set up
|
||||
the model used to hold the example data. Since this is a key part of the
|
||||
example, we will look at this first.
|
||||
|
||||
The model is initialized in the window's \c{setupModel()} function. Here,
|
||||
we create a SQLite database containing a "person" table with primary key,
|
||||
name, address and type fields.
|
||||
|
||||
\snippet sqlwidgetmapper/window.cpp Set up the main table
|
||||
|
||||
On each row of the table, we insert default values for these fields,
|
||||
including values for the address types that correspond to the address
|
||||
types are stored in a separate table.
|
||||
|
||||
\borderedimage widgetmapper-sql-mapping-table.png
|
||||
|
||||
We create an "addresstype" table containing the identifiers used in the
|
||||
"person" table and the corresponding strings:
|
||||
|
||||
\snippet sqlwidgetmapper/window.cpp Set up the address type table
|
||||
|
||||
The "typeid" field in the "person" table is related to the contents of
|
||||
the "addresstype" table via a relation in a QSqlRelationalTableModel.
|
||||
This kind of model performs all the necessary work to store the data in
|
||||
a database and also allows any relations to be used as models in their
|
||||
own right.
|
||||
|
||||
In this case, we have defined a relation for the "typeid" field in the
|
||||
"person" table that relates it to the "id" field in the "addresstype"
|
||||
table and which causes the contents of the "description" field to be
|
||||
used wherever the "typeid" is presented to the user. (See the
|
||||
QSqlRelationalTableModel::setRelation() documentation for details.)
|
||||
|
||||
\borderedimage widgetmapper-sql-mapping.png
|
||||
|
||||
The constructor of the \c Window class can be explained in three parts.
|
||||
In the first part, we set up the model used to hold the data, then we set
|
||||
up the widgets used for the user interface:
|
||||
|
||||
\snippet sqlwidgetmapper/window.cpp Set up widgets
|
||||
|
||||
We obtain a model for the combo box from the main model, based on the
|
||||
relation we set up for the "typeid" field. The call to the combo box's
|
||||
\l{QComboBox::}{setModelColumn()} selects the field in the field in the
|
||||
model to display.
|
||||
|
||||
Note that this approach is similar to the one used in the
|
||||
\l{Combo Widget Mapper Example} in that we set up a model for the
|
||||
combo box. However, in this case, we obtain a model based on a relation
|
||||
in the QSqlRelationalTableModel rather than create a separate one.
|
||||
|
||||
Next, we set up the widget mapper, relating each input widget to a field
|
||||
in the model:
|
||||
|
||||
\snippet sqlwidgetmapper/window.cpp Set up the mapper
|
||||
|
||||
For the combo box, we already know the index of the field in the model
|
||||
from the \c{setupModel()} function. We use a QSqlRelationalDelegate as
|
||||
a proxy between the mapper and the input widgets to match up the "typeid"
|
||||
values in the model with those in the combo box's model and populate the
|
||||
combo box with descriptions rather than integer values.
|
||||
|
||||
As a result, the user is able to select an item from the combo box,
|
||||
and the associated value is written back to the model.
|
||||
|
||||
The rest of the constructor is very similar to that of the
|
||||
\l{Simple Widget Mapper Example}:
|
||||
|
||||
\snippet sqlwidgetmapper/window.cpp Set up connections and layouts
|
||||
|
||||
We show the implementation of the \c{updateButtons()} slot for
|
||||
completeness:
|
||||
|
||||
\snippet sqlwidgetmapper/window.cpp Slot for updating the buttons
|
||||
|
||||
\omit
|
||||
\section1 Delegate Class Definition and Implementation
|
||||
|
||||
The delegate we use to mediate interaction between the widget mapper and
|
||||
the input widgets is a small QItemDelegate subclass:
|
||||
|
||||
\snippet sqlwidgetmapper/delegate.h Delegate class definition
|
||||
|
||||
This provides implementations of the two standard functions used to pass
|
||||
data between editor widgets and the model (see the \l{Delegate Classes}
|
||||
documentation for a more general description of these functions).
|
||||
|
||||
Since we only provide an empty implementation of the constructor, we
|
||||
concentrate on the other two functions.
|
||||
|
||||
The \l{QItemDelegate::}{setEditorData()} implementation takes the data
|
||||
referred to by the model index supplied and processes it according to
|
||||
the presence of a \c currentIndex property in the editor widget:
|
||||
|
||||
\snippet sqlwidgetmapper/delegate.cpp setEditorData implementation
|
||||
|
||||
If, like QComboBox, the editor widget has this property, it is set using
|
||||
the value from the model. Since we are passing around QVariant values,
|
||||
the strings stored in the model are automatically converted to the integer
|
||||
values needed for the \c currentIndex property.
|
||||
|
||||
As a result, instead of showing "0", "1" or "2" in the combo box, one of
|
||||
its predefined set of items is shown. We call QItemDelegate::setEditorData()
|
||||
for widgets without the \c currentIndex property.
|
||||
|
||||
The \l{QItemDelegate::}{setModelData()} implementation performs the reverse
|
||||
process, taking the value stored in the widget's \c currentIndex property
|
||||
and storing it back in the model:
|
||||
|
||||
\snippet sqlwidgetmapper/delegate.cpp setModelData implementation
|
||||
\endomit
|
||||
|
||||
\section1 Summary and Further Reading
|
||||
|
||||
The use of a separate model for the combo box and a special delegate for the
|
||||
widget mapper allows us to present a menu of choices to the user. Although
|
||||
the choices are stored in the same database as the user's data, they are held
|
||||
in a separate table. Using this approach, we can reconstructed complete records
|
||||
at a later time while using database features appropriately.
|
||||
|
||||
If SQL models are not being used, it is still possible to use more than
|
||||
one model to present choices to the user. This is covered by the
|
||||
\l{Combo Widget Mapper Example}.
|
||||
*/
|
13
examples/sql/doc/src/tablemodel.qdoc
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example tablemodel
|
||||
\title Table Model Example
|
||||
\ingroup sql_examples
|
||||
|
||||
\brief The Table Model example shows how to use a specialized SQL table model with table
|
||||
views to edit information in a database.
|
||||
|
||||
\borderedimage tablemodel-example.png
|
||||
*/
|
56
examples/sql/drilldown/CMakeLists.txt
Normal file
@ -0,0 +1,56 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(drilldown LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/sql/drilldown")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Sql Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(drilldown
|
||||
../connection.h
|
||||
imageitem.cpp imageitem.h
|
||||
informationwindow.cpp informationwindow.h
|
||||
main.cpp
|
||||
view.cpp view.h
|
||||
)
|
||||
|
||||
set_target_properties(drilldown PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(drilldown PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Sql
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
# Resources:
|
||||
set(drilldown_resource_files
|
||||
"images/qt-creator.png"
|
||||
"images/qt-logo.png"
|
||||
"images/qt-project.png"
|
||||
"images/qt-quick.png"
|
||||
)
|
||||
|
||||
qt_add_resources(drilldown "drilldown"
|
||||
PREFIX
|
||||
"/"
|
||||
FILES
|
||||
${drilldown_resource_files}
|
||||
)
|
||||
|
||||
install(TARGETS drilldown
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
15
examples/sql/drilldown/drilldown.pro
Normal file
@ -0,0 +1,15 @@
|
||||
HEADERS = ../connection.h \
|
||||
imageitem.h \
|
||||
informationwindow.h \
|
||||
view.h
|
||||
RESOURCES = drilldown.qrc
|
||||
SOURCES = imageitem.cpp \
|
||||
informationwindow.cpp \
|
||||
main.cpp \
|
||||
view.cpp
|
||||
QT += sql widgets
|
||||
requires(qtConfig(combobox))
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/sql/drilldown
|
||||
INSTALLS += target
|
8
examples/sql/drilldown/drilldown.qrc
Normal file
@ -0,0 +1,8 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>images/qt-logo.png</file>
|
||||
<file>images/qt-quick.png</file>
|
||||
<file>images/qt-creator.png</file>
|
||||
<file>images/qt-project.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
84
examples/sql/drilldown/imageitem.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "imageitem.h"
|
||||
|
||||
//! [0]
|
||||
ImageItem::ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent)
|
||||
: QGraphicsPixmapItem(pixmap, parent)
|
||||
{
|
||||
recordId = id;
|
||||
setAcceptHoverEvents(true);
|
||||
|
||||
timeLine.setDuration(150);
|
||||
timeLine.setFrameRange(0, 150);
|
||||
|
||||
connect(&timeLine, &QTimeLine::frameChanged, this, &ImageItem::setFrame);
|
||||
connect(&timeLine, &QTimeLine::finished, this, &ImageItem::updateItemPosition);
|
||||
|
||||
adjust();
|
||||
}
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
void ImageItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
|
||||
{
|
||||
timeLine.setDirection(QTimeLine::Forward);
|
||||
|
||||
if (z != 1.0) {
|
||||
z = 1.0;
|
||||
updateItemPosition();
|
||||
}
|
||||
|
||||
if (timeLine.state() == QTimeLine::NotRunning)
|
||||
timeLine.start();
|
||||
}
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
void ImageItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
|
||||
{
|
||||
timeLine.setDirection(QTimeLine::Backward);
|
||||
if (z != 0.0)
|
||||
z = 0.0;
|
||||
|
||||
if (timeLine.state() == QTimeLine::NotRunning)
|
||||
timeLine.start();
|
||||
}
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
void ImageItem::setFrame(int frame)
|
||||
{
|
||||
adjust();
|
||||
QPointF center = boundingRect().center();
|
||||
|
||||
setTransform(QTransform::fromTranslate(center.x(), center.y()), true);
|
||||
setTransform(QTransform::fromScale(1 + frame / 330.0, 1 + frame / 330.0), true);
|
||||
setTransform(QTransform::fromTranslate(-center.x(), -center.y()), true);
|
||||
}
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
void ImageItem::adjust()
|
||||
{
|
||||
setTransform(QTransform::fromScale(120 / boundingRect().width(),
|
||||
120 / boundingRect().height()));
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
int ImageItem::id() const
|
||||
{
|
||||
return recordId;
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
void ImageItem::updateItemPosition()
|
||||
{
|
||||
setZValue(z);
|
||||
}
|
||||
//! [6]
|
||||
|
||||
|
39
examples/sql/drilldown/imageitem.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef IMAGEITEM_H
|
||||
#define IMAGEITEM_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QtWidgets/QGraphicsPixmapItem>
|
||||
|
||||
//! [0]
|
||||
class ImageItem : public QObject, public QGraphicsPixmapItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum { Type = UserType + 1 };
|
||||
|
||||
ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent = nullptr);
|
||||
|
||||
int type() const override { return Type; }
|
||||
void adjust();
|
||||
int id() const;
|
||||
|
||||
protected:
|
||||
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
|
||||
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void setFrame(int frame);
|
||||
void updateItemPosition();
|
||||
|
||||
private:
|
||||
QTimeLine timeLine;
|
||||
int recordId;
|
||||
double z;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
BIN
examples/sql/drilldown/images/qt-creator.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
examples/sql/drilldown/images/qt-logo.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
examples/sql/drilldown/images/qt-project.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
examples/sql/drilldown/images/qt-quick.png
Normal file
After Width: | Height: | Size: 26 KiB |
128
examples/sql/drilldown/informationwindow.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "informationwindow.h"
|
||||
|
||||
//! [0]
|
||||
InformationWindow::InformationWindow(int id, QSqlRelationalTableModel *items,
|
||||
QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
//! [0] //! [1]
|
||||
QLabel *itemLabel = new QLabel(tr("Item: "));
|
||||
QLabel *descriptionLabel = new QLabel(tr("Description: "));
|
||||
QLabel *imageFileLabel = new QLabel(tr("Image file: "));
|
||||
|
||||
createButtons();
|
||||
|
||||
itemText = new QLabel;
|
||||
descriptionEditor = new QTextEdit;
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
imageFileEditor = new QComboBox;
|
||||
imageFileEditor->setModel(items->relationModel(1));
|
||||
imageFileEditor->setModelColumn(items->relationModel(1)->fieldIndex("file"));
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
mapper = new QDataWidgetMapper(this);
|
||||
mapper->setModel(items);
|
||||
mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
|
||||
mapper->setItemDelegate(new QSqlRelationalDelegate(mapper));
|
||||
mapper->addMapping(imageFileEditor, 1);
|
||||
mapper->addMapping(itemText, 2, "text");
|
||||
mapper->addMapping(descriptionEditor, 3);
|
||||
mapper->setCurrentIndex(id);
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
connect(descriptionEditor, &QTextEdit::textChanged, [=]() {
|
||||
enableButtons();
|
||||
});
|
||||
connect(imageFileEditor, &QComboBox::currentIndexChanged, [=]() {
|
||||
enableButtons();
|
||||
});
|
||||
|
||||
QFormLayout *formLayout = new QFormLayout;
|
||||
formLayout->addRow(itemLabel, itemText);
|
||||
formLayout->addRow(imageFileLabel, imageFileEditor);
|
||||
formLayout->addRow(descriptionLabel, descriptionEditor);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->addLayout(formLayout);
|
||||
layout->addWidget(buttonBox);
|
||||
setLayout(layout);
|
||||
|
||||
itemId = id;
|
||||
displayedImage = imageFileEditor->currentText();
|
||||
|
||||
setWindowFlags(Qt::Window);
|
||||
enableButtons(false);
|
||||
setWindowTitle(itemText->text());
|
||||
}
|
||||
//! [4]
|
||||
|
||||
//! [5]
|
||||
int InformationWindow::id() const
|
||||
{
|
||||
return itemId;
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
void InformationWindow::revert()
|
||||
{
|
||||
mapper->revert();
|
||||
enableButtons(false);
|
||||
}
|
||||
//! [6]
|
||||
|
||||
//! [7]
|
||||
void InformationWindow::submit()
|
||||
{
|
||||
QString newImage(imageFileEditor->currentText());
|
||||
|
||||
if (displayedImage != newImage) {
|
||||
displayedImage = newImage;
|
||||
emit imageChanged(itemId, newImage);
|
||||
}
|
||||
|
||||
mapper->submit();
|
||||
mapper->setCurrentIndex(itemId);
|
||||
|
||||
enableButtons(false);
|
||||
}
|
||||
//! [7]
|
||||
|
||||
//! [8]
|
||||
void InformationWindow::createButtons()
|
||||
{
|
||||
closeButton = new QPushButton(tr("&Close"));
|
||||
revertButton = new QPushButton(tr("&Revert"));
|
||||
submitButton = new QPushButton(tr("&Submit"));
|
||||
|
||||
closeButton->setDefault(true);
|
||||
|
||||
connect(closeButton, &QPushButton::clicked, this, &InformationWindow::close);
|
||||
connect(revertButton, &QPushButton::clicked, this, &InformationWindow::revert);
|
||||
connect(submitButton, &QPushButton::clicked, this, &InformationWindow::submit);
|
||||
//! [8]
|
||||
|
||||
//! [9]
|
||||
buttonBox = new QDialogButtonBox(this);
|
||||
buttonBox->addButton(submitButton, QDialogButtonBox::AcceptRole);
|
||||
buttonBox->addButton(revertButton, QDialogButtonBox::ResetRole);
|
||||
buttonBox->addButton(closeButton, QDialogButtonBox::RejectRole);
|
||||
}
|
||||
//! [9]
|
||||
|
||||
//! [10]
|
||||
void InformationWindow::enableButtons(bool enable)
|
||||
{
|
||||
revertButton->setEnabled(enable);
|
||||
submitButton->setEnabled(enable);
|
||||
}
|
||||
//! [10]
|
||||
|
||||
|
52
examples/sql/drilldown/informationwindow.h
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef INFORMATIONWINDOW_H
|
||||
#define INFORMATIONWINDOW_H
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
|
||||
//! [0]
|
||||
class InformationWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InformationWindow(int id, QSqlRelationalTableModel *items,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
int id() const;
|
||||
|
||||
signals:
|
||||
void imageChanged(int id, const QString &fileName);
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
private slots:
|
||||
void revert();
|
||||
void submit();
|
||||
void enableButtons(bool enable = true);
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
private:
|
||||
void createButtons();
|
||||
|
||||
int itemId;
|
||||
QString displayedImage;
|
||||
|
||||
QComboBox *imageFileEditor = nullptr;
|
||||
QLabel *itemText = nullptr;
|
||||
QTextEdit *descriptionEditor = nullptr;
|
||||
|
||||
QPushButton *closeButton = nullptr;
|
||||
QPushButton *submitButton = nullptr;
|
||||
QPushButton *revertButton = nullptr;
|
||||
QDialogButtonBox *buttonBox = nullptr;
|
||||
|
||||
QDataWidgetMapper *mapper = nullptr;
|
||||
};
|
||||
//! [2]
|
||||
|
||||
#endif
|
27
examples/sql/drilldown/main.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "view.h"
|
||||
#include "../connection.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Q_INIT_RESOURCE(drilldown);
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
if (!createConnection())
|
||||
return EXIT_FAILURE;
|
||||
|
||||
View view("items", "images");
|
||||
view.show();
|
||||
#ifdef QT_KEYPAD_NAVIGATION
|
||||
QApplication::setNavigationMode(Qt::NavigationModeCursorAuto);
|
||||
#endif
|
||||
return app.exec();
|
||||
}
|
135
examples/sql/drilldown/view.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "informationwindow.h"
|
||||
#include "imageitem.h"
|
||||
#include "view.h"
|
||||
|
||||
//! [0]
|
||||
View::View(const QString &items, const QString &images, QWidget *parent)
|
||||
: QGraphicsView(parent)
|
||||
{
|
||||
itemTable = new QSqlRelationalTableModel(this);
|
||||
itemTable->setTable(items);
|
||||
itemTable->setRelation(1, QSqlRelation(images, "itemid", "file"));
|
||||
itemTable->select();
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
scene = new QGraphicsScene(this);
|
||||
scene->setSceneRect(0, 0, 465, 365);
|
||||
setScene(scene);
|
||||
|
||||
addItems();
|
||||
|
||||
setMinimumSize(470, 370);
|
||||
setMaximumSize(470, 370);
|
||||
|
||||
QLinearGradient gradient(QPointF(0, 0), QPointF(0, 370));
|
||||
gradient.setColorAt(0, QColor("#868482"));
|
||||
gradient.setColorAt(1, QColor("#5d5b59"));
|
||||
setBackgroundBrush(gradient);
|
||||
}
|
||||
//! [1]
|
||||
|
||||
//! [3]
|
||||
void View::addItems()
|
||||
{
|
||||
int itemCount = itemTable->rowCount();
|
||||
|
||||
int imageOffset = 150;
|
||||
int leftMargin = 70;
|
||||
int topMargin = 40;
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
QSqlRecord record = itemTable->record(i);
|
||||
|
||||
int id = record.value("id").toInt();
|
||||
QString file = record.value("file").toString();
|
||||
QString item = record.value("itemtype").toString();
|
||||
|
||||
int columnOffset = ((i % 2) * 37);
|
||||
int x = ((i % 2) * imageOffset) + leftMargin + columnOffset;
|
||||
int y = ((i / 2) * imageOffset) + topMargin;
|
||||
|
||||
ImageItem *image = new ImageItem(id, QPixmap(":/" + file));
|
||||
image->setData(0, i);
|
||||
image->setPos(x, y);
|
||||
scene->addItem(image);
|
||||
|
||||
QGraphicsTextItem *label = scene->addText(item);
|
||||
label->setDefaultTextColor(QColor("#d7d6d5"));
|
||||
QPointF labelOffset((120 - label->boundingRect().width()) / 2, 120.0);
|
||||
label->setPos(QPointF(x, y) + labelOffset);
|
||||
}
|
||||
}
|
||||
//! [3]
|
||||
|
||||
//! [5]
|
||||
void View::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (QGraphicsItem *item = itemAt(event->position().toPoint())) {
|
||||
if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item))
|
||||
showInformation(image);
|
||||
}
|
||||
QGraphicsView::mouseReleaseEvent(event);
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
void View::showInformation(ImageItem *image)
|
||||
{
|
||||
int id = image->id();
|
||||
if (id < 0 || id >= itemTable->rowCount())
|
||||
return;
|
||||
|
||||
InformationWindow *window = findWindow(id);
|
||||
if (!window) {
|
||||
window = new InformationWindow(id, itemTable, this);
|
||||
|
||||
connect(window, QOverload<int,const QString &>::of(&InformationWindow::imageChanged),
|
||||
this, QOverload<int,const QString &>::of(&View::updateImage));
|
||||
|
||||
window->move(pos() + QPoint(20, 40));
|
||||
window->show();
|
||||
informationWindows.append(window);
|
||||
}
|
||||
|
||||
if (window->isVisible()) {
|
||||
window->raise();
|
||||
window->activateWindow();
|
||||
} else
|
||||
window->show();
|
||||
}
|
||||
//! [6]
|
||||
|
||||
//! [7]
|
||||
void View::updateImage(int id, const QString &fileName)
|
||||
{
|
||||
QList<QGraphicsItem *> items = scene->items();
|
||||
|
||||
while(!items.empty()) {
|
||||
QGraphicsItem *item = items.takeFirst();
|
||||
|
||||
if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item)) {
|
||||
if (image->id() == id){
|
||||
image->setPixmap(QPixmap(":/" +fileName));
|
||||
image->adjust();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//! [7]
|
||||
|
||||
//! [8]
|
||||
InformationWindow *View::findWindow(int id) const
|
||||
{
|
||||
for (auto window : informationWindows) {
|
||||
if (window && (window->id() == id))
|
||||
return window;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
//! [8]
|
||||
|
43
examples/sql/drilldown/view.h
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef VIEW_H
|
||||
#define VIEW_H
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
|
||||
class ImageItem;
|
||||
class InformationWindow;
|
||||
|
||||
//! [0]
|
||||
class View : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
View(const QString &items, const QString &images, QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
private slots:
|
||||
void updateImage(int id, const QString &fileName);
|
||||
//! [1]
|
||||
|
||||
//! [2]
|
||||
private:
|
||||
void addItems();
|
||||
InformationWindow *findWindow(int id) const;
|
||||
void showInformation(ImageItem *image);
|
||||
|
||||
QGraphicsScene *scene;
|
||||
QList<InformationWindow *> informationWindows;
|
||||
//! [2] //! [3]
|
||||
QSqlRelationalTableModel *itemTable;
|
||||
};
|
||||
//! [3]
|
||||
|
||||
#endif
|
54
examples/sql/masterdetail/CMakeLists.txt
Normal file
@ -0,0 +1,54 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(masterdetail LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/sql/masterdetail")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Sql Widgets Xml)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(masterdetail
|
||||
database.h
|
||||
dialog.cpp dialog.h
|
||||
main.cpp
|
||||
mainwindow.cpp mainwindow.h
|
||||
)
|
||||
|
||||
set_target_properties(masterdetail PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(masterdetail PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Sql
|
||||
Qt6::Widgets
|
||||
Qt6::Xml
|
||||
)
|
||||
|
||||
# Resources:
|
||||
set(masterdetail_resource_files
|
||||
"images/icon.png"
|
||||
"images/image.png"
|
||||
)
|
||||
|
||||
qt_add_resources(masterdetail "masterdetail"
|
||||
PREFIX
|
||||
"/"
|
||||
FILES
|
||||
${masterdetail_resource_files}
|
||||
)
|
||||
|
||||
install(TARGETS masterdetail
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
98
examples/sql/masterdetail/albumdetails.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<archive>
|
||||
<album id="1" >
|
||||
<track number="01" >Humming one of your songs</track>
|
||||
<track number="02" >Are they saying goodbye</track>
|
||||
<track number="03" >On off</track>
|
||||
<track number="04" >I shot my heart</track>
|
||||
<track number="05" >Drowning in Those Eyes</track>
|
||||
<track number="06" >So you did it again</track>
|
||||
<track number="07" >One more time</track>
|
||||
<track number="08" >Headphone silence</track>
|
||||
<track number="09" >What I want</track>
|
||||
<track number="10" >Sleeping by the Fyris River</track>
|
||||
<track number="11" >Wooden Body</track>
|
||||
<track number="12" >Humming one of your songs (encore)</track>
|
||||
</album>
|
||||
<album id="2" >
|
||||
<track number="01" >To let myself go</track>
|
||||
<track number="02" >Rubber and Soul</track>
|
||||
<track number="03" >Balloon ranger</track>
|
||||
<track number="04" >My lover will go</track>
|
||||
<track number="05" >Temporary dive</track>
|
||||
<track number="06" >Laid in earth</track>
|
||||
<track number="07" >This voice</track>
|
||||
<track number="08" >Where friend rhymes with end</track>
|
||||
<track number="09" >Song No.6 feat Ron Sexsmith</track>
|
||||
<track number="10" >The Fight Song</track>
|
||||
</album>
|
||||
<album id="3" >
|
||||
<track number="01" >From Grace</track>
|
||||
<track number="02" >All's not last</track>
|
||||
<track number="03" >That Great October Sound</track>
|
||||
<track number="04" >Life Here Is Gold</track>
|
||||
<track number="05" >Tomorrow Stays The Same</track>
|
||||
<track number="06" >Postulate</track>
|
||||
<track number="07" >Adelaide</track>
|
||||
<track number="08" >John Wayne</track>
|
||||
<track number="09" >Love's Lost</track>
|
||||
<track number="10" >Dreamweaver</track>
|
||||
<track number="11" >Outro</track>
|
||||
</album>
|
||||
<album id="4" >
|
||||
<track number="01" >Rain down on me</track>
|
||||
<track number="02" >Cecilia</track>
|
||||
<track number="03" >Make a mess of yourself</track>
|
||||
<track number="04" >Pale green eyes</track>
|
||||
<track number="05" >Either way I'm gone</track>
|
||||
<track number="06" >Honey</track>
|
||||
<track number="07" >Rise in shame</track>
|
||||
<track number="08" >Stray dogs</track>
|
||||
<track number="09" >The willow</track>
|
||||
<track number="10" >Stay home</track>
|
||||
<track number="11" >Outro</track>
|
||||
</album>
|
||||
<album id="5" >
|
||||
</album>
|
||||
<album id="6" >
|
||||
<track number="01" >Kontroll på kontinentet</track>
|
||||
<track number="02" >Ompa til du dør</track>
|
||||
<track number="03" >Bøn fra helvete</track>
|
||||
<track number="04" >170</track>
|
||||
<track number="05" >Rullett</track>
|
||||
<track number="06" >Dr. Mowinckel</track>
|
||||
<track number="07" >Fra sjåfør til passasjer</track>
|
||||
<track number="08" >Resistansen</track>
|
||||
<track number="09" >Dekk bord</track>
|
||||
<track number="10" >Bak et halleluja</track>
|
||||
<track number="11" >Bris</track>
|
||||
<track number="12" >Mr. Kaizer, hans Constanze og meg</track>
|
||||
</album>
|
||||
<album id="7" >
|
||||
<track number="01" >Di grind</track>
|
||||
<track number="02" >Hevnervals</track>
|
||||
<track number="03" >Evig pint</track>
|
||||
<track number="04" >De involverte</track>
|
||||
<track number="05" >Djevelens orkester</track>
|
||||
<track number="06" >Container</track>
|
||||
<track number="07" >Naade</track>
|
||||
<track number="08" >Min kvite russer</track>
|
||||
<track number="09" >Veterans klage</track>
|
||||
<track number="10" >Til depotet</track>
|
||||
<track number="11" >Salt og pepper</track>
|
||||
<track number="12" >Drøm Hardt (Requiem Part I)</track>
|
||||
</album>
|
||||
<album id="8" >
|
||||
<track number="01" >KGB</track>
|
||||
<track number="02" >Maestro</track>
|
||||
<track number="03" >Knekker deg til sist</track>
|
||||
<track number="04" >Senor Flamingos Adieu</track>
|
||||
<track number="05" >Blitzregn baby</track>
|
||||
<track number="06" >Dieter Meyers Inst.</track>
|
||||
<track number="07" >Christiania</track>
|
||||
<track number="08" >Delikatessen</track>
|
||||
<track number="09" >Jævel av en tango</track>
|
||||
<track number="10" >Papa har lov</track>
|
||||
<track number="11" >Auksjon (i Dieter Meyers hall)</track>
|
||||
<track number="12" >På ditt skift</track>
|
||||
</album>
|
||||
</archive>
|
59
examples/sql/masterdetail/database.h
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef DATABASE_H
|
||||
#define DATABASE_H
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QSqlQuery>
|
||||
|
||||
static bool createConnection()
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
|
||||
db.setDatabaseName(":memory:");
|
||||
if (!db.open()) {
|
||||
QMessageBox::critical(nullptr, QObject::tr("Cannot open database"),
|
||||
QObject::tr("Unable to establish a database connection.\n"
|
||||
"This example needs SQLite support. Please read "
|
||||
"the Qt SQL driver documentation for information how "
|
||||
"to build it.\n\n"
|
||||
"Click Cancel to exit."), QMessageBox::Cancel);
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery query;
|
||||
|
||||
query.exec("create table artists (id int primary key, "
|
||||
"artist varchar(40), "
|
||||
"albumcount int)");
|
||||
|
||||
query.exec("insert into artists values(0, '<all>', 0)");
|
||||
query.exec("insert into artists values(1, 'Ane Brun', 2)");
|
||||
query.exec("insert into artists values(2, 'Thomas Dybdahl', 3)");
|
||||
query.exec("insert into artists values(3, 'Kaizers Orchestra', 3)");
|
||||
|
||||
query.exec("create table albums (albumid int primary key, "
|
||||
"title varchar(50), "
|
||||
"artistid int, "
|
||||
"year int)");
|
||||
|
||||
query.exec("insert into albums values(1, 'Spending Time With Morgan', 1, "
|
||||
"2003)");
|
||||
query.exec("insert into albums values(2, 'A Temporary Dive', 1, 2005)");
|
||||
query.exec("insert into albums values(3, '...The Great October Sound', 2, "
|
||||
"2002)");
|
||||
query.exec("insert into albums values(4, 'Stray Dogs', 2, 2003)");
|
||||
query.exec("insert into albums values(5, "
|
||||
"'One day you`ll dance for me, New York City', 2, 2004)");
|
||||
query.exec("insert into albums values(6, 'Ompa Til Du D\xc3\xb8r', 3, 2001)");
|
||||
query.exec("insert into albums values(7, 'Evig Pint', 3, 2002)");
|
||||
query.exec("insert into albums values(8, 'Maestro', 3, 2005)");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
245
examples/sql/masterdetail/dialog.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "dialog.h"
|
||||
|
||||
int uniqueAlbumId;
|
||||
int uniqueArtistId;
|
||||
|
||||
Dialog::Dialog(QSqlRelationalTableModel *albums, QDomDocument details,
|
||||
QFile *output, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
model = albums;
|
||||
albumDetails = details;
|
||||
outputFile = output;
|
||||
|
||||
QGroupBox *inputWidgetBox = createInputWidgets();
|
||||
QDialogButtonBox *buttonBox = createButtons();
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->addWidget(inputWidgetBox);
|
||||
layout->addWidget(buttonBox);
|
||||
setLayout(layout);
|
||||
|
||||
setWindowTitle(tr("Add Album"));
|
||||
}
|
||||
|
||||
void Dialog::submit()
|
||||
{
|
||||
QString artist = artistEditor->text();
|
||||
QString title = titleEditor->text();
|
||||
|
||||
if (artist.isEmpty() || title.isEmpty()) {
|
||||
QString message(tr("Please provide both the name of the artist "
|
||||
"and the title of the album."));
|
||||
QMessageBox::information(this, tr("Add Album"), message);
|
||||
} else {
|
||||
int artistId = findArtistId(artist);
|
||||
int albumId = addNewAlbum(title, artistId);
|
||||
|
||||
QStringList tracks;
|
||||
tracks = tracksEditor->text().split(QLatin1Char(','), Qt::SkipEmptyParts);
|
||||
addTracks(albumId, tracks);
|
||||
|
||||
increaseAlbumCount(indexOfArtist(artist));
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
int Dialog::findArtistId(const QString &artist)
|
||||
{
|
||||
QSqlTableModel *artistModel = model->relationModel(2);
|
||||
int row = 0;
|
||||
|
||||
while (row < artistModel->rowCount()) {
|
||||
QSqlRecord record = artistModel->record(row);
|
||||
if (record.value("artist") == artist)
|
||||
return record.value("id").toInt();
|
||||
else
|
||||
row++;
|
||||
}
|
||||
return addNewArtist(artist);
|
||||
}
|
||||
|
||||
|
||||
int Dialog::addNewArtist(const QString &name)
|
||||
{
|
||||
QSqlTableModel *artistModel = model->relationModel(2);
|
||||
QSqlRecord record;
|
||||
|
||||
int id = generateArtistId();
|
||||
|
||||
QSqlField f1("id", QMetaType(QMetaType::Int));
|
||||
QSqlField f2("artist", QMetaType(QMetaType::QString));
|
||||
QSqlField f3("albumcount", QMetaType(QMetaType::Int));
|
||||
|
||||
f1.setValue(QVariant(id));
|
||||
f2.setValue(QVariant(name));
|
||||
f3.setValue(QVariant(0));
|
||||
record.append(f1);
|
||||
record.append(f2);
|
||||
record.append(f3);
|
||||
|
||||
artistModel->insertRecord(-1, record);
|
||||
return id;
|
||||
}
|
||||
|
||||
int Dialog::addNewAlbum(const QString &title, int artistId)
|
||||
{
|
||||
int id = generateAlbumId();
|
||||
QSqlRecord record;
|
||||
|
||||
QSqlField f1("albumid", QMetaType(QMetaType::Int));
|
||||
QSqlField f2("title", QMetaType(QMetaType::QString));
|
||||
QSqlField f3("artistid", QMetaType(QMetaType::Int));
|
||||
QSqlField f4("year", QMetaType(QMetaType::Int));
|
||||
|
||||
f1.setValue(QVariant(id));
|
||||
f2.setValue(QVariant(title));
|
||||
f3.setValue(QVariant(artistId));
|
||||
f4.setValue(QVariant(yearEditor->value()));
|
||||
record.append(f1);
|
||||
record.append(f2);
|
||||
record.append(f3);
|
||||
record.append(f4);
|
||||
|
||||
model->insertRecord(-1, record);
|
||||
return id;
|
||||
}
|
||||
|
||||
void Dialog::addTracks(int albumId, const QStringList &tracks)
|
||||
{
|
||||
QDomElement albumNode = albumDetails.createElement("album");
|
||||
albumNode.setAttribute("id", albumId);
|
||||
|
||||
for (int i = 0; i < tracks.count(); ++i) {
|
||||
QString trackNumber = QString::number(i);
|
||||
if (i < 10)
|
||||
trackNumber.prepend('0');
|
||||
|
||||
QDomText textNode = albumDetails.createTextNode(tracks.at(i));
|
||||
|
||||
QDomElement trackNode = albumDetails.createElement("track");
|
||||
trackNode.setAttribute("number", trackNumber);
|
||||
trackNode.appendChild(textNode);
|
||||
|
||||
albumNode.appendChild(trackNode);
|
||||
}
|
||||
|
||||
QDomNodeList archive = albumDetails.elementsByTagName("archive");
|
||||
archive.item(0).appendChild(albumNode);
|
||||
|
||||
/*
|
||||
The following code is commented out since the example uses an in
|
||||
memory database, i.e., altering the XML file will bring the data
|
||||
out of sync.
|
||||
|
||||
if (!outputFile->open(QIODevice::WriteOnly)) {
|
||||
return;
|
||||
} else {
|
||||
QTextStream stream(outputFile);
|
||||
archive.item(0).save(stream, 4);
|
||||
outputFile->close();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void Dialog::increaseAlbumCount(QModelIndex artistIndex)
|
||||
{
|
||||
QSqlTableModel *artistModel = model->relationModel(2);
|
||||
|
||||
QModelIndex albumCountIndex;
|
||||
albumCountIndex = artistIndex.sibling(artistIndex.row(), 2);
|
||||
|
||||
int albumCount = albumCountIndex.data().toInt();
|
||||
artistModel->setData(albumCountIndex, QVariant(albumCount + 1));
|
||||
}
|
||||
|
||||
|
||||
void Dialog::revert()
|
||||
{
|
||||
artistEditor->clear();
|
||||
titleEditor->clear();
|
||||
yearEditor->setValue(QDate::currentDate().year());
|
||||
tracksEditor->clear();
|
||||
}
|
||||
|
||||
QGroupBox *Dialog::createInputWidgets()
|
||||
{
|
||||
QGroupBox *box = new QGroupBox(tr("Add Album"));
|
||||
|
||||
QLabel *artistLabel = new QLabel(tr("Artist:"));
|
||||
QLabel *titleLabel = new QLabel(tr("Title:"));
|
||||
QLabel *yearLabel = new QLabel(tr("Year:"));
|
||||
QLabel *tracksLabel = new QLabel(tr("Tracks (separated by comma):"));
|
||||
|
||||
artistEditor = new QLineEdit;
|
||||
titleEditor = new QLineEdit;
|
||||
|
||||
yearEditor = new QSpinBox;
|
||||
yearEditor->setMinimum(1900);
|
||||
yearEditor->setMaximum(QDate::currentDate().year());
|
||||
yearEditor->setValue(yearEditor->maximum());
|
||||
yearEditor->setReadOnly(false);
|
||||
|
||||
tracksEditor = new QLineEdit;
|
||||
|
||||
QGridLayout *layout = new QGridLayout;
|
||||
layout->addWidget(artistLabel, 0, 0);
|
||||
layout->addWidget(artistEditor, 0, 1);
|
||||
layout->addWidget(titleLabel, 1, 0);
|
||||
layout->addWidget(titleEditor, 1, 1);
|
||||
layout->addWidget(yearLabel, 2, 0);
|
||||
layout->addWidget(yearEditor, 2, 1);
|
||||
layout->addWidget(tracksLabel, 3, 0, 1, 2);
|
||||
layout->addWidget(tracksEditor, 4, 0, 1, 2);
|
||||
box->setLayout(layout);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
QDialogButtonBox *Dialog::createButtons()
|
||||
{
|
||||
QPushButton *closeButton = new QPushButton(tr("&Close"));
|
||||
QPushButton *revertButton = new QPushButton(tr("&Revert"));
|
||||
QPushButton *submitButton = new QPushButton(tr("&Submit"));
|
||||
|
||||
closeButton->setDefault(true);
|
||||
|
||||
connect(closeButton, &QPushButton::clicked, this, &Dialog::close);
|
||||
connect(revertButton, &QPushButton::clicked, this, &Dialog::revert);
|
||||
connect(submitButton, &QPushButton::clicked, this, &Dialog::submit);
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox;
|
||||
buttonBox->addButton(submitButton, QDialogButtonBox::ResetRole);
|
||||
buttonBox->addButton(revertButton, QDialogButtonBox::ResetRole);
|
||||
buttonBox->addButton(closeButton, QDialogButtonBox::RejectRole);
|
||||
|
||||
return buttonBox;
|
||||
}
|
||||
|
||||
QModelIndex Dialog::indexOfArtist(const QString &artist)
|
||||
{
|
||||
QSqlTableModel *artistModel = model->relationModel(2);
|
||||
|
||||
for (int i = 0; i < artistModel->rowCount(); ++i) {
|
||||
QSqlRecord record = artistModel->record(i);
|
||||
if (record.value("artist") == artist)
|
||||
return artistModel->index(i, 1);
|
||||
}
|
||||
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
int Dialog::generateArtistId()
|
||||
{
|
||||
uniqueArtistId += 1;
|
||||
return uniqueArtistId;
|
||||
}
|
||||
|
||||
int Dialog::generateAlbumId()
|
||||
{
|
||||
uniqueAlbumId += 1;
|
||||
return uniqueAlbumId;
|
||||
}
|
45
examples/sql/masterdetail/dialog.h
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef DIALOG_H
|
||||
#define DIALOG_H
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
#include <QtXml>
|
||||
|
||||
class Dialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Dialog(QSqlRelationalTableModel *albums, QDomDocument details,
|
||||
QFile *output, QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void revert();
|
||||
void submit();
|
||||
|
||||
private:
|
||||
int addNewAlbum(const QString &title, int artistId);
|
||||
int addNewArtist(const QString &name);
|
||||
void addTracks(int albumId, const QStringList &tracks);
|
||||
QDialogButtonBox *createButtons();
|
||||
QGroupBox *createInputWidgets();
|
||||
int findArtistId(const QString &artist);
|
||||
static int generateAlbumId();
|
||||
static int generateArtistId();
|
||||
void increaseAlbumCount(QModelIndex artistIndex);
|
||||
QModelIndex indexOfArtist(const QString &artist);
|
||||
|
||||
QSqlRelationalTableModel *model;
|
||||
QDomDocument albumDetails;
|
||||
QFile *outputFile;
|
||||
|
||||
QLineEdit *artistEditor;
|
||||
QLineEdit *titleEditor;
|
||||
QSpinBox *yearEditor;
|
||||
QLineEdit *tracksEditor;
|
||||
};
|
||||
|
||||
#endif
|
BIN
examples/sql/masterdetail/images/icon.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
examples/sql/masterdetail/images/image.png
Normal file
After Width: | Height: | Size: 80 KiB |
25
examples/sql/masterdetail/main.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "database.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Q_INIT_RESOURCE(masterdetail);
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
if (!createConnection())
|
||||
return EXIT_FAILURE;
|
||||
|
||||
QFile albumDetails("albumdetails.xml");
|
||||
MainWindow window("artists", "albums", &albumDetails);
|
||||
window.show();
|
||||
return app.exec();
|
||||
}
|
397
examples/sql/masterdetail/mainwindow.cpp
Normal file
@ -0,0 +1,397 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "dialog.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
#include <QtXml>
|
||||
|
||||
extern int uniqueAlbumId;
|
||||
extern int uniqueArtistId;
|
||||
|
||||
MainWindow::MainWindow(const QString &artistTable, const QString &albumTable,
|
||||
QFile *albumDetails, QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
{
|
||||
file = albumDetails;
|
||||
readAlbumData();
|
||||
|
||||
model = new QSqlRelationalTableModel(this);
|
||||
model->setTable(albumTable);
|
||||
model->setRelation(2, QSqlRelation(artistTable, "id", "artist"));
|
||||
model->select();
|
||||
|
||||
QGroupBox *artists = createArtistGroupBox();
|
||||
QGroupBox *albums = createAlbumGroupBox();
|
||||
QGroupBox *details = createDetailsGroupBox();
|
||||
|
||||
artistView->setCurrentIndex(0);
|
||||
uniqueAlbumId = model->rowCount();
|
||||
uniqueArtistId = artistView->count();
|
||||
|
||||
connect(model, &QSqlRelationalTableModel::rowsInserted,
|
||||
this, &MainWindow::updateHeader);
|
||||
connect(model, &QSqlRelationalTableModel::rowsRemoved,
|
||||
this, &MainWindow::updateHeader);
|
||||
|
||||
QGridLayout *layout = new QGridLayout;
|
||||
layout->addWidget(artists, 0, 0);
|
||||
layout->addWidget(albums, 1, 0);
|
||||
layout->addWidget(details, 0, 1, 2, 1);
|
||||
layout->setColumnStretch(1, 1);
|
||||
layout->setColumnMinimumWidth(0, 500);
|
||||
|
||||
QWidget *widget = new QWidget;
|
||||
widget->setLayout(layout);
|
||||
setCentralWidget(widget);
|
||||
createMenuBar();
|
||||
|
||||
showImageLabel();
|
||||
resize(850, 400);
|
||||
setWindowTitle(tr("Music Archive"));
|
||||
}
|
||||
|
||||
void MainWindow::changeArtist(int row)
|
||||
{
|
||||
if (row > 0) {
|
||||
QModelIndex index = model->relationModel(2)->index(row, 1);
|
||||
model->setFilter("artist = '" + index.data().toString() + '\'') ;
|
||||
showArtistProfile(index);
|
||||
} else if (row == 0) {
|
||||
model->setFilter(QString());
|
||||
showImageLabel();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::showArtistProfile(QModelIndex index)
|
||||
{
|
||||
QSqlRecord record = model->relationModel(2)->record(index.row());
|
||||
|
||||
QString name = record.value("artist").toString();
|
||||
QString count = record.value("albumcount").toString();
|
||||
profileLabel->setText(tr("Artist : %1 \n" \
|
||||
"Number of Albums: %2").arg(name).arg(count));
|
||||
|
||||
profileLabel->show();
|
||||
iconLabel->show();
|
||||
|
||||
titleLabel->hide();
|
||||
trackList->hide();
|
||||
imageLabel->hide();
|
||||
}
|
||||
|
||||
void MainWindow::showAlbumDetails(QModelIndex index)
|
||||
{
|
||||
QSqlRecord record = model->record(index.row());
|
||||
|
||||
QString artist = record.value("artist").toString();
|
||||
QString title = record.value("title").toString();
|
||||
QString year = record.value("year").toString();
|
||||
QString albumId = record.value("albumid").toString();
|
||||
|
||||
showArtistProfile(indexOfArtist(artist));
|
||||
titleLabel->setText(tr("Title: %1 (%2)").arg(title).arg(year));
|
||||
titleLabel->show();
|
||||
|
||||
QDomNodeList albums = albumData.elementsByTagName("album");
|
||||
for (int i = 0; i < albums.count(); ++i) {
|
||||
QDomNode album = albums.item(i);
|
||||
if (album.toElement().attribute("id") == albumId) {
|
||||
getTrackList(album.toElement());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trackList->count() != 0)
|
||||
trackList->show();
|
||||
}
|
||||
|
||||
void MainWindow::getTrackList(QDomNode album)
|
||||
{
|
||||
trackList->clear();
|
||||
|
||||
QDomNodeList tracks = album.childNodes();
|
||||
QDomNode track;
|
||||
QString trackNumber;
|
||||
|
||||
for (int i = 0; i < tracks.count(); ++i) {
|
||||
|
||||
track = tracks.item(i);
|
||||
trackNumber = track.toElement().attribute("number");
|
||||
|
||||
QListWidgetItem *item = new QListWidgetItem(trackList);
|
||||
item->setText(trackNumber + ": " + track.toElement().text());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::addAlbum()
|
||||
{
|
||||
Dialog *dialog = new Dialog(model, albumData, file, this);
|
||||
int accepted = dialog->exec();
|
||||
|
||||
if (accepted == 1) {
|
||||
int lastRow = model->rowCount() - 1;
|
||||
albumView->selectRow(lastRow);
|
||||
albumView->scrollToBottom();
|
||||
showAlbumDetails(model->index(lastRow, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::deleteAlbum()
|
||||
{
|
||||
QModelIndexList selection = albumView->selectionModel()->selectedRows(0);
|
||||
|
||||
if (!selection.empty()) {
|
||||
QModelIndex idIndex = selection.at(0);
|
||||
int id = idIndex.data().toInt();
|
||||
QString title = idIndex.sibling(idIndex.row(), 1).data().toString();
|
||||
QString artist = idIndex.sibling(idIndex.row(), 2).data().toString();
|
||||
|
||||
QMessageBox::StandardButton button;
|
||||
button = QMessageBox::question(this, tr("Delete Album"),
|
||||
tr("Are you sure you want to "
|
||||
"delete '%1' by '%2'?")
|
||||
.arg(title, artist),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (button == QMessageBox::Yes) {
|
||||
removeAlbumFromFile(id);
|
||||
removeAlbumFromDatabase(idIndex);
|
||||
decreaseAlbumCount(indexOfArtist(artist));
|
||||
|
||||
showImageLabel();
|
||||
}
|
||||
} else {
|
||||
QMessageBox::information(this, tr("Delete Album"),
|
||||
tr("Select the album you want to delete."));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::removeAlbumFromFile(int id)
|
||||
{
|
||||
|
||||
QDomNodeList albums = albumData.elementsByTagName("album");
|
||||
|
||||
for (int i = 0; i < albums.count(); ++i) {
|
||||
QDomNode node = albums.item(i);
|
||||
if (node.toElement().attribute("id").toInt() == id) {
|
||||
albumData.elementsByTagName("archive").item(0).removeChild(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
The following code is commented out since the example uses an in
|
||||
memory database, i.e., altering the XML file will bring the data
|
||||
out of sync.
|
||||
|
||||
if (!file->open(QIODevice::WriteOnly)) {
|
||||
return;
|
||||
} else {
|
||||
QTextStream stream(file);
|
||||
albumData.elementsByTagName("archive").item(0).save(stream, 4);
|
||||
file->close();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void MainWindow::removeAlbumFromDatabase(QModelIndex index)
|
||||
{
|
||||
model->removeRow(index.row());
|
||||
}
|
||||
|
||||
void MainWindow::decreaseAlbumCount(QModelIndex artistIndex)
|
||||
{
|
||||
int row = artistIndex.row();
|
||||
QModelIndex albumCountIndex = artistIndex.sibling(row, 2);
|
||||
int albumCount = albumCountIndex.data().toInt();
|
||||
|
||||
QSqlTableModel *artists = model->relationModel(2);
|
||||
|
||||
if (albumCount == 1) {
|
||||
artists->removeRow(row);
|
||||
showImageLabel();
|
||||
} else {
|
||||
artists->setData(albumCountIndex, QVariant(albumCount - 1));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::readAlbumData()
|
||||
{
|
||||
if (!file->open(QIODevice::ReadOnly))
|
||||
return;
|
||||
|
||||
if (!albumData.setContent(file)) {
|
||||
file->close();
|
||||
return;
|
||||
}
|
||||
file->close();
|
||||
}
|
||||
|
||||
QGroupBox* MainWindow::createArtistGroupBox()
|
||||
{
|
||||
artistView = new QComboBox;
|
||||
artistView->setModel(model->relationModel(2));
|
||||
artistView->setModelColumn(1);
|
||||
|
||||
connect(artistView, &QComboBox::currentIndexChanged,
|
||||
this, &MainWindow::changeArtist);
|
||||
|
||||
QGroupBox *box = new QGroupBox(tr("Artist"));
|
||||
|
||||
QGridLayout *layout = new QGridLayout;
|
||||
layout->addWidget(artistView, 0, 0);
|
||||
box->setLayout(layout);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
QGroupBox* MainWindow::createAlbumGroupBox()
|
||||
{
|
||||
QGroupBox *box = new QGroupBox(tr("Album"));
|
||||
|
||||
albumView = new QTableView;
|
||||
albumView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
albumView->setSortingEnabled(true);
|
||||
albumView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
albumView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
albumView->setShowGrid(false);
|
||||
albumView->verticalHeader()->hide();
|
||||
albumView->setAlternatingRowColors(true);
|
||||
albumView->setModel(model);
|
||||
adjustHeader();
|
||||
|
||||
QLocale locale = albumView->locale();
|
||||
locale.setNumberOptions(QLocale::OmitGroupSeparator);
|
||||
albumView->setLocale(locale);
|
||||
|
||||
connect(albumView, &QTableView::clicked,
|
||||
this, &MainWindow::showAlbumDetails);
|
||||
connect(albumView, &QTableView::activated,
|
||||
this, &MainWindow::showAlbumDetails);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->addWidget(albumView, 0, { });
|
||||
box->setLayout(layout);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
QGroupBox* MainWindow::createDetailsGroupBox()
|
||||
{
|
||||
QGroupBox *box = new QGroupBox(tr("Details"));
|
||||
|
||||
profileLabel = new QLabel;
|
||||
profileLabel->setWordWrap(true);
|
||||
profileLabel->setAlignment(Qt::AlignBottom);
|
||||
|
||||
titleLabel = new QLabel;
|
||||
titleLabel->setWordWrap(true);
|
||||
titleLabel->setAlignment(Qt::AlignBottom);
|
||||
|
||||
iconLabel = new QLabel();
|
||||
iconLabel->setAlignment(Qt::AlignBottom | Qt::AlignRight);
|
||||
iconLabel->setPixmap(QPixmap(":/images/icon.png"));
|
||||
|
||||
imageLabel = new QLabel;
|
||||
imageLabel->setWordWrap(true);
|
||||
imageLabel->setAlignment(Qt::AlignCenter);
|
||||
imageLabel->setPixmap(QPixmap(":/images/image.png"));
|
||||
|
||||
trackList = new QListWidget;
|
||||
|
||||
QGridLayout *layout = new QGridLayout;
|
||||
layout->addWidget(imageLabel, 0, 0, 3, 2);
|
||||
layout->addWidget(profileLabel, 0, 0);
|
||||
layout->addWidget(iconLabel, 0, 1);
|
||||
layout->addWidget(titleLabel, 1, 0, 1, 2);
|
||||
layout->addWidget(trackList, 2, 0, 1, 2);
|
||||
layout->setRowStretch(2, 1);
|
||||
box->setLayout(layout);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
void MainWindow::createMenuBar()
|
||||
{
|
||||
QAction *addAction = new QAction(tr("&Add album..."), this);
|
||||
QAction *deleteAction = new QAction(tr("&Delete album..."), this);
|
||||
QAction *quitAction = new QAction(tr("&Quit"), this);
|
||||
QAction *aboutAction = new QAction(tr("&About"), this);
|
||||
QAction *aboutQtAction = new QAction(tr("About &Qt"), this);
|
||||
|
||||
addAction->setShortcut(tr("Ctrl+A"));
|
||||
deleteAction->setShortcut(tr("Ctrl+D"));
|
||||
quitAction->setShortcuts(QKeySequence::Quit);
|
||||
|
||||
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
|
||||
fileMenu->addAction(addAction);
|
||||
fileMenu->addAction(deleteAction);
|
||||
fileMenu->addSeparator();
|
||||
fileMenu->addAction(quitAction);
|
||||
|
||||
QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
|
||||
helpMenu->addAction(aboutAction);
|
||||
helpMenu->addAction(aboutQtAction);
|
||||
|
||||
connect(addAction, &QAction::triggered,
|
||||
this, &MainWindow::addAlbum);
|
||||
connect(deleteAction, &QAction::triggered,
|
||||
this, &MainWindow::deleteAlbum);
|
||||
connect(quitAction, &QAction::triggered,
|
||||
qApp, &QCoreApplication::quit);
|
||||
connect(aboutAction, &QAction::triggered,
|
||||
this, &MainWindow::about);
|
||||
connect(aboutQtAction, &QAction::triggered,
|
||||
qApp, &QApplication::aboutQt);
|
||||
}
|
||||
|
||||
void MainWindow::showImageLabel()
|
||||
{
|
||||
profileLabel->hide();
|
||||
titleLabel->hide();
|
||||
iconLabel->hide();
|
||||
trackList->hide();
|
||||
|
||||
imageLabel->show();
|
||||
}
|
||||
|
||||
QModelIndex MainWindow::indexOfArtist(const QString &artist)
|
||||
{
|
||||
QSqlTableModel *artistModel = model->relationModel(2);
|
||||
|
||||
for (int i = 0; i < artistModel->rowCount(); i++) {
|
||||
QSqlRecord record = artistModel->record(i);
|
||||
if (record.value("artist") == artist)
|
||||
return artistModel->index(i, 1);
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
void MainWindow::updateHeader(QModelIndex, int, int)
|
||||
{
|
||||
adjustHeader();
|
||||
}
|
||||
|
||||
void MainWindow::adjustHeader()
|
||||
{
|
||||
albumView->hideColumn(0);
|
||||
albumView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
albumView->resizeColumnToContents(2);
|
||||
albumView->resizeColumnToContents(3);
|
||||
}
|
||||
|
||||
void MainWindow::about()
|
||||
{
|
||||
QMessageBox::about(this, tr("About Music Archive"),
|
||||
tr("<p>The <b>Music Archive</b> example shows how to present "
|
||||
"data from different data sources in the same application. "
|
||||
"The album titles, and the corresponding artists and release dates, "
|
||||
"are kept in a database, while each album's tracks are stored "
|
||||
"in an XML file. </p><p>The example also shows how to add as "
|
||||
"well as remove data from both the database and the "
|
||||
"associated XML file using the API provided by the Qt SQL and "
|
||||
"Qt XML modules, respectively.</p>"));
|
||||
}
|
66
examples/sql/masterdetail/mainwindow.h
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QMainWindow>
|
||||
#include <QModelIndex>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QComboBox;
|
||||
class QFile;
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QSqlRelationalTableModel;
|
||||
class QTableView;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow(const QString &artistTable, const QString &albumTable,
|
||||
QFile *albumDetails, QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void about();
|
||||
void addAlbum();
|
||||
void changeArtist(int row);
|
||||
void deleteAlbum();
|
||||
void showAlbumDetails(QModelIndex index);
|
||||
void showArtistProfile(QModelIndex index);
|
||||
void updateHeader(QModelIndex, int, int);
|
||||
|
||||
private:
|
||||
void adjustHeader();
|
||||
QGroupBox *createAlbumGroupBox();
|
||||
QGroupBox *createArtistGroupBox();
|
||||
QGroupBox *createDetailsGroupBox();
|
||||
void createMenuBar();
|
||||
void decreaseAlbumCount(QModelIndex artistIndex);
|
||||
void getTrackList(QDomNode album);
|
||||
QModelIndex indexOfArtist(const QString &artist);
|
||||
void readAlbumData();
|
||||
void removeAlbumFromDatabase(QModelIndex album);
|
||||
void removeAlbumFromFile(int id);
|
||||
void showImageLabel();
|
||||
|
||||
QTableView *albumView;
|
||||
QComboBox *artistView;
|
||||
QListWidget *trackList;
|
||||
|
||||
QLabel *iconLabel;
|
||||
QLabel *imageLabel;
|
||||
QLabel *profileLabel;
|
||||
QLabel *titleLabel;
|
||||
|
||||
QDomDocument albumData;
|
||||
QFile *file;
|
||||
QSqlRelationalTableModel *model;
|
||||
};
|
||||
|
||||
#endif
|
17
examples/sql/masterdetail/masterdetail.pro
Normal file
@ -0,0 +1,17 @@
|
||||
HEADERS = database.h \
|
||||
dialog.h \
|
||||
mainwindow.h
|
||||
RESOURCES = masterdetail.qrc
|
||||
SOURCES = dialog.cpp \
|
||||
main.cpp \
|
||||
mainwindow.cpp
|
||||
|
||||
QT += sql widgets
|
||||
QT += xml widgets
|
||||
requires(qtConfig(tableview))
|
||||
|
||||
EXAMPLE_FILES = albumdetails.xml
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/sql/masterdetail
|
||||
INSTALLS += target
|
6
examples/sql/masterdetail/masterdetail.qrc
Normal file
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file>images/icon.png</file>
|
||||
<file>images/image.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
40
examples/sql/querymodel/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(querymodel LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/sql/querymodel")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Sql Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(querymodel
|
||||
../connection.h
|
||||
customsqlmodel.cpp customsqlmodel.h
|
||||
editablesqlmodel.cpp editablesqlmodel.h
|
||||
main.cpp
|
||||
)
|
||||
|
||||
set_target_properties(querymodel PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(querymodel PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Sql
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS querymodel
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
27
examples/sql/querymodel/customsqlmodel.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "customsqlmodel.h"
|
||||
|
||||
CustomSqlModel::CustomSqlModel(QObject *parent)
|
||||
: QSqlQueryModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
//! [0]
|
||||
QVariant CustomSqlModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
QVariant value = QSqlQueryModel::data(index, role);
|
||||
if (value.isValid() && role == Qt::DisplayRole) {
|
||||
if (index.column() == 0)
|
||||
return value.toString().prepend('#');
|
||||
else if (index.column() == 2)
|
||||
return value.toString().toUpper();
|
||||
}
|
||||
if (role == Qt::ForegroundRole && index.column() == 1)
|
||||
return QVariant::fromValue(QColor(Qt::blue));
|
||||
return value;
|
||||
}
|
||||
//! [0]
|
21
examples/sql/querymodel/customsqlmodel.h
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CUSTOMSQLMODEL_H
|
||||
#define CUSTOMSQLMODEL_H
|
||||
|
||||
#include <QSqlQueryModel>
|
||||
|
||||
//! [0]
|
||||
class CustomSqlModel : public QSqlQueryModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CustomSqlModel(QObject *parent = nullptr);
|
||||
|
||||
QVariant data(const QModelIndex &item, int role) const override;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
#endif
|
72
examples/sql/querymodel/editablesqlmodel.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtSql>
|
||||
|
||||
#include "editablesqlmodel.h"
|
||||
|
||||
EditableSqlModel::EditableSqlModel(QObject *parent)
|
||||
: QSqlQueryModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
//! [0]
|
||||
Qt::ItemFlags EditableSqlModel::flags(
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags flags = QSqlQueryModel::flags(index);
|
||||
if (index.column() == 1 || index.column() == 2)
|
||||
flags |= Qt::ItemIsEditable;
|
||||
return flags;
|
||||
}
|
||||
//! [0]
|
||||
|
||||
//! [1]
|
||||
bool EditableSqlModel::setData(const QModelIndex &index, const QVariant &value, int /* role */)
|
||||
{
|
||||
if (index.column() < 1 || index.column() > 2)
|
||||
return false;
|
||||
|
||||
QModelIndex primaryKeyIndex = QSqlQueryModel::index(index.row(), 0);
|
||||
int id = data(primaryKeyIndex).toInt();
|
||||
|
||||
clear();
|
||||
|
||||
bool ok;
|
||||
if (index.column() == 1) {
|
||||
ok = setFirstName(id, value.toString());
|
||||
} else {
|
||||
ok = setLastName(id, value.toString());
|
||||
}
|
||||
refresh();
|
||||
return ok;
|
||||
}
|
||||
//! [1]
|
||||
|
||||
void EditableSqlModel::refresh()
|
||||
{
|
||||
setQuery("select * from person");
|
||||
setHeaderData(0, Qt::Horizontal, QObject::tr("ID"));
|
||||
setHeaderData(1, Qt::Horizontal, QObject::tr("First name"));
|
||||
setHeaderData(2, Qt::Horizontal, QObject::tr("Last name"));
|
||||
}
|
||||
|
||||
//! [2]
|
||||
bool EditableSqlModel::setFirstName(int personId, const QString &firstName)
|
||||
{
|
||||
QSqlQuery query;
|
||||
query.prepare("update person set firstname = ? where id = ?");
|
||||
query.addBindValue(firstName);
|
||||
query.addBindValue(personId);
|
||||
return query.exec();
|
||||
}
|
||||
//! [2]
|
||||
|
||||
bool EditableSqlModel::setLastName(int personId, const QString &lastName)
|
||||
{
|
||||
QSqlQuery query;
|
||||
query.prepare("update person set lastname = ? where id = ?");
|
||||
query.addBindValue(lastName);
|
||||
query.addBindValue(personId);
|
||||
return query.exec();
|
||||
}
|
25
examples/sql/querymodel/editablesqlmodel.h
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef EDITABLESQLMODEL_H
|
||||
#define EDITABLESQLMODEL_H
|
||||
|
||||
#include <QSqlQueryModel>
|
||||
|
||||
class EditableSqlModel : public QSqlQueryModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
EditableSqlModel(QObject *parent = nullptr);
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
|
||||
private:
|
||||
bool setFirstName(int personId, const QString &firstName);
|
||||
bool setLastName(int personId, const QString &lastName);
|
||||
void refresh();
|
||||
};
|
||||
|
||||
#endif
|
54
examples/sql/querymodel/main.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "../connection.h"
|
||||
#include "customsqlmodel.h"
|
||||
#include "editablesqlmodel.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QTableView>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void initializeModel(QSqlQueryModel *model)
|
||||
{
|
||||
model->setQuery("select * from person");
|
||||
model->setHeaderData(0, Qt::Horizontal, QObject::tr("ID"));
|
||||
model->setHeaderData(1, Qt::Horizontal, QObject::tr("First name"));
|
||||
model->setHeaderData(2, Qt::Horizontal, QObject::tr("Last name"));
|
||||
}
|
||||
|
||||
QTableView* createView(QSqlQueryModel *model, const QString &title = "")
|
||||
{
|
||||
QTableView *view = new QTableView;
|
||||
view->setModel(model);
|
||||
static int offset = 0;
|
||||
|
||||
view->setWindowTitle(title);
|
||||
view->move(100 + offset, 100 + offset);
|
||||
offset += 20;
|
||||
view->show();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
if (!createConnection())
|
||||
return EXIT_FAILURE;
|
||||
|
||||
QSqlQueryModel plainModel;
|
||||
EditableSqlModel editableModel;
|
||||
CustomSqlModel customModel;
|
||||
|
||||
initializeModel(&plainModel);
|
||||
initializeModel(&editableModel);
|
||||
initializeModel(&customModel);
|
||||
|
||||
createView(&plainModel, QObject::tr("Plain Query Model"));
|
||||
createView(&editableModel, QObject::tr("Editable Query Model"));
|
||||
createView(&customModel, QObject::tr("Custom Query Model"));
|
||||
|
||||
return app.exec();
|
||||
}
|
13
examples/sql/querymodel/querymodel.pro
Normal file
@ -0,0 +1,13 @@
|
||||
HEADERS = ../connection.h \
|
||||
customsqlmodel.h \
|
||||
editablesqlmodel.h
|
||||
SOURCES = customsqlmodel.cpp \
|
||||
editablesqlmodel.cpp \
|
||||
main.cpp
|
||||
QT += sql widgets
|
||||
requires(qtConfig(tableview))
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/sql/querymodel
|
||||
INSTALLS += target
|
||||
|
38
examples/sql/relationaltablemodel/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(relationaltablemodel LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/sql/relationaltablemodel")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Sql Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(relationaltablemodel
|
||||
../connection.h
|
||||
relationaltablemodel.cpp
|
||||
)
|
||||
|
||||
set_target_properties(relationaltablemodel PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(relationaltablemodel PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Sql
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS relationaltablemodel
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
80
examples/sql/relationaltablemodel/relationaltablemodel.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
|
||||
#include "../connection.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
void initializeModel(QSqlRelationalTableModel *model)
|
||||
{
|
||||
//! [0]
|
||||
model->setTable("employee");
|
||||
//! [0]
|
||||
|
||||
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
|
||||
//! [1]
|
||||
model->setRelation(2, QSqlRelation("city", "id", "name"));
|
||||
//! [1] //! [2]
|
||||
model->setRelation(3, QSqlRelation("country", "id", "name"));
|
||||
//! [2]
|
||||
|
||||
//! [3]
|
||||
model->setHeaderData(0, Qt::Horizontal, QObject::tr("ID"));
|
||||
model->setHeaderData(1, Qt::Horizontal, QObject::tr("Name"));
|
||||
model->setHeaderData(2, Qt::Horizontal, QObject::tr("City"));
|
||||
model->setHeaderData(3, Qt::Horizontal, QObject::tr("Country"));
|
||||
//! [3]
|
||||
|
||||
model->select();
|
||||
}
|
||||
|
||||
std::unique_ptr<QTableView> createView(const QString &title, QSqlTableModel *model)
|
||||
{
|
||||
//! [4]
|
||||
std::unique_ptr<QTableView> view{new QTableView};
|
||||
view->setModel(model);
|
||||
view->setItemDelegate(new QSqlRelationalDelegate(view.get()));
|
||||
//! [4]
|
||||
view->setWindowTitle(title);
|
||||
return view;
|
||||
}
|
||||
|
||||
void createRelationalTables()
|
||||
{
|
||||
QSqlQuery query;
|
||||
query.exec("create table employee(id int primary key, name varchar(20), city int, country int)");
|
||||
query.exec("insert into employee values(1, 'Espen', 5000, 47)");
|
||||
query.exec("insert into employee values(2, 'Harald', 80000, 49)");
|
||||
query.exec("insert into employee values(3, 'Sam', 100, 1)");
|
||||
|
||||
query.exec("create table city(id int, name varchar(20))");
|
||||
query.exec("insert into city values(100, 'San Jose')");
|
||||
query.exec("insert into city values(5000, 'Oslo')");
|
||||
query.exec("insert into city values(80000, 'Munich')");
|
||||
|
||||
query.exec("create table country(id int, name varchar(20))");
|
||||
query.exec("insert into country values(1, 'USA')");
|
||||
query.exec("insert into country values(47, 'Norway')");
|
||||
query.exec("insert into country values(49, 'Germany')");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
if (!createConnection())
|
||||
return EXIT_FAILURE;
|
||||
|
||||
createRelationalTables();
|
||||
|
||||
QSqlRelationalTableModel model;
|
||||
|
||||
initializeModel(&model);
|
||||
|
||||
std::unique_ptr<QTableView> view = createView(QObject::tr("Relational Table Model"), &model);
|
||||
view->show();
|
||||
|
||||
return app.exec();
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
HEADERS = ../connection.h
|
||||
SOURCES = relationaltablemodel.cpp
|
||||
QT += sql widgets
|
||||
requires(qtConfig(tableview))
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/sql/relationaltablemodel
|
||||
INSTALLS += target
|
||||
|
21
examples/sql/sql.pro
Normal file
@ -0,0 +1,21 @@
|
||||
requires(qtHaveModule(widgets))
|
||||
|
||||
TEMPLATE = subdirs
|
||||
|
||||
SUBDIRS = books \
|
||||
drilldown \
|
||||
cachedtable \
|
||||
querymodel \
|
||||
relationaltablemodel \
|
||||
sqlwidgetmapper \
|
||||
tablemodel
|
||||
|
||||
qtHaveModule(xml): SUBDIRS += masterdetail
|
||||
|
||||
!cross_compile:{
|
||||
contains(QT_BUILD_PARTS, tools):{
|
||||
SUBDIRS += sqlbrowser
|
||||
}
|
||||
}
|
||||
|
||||
EXAMPLE_FILES = connection.h
|
43
examples/sql/sqlbrowser/CMakeLists.txt
Normal file
@ -0,0 +1,43 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(sqlbrowser LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/sql/sqlbrowser")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Sql Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(sqlbrowser
|
||||
browser.cpp browser.h
|
||||
browserwidget.ui
|
||||
connectionwidget.cpp connectionwidget.h
|
||||
main.cpp
|
||||
qsqlconnectiondialog.cpp qsqlconnectiondialog.h qsqlconnectiondialog.ui
|
||||
)
|
||||
|
||||
set_target_properties(sqlbrowser PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(sqlbrowser PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Sql
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS sqlbrowser
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
268
examples/sql/sqlbrowser/browser.cpp
Normal file
@ -0,0 +1,268 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "browser.h"
|
||||
#include "qsqlconnectiondialog.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
|
||||
Browser::Browser(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setupUi(this);
|
||||
|
||||
table->addAction(insertRowAction);
|
||||
table->addAction(deleteRowAction);
|
||||
table->addAction(fieldStrategyAction);
|
||||
table->addAction(rowStrategyAction);
|
||||
table->addAction(manualStrategyAction);
|
||||
table->addAction(submitAction);
|
||||
table->addAction(revertAction);
|
||||
table->addAction(selectAction);
|
||||
|
||||
if (QSqlDatabase::drivers().isEmpty())
|
||||
QMessageBox::information(this, tr("No database drivers found"),
|
||||
tr("This demo requires at least one Qt database driver. "
|
||||
"Please check the documentation how to build the "
|
||||
"Qt SQL plugins."));
|
||||
|
||||
emit statusMessage(tr("Ready."));
|
||||
}
|
||||
|
||||
Browser::~Browser()
|
||||
{
|
||||
}
|
||||
|
||||
void Browser::exec()
|
||||
{
|
||||
QSqlQueryModel *model = new QSqlQueryModel(table);
|
||||
model->setQuery(QSqlQuery(sqlEdit->toPlainText(), connectionWidget->currentDatabase()));
|
||||
table->setModel(model);
|
||||
|
||||
if (model->lastError().type() != QSqlError::NoError)
|
||||
emit statusMessage(model->lastError().text());
|
||||
else if (model->query().isSelect())
|
||||
emit statusMessage(tr("Query OK."));
|
||||
else
|
||||
emit statusMessage(tr("Query OK, number of affected rows: %1").arg(
|
||||
model->query().numRowsAffected()));
|
||||
|
||||
updateActions();
|
||||
}
|
||||
|
||||
QSqlError Browser::addConnection(const QString &driver, const QString &dbName, const QString &host,
|
||||
const QString &user, const QString &passwd, int port)
|
||||
{
|
||||
static int cCount = 0;
|
||||
|
||||
QSqlError err;
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase(driver, QString("Browser%1").arg(++cCount));
|
||||
db.setDatabaseName(dbName);
|
||||
db.setHostName(host);
|
||||
db.setPort(port);
|
||||
if (!db.open(user, passwd)) {
|
||||
err = db.lastError();
|
||||
db = QSqlDatabase();
|
||||
QSqlDatabase::removeDatabase(QString("Browser%1").arg(cCount));
|
||||
}
|
||||
connectionWidget->refresh();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void Browser::addConnection()
|
||||
{
|
||||
QSqlConnectionDialog dialog(this);
|
||||
if (dialog.exec() != QDialog::Accepted)
|
||||
return;
|
||||
|
||||
if (dialog.useInMemoryDatabase()) {
|
||||
QSqlDatabase::database("in_mem_db", false).close();
|
||||
QSqlDatabase::removeDatabase("in_mem_db");
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "in_mem_db");
|
||||
db.setDatabaseName(":memory:");
|
||||
if (!db.open())
|
||||
QMessageBox::warning(this, tr("Unable to open database"), tr("An error occurred while "
|
||||
"opening the connection: ") + db.lastError().text());
|
||||
QSqlQuery q("", db);
|
||||
q.exec("drop table Movies");
|
||||
q.exec("drop table Names");
|
||||
q.exec("create table Movies (id integer primary key, Title varchar, Director varchar, Rating number)");
|
||||
q.exec("insert into Movies values (0, 'Metropolis', 'Fritz Lang', '8.4')");
|
||||
q.exec("insert into Movies values (1, 'Nosferatu, eine Symphonie des Grauens', 'F.W. Murnau', '8.1')");
|
||||
q.exec("insert into Movies values (2, 'Bis ans Ende der Welt', 'Wim Wenders', '6.5')");
|
||||
q.exec("insert into Movies values (3, 'Hardware', 'Richard Stanley', '5.2')");
|
||||
q.exec("insert into Movies values (4, 'Mitchell', 'Andrew V. McLaglen', '2.1')");
|
||||
q.exec("create table Names (id integer primary key, FirstName varchar, LastName varchar, City varchar)");
|
||||
q.exec("insert into Names values (0, 'Sala', 'Palmer', 'Morristown')");
|
||||
q.exec("insert into Names values (1, 'Christopher', 'Walker', 'Morristown')");
|
||||
q.exec("insert into Names values (2, 'Donald', 'Duck', 'Andeby')");
|
||||
q.exec("insert into Names values (3, 'Buck', 'Rogers', 'Paris')");
|
||||
q.exec("insert into Names values (4, 'Sherlock', 'Holmes', 'London')");
|
||||
connectionWidget->refresh();
|
||||
} else {
|
||||
QSqlError err = addConnection(dialog.driverName(), dialog.databaseName(), dialog.hostName(),
|
||||
dialog.userName(), dialog.password(), dialog.port());
|
||||
if (err.type() != QSqlError::NoError)
|
||||
QMessageBox::warning(this, tr("Unable to open database"), tr("An error occurred while "
|
||||
"opening the connection: ") + err.text());
|
||||
}
|
||||
}
|
||||
|
||||
void Browser::showTable(const QString &t)
|
||||
{
|
||||
QSqlTableModel *model = new CustomModel(table, connectionWidget->currentDatabase());
|
||||
model->setEditStrategy(QSqlTableModel::OnRowChange);
|
||||
model->setTable(connectionWidget->currentDatabase().driver()->escapeIdentifier(t, QSqlDriver::TableName));
|
||||
model->select();
|
||||
if (model->lastError().type() != QSqlError::NoError)
|
||||
emit statusMessage(model->lastError().text());
|
||||
|
||||
table->setModel(model);
|
||||
table->setEditTriggers(QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed);
|
||||
connect(table->selectionModel(), &QItemSelectionModel::currentRowChanged,
|
||||
this, &Browser::currentChanged);
|
||||
|
||||
updateActions();
|
||||
}
|
||||
|
||||
void Browser::showMetaData(const QString &t)
|
||||
{
|
||||
QSqlRecord rec = connectionWidget->currentDatabase().record(t);
|
||||
QStandardItemModel *model = new QStandardItemModel(table);
|
||||
|
||||
model->insertRows(0, rec.count());
|
||||
model->insertColumns(0, 7);
|
||||
|
||||
model->setHeaderData(0, Qt::Horizontal, "Fieldname");
|
||||
model->setHeaderData(1, Qt::Horizontal, "Type");
|
||||
model->setHeaderData(2, Qt::Horizontal, "Length");
|
||||
model->setHeaderData(3, Qt::Horizontal, "Precision");
|
||||
model->setHeaderData(4, Qt::Horizontal, "Required");
|
||||
model->setHeaderData(5, Qt::Horizontal, "AutoValue");
|
||||
model->setHeaderData(6, Qt::Horizontal, "DefaultValue");
|
||||
|
||||
for (int i = 0; i < rec.count(); ++i) {
|
||||
QSqlField fld = rec.field(i);
|
||||
model->setData(model->index(i, 0), fld.name());
|
||||
model->setData(model->index(i, 1), fld.typeID() == -1
|
||||
? QString(fld.metaType().name())
|
||||
: QString("%1 (%2)").arg(fld.metaType().name()).arg(fld.typeID()));
|
||||
model->setData(model->index(i, 2), fld.length());
|
||||
model->setData(model->index(i, 3), fld.precision());
|
||||
model->setData(model->index(i, 4), fld.requiredStatus() == -1 ? QVariant("?")
|
||||
: QVariant(bool(fld.requiredStatus())));
|
||||
model->setData(model->index(i, 5), fld.isAutoValue());
|
||||
model->setData(model->index(i, 6), fld.defaultValue());
|
||||
}
|
||||
|
||||
table->setModel(model);
|
||||
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
||||
updateActions();
|
||||
}
|
||||
|
||||
void Browser::insertRow()
|
||||
{
|
||||
QSqlTableModel *model = qobject_cast<QSqlTableModel *>(table->model());
|
||||
if (!model)
|
||||
return;
|
||||
|
||||
QModelIndex insertIndex = table->currentIndex();
|
||||
int row = insertIndex.row() == -1 ? 0 : insertIndex.row();
|
||||
model->insertRow(row);
|
||||
insertIndex = model->index(row, 0);
|
||||
table->setCurrentIndex(insertIndex);
|
||||
table->edit(insertIndex);
|
||||
}
|
||||
|
||||
void Browser::deleteRow()
|
||||
{
|
||||
QSqlTableModel *model = qobject_cast<QSqlTableModel *>(table->model());
|
||||
if (!model)
|
||||
return;
|
||||
|
||||
QModelIndexList currentSelection = table->selectionModel()->selectedIndexes();
|
||||
for (int i = 0; i < currentSelection.count(); ++i) {
|
||||
if (currentSelection.at(i).column() != 0)
|
||||
continue;
|
||||
model->removeRow(currentSelection.at(i).row());
|
||||
}
|
||||
|
||||
updateActions();
|
||||
}
|
||||
|
||||
void Browser::updateActions()
|
||||
{
|
||||
QSqlTableModel * tm = qobject_cast<QSqlTableModel *>(table->model());
|
||||
bool enableIns = tm;
|
||||
bool enableDel = enableIns && table->currentIndex().isValid();
|
||||
|
||||
insertRowAction->setEnabled(enableIns);
|
||||
deleteRowAction->setEnabled(enableDel);
|
||||
|
||||
fieldStrategyAction->setEnabled(tm);
|
||||
rowStrategyAction->setEnabled(tm);
|
||||
manualStrategyAction->setEnabled(tm);
|
||||
submitAction->setEnabled(tm);
|
||||
revertAction->setEnabled(tm);
|
||||
selectAction->setEnabled(tm);
|
||||
|
||||
if (tm) {
|
||||
QSqlTableModel::EditStrategy es = tm->editStrategy();
|
||||
fieldStrategyAction->setChecked(es == QSqlTableModel::OnFieldChange);
|
||||
rowStrategyAction->setChecked(es == QSqlTableModel::OnRowChange);
|
||||
manualStrategyAction->setChecked(es == QSqlTableModel::OnManualSubmit);
|
||||
}
|
||||
}
|
||||
|
||||
void Browser::about()
|
||||
{
|
||||
QMessageBox::about(this, tr("About"), tr("The SQL Browser demonstration "
|
||||
"shows how a data browser can be used to visualize the results of SQL"
|
||||
"statements on a live database"));
|
||||
}
|
||||
|
||||
void Browser::on_fieldStrategyAction_triggered()
|
||||
{
|
||||
QSqlTableModel * tm = qobject_cast<QSqlTableModel *>(table->model());
|
||||
if (tm)
|
||||
tm->setEditStrategy(QSqlTableModel::OnFieldChange);
|
||||
}
|
||||
|
||||
void Browser::on_rowStrategyAction_triggered()
|
||||
{
|
||||
QSqlTableModel * tm = qobject_cast<QSqlTableModel *>(table->model());
|
||||
if (tm)
|
||||
tm->setEditStrategy(QSqlTableModel::OnRowChange);
|
||||
}
|
||||
|
||||
void Browser::on_manualStrategyAction_triggered()
|
||||
{
|
||||
QSqlTableModel * tm = qobject_cast<QSqlTableModel *>(table->model());
|
||||
if (tm)
|
||||
tm->setEditStrategy(QSqlTableModel::OnManualSubmit);
|
||||
}
|
||||
|
||||
void Browser::on_submitAction_triggered()
|
||||
{
|
||||
QSqlTableModel * tm = qobject_cast<QSqlTableModel *>(table->model());
|
||||
if (tm)
|
||||
tm->submitAll();
|
||||
}
|
||||
|
||||
void Browser::on_revertAction_triggered()
|
||||
{
|
||||
QSqlTableModel * tm = qobject_cast<QSqlTableModel *>(table->model());
|
||||
if (tm)
|
||||
tm->revertAll();
|
||||
}
|
||||
|
||||
void Browser::on_selectAction_triggered()
|
||||
{
|
||||
QSqlTableModel * tm = qobject_cast<QSqlTableModel *>(table->model());
|
||||
if (tm)
|
||||
tm->select();
|
||||
}
|
||||
|
83
examples/sql/sqlbrowser/browser.h
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef BROWSER_H
|
||||
#define BROWSER_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QSqlTableModel>
|
||||
#include "ui_browserwidget.h"
|
||||
|
||||
class ConnectionWidget;
|
||||
QT_FORWARD_DECLARE_CLASS(QTableView)
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
QT_FORWARD_DECLARE_CLASS(QTextEdit)
|
||||
QT_FORWARD_DECLARE_CLASS(QSqlError)
|
||||
|
||||
class Browser: public QWidget, private Ui::Browser
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Browser(QWidget *parent = nullptr);
|
||||
virtual ~Browser();
|
||||
|
||||
QSqlError addConnection(const QString &driver, const QString &dbName, const QString &host,
|
||||
const QString &user, const QString &passwd, int port = -1);
|
||||
|
||||
void insertRow();
|
||||
void deleteRow();
|
||||
void updateActions();
|
||||
|
||||
public slots:
|
||||
void exec();
|
||||
void showTable(const QString &table);
|
||||
void showMetaData(const QString &table);
|
||||
void addConnection();
|
||||
void currentChanged() { updateActions(); }
|
||||
void about();
|
||||
|
||||
void on_insertRowAction_triggered()
|
||||
{ insertRow(); }
|
||||
void on_deleteRowAction_triggered()
|
||||
{ deleteRow(); }
|
||||
void on_fieldStrategyAction_triggered();
|
||||
void on_rowStrategyAction_triggered();
|
||||
void on_manualStrategyAction_triggered();
|
||||
void on_submitAction_triggered();
|
||||
void on_revertAction_triggered();
|
||||
void on_selectAction_triggered();
|
||||
void on_connectionWidget_tableActivated(const QString &table)
|
||||
{ showTable(table); }
|
||||
void on_connectionWidget_metaDataRequested(const QString &table)
|
||||
{ showMetaData(table); }
|
||||
void on_submitButton_clicked()
|
||||
{
|
||||
exec();
|
||||
sqlEdit->setFocus();
|
||||
}
|
||||
void on_clearButton_clicked()
|
||||
{
|
||||
sqlEdit->clear();
|
||||
sqlEdit->setFocus();
|
||||
}
|
||||
|
||||
signals:
|
||||
void statusMessage(const QString &message);
|
||||
};
|
||||
|
||||
class CustomModel: public QSqlTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CustomModel(QObject *parent = nullptr, QSqlDatabase db = QSqlDatabase())
|
||||
: QSqlTableModel(parent, db) {}
|
||||
|
||||
QVariant data(const QModelIndex &idx, int role) const override
|
||||
{
|
||||
if (role == Qt::BackgroundRole && isDirty(idx))
|
||||
return QBrush(QColor(Qt::yellow));
|
||||
return QSqlTableModel::data(idx, role);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
241
examples/sql/sqlbrowser/browserwidget.ui
Normal file
@ -0,0 +1,241 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Browser</class>
|
||||
<widget class="QWidget" name="Browser">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>765</width>
|
||||
<height>515</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Qt SQL Browser</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="ConnectionWidget" name="connectionWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Ignored" vsizetype="Expanding">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableView" name="table">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>2</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::ActionsContextMenu</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>180</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>SQL Query</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="sqlEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>18</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>120</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clearButton">
|
||||
<property name="text">
|
||||
<string>&Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="submitButton">
|
||||
<property name="text">
|
||||
<string>&Submit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<action name="insertRowAction">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Insert Row</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Inserts a new Row</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="deleteRowAction">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Delete Row</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Deletes the current Row</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="fieldStrategyAction">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Submit on &Field Change</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Commit on Field Change</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="rowStrategyAction">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Submit on &Row Change</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Commit on Row Change</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="manualStrategyAction">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Submit &Manually</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Commit Manually</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="submitAction">
|
||||
<property name="text">
|
||||
<string>&Submit All</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Submit Changes</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="revertAction">
|
||||
<property name="text">
|
||||
<string>&Revert All</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Revert</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="selectAction">
|
||||
<property name="text">
|
||||
<string>S&elect</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Refresh Data from Database</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ConnectionWidget</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>connectionwidget.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>sqlEdit</tabstop>
|
||||
<tabstop>clearButton</tabstop>
|
||||
<tabstop>submitButton</tabstop>
|
||||
<tabstop>connectionWidget</tabstop>
|
||||
<tabstop>table</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
126
examples/sql/sqlbrowser/connectionwidget.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "connectionwidget.h"
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
|
||||
ConnectionWidget::ConnectionWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
tree = new QTreeWidget(this);
|
||||
tree->setObjectName(QLatin1String("tree"));
|
||||
tree->setHeaderLabels(QStringList(tr("database")));
|
||||
tree->header()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
QAction *refreshAction = new QAction(tr("Refresh"), tree);
|
||||
metaDataAction = new QAction(tr("Show Schema"), tree);
|
||||
connect(refreshAction, &QAction::triggered, this, &ConnectionWidget::refresh);
|
||||
connect(metaDataAction, &QAction::triggered, this, &ConnectionWidget::showMetaData);
|
||||
tree->addAction(refreshAction);
|
||||
tree->addAction(metaDataAction);
|
||||
tree->setContextMenuPolicy(Qt::ActionsContextMenu);
|
||||
|
||||
layout->addWidget(tree);
|
||||
|
||||
QMetaObject::connectSlotsByName(this);
|
||||
}
|
||||
|
||||
ConnectionWidget::~ConnectionWidget()
|
||||
{
|
||||
}
|
||||
|
||||
static QString qDBCaption(const QSqlDatabase &db)
|
||||
{
|
||||
QString nm = db.driverName();
|
||||
nm.append(QLatin1Char(':'));
|
||||
if (!db.userName().isEmpty())
|
||||
nm.append(db.userName()).append(QLatin1Char('@'));
|
||||
nm.append(db.databaseName());
|
||||
return nm;
|
||||
}
|
||||
|
||||
void ConnectionWidget::refresh()
|
||||
{
|
||||
tree->clear();
|
||||
QStringList connectionNames = QSqlDatabase::connectionNames();
|
||||
|
||||
bool gotActiveDb = false;
|
||||
for (int i = 0; i < connectionNames.count(); ++i) {
|
||||
QTreeWidgetItem *root = new QTreeWidgetItem(tree);
|
||||
QSqlDatabase db = QSqlDatabase::database(connectionNames.at(i), false);
|
||||
root->setText(0, qDBCaption(db));
|
||||
if (connectionNames.at(i) == activeDb) {
|
||||
gotActiveDb = true;
|
||||
setActive(root);
|
||||
}
|
||||
if (db.isOpen()) {
|
||||
QStringList tables = db.tables();
|
||||
for (int t = 0; t < tables.count(); ++t) {
|
||||
QTreeWidgetItem *table = new QTreeWidgetItem(root);
|
||||
table->setText(0, tables.at(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!gotActiveDb) {
|
||||
activeDb = connectionNames.value(0);
|
||||
setActive(tree->topLevelItem(0));
|
||||
}
|
||||
|
||||
tree->doItemsLayout(); // HACK
|
||||
}
|
||||
|
||||
QSqlDatabase ConnectionWidget::currentDatabase() const
|
||||
{
|
||||
return QSqlDatabase::database(activeDb);
|
||||
}
|
||||
|
||||
static void qSetBold(QTreeWidgetItem *item, bool bold)
|
||||
{
|
||||
QFont font = item->font(0);
|
||||
font.setBold(bold);
|
||||
item->setFont(0, font);
|
||||
}
|
||||
|
||||
void ConnectionWidget::setActive(QTreeWidgetItem *item)
|
||||
{
|
||||
for (int i = 0; i < tree->topLevelItemCount(); ++i) {
|
||||
if (tree->topLevelItem(i)->font(0).bold())
|
||||
qSetBold(tree->topLevelItem(i), false);
|
||||
}
|
||||
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
qSetBold(item, true);
|
||||
activeDb = QSqlDatabase::connectionNames().value(tree->indexOfTopLevelItem(item));
|
||||
}
|
||||
|
||||
void ConnectionWidget::on_tree_itemActivated(QTreeWidgetItem *item, int /* column */)
|
||||
{
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
if (!item->parent()) {
|
||||
setActive(item);
|
||||
} else {
|
||||
setActive(item->parent());
|
||||
emit tableActivated(item->text(0));
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionWidget::showMetaData()
|
||||
{
|
||||
QTreeWidgetItem *cItem = tree->currentItem();
|
||||
if (!cItem || !cItem->parent())
|
||||
return;
|
||||
setActive(cItem->parent());
|
||||
emit metaDataRequested(cItem->text(0));
|
||||
}
|
||||
|
||||
void ConnectionWidget::on_tree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *)
|
||||
{
|
||||
metaDataAction->setEnabled(current && current->parent());
|
||||
}
|
||||
|
41
examples/sql/sqlbrowser/connectionwidget.h
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef CONNECTIONWIDGET_H
|
||||
#define CONNECTIONWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QTreeWidget)
|
||||
QT_FORWARD_DECLARE_CLASS(QTreeWidgetItem)
|
||||
QT_FORWARD_DECLARE_CLASS(QSqlDatabase)
|
||||
QT_FORWARD_DECLARE_CLASS(QMenu)
|
||||
|
||||
class ConnectionWidget: public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ConnectionWidget(QWidget *parent = nullptr);
|
||||
virtual ~ConnectionWidget();
|
||||
|
||||
QSqlDatabase currentDatabase() const;
|
||||
|
||||
signals:
|
||||
void tableActivated(const QString &table);
|
||||
void metaDataRequested(const QString &tableName);
|
||||
|
||||
public slots:
|
||||
void refresh();
|
||||
void showMetaData();
|
||||
void on_tree_itemActivated(QTreeWidgetItem *item, int column);
|
||||
void on_tree_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
|
||||
|
||||
private:
|
||||
void setActive(QTreeWidgetItem *);
|
||||
|
||||
QTreeWidget *tree;
|
||||
QAction *metaDataAction;
|
||||
QString activeDb;
|
||||
};
|
||||
|
||||
#endif
|
55
examples/sql/sqlbrowser/main.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "browser.h"
|
||||
|
||||
#include <QtCore>
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
|
||||
void addConnectionsFromCommandline(const QStringList &args, Browser *browser)
|
||||
{
|
||||
for (int i = 1; i < args.count(); ++i) {
|
||||
QUrl url(args.at(i), QUrl::TolerantMode);
|
||||
if (!url.isValid()) {
|
||||
qWarning("Invalid URL: %s", qPrintable(args.at(i)));
|
||||
continue;
|
||||
}
|
||||
QSqlError err = browser->addConnection(url.scheme(), url.path().mid(1), url.host(),
|
||||
url.userName(), url.password(), url.port(-1));
|
||||
if (err.type() != QSqlError::NoError)
|
||||
qDebug() << "Unable to open connection:" << err;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QMainWindow mainWin;
|
||||
mainWin.setWindowTitle(QObject::tr("Qt SQL Browser"));
|
||||
|
||||
Browser browser(&mainWin);
|
||||
mainWin.setCentralWidget(&browser);
|
||||
|
||||
QMenu *fileMenu = mainWin.menuBar()->addMenu(QObject::tr("&File"));
|
||||
fileMenu->addAction(QObject::tr("Add &Connection..."),
|
||||
[&]() { browser.addConnection(); });
|
||||
fileMenu->addSeparator();
|
||||
fileMenu->addAction(QObject::tr("&Quit"), []() { qApp->quit(); });
|
||||
|
||||
QMenu *helpMenu = mainWin.menuBar()->addMenu(QObject::tr("&Help"));
|
||||
helpMenu->addAction(QObject::tr("About"), [&]() { browser.about(); });
|
||||
helpMenu->addAction(QObject::tr("About Qt"), []() { qApp->aboutQt(); });
|
||||
|
||||
QObject::connect(&browser, &Browser::statusMessage, [&mainWin](const QString &text) {
|
||||
mainWin.statusBar()->showMessage(text);
|
||||
});
|
||||
|
||||
addConnectionsFromCommandline(app.arguments(), &browser);
|
||||
mainWin.show();
|
||||
if (QSqlDatabase::connectionNames().isEmpty())
|
||||
QMetaObject::invokeMethod(&browser, "addConnection", Qt::QueuedConnection);
|
||||
|
||||
return app.exec();
|
||||
}
|
70
examples/sql/sqlbrowser/qsqlconnectiondialog.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "qsqlconnectiondialog.h"
|
||||
#include "ui_qsqlconnectiondialog.h"
|
||||
|
||||
#include <QSqlDatabase>
|
||||
|
||||
QSqlConnectionDialog::QSqlConnectionDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
ui.setupUi(this);
|
||||
|
||||
QStringList drivers = QSqlDatabase::drivers();
|
||||
|
||||
if (!drivers.contains("QSQLITE"))
|
||||
ui.dbCheckBox->setEnabled(false);
|
||||
|
||||
ui.comboDriver->addItems(drivers);
|
||||
}
|
||||
|
||||
QSqlConnectionDialog::~QSqlConnectionDialog()
|
||||
{
|
||||
}
|
||||
|
||||
QString QSqlConnectionDialog::driverName() const
|
||||
{
|
||||
return ui.comboDriver->currentText();
|
||||
}
|
||||
|
||||
QString QSqlConnectionDialog::databaseName() const
|
||||
{
|
||||
return ui.editDatabase->text();
|
||||
}
|
||||
|
||||
QString QSqlConnectionDialog::userName() const
|
||||
{
|
||||
return ui.editUsername->text();
|
||||
}
|
||||
|
||||
QString QSqlConnectionDialog::password() const
|
||||
{
|
||||
return ui.editPassword->text();
|
||||
}
|
||||
|
||||
QString QSqlConnectionDialog::hostName() const
|
||||
{
|
||||
return ui.editHostname->text();
|
||||
}
|
||||
|
||||
int QSqlConnectionDialog::port() const
|
||||
{
|
||||
return ui.portSpinBox->value();
|
||||
}
|
||||
|
||||
bool QSqlConnectionDialog::useInMemoryDatabase() const
|
||||
{
|
||||
return ui.dbCheckBox->isChecked();
|
||||
}
|
||||
|
||||
void QSqlConnectionDialog::on_okButton_clicked()
|
||||
{
|
||||
if (ui.comboDriver->currentText().isEmpty()) {
|
||||
QMessageBox::information(this, tr("No database driver selected"),
|
||||
tr("Please select a database driver"));
|
||||
ui.comboDriver->setFocus();
|
||||
} else {
|
||||
accept();
|
||||
}
|
||||
}
|
36
examples/sql/sqlbrowser/qsqlconnectiondialog.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef QSQLCONNECTIONDIALOG_H
|
||||
#define QSQLCONNECTIONDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "ui_qsqlconnectiondialog.h"
|
||||
|
||||
class QSqlConnectionDialog: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QSqlConnectionDialog(QWidget *parent = nullptr);
|
||||
~QSqlConnectionDialog();
|
||||
|
||||
QString driverName() const;
|
||||
QString databaseName() const;
|
||||
QString userName() const;
|
||||
QString password() const;
|
||||
QString hostName() const;
|
||||
int port() const;
|
||||
bool useInMemoryDatabase() const;
|
||||
|
||||
private slots:
|
||||
void on_okButton_clicked();
|
||||
void on_cancelButton_clicked() { reject(); }
|
||||
void on_dbCheckBox_clicked() { ui.connGroupBox->setEnabled(!ui.dbCheckBox->isChecked()); }
|
||||
|
||||
private:
|
||||
Ui::QSqlConnectionDialogUi ui;
|
||||
};
|
||||
|
||||
#endif
|
224
examples/sql/sqlbrowser/qsqlconnectiondialog.ui
Normal file
@ -0,0 +1,224 @@
|
||||
<ui version="4.0" >
|
||||
<author></author>
|
||||
<comment></comment>
|
||||
<exportmacro></exportmacro>
|
||||
<class>QSqlConnectionDialogUi</class>
|
||||
<widget class="QDialog" name="QSqlConnectionDialogUi" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>315</width>
|
||||
<height>302</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Connect...</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="connGroupBox" >
|
||||
<property name="title" >
|
||||
<string>Connection settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<property name="margin" >
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="1" >
|
||||
<widget class="QComboBox" name="comboDriver" />
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="textLabel4" >
|
||||
<property name="text" >
|
||||
<string>&Username:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>editUsername</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="textLabel2" >
|
||||
<property name="text" >
|
||||
<string>D&river</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>comboDriver</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QLineEdit" name="editDatabase" />
|
||||
</item>
|
||||
<item row="5" column="1" >
|
||||
<widget class="QSpinBox" name="portSpinBox" >
|
||||
<property name="specialValueText" >
|
||||
<string>Default</string>
|
||||
</property>
|
||||
<property name="maximum" >
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="minimum" >
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="value" >
|
||||
<number>-1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="textLabel3" >
|
||||
<property name="text" >
|
||||
<string>Database Name:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>editDatabase</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" >
|
||||
<widget class="QLineEdit" name="editPassword" >
|
||||
<property name="echoMode" >
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QLineEdit" name="editUsername" />
|
||||
</item>
|
||||
<item row="4" column="1" >
|
||||
<widget class="QLineEdit" name="editHostname" />
|
||||
</item>
|
||||
<item row="4" column="0" >
|
||||
<widget class="QLabel" name="textLabel5" >
|
||||
<property name="text" >
|
||||
<string>&Hostname:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>editHostname</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" >
|
||||
<widget class="QLabel" name="textLabel5_2" >
|
||||
<property name="text" >
|
||||
<string>P&ort:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>portSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" >
|
||||
<widget class="QLabel" name="textLabel4_2" >
|
||||
<property name="text" >
|
||||
<string>&Password:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>editPassword</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="dbCheckBox" >
|
||||
<property name="text" >
|
||||
<string>Us&e predefined in-memory database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType" >
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" >
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="okButton" >
|
||||
<property name="text" >
|
||||
<string>&OK</string>
|
||||
</property>
|
||||
<property name="default" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancelButton" >
|
||||
<property name="text" >
|
||||
<string>&Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<pixmapfunction></pixmapfunction>
|
||||
<tabstops>
|
||||
<tabstop>comboDriver</tabstop>
|
||||
<tabstop>editDatabase</tabstop>
|
||||
<tabstop>editUsername</tabstop>
|
||||
<tabstop>editPassword</tabstop>
|
||||
<tabstop>editHostname</tabstop>
|
||||
<tabstop>portSpinBox</tabstop>
|
||||
<tabstop>dbCheckBox</tabstop>
|
||||
<tabstop>okButton</tabstop>
|
||||
<tabstop>cancelButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
18
examples/sql/sqlbrowser/sqlbrowser.pro
Normal file
@ -0,0 +1,18 @@
|
||||
TEMPLATE = app
|
||||
TARGET = sqlbrowser
|
||||
|
||||
QT += sql widgets
|
||||
requires(qtConfig(tableview))
|
||||
|
||||
HEADERS = browser.h connectionwidget.h qsqlconnectiondialog.h
|
||||
SOURCES = main.cpp browser.cpp connectionwidget.cpp qsqlconnectiondialog.cpp
|
||||
|
||||
FORMS = browserwidget.ui qsqlconnectiondialog.ui
|
||||
build_all:!build_pass {
|
||||
CONFIG -= build_all
|
||||
CONFIG += release
|
||||
}
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/sql/sqlbrowser
|
||||
INSTALLS += target
|
38
examples/sql/sqlwidgetmapper/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(sqlwidgetmapper LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/sql/sqlwidgetmapper")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Sql Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(sqlwidgetmapper
|
||||
main.cpp
|
||||
window.cpp window.h
|
||||
)
|
||||
|
||||
set_target_properties(sqlwidgetmapper PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(sqlwidgetmapper PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Sql
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS sqlwidgetmapper
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
14
examples/sql/sqlwidgetmapper/main.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
#include "window.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
Window window;
|
||||
window.show();
|
||||
return app.exec();
|
||||
}
|
11
examples/sql/sqlwidgetmapper/sqlwidgetmapper.pro
Normal file
@ -0,0 +1,11 @@
|
||||
HEADERS = window.h
|
||||
SOURCES = main.cpp \
|
||||
window.cpp
|
||||
QT += sql widgets
|
||||
requires(qtConfig(combobox))
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/sql/sqlwidgetmapper
|
||||
INSTALLS += target
|
||||
|
||||
|
121
examples/sql/sqlwidgetmapper/window.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QtSql>
|
||||
|
||||
#include "window.h"
|
||||
|
||||
//! [Set up widgets]
|
||||
Window::Window(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setupModel();
|
||||
|
||||
nameLabel = new QLabel(tr("Na&me:"));
|
||||
nameEdit = new QLineEdit();
|
||||
addressLabel = new QLabel(tr("&Address:"));
|
||||
addressEdit = new QTextEdit();
|
||||
typeLabel = new QLabel(tr("&Type:"));
|
||||
typeComboBox = new QComboBox();
|
||||
nextButton = new QPushButton(tr("&Next"));
|
||||
previousButton = new QPushButton(tr("&Previous"));
|
||||
|
||||
nameLabel->setBuddy(nameEdit);
|
||||
addressLabel->setBuddy(addressEdit);
|
||||
typeLabel->setBuddy(typeComboBox);
|
||||
//! [Set up widgets]
|
||||
|
||||
//! [Set up the mapper]
|
||||
QSqlTableModel *relModel = model->relationModel(typeIndex);
|
||||
typeComboBox->setModel(relModel);
|
||||
typeComboBox->setModelColumn(relModel->fieldIndex("description"));
|
||||
|
||||
mapper = new QDataWidgetMapper(this);
|
||||
mapper->setModel(model);
|
||||
mapper->setItemDelegate(new QSqlRelationalDelegate(this));
|
||||
mapper->addMapping(nameEdit, model->fieldIndex("name"));
|
||||
mapper->addMapping(addressEdit, model->fieldIndex("address"));
|
||||
mapper->addMapping(typeComboBox, typeIndex);
|
||||
//! [Set up the mapper]
|
||||
|
||||
//! [Set up connections and layouts]
|
||||
connect(previousButton, &QPushButton::clicked,
|
||||
mapper, &QDataWidgetMapper::toPrevious);
|
||||
connect(nextButton, &QPushButton::clicked,
|
||||
mapper, &QDataWidgetMapper::toNext);
|
||||
connect(mapper, &QDataWidgetMapper::currentIndexChanged,
|
||||
this, &Window::updateButtons);
|
||||
|
||||
QGridLayout *layout = new QGridLayout();
|
||||
layout->addWidget(nameLabel, 0, 0, 1, 1);
|
||||
layout->addWidget(nameEdit, 0, 1, 1, 1);
|
||||
layout->addWidget(previousButton, 0, 2, 1, 1);
|
||||
layout->addWidget(addressLabel, 1, 0, 1, 1);
|
||||
layout->addWidget(addressEdit, 1, 1, 2, 1);
|
||||
layout->addWidget(nextButton, 1, 2, 1, 1);
|
||||
layout->addWidget(typeLabel, 3, 0, 1, 1);
|
||||
layout->addWidget(typeComboBox, 3, 1, 1, 1);
|
||||
setLayout(layout);
|
||||
|
||||
setWindowTitle(tr("SQL Widget Mapper"));
|
||||
mapper->toFirst();
|
||||
}
|
||||
//! [Set up connections and layouts]
|
||||
|
||||
//! [Set up the main table]
|
||||
void Window::setupModel()
|
||||
{
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
|
||||
db.setDatabaseName(":memory:");
|
||||
if (!db.open()) {
|
||||
QMessageBox::critical(0, tr("Cannot open database"),
|
||||
tr("Unable to establish a database connection.\n"
|
||||
"This example needs SQLite support. Please read "
|
||||
"the Qt SQL driver documentation for information how "
|
||||
"to build it."), QMessageBox::Cancel);
|
||||
return;
|
||||
}
|
||||
|
||||
QSqlQuery query;
|
||||
query.exec("create table person (id int primary key, "
|
||||
"name varchar(20), address varchar(200), typeid int)");
|
||||
query.exec("insert into person values(1, 'Alice', "
|
||||
"'<qt>123 Main Street<br/>Market Town</qt>', 101)");
|
||||
query.exec("insert into person values(2, 'Bob', "
|
||||
"'<qt>PO Box 32<br/>Mail Handling Service"
|
||||
"<br/>Service City</qt>', 102)");
|
||||
query.exec("insert into person values(3, 'Carol', "
|
||||
"'<qt>The Lighthouse<br/>Remote Island</qt>', 103)");
|
||||
query.exec("insert into person values(4, 'Donald', "
|
||||
"'<qt>47338 Park Avenue<br/>Big City</qt>', 101)");
|
||||
query.exec("insert into person values(5, 'Emma', "
|
||||
"'<qt>Research Station<br/>Base Camp<br/>"
|
||||
"Big Mountain</qt>', 103)");
|
||||
//! [Set up the main table]
|
||||
|
||||
//! [Set up the address type table]
|
||||
query.exec("create table addresstype (id int, description varchar(20))");
|
||||
query.exec("insert into addresstype values(101, 'Home')");
|
||||
query.exec("insert into addresstype values(102, 'Work')");
|
||||
query.exec("insert into addresstype values(103, 'Other')");
|
||||
|
||||
model = new QSqlRelationalTableModel(this);
|
||||
model->setTable("person");
|
||||
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
|
||||
|
||||
typeIndex = model->fieldIndex("typeid");
|
||||
|
||||
model->setRelation(typeIndex,
|
||||
QSqlRelation("addresstype", "id", "description"));
|
||||
model->select();
|
||||
}
|
||||
//! [Set up the address type table]
|
||||
|
||||
//! [Slot for updating the buttons]
|
||||
void Window::updateButtons(int row)
|
||||
{
|
||||
previousButton->setEnabled(row > 0);
|
||||
nextButton->setEnabled(row < model->rowCount() - 1);
|
||||
}
|
||||
//! [Slot for updating the buttons]
|
52
examples/sql/sqlwidgetmapper/window.h
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef WINDOW_H
|
||||
#define WINDOW_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QComboBox;
|
||||
class QDataWidgetMapper;
|
||||
class QItemSelectionModel;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QSqlRelationalTableModel;
|
||||
class QStandardItemModel;
|
||||
class QStringListModel;
|
||||
class QTextEdit;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
//! [Window definition]
|
||||
class Window : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Window(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void updateButtons(int row);
|
||||
|
||||
private:
|
||||
void setupModel();
|
||||
|
||||
QLabel *nameLabel;
|
||||
QLabel *addressLabel;
|
||||
QLabel *typeLabel;
|
||||
QLineEdit *nameEdit;
|
||||
QTextEdit *addressEdit;
|
||||
QComboBox *typeComboBox;
|
||||
QPushButton *nextButton;
|
||||
QPushButton *previousButton;
|
||||
|
||||
QSqlRelationalTableModel *model;
|
||||
QItemSelectionModel *selectionModel;
|
||||
QDataWidgetMapper *mapper;
|
||||
int typeIndex;
|
||||
};
|
||||
//! [Window definition]
|
||||
|
||||
#endif
|
38
examples/sql/tablemodel/CMakeLists.txt
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(tablemodel LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/sql/tablemodel")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Sql Widgets)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(tablemodel
|
||||
../connection.h
|
||||
tablemodel.cpp
|
||||
)
|
||||
|
||||
set_target_properties(tablemodel PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(tablemodel PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Sql
|
||||
Qt6::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS tablemodel
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
48
examples/sql/tablemodel/tablemodel.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
#include "../connection.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QSqlTableModel>
|
||||
#include <QTableView>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void initializeModel(QSqlTableModel *model)
|
||||
{
|
||||
model->setTable("person");
|
||||
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
|
||||
model->select();
|
||||
|
||||
model->setHeaderData(0, Qt::Horizontal, QObject::tr("ID"));
|
||||
model->setHeaderData(1, Qt::Horizontal, QObject::tr("First name"));
|
||||
model->setHeaderData(2, Qt::Horizontal, QObject::tr("Last name"));
|
||||
}
|
||||
|
||||
QTableView *createView(QSqlTableModel *model, const QString &title = "")
|
||||
{
|
||||
QTableView *view = new QTableView;
|
||||
view->setModel(model);
|
||||
view->setWindowTitle(title);
|
||||
return view;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
if (!createConnection())
|
||||
return EXIT_FAILURE;
|
||||
|
||||
QSqlTableModel model;
|
||||
|
||||
initializeModel(&model);
|
||||
|
||||
QTableView *view1 = createView(&model, QObject::tr("Table Model (View 1)"));
|
||||
QTableView *view2 = createView(&model, QObject::tr("Table Model (View 2)"));
|
||||
|
||||
view1->show();
|
||||
view2->move(view1->x() + view1->width() + 20, view1->y());
|
||||
view2->show();
|
||||
|
||||
return app.exec();
|
||||
}
|
9
examples/sql/tablemodel/tablemodel.pro
Normal file
@ -0,0 +1,9 @@
|
||||
HEADERS = ../connection.h
|
||||
SOURCES = tablemodel.cpp
|
||||
QT += sql widgets
|
||||
requires(qtConfig(tableview))
|
||||
|
||||
# install
|
||||
target.path = $$[QT_INSTALL_EXAMPLES]/sql/tablemodel
|
||||
INSTALLS += target
|
||||
|