From 217893db033788ed0770927141e27990762fefb4 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Sat, 14 Dec 2019 12:57:55 -0600 Subject: Qt UI: Implement custom package selection --- ui/qt5/CMakeLists.txt | 2 + ui/qt5/horizon.qrc | 1 + ui/qt5/horizonwizard.cc | 2 + ui/qt5/pkgcustom.cc | 58 +++++++++ ui/qt5/pkgcustom.hh | 29 +++++ ui/qt5/pkgmodel.cc | 296 ++++++++++++++++++++++++++++++++++++++++++ ui/qt5/pkgmodel.hh | 76 +++++++++++ ui/qt5/resources/packages.txt | 62 +++++++++ 8 files changed, 526 insertions(+) create mode 100644 ui/qt5/pkgcustom.cc create mode 100644 ui/qt5/pkgcustom.hh create mode 100644 ui/qt5/pkgmodel.cc create mode 100644 ui/qt5/pkgmodel.hh create mode 100644 ui/qt5/resources/packages.txt diff --git a/ui/qt5/CMakeLists.txt b/ui/qt5/CMakeLists.txt index 23f269b..b286915 100644 --- a/ui/qt5/CMakeLists.txt +++ b/ui/qt5/CMakeLists.txt @@ -19,6 +19,8 @@ set(UI_SOURCES datetimepage.cc hostnamepage.cc pkgsimple.cc + pkgmodel.cc + pkgcustom.cc pkgdefaults.cc bootpage.cc rootpwpage.cc diff --git a/ui/qt5/horizon.qrc b/ui/qt5/horizon.qrc index 58272c1..709cdc2 100644 --- a/ui/qt5/horizon.qrc +++ b/ui/qt5/horizon.qrc @@ -33,5 +33,6 @@ ../../assets/horizon-256.png + resources/packages.txt diff --git a/ui/qt5/horizonwizard.cc b/ui/qt5/horizonwizard.cc index a71b824..ccf6b8f 100644 --- a/ui/qt5/horizonwizard.cc +++ b/ui/qt5/horizonwizard.cc @@ -48,6 +48,7 @@ extern "C" { #include "datetimepage.hh" #include "hostnamepage.hh" #include "pkgsimple.hh" +#include "pkgcustom.hh" #include "pkgdefaults.hh" #include "bootpage.hh" #include "rootpwpage.hh" @@ -207,6 +208,7 @@ HorizonWizard::HorizonWizard(QWidget *parent) : QWizard(parent) { setPage(Page_DateTime, new DateTimePage); setPage(Page_Hostname, new HostnamePage); setPage(Page_PkgSimple, new PkgSimplePage); + setPage(Page_PkgCustom, new PkgCustomPage); setPage(Page_PkgCustomDefault, new PkgDefaultsPage); setPage(Page_Boot, new BootPage); setPage(Page_Root, new RootPassphrasePage); diff --git a/ui/qt5/pkgcustom.cc b/ui/qt5/pkgcustom.cc new file mode 100644 index 0000000..33a56b3 --- /dev/null +++ b/ui/qt5/pkgcustom.cc @@ -0,0 +1,58 @@ +/* + * pkgcustom.cc - Implementation of the UI.Packages.Custom page + * horizon-qt5, the Qt 5 user interface for + * Project Horizon + * + * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. + * This code is licensed under the AGPL 3.0 license, as noted in the + * LICENSE-code file in the root directory of this repository. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#include "pkgcustom.hh" + +#include +#include +#include + +PkgCustomPage::PkgCustomPage(QWidget *parent) : HorizonWizardPage(parent) { + setTitle(tr("Software Selection")); + loadWatermark("software"); + + QLabel *descLabel = new QLabel(tr( + "Choose the software you want to install on this computer.\n\n" + "You may select any category or software package to read a brief description of it.")); + descLabel->setWordWrap(true); + + QTreeView *pkgTree = new QTreeView; + model = new PkgItemModel(nullptr, this); + pkgTree->setHeaderHidden(true); + pkgTree->setModel(model); + pkgTree->setUniformRowHeights(true); + + QItemSelectionModel *selModel = pkgTree->selectionModel(); + connect(selModel, &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t){ + QVariant data = model->data(current, Qt::StatusTipRole); + if(data.isValid()) { + pkgDescLabel->setText(data.toString()); + } + }); + + pkgDescLabel = new QLabel(tr("Select an item to view its description.\n\n ")); + pkgDescLabel->setWordWrap(true); + QGroupBox *descBox = new QGroupBox("Description"); + QVBoxLayout *descLayout = new QVBoxLayout; + descLayout->addWidget(pkgDescLabel); + descBox->setLayout(descLayout); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(descLabel); + layout->addWidget(pkgTree); + layout->addWidget(descBox); + setLayout(layout); +} + +void PkgCustomPage::initializePage() { + model->setPackageList(&(horizonWizard()->packages)); +} diff --git a/ui/qt5/pkgcustom.hh b/ui/qt5/pkgcustom.hh new file mode 100644 index 0000000..27d2cd2 --- /dev/null +++ b/ui/qt5/pkgcustom.hh @@ -0,0 +1,29 @@ +/* + * pkgcustom.hh - Definition of the UI.Packages.Custom page + * horizon-qt5, the Qt 5 user interface for + * Project Horizon + * + * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. + * This code is licensed under the AGPL 3.0 license, as noted in the + * LICENSE-code file in the root directory of this repository. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#ifndef PKGCUSTOM_HH +#define PKGCUSTOM_HH + +#include "horizonwizardpage.hh" +#include "pkgmodel.hh" +#include + +class PkgCustomPage : public HorizonWizardPage { +public: + PkgCustomPage(QWidget *parent = nullptr); + void initializePage(); +private: + QLabel *pkgDescLabel; + PkgItemModel *model; +}; + +#endif /* !PKGCUSTOM_HH */ diff --git a/ui/qt5/pkgmodel.cc b/ui/qt5/pkgmodel.cc new file mode 100644 index 0000000..4cd9170 --- /dev/null +++ b/ui/qt5/pkgmodel.cc @@ -0,0 +1,296 @@ +/* + * pkgmodel.cc - Implementation of the custom package selection model classes + * horizon-qt5, the Qt 5 user interface for + * Project Horizon + * + * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. + * This code is licensed under the AGPL 3.0 license, as noted in the + * LICENSE-code file in the root directory of this repository. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#include "pkgmodel.hh" +#include +#include +#include + +PkgItem::PkgItem(PkgItem *parent, QString friendly, QString internal, + QString description, QString icon) : _parent(parent), + _friendly(friendly), + _internal(internal), + _desc(description) { + if(icon.isEmpty()) { + _icon = internal; + } else { + _icon = icon; + } +} + +PkgItem::~PkgItem() { + qDeleteAll(_children); +} + +void PkgItem::addChild(PkgItem *pkg) { + _children.append(pkg); +} + +PkgItem *PkgItem::child(int row) { + if(row < 0 || row > _children.size()) { + return nullptr; + } + + return _children.at(row); +} + +int PkgItem::childCount() const { + return _children.size(); +} + +QVariant PkgItem::data(int column, int role) { + if(column != 0) { + return QVariant(); + } + + switch(role) { + case Qt::DisplayRole: + return QVariant(_friendly); + case Qt::DecorationRole: + return QVariant(QIcon::fromTheme(_icon)); + case Qt::StatusTipRole: + case Qt::WhatsThisRole: + return QVariant(_desc); + case Qt::UserRole: + return QVariant(_internal); + } + + return QVariant(); +} + +QStringList PkgItem::internalNames() const { + QStringList list; + + if(!_internal.isEmpty()) { + list.append(_internal); + } + + for(auto &child : _children) { + list.append(child->internalNames()); + } + + return list; +} + +PkgItem *PkgItem::parent() { + return _parent; +} + +int PkgItem::row() const { + if(_parent == nullptr) { + return 0; + } + + return _parent->_children.indexOf(const_cast(this)); +} + + +PkgItemModel::PkgItemModel(QStringList *pkgList, QObject *parent) + : QAbstractItemModel(parent), _packages(pkgList) { + _root = new PkgItem(nullptr, "Root Item", "root", "The root of the tree. Never seen."); + + loadPackages(); +} + +PkgItemModel::~PkgItemModel() { + delete _root; +} + +int PkgItemModel::columnCount(const QModelIndex &) const { + return 1; +} + +QVariant PkgItemModel::data(const QModelIndex &index, int role) const { + if(!index.isValid()) { + return QVariant(); + } + + PkgItem *item = static_cast(index.internalPointer()); + if(role == Qt::CheckStateRole && index.column() == 0) { + QStringList pkgs = item->internalNames(); + if(_packages && !pkgs.isEmpty()) { + bool all = true, any = false; + for(auto &pkg : pkgs) { + if(_packages->contains(pkg)) { + any = true; + } else { + all = false; + } + } + + if(all) return Qt::Checked; + if(any) return Qt::PartiallyChecked; + } + return Qt::Unchecked; + } + + return item->data(index.column(), role); +} + +Qt::ItemFlags PkgItemModel::flags(const QModelIndex &index) const { + if(!index.isValid()) { + return Qt::NoItemFlags; + } + + Qt::ItemFlags flags = Qt::ItemIsEnabled; + if(index.column() == 0) { + flags |= Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsAutoTristate; + } + + return flags; +} + +QVariant PkgItemModel::headerData(int, Qt::Orientation, int) const { + return QVariant(); +} + +QModelIndex PkgItemModel::index(int row, int column, + const QModelIndex &parent) const { + if(!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + PkgItem *p; + + if(parent.isValid()) { + p = static_cast(parent.internalPointer()); + } else { + p = _root; + } + + PkgItem *item = p->child(row); + if(item) { + return createIndex(row, column, item); + } else { + return QModelIndex(); + } +} + +QModelIndex PkgItemModel::parent(const QModelIndex &child) const { + if(!child.isValid()) { + return QModelIndex(); + } + + PkgItem *item = static_cast(child.internalPointer()); + PkgItem *parent = item->parent(); + + if(parent == _root) { + return QModelIndex(); + } else { + return createIndex(parent->row(), 0, parent); + } +} + +int PkgItemModel::rowCount(const QModelIndex &parent) const { + if(parent.column() > 0) { + return 0; + } + + if(parent.isValid()) { + PkgItem *item = static_cast(parent.internalPointer()); + return item->childCount(); + } else { + return _root->childCount(); + } +} + +bool PkgItemModel::setData(const QModelIndex &index, const QVariant &value, + int role) { + if(!index.isValid() || role != Qt::CheckStateRole) { + return false; + } + + PkgItem *item = static_cast(index.internalPointer()); + if(item) { + QStringList pkgs = item->internalNames(); + if(value.toBool()) { + _packages->append(pkgs); + } else { + for(auto &pkg : pkgs) { + _packages->removeAll(pkg); + } + } + emit dataChanged(index, index, {Qt::CheckStateRole}); + if(item->childCount() > 0) { + emit dataChanged(createIndex(0, 0, item), + createIndex(item->childCount(), 0, item), + {Qt::CheckStateRole}); + } + + PkgItem *parent = item; + while(parent->parent() != _root) { + parent = parent->parent(); + QModelIndex pidx = createIndex(parent->row(), 0, parent); + emit dataChanged(pidx, pidx, {Qt::CheckStateRole}); + } + } + + return true; +} + +void PkgItemModel::setPackageList(QStringList *pkgList) { + _packages = pkgList; +} + +void PkgItemModel::loadPackages() { + QFile myList(":/packages.txt"); + if(!myList.open(QFile::ReadOnly)) { + QMessageBox::critical(nullptr, tr("Could Not Load Software List"), + tr("An issue occurred loading the available software list.")); + return; + } + + QByteArray raw_pkgs = myList.readAll(); + QList pkgs = raw_pkgs.split('\n'); + int indent, lastIndent = 0; + PkgItem *temp = new PkgItem(_root, "Placeholder", "Placeholder", "Used for loading only"); + PkgItem *last = temp; + + for(auto &pkg : pkgs) { + indent = 0; + + if(pkg.isEmpty()) continue; + + while(pkg.size() > 1 && pkg.at(0) == ' ') { + indent++; + pkg.remove(0, 1); + } + + /* minimum valid size: A\tb\tC */ + if(pkg.size() < 6) continue; + + PkgItem *myParent; + + if(indent == lastIndent) { + /* We're siblings of the last item. */ + myParent = last->parent(); + } else if(indent < lastIndent) { + /* Traverse up until we're equal */ + while(lastIndent-- > indent) { + last = last->parent(); + } + myParent = last->parent(); + } else /*(indent > lastIndent)*/ { + /* We're children of the last item. */ + myParent = last; + } + + QList fields = pkg.split('\t'); + /* at least friendly, internal, desc required */ + if(fields.size() < 3) continue; + last = new PkgItem(myParent, fields[0], fields[1], fields[2], + (fields.size() == 4 ? fields[3] : fields[1])); + myParent->addChild(last); + lastIndent = indent; + } + delete temp; +} diff --git a/ui/qt5/pkgmodel.hh b/ui/qt5/pkgmodel.hh new file mode 100644 index 0000000..85070ee --- /dev/null +++ b/ui/qt5/pkgmodel.hh @@ -0,0 +1,76 @@ +/* + * pkgmodel.hh - Definition of the custom package selection model classes + * horizon-qt5, the Qt 5 user interface for + * Project Horizon + * + * Copyright (c) 2019 Adélie Linux and contributors. All rights reserved. + * This code is licensed under the AGPL 3.0 license, as noted in the + * LICENSE-code file in the root directory of this repository. + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +#ifndef PKGMODEL_HH +#define PKGMODEL_HH + +#include + +class PkgItem { +public: + explicit PkgItem(PkgItem *parent, QString friendly, QString internal, + QString description, QString icon = ""); + ~PkgItem(); + + void addChild(PkgItem *pkg); + PkgItem *child(int row); + int childCount() const; + QVariant data(int column, int role); + QStringList internalNames() const; + int row() const; + PkgItem *parent(); + +private: + QVector _children; + PkgItem *_parent; + /*! The "friendly" or displayed name for this item. */ + QString _friendly; + /*! The internal name. Typically the APK package name. */ + QString _internal; + /*! The description of the item. */ + QString _desc; + /*! The icon to display for this item. Defaults to internal. */ + QString _icon; +}; + +class PkgItemModel : public QAbstractItemModel { +Q_OBJECT +public: + explicit PkgItemModel(QStringList *pkgList = nullptr, + QObject *parent = nullptr); + ~PkgItemModel(); + + QVariant data(const QModelIndex &index, + int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override; + void setPackageList(QStringList *pkgList); + +private: + void loadPackages(); + /*! The list of packages that will be installed. + * Determines which packages are ticked. */ + QStringList *_packages; + /*! The special root item, which all other items have as an eventual + * parent. */ + PkgItem *_root; +}; + +#endif /* !PKGMODEL_HH */ diff --git a/ui/qt5/resources/packages.txt b/ui/qt5/resources/packages.txt new file mode 100644 index 0000000..ae88ab1 --- /dev/null +++ b/ui/qt5/resources/packages.txt @@ -0,0 +1,62 @@ +Desktop Environments X11-based desktop environments. preferences-theme + KDE Plasma 5 plasma-desktop The KDE Plasma desktop environment. kde-frameworks + LXQt lxqt-desktop Lightweight desktop environment using the Openbox window manager and Qt widgets. qt + MATE mate-complete The MATE desktop environment, featuring a classic desktop experience with Gtk+ 2. mate-logo + XFCE xfce-desktop The XFCE desktop environment. xfce4-logo +Documentation doc Install documentation for the software you select. application-x-fictionbook +Games Video games for your Adélie Linux computer. applications-games + Mednafen mednaffe An emulator for playing games from many types of 8-bit and 16-bit home consoles. + OpenTTD openttd Transportation simulation game. + TORCS torcs The Open Racing Car Simulator, a racing simulation. + Trigger trigger-rally A rally racing game. applications-games + X-Moto xmoto A motorcross game +Internet Software Software to explore the Internet. applications-internet + Firefox Web browser firefox-esr Popular, powerful Web browser. Includes JavaScript and multimedia (audio/video) playback support. + Netsurf Web browser netsurf Lightweight Web browser. Does not include JavaScript support. + Otter Browser otter-browser WebKit-based Web browser with JavaScript support. + Thunderbird Email thunderbird Read and compose email, and participate in newsgroups. +KDE Applications kde A variety of cohesive applications including a word processor, media player, and many games. kde-logo + Default Theme adelie-kde-theme The Adélie KDE theme, including wallpapers and colour themes. kcontrol + Educational Software kde-education Computer-assisted learning software. applications-education-mathematics + Games kde-games A variety of arcade games, card games, strategy games, and more. applications-games + Graphics kde-graphics Photo viewing and editing software, including digital cameras and photo albums. applications-graphics + Multimedia kde-multimedia Audio/video playback, recording, and editing. applications-multimedia + System Utilities kde-system Utilities for managing and maintaining your computer. applications-system + User Manager user-manager The KDE user account manager, for creating and modifying user accounts. preferences-system-users + Utilities kde-utilities Miscellaneous accessories. applications-utilities +Multimedia Software Software to play audio and video. applications-multimedia + MPV mpv Command-line based multimedia player. + VLC Media Player vlc Multimedia player with support for a wide variety of audio and video types. +Office and Productivity Software Software to take care of business. applications-office + AbiWord abiword Lightweight word processor. + Calligra calligra KDE office suite featuring a word processor, spreadsheet, project planning, and presentation software. calligrawords + FeatherPad featherpad Lightweight text editor. kwrite + Gnumeric gnumeric Lightweight spreadsheet. + LibreOffice libreoffice Extensible office suite featuring a word processor, spreadsheet, database, and presentation software. +Programming Environments Interpreters, language tools, and development environments. applications-interfacedesign + Development Tools build-tools Tools used to create, build, and test software. applications-development + Ada gcc-gnat The Ada programming language. applications-interfacedesign + COBOL gnucobol The COBOL programming language. applications-interfacedesign + Go gcc-go The Go programming language. text-x-go + Java openjdk8-jdk The Java 8 programming environment. java + Node.js node The Node JavaScript programming environment. javascript + Perl perl The Perl programming language. text-x-perl + PHP 7 php7 The PHP 7 programming language. application-x-php + Python python3 The Python programming language. (3.6) python + Ruby ruby The Ruby programming language. text-x-ruby + Rust rust The Rust programming language. text-x-rust +Server Software Software for sharing computer resources with others over a network. network-server + Apache Web server apache-httpd The world's most popular Web server. yast-network_services + MariaDB mariadb A popular relational database server. network-server + NFS Server nfs-utils Unix file sharing system. yast-samba-server + OpenSSH openssh Remote shell and file transferring system. text-x-script + PostgreSQL postgresql Reliable, professional relational database server. openoffice4-base + Postfix postfix Server for transmitting email. internet-mail + Qmail netqmail Server for transmitting email. internet-mail +Window Managers Individual window managers that are not part of a desktop environment. window_list + Awesome awesome The Awesome window manager. window_list + Fluxbox fluxbox The Fluxbox window manager. window_list + i3 i3wm The i3 window manager. window_list + iceWM icewm The iceWM window manager. ice + Openbox openbox The Openbox window manager. openbox + spectrwm spectrwm The spectrwm window manager. window_list -- cgit v1.2.3-70-g09d2