BibleTime
btbookshelfworkspage.cpp
Go to the documentation of this file.
1/*********
2*
3* In the name of the Father, and of the Son, and of the Holy Spirit.
4*
5* This file is part of BibleTime's source code, https://bibletime.info/
6*
7* Copyright 1999-2026 by the BibleTime developers.
8* The BibleTime source code is licensed under the GNU General Public License
9* version 2.0.
10*
11**********/
12
14
15#include <algorithm>
16#include <QAbstractItemView>
17#include <QApplication>
18#include <QByteArray>
19#include <QComboBox>
20#include <QDir>
21#include <QFileInfo>
22#include <QHBoxLayout>
23#include <QHeaderView>
24#include <QLabel>
25#include <QLineEdit>
26#include <QMenu>
27#include <QNonConstOverload>
28#include <QSet>
29#include <QSizePolicy>
30#include <QStringList>
31#include <QStringLiteral>
32#include <Qt>
33#include <QToolButton>
34#include <QVBoxLayout>
35#include <QWizard>
36#include <utility>
37#include "../../backend/btinstallbackend.h"
38#include "../../backend/bookshelfmodel/btbookshelfmodel.h"
39#include "../../backend/bookshelfmodel/btbookshelffiltermodel.h"
40#include "../../backend/bookshelfmodel/btbookshelftreemodel.h"
41#include "../../backend/config/btconfig.h"
42#include "../../backend/drivers/btmoduleset.h"
43#include "../../backend/drivers/cswordmoduleinfo.h"
44#include "../../backend/managers/cswordbackend.h"
45#include "../../util/btassert.h"
46#include "../../util/btconnect.h"
47#include "../btbookshelfgroupingmenu.h"
48#include "../btbookshelfview.h"
50#include "btbookshelfwizard.h"
51#include "btinstallpagemodel.h"
52
53// Sword includes:
54#ifdef __GNUC__
55#pragma GCC diagnostic push
56#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
57#endif
58#include <installmgr.h>
59#include <swbuf.h>
60#include <swversion.h>
61#ifdef __GNUC__
62#pragma GCC diagnostic pop
63#endif
64
65
66namespace {
67QString const groupingOrderKey("GUI/BookshelfWizard/InstallPage/grouping");
68QString const installPathKey(
69 "GUI/BookshelfWizard/InstallPage/installPathIndex");
70
71inline bool filter(WizardTaskType const taskType,
72 QStringList const & languages,
73 CSwordModuleInfo const * const mInfo)
74{
75 if (taskType == WizardTaskType::installWorks) {
77 && languages.contains(mInfo->language()->translatedName());
78 } else if (taskType == WizardTaskType::updateWorks) {
79 using CSMI = CSwordModuleInfo;
80 using CSV = sword::SWVersion const;
81 CSMI const * const installedModule =
83 return installedModule
84 && (CSV(installedModule->config(CSMI::ModuleVersion).toLatin1())
85 < CSV(mInfo->config(CSMI::ModuleVersion).toLatin1()));
86 } else {
89 }
90}
91
92} // anonymous namespace
93
95 QWidget * parent)
96 : BtBookshelfWizardPage(parent)
97 , m_taskType(iType)
98{
99 if (!m_groupingOrder.loadFrom(btConfig(), groupingOrderKey))
101
102 // Initialize menus:
105 m_contextMenu = new QMenu(this);
108
109 // Setup UI:
110 QVBoxLayout * const verticalLayout = new QVBoxLayout(this);
111
112 QHBoxLayout * horizontalLayout = new QHBoxLayout;
113 m_nameFilterLabel = new QLabel(this);
114 horizontalLayout->addWidget(m_nameFilterLabel);
115 m_nameFilterEdit = new QLineEdit(this);
117 horizontalLayout->addWidget(m_nameFilterEdit);
118 verticalLayout->addLayout(horizontalLayout);
119
121 m_bookshelfView->setHeaderHidden(false);
122 m_bookshelfView->header()->setSectionResizeMode(
123 QHeaderView::ResizeToContents);
124 verticalLayout->addWidget(m_bookshelfView);
125
126 m_msgLabel = new QLabel(this);
127 m_msgLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
128 m_msgLabel->setWordWrap(true);
129 verticalLayout->addWidget(m_msgLabel);
130
131 QHBoxLayout * const pathLayout = new QHBoxLayout();
133 m_pathCombo = new QComboBox(this);
134 m_pathCombo->setMinimumContentsLength(20);
135 m_pathCombo->setSizeAdjustPolicy(
136 QComboBox::AdjustToMinimumContentsLengthWithIcon);
137 m_pathCombo->setSizePolicy(QSizePolicy::Expanding,
138 QSizePolicy::Minimum);
139 m_pathCombo->view()->setTextElideMode(Qt::ElideMiddle);
140
141 m_pathLabel = new QLabel(this);
142 m_pathLabel->setBuddy(m_pathCombo);
144
145 pathLayout->setContentsMargins(0, 8, 0, 0);
146 pathLayout->addWidget(m_pathLabel);
147 pathLayout->addWidget(m_pathCombo);
148 }
149 pathLayout->addStretch();
150
151 m_groupingLabel = new QLabel(this);
152 pathLayout->addWidget(m_groupingLabel);
153
154 m_groupingButton = new QToolButton(this);
155 m_groupingButton->setPopupMode(QToolButton::InstantPopup);
157 m_groupingButton->setIcon(m_groupingMenu->icon());
158 m_groupingButton->setAutoRaise(true);
159 pathLayout->addWidget(m_groupingButton);
160
161 verticalLayout->addLayout(pathLayout);
162
163 // Setup models:
164 BtBookshelfFilterModel * const filterModel =
165 new BtBookshelfFilterModel(this);
166 m_bookshelfView->setModel(filterModel);
167
171 filterModel->setSourceModel(m_installPageModel);
172
173 /// \todo is this useless if m_taskType == WizardTaskType::removeWorks?
177 } else {
179 }
181 m_bookshelfView->setColumnHidden(1, true);
182
183 // Initialize connections:
186 this,
189 BT_CONNECT(m_pathCombo, qOverload<int>(&QComboBox::currentIndexChanged),
194 }
196 this, &BtBookshelfWorksPage::completeChanged);
199 BT_CONNECT(m_nameFilterEdit, &QLineEdit::textEdited,
201
203}
204
206 m_nameFilterLabel->setText(tr("Fi&lter:"));
207 if (m_taskType == installWorks) {
208 setTitle(QApplication::translate("BookshelfWizard", "Install Works"));
209 setSubTitle(QApplication::translate("BookshelfWizard",
210 "Choose one or more works to "
211 "install."));
212 setButtonText(QWizard::NextButton,tr("Install Works >"));
214 setTitle(QApplication::translate("BookshelfWizard", "Update Works"));
215 setSubTitle(QApplication::translate("BookshelfWizard",
216 "Choose one or more works to "
217 "update."));
218 setButtonText(QWizard::NextButton,tr("Update Works >"));
219 } else {
221 setTitle(QApplication::translate("BookshelfWizard", "Remove Works"));
222 setSubTitle(QApplication::translate("BookshelfWizard",
223 "Choose one or more works to "
224 "remove."));
225 setButtonText(QWizard::NextButton,tr("Remove Works >"));
226 }
227
229 m_pathLabel->setText(tr("Install &folder:"));
230 m_pathCombo->setToolTip(tr("The folder where the new works will be "
231 "installed"));
232 }
233
235 m_msgLabel->setText(tr("There are no works to update."));
237 m_msgLabel->setText(tr("No works are currently installed so they "
238 "cannot be removed."));
239 } else {
241 m_msgLabel->setText(
242 tr("No works can be installed with the current selection of "
243 "remote libraries and languages. Please go back and make a "
244 "different selection."));
245 }
246
247 m_groupingLabel->setText(tr("Grouping:"));
248 m_groupingButton->setText(tr("Grouping"));
249 m_groupingButton->setToolTip(
250 tr("Change the grouping of items in the bookshelf."));
251}
252
258
260 // Update models:
261 QStringList sources;
262 QStringList languages;
263 if (m_taskType == installWorks) {
264 sources = btWizard().selectedSources();
265 languages = btWizard().selectedLanguages();
266 } else {
268 }
269
270 {
271 QSet<QString> addedModuleNames;
272 m_bookshelfModel->clear();
273 m_usedBackends.clear();
274 for (auto const & sourceName : sources) {
275 sword::InstallSource const source =
276 BtInstallBackend::source(sourceName);
277 std::unique_ptr<CSwordBackend const> backend(
279 bool backendUsed = false;
280 for (auto * const module : backend->moduleList()) {
281 if (filter(m_taskType, languages, module)) {
282 QString const & moduleName = module->name();
283 if (addedModuleNames.contains(moduleName))
284 continue;
285 addedModuleNames.insert(moduleName);
286 m_bookshelfModel->addModule(module);
287 module->setProperty("installSourceName",
288 QString(source.caption.c_str()));
289 backendUsed = true;
290 }
291 }
292 if (backendUsed)
293 m_usedBackends.emplace_back(std::move(backend));
294 }
296 m_bookshelfView->expandAll();
297
298 bool const noWorks = (addedModuleNames.count() == 0);
299 m_msgLabel->setVisible(noWorks);
300 m_bookshelfView->setVisible(!noWorks);
301 m_groupingButton->setVisible(!noWorks);
302 m_groupingLabel->setVisible(!noWorks);
303 }
304
305 // Set grouping:
306 auto const & cat = BtBookshelfTreeModel::Grouping::CAT;
307 auto const & catLang = BtBookshelfTreeModel::Grouping::CAT_LANG;
308 if (languages.count() == 1) {
310 } else if (languages.count() > 1 && m_groupingOrder == cat) {
312 }
313}
314
316 BtBookshelfTreeModel::Grouping const & grouping)
317{
319 m_bookshelfView->setRootIsDecorated(!grouping.list().isEmpty());
320}
321
323 auto const numModules = checkedModules().size();
324 return (numModules > 0)
326}
327
334
337
339{ return m_pathCombo->currentText(); }
340
342{ btConfig().setValue(installPathKey, index); }
343
344static bool installPathIsUsable(QString const & path) {
345 if (path.isEmpty())
346 return false;
347 QDir const dir(path);
348 if (!dir.exists() || !dir.isReadable())
349 return false;
350 return QFileInfo(dir.canonicalPath()).isWritable();
351}
352
354 m_pathCombo->clear();
355 bool haveUsableTargets = false;
356 for (auto const & target : CSwordBackend::instance().swordDirList()) {
357 if (installPathIsUsable(target)) {
358 m_pathCombo->addItem(QDir::toNativeSeparators(target));
359 haveUsableTargets = true;
360 }
361 }
362
363 m_pathCombo->setVisible(haveUsableTargets);
364 m_pathLabel->setVisible(haveUsableTargets);
365 if (haveUsableTargets) {
366 /* Choose the current value from config but check whether we have so
367 many items: */
368 int const cfgValue = btConfig().value<int>(installPathKey, 0);
369 int const lastIdx = m_pathCombo->count() - 1;
370 m_pathCombo->setCurrentIndex(cfgValue > lastIdx ? lastIdx : cfgValue);
371 }
372}
#define BT_ASSERT(...)
Definition btassert.h:17
@ removeFinalPage
@ installFinalPage
static bool installPathIsUsable(QString const &path)
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition btconfig.h:305
#define BT_CONNECT(...)
Definition btconnect.h:20
void setNameFilterFixedString(QString const &nameFilter)
void setGrouping(BtBookshelfTreeModel::Grouping const &grouping)
void signalGroupingOrderChanged(const BtBookshelfTreeModel::Grouping &)
static constexpr int const MAX_MODULES
static std::shared_ptr< BtBookshelfModel > newInstance()
auto const & list() const noexcept
void saveTo(BtConfigCore &config, QString const &key) const
bool loadFrom(BtConfigCore const &config, QString const &key)
void groupingOrderChanged(BtBookshelfTreeModel::Grouping newGrouping)
void setDefaultChecked(CheckedBehavior const b)
BtModuleSet const & checkedModules() const
void setGroupingOrder(BtBookshelfTreeModel::Grouping const &groupingOrder, bool emitSignal=true)
void setSourceModel(std::shared_ptr< QAbstractItemModel > sourceModel)
void moduleChecked(CSwordModuleInfo *module, bool checked)
BtBookshelfWizard & btWizard() const noexcept
QStringList selectedSources() const
QStringList selectedLanguages() const
bool isComplete() const final override
void initializePage() final override
std::shared_ptr< BtBookshelfModel > m_bookshelfModel
std::vector< std::unique_ptr< CSwordBackend const > > m_usedBackends
void slotGroupingActionTriggered(BtBookshelfTreeModel::Grouping const &grouping)
BtModuleSet const & checkedModules() const
int nextId() const final override
void slotGroupingOrderChanged(BtBookshelfTreeModel::Grouping const &g)
WizardTaskType const m_taskType
BtBookshelfTreeModel::Grouping m_groupingOrder
void slotPathChanged(int const index)
BtBookshelfGroupingMenu * m_groupingMenu
BtBookshelfWorksPage(WizardTaskType iType, QWidget *parent=nullptr)
BtBookshelfView * m_bookshelfView
BtInstallPageModel * m_installPageModel
T value(QString const &key, T const &defaultValue=T()) const
Returns the settings value for the given global key.
void setValue(QString const &key, T const &value)
Sets a value for a key.
QStringList swordDirList() const
Sword prefix list.
CSwordModuleInfo * findModuleByName(const QString &name) const
Searches for a module with the given name.
void sigSwordSetupChanged()
static CSwordBackend & instance() noexcept
QString config(const CSwordModuleInfo::ConfigEntry entry) const
QString const & name() const
std::shared_ptr< Language const > language() const
sword::InstallSource source(const QString &name)
QStringList sourceNameList()
std::unique_ptr< CSwordBackend > backend(sword::InstallSource const &is)
QString const groupingOrderKey("GUI/BookshelfWizard/InstallPage/grouping")
QString const installPathKey("GUI/BookshelfWizard/InstallPage/installPathIndex")
bool filter(WizardTaskType const taskType, QStringList const &languages, CSwordModuleInfo const *const mInfo)