15.1.331. tablet_qt/menu/settingsmenu.cpp¶
/*
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CamCOPS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/
#define OFFER_VIEW_SQL // debugging only
#include "settingsmenu.h"
#include <QDebug>
#include <QFileDialog>
#include <QTextStream>
#include "common/platform.h"
#include "common/uiconst.h"
#include "common/varconst.h"
#include "core/networkmanager.h"
#include "db/databasemanager.h"
#include "db/dbnestabletransaction.h"
#include "db/dumpsql.h"
#include "db/fieldref.h"
#include "dbobjects/extrastring.h"
#include "dbobjects/idnumdescription.h"
#include "dialogs/logmessagebox.h"
#include "lib/convert.h"
#include "lib/stringfunc.h"
#include "lib/uifunc.h"
#include "menu/testmenu.h"
#include "menulib/fontsizeanddpiwindow.h"
#include "menulib/menuitem.h"
#include "menulib/serversettingswindow.h"
#include "questionnairelib/commonoptions.h"
#include "questionnairelib/qugridcontainer.h"
#include "questionnairelib/questionnaire.h"
#include "questionnairelib/questionnairefunc.h"
#include "questionnairelib/qugridcell.h"
#include "questionnairelib/quhorizontalline.h"
#include "questionnairelib/qulineedit.h"
#include "questionnairelib/qumcq.h"
#include "questionnairelib/qupage.h"
#include "questionnairelib/qutext.h"
#include "lib/slowguiguard.h"
// For IP settings:
const QString TAG_IP_CLINICAL_WARNING("clinical");
SettingsMenu::SettingsMenu(CamcopsApp& app) :
MenuWindow(app, uifunc::iconFilename(uiconst::ICON_SETTINGS)),
m_plaintext_pw_live(false),
m_ip_questionnaire(nullptr)
{
m_ip_clinical_fr = m_app.storedVarFieldRef(
varconst::IP_USE_CLINICAL, false);
}
QString SettingsMenu::title() const
{
return tr("Settings");
}
void SettingsMenu::makeItems()
{
const QString PRIVPREFIX("(†) ");
const QString spanner(uifunc::iconFilename(uiconst::CBS_SPANNER));
// Safe object lifespan signal: can use std::bind
m_items = {
// --------------------------------------------------------------------
MenuItem(tr("Common user settings")).setLabelOnly(),
// --------------------------------------------------------------------
MenuItem(
tr("Choose language"),
std::bind(&SettingsMenu::chooseLanguage, this),
uifunc::iconFilename(uiconst::CBS_LANGUAGE)
),
MenuItem(
tr("Questionnaire font size and DPI settings"),
MenuItem::OpenableWidgetMaker(
std::bind(&SettingsMenu::setQuestionnaireFontSize, this,
std::placeholders::_1)
)
),
MenuItem(
tr("User settings"),
MenuItem::OpenableWidgetMaker(
std::bind(&SettingsMenu::configureUser, this,
std::placeholders::_1)
)
).setNotIfLocked(),
MenuItem(
tr("Intellectual property (IP) permissions"),
MenuItem::OpenableWidgetMaker(
std::bind(&SettingsMenu::configureIntellectualProperty, this,
std::placeholders::_1)
)
).setNotIfLocked(),
MenuItem(
tr("Change app password"),
std::bind(&SettingsMenu::changeAppPassword, this)
).setNotIfLocked(),
MenuItem(tr("Information")).setLabelOnly(),
MenuItem(
tr("Show server information"),
MenuItem::OpenableWidgetMaker(
std::bind(&SettingsMenu::viewServerInformation, this,
std::placeholders::_1)
)
).setNotIfLocked(),
// --------------------------------------------------------------------
MenuItem(tr("Infrequent user functions")).setLabelOnly(),
// --------------------------------------------------------------------
MenuItem(
tr("Change operating mode"),
std::bind(&SettingsMenu::changeMode, this)
).setNotIfLocked(),
MenuItem(
tr("Fetch all server info"),
std::bind(&SettingsMenu::fetchAllServerInfo, this)
).setNotIfLocked(),
#if 0
MenuItem(
tr("Re-accept ID descriptions from the server"),
std::bind(&SettingsMenu::fetchIdDescriptions, this)
).setNotIfLocked(),
MenuItem(
tr("Re-fetch extra task strings from the server"),
std::bind(&SettingsMenu::fetchExtraStrings, this)
).setNotIfLocked(),
#endif
// --------------------------------------------------------------------
MenuItem(tr("Administrator functions")).setLabelOnly(),
// --------------------------------------------------------------------
MenuItem(
tr("Set privileged mode (for items marked †)"),
std::bind(&SettingsMenu::setPrivilege, this)
).setNotIfLocked(),
// PRIVILEGED FUNCTIONS BELOW HERE
MenuItem(
PRIVPREFIX + tr("Configure server settings"),
MenuItem::OpenableWidgetMaker(
std::bind(&SettingsMenu::configureServer, this,
std::placeholders::_1)
)
).setNeedsPrivilege(),
MenuItem(
PRIVPREFIX + tr("Register this device with the server"),
std::bind(&SettingsMenu::registerWithServer, this)
).setNeedsPrivilege(),
MenuItem(
PRIVPREFIX + tr("Change privileged-mode password"),
std::bind(&SettingsMenu::changePrivPassword, this)
).setNeedsPrivilege(),
// --------------------------------------------------------------------
MenuItem(tr("Rare functions")).setLabelOnly(),
// --------------------------------------------------------------------
MAKE_MENU_MENU_ITEM(TestMenu, m_app),
MenuItem(
PRIVPREFIX + tr("Wipe extra strings downloaded from server"),
[this](){ deleteAllExtraStrings(); } // alternative lambda syntax
).setNeedsPrivilege(),
MenuItem(
PRIVPREFIX + tr("View record counts for all data tables"),
std::bind(&SettingsMenu::viewDataCounts, this),
spanner
).setNeedsPrivilege(),
MenuItem(
PRIVPREFIX + tr("View record counts for all system tables"),
std::bind(&SettingsMenu::viewSystemCounts, this),
spanner
).setNeedsPrivilege(),
// --------------------------------------------------------------------
MenuItem(tr("Rescue operations")).setLabelOnly(),
// --------------------------------------------------------------------
MenuItem(
tr("Drop unknown tables"),
std::bind(&SettingsMenu::dropUnknownTables, this),
spanner
).setNotIfLocked(),
#ifdef OFFER_VIEW_SQL
MenuItem(
PRIVPREFIX + tr("View data database as SQL"),
std::bind(&SettingsMenu::viewDataDbAsSql, this),
spanner
).setNeedsPrivilege(),
MenuItem(
PRIVPREFIX + tr("View system database as SQL"),
std::bind(&SettingsMenu::viewSystemDbAsSql, this),
spanner
).setNeedsPrivilege(),
#endif
MenuItem(
PRIVPREFIX + tr("Send decrypted data database to debugging stream"),
std::bind(&SettingsMenu::debugDataDbAsSql, this),
spanner
).setNeedsPrivilege(),
MenuItem(
PRIVPREFIX + tr("Send decrypted system database to debugging stream"),
std::bind(&SettingsMenu::debugSystemDbAsSql, this),
spanner
).setNeedsPrivilege(),
};
if (!platform::PLATFORM_IOS) {
// These options are not supported under iOS.
m_items.append({
MenuItem(
PRIVPREFIX + tr("Dump decrypted data database to SQL file"),
std::bind(&SettingsMenu::saveDataDbAsSql, this),
spanner
).setNeedsPrivilege().setUnsupported(platform::PLATFORM_IOS),
MenuItem(
PRIVPREFIX + tr("Dump decrypted system database to SQL file"),
std::bind(&SettingsMenu::saveSystemDbAsSql, this),
spanner
).setNeedsPrivilege().setUnsupported(platform::PLATFORM_IOS),
});
}
connect(&m_app, &CamcopsApp::fontSizeChanged,
this, &SettingsMenu::reloadStyleSheet);
}
OpenableWidget* SettingsMenu::configureServer(CamcopsApp& app)
{
auto window = new ServerSettingsWindow(app);
return window->editor();
}
OpenableWidget* SettingsMenu::configureIntellectualProperty(CamcopsApp& app)
{
app.clearCachedVars(); // ... in case any are left over
const QString label_ip_reason = tr(
"The settings here influence whether CamCOPS will consider some "
"third-party tasks “permitted” on your behalf, according to their "
"published use criteria. They do <b>not</b> remove your "
"responsibility to ensure that you use them in accordance with their "
"own requirements.");
const QString label_ip_warning = tr(
"WARNING. Providing incorrect information here may lead to you "
"VIOLATING copyright law, by using a task for a purpose that is not "
"permitted, and being subject to damages and/or prosecution.");
const QString label_ip_disclaimer = tr(
"The authors of CamCOPS cannot be held responsible or liable for any "
"consequences of you misusing materials subject to copyright."
);
const QString label_ip_preamble = tr("Are you using this application for:");
FieldRefPtr commercial_fr = app.storedVarFieldRef(varconst::IP_USE_COMMERCIAL);
FieldRefPtr educational_fr = app.storedVarFieldRef(varconst::IP_USE_EDUCATIONAL);
FieldRefPtr research_fr = app.storedVarFieldRef(varconst::IP_USE_RESEARCH);
connect(m_ip_clinical_fr.data(), &FieldRef::valueChanged,
this, &SettingsMenu::ipClinicalChanged,
Qt::UniqueConnection);
// I tried putting these in a grid, but when you have just QuMCQ/horizontal
// on the right, it expands too much vertically. Layout problem,
// likely to do with FlowLayout (which is Qt code).
// Probably fixed now; anyway, this is fine.
QuPagePtr page(new QuPage{
new QuText(label_ip_reason),
(new QuText(label_ip_warning))->setBold(true),
(new QuText(label_ip_disclaimer))->setItalic(true),
new QuText(label_ip_preamble),
(new QuText(tr("Clinical use?")))->setBold(true),
(new QuMcq(m_ip_clinical_fr,
CommonOptions::unknownNoYesInteger()))->setHorizontal(true),
(new QuText(tr("WARNING: NOT FOR GENERAL CLINICAL USE; not a Medical Device; "
"see Terms and Conditions")))
->setWarning(true)
->addTag(TAG_IP_CLINICAL_WARNING),
(new QuText(tr("Commercial use?")))->setBold(true),
(new QuMcq(commercial_fr,
CommonOptions::unknownNoYesInteger()))->setHorizontal(true),
(new QuText(tr("Educational use?")))->setBold(true),
(new QuMcq(educational_fr,
CommonOptions::unknownNoYesInteger()))->setHorizontal(true),
(new QuText(tr("Research use?")))->setBold(true),
(new QuMcq(research_fr,
CommonOptions::unknownNoYesInteger()))->setHorizontal(true),
});
page->setTitle(tr("Intellectual property (IP) permissions"));
page->setType(QuPage::PageType::Config);
m_ip_questionnaire = new Questionnaire(m_app, {page});
m_ip_questionnaire->setFinishButtonIconToTick();
connect(m_ip_questionnaire, &Questionnaire::completed,
this, &SettingsMenu::ipSaved);
connect(m_ip_questionnaire, &Questionnaire::cancelled,
this, &SettingsMenu::ipCancelled);
ipClinicalChanged(); // sets warning visibility
return m_ip_questionnaire;
}
void SettingsMenu::ipClinicalChanged()
{
if (!m_ip_questionnaire || !m_ip_clinical_fr) {
return;
}
const bool show = m_ip_clinical_fr->value() != CommonOptions::NO_INT;
m_ip_questionnaire->setVisibleByTag(TAG_IP_CLINICAL_WARNING, show);
}
void SettingsMenu::ipSaved()
{
m_app.saveCachedVars();
m_ip_questionnaire = nullptr;
}
void SettingsMenu::ipCancelled()
{
m_app.clearCachedVars();
m_ip_questionnaire = nullptr;
}
OpenableWidget* SettingsMenu::configureUser(CamcopsApp& app)
{
app.clearCachedVars(); // ... in case any are left over
const bool storing_password = app.storingServerPassword();
const QString label_server = tr("Interactions with the server");
FieldRefPtr devicename_fr = app.storedVarFieldRef(varconst::DEVICE_FRIENDLY_NAME);
const QString devicename_t = tr("Device friendly name");
const QString devicename_h = tr("e.g. “Research tablet 17 (Bob’s)”");
FieldRefPtr username_fr = app.storedVarFieldRef(varconst::SERVER_USERNAME);
const QString username_t = tr("Username on server");
// Safe object lifespan signal: can use std::bind
FieldRef::GetterFunction getter = std::bind(
&SettingsMenu::serverPasswordGetter, this);
FieldRef::SetterFunction setter = std::bind(
&SettingsMenu::serverPasswordSetter, this, std::placeholders::_1);
FieldRefPtr password_fr = FieldRefPtr(new FieldRef(getter, setter, true));
const QString password_t = tr("Password on server");
FieldRefPtr upload_after_edit_fr = app.storedVarFieldRef(varconst::OFFER_UPLOAD_AFTER_EDIT);
const QString upload_after_edit_t = tr("Offer to upload every time a task is edited?");
const QString label_clinician = tr("Default clinician/researcher’s details (to save you typing)");
FieldRefPtr clin_specialty_fr = app.storedVarFieldRef(
varconst::DEFAULT_CLINICIAN_SPECIALTY, false);
const QString clin_specialty_t = tr("Default clinician/researcher’s specialty");
const QString clin_specialty_h = tr("e.g. “Liaison Psychiatry”");
FieldRefPtr clin_name_fr = app.storedVarFieldRef(
varconst::DEFAULT_CLINICIAN_NAME, false);
const QString clin_name_t = tr("Default clinician/researcher’s name");
const QString clin_name_h = tr("e.g. “Dr Bob Smith”");
FieldRefPtr clin_profreg_fr = app.storedVarFieldRef(
varconst::DEFAULT_CLINICIAN_PROFESSIONAL_REGISTRATION, false);
const QString clin_profreg_t = tr("Default clinician/researcher’s professional registration");
const QString clin_profreg_h = tr("e.g. “GMC# 12345”");
FieldRefPtr clin_post_fr = app.storedVarFieldRef(
varconst::DEFAULT_CLINICIAN_POST, false);
const QString clin_post_t = tr("Default clinician/researcher’s post");
const QString clin_post_h = tr("e.g. “Specialist registrar”");
FieldRefPtr clin_service_fr = app.storedVarFieldRef(
varconst::DEFAULT_CLINICIAN_SERVICE, false);
const QString clin_service_t = tr("Default clinician/researcher’s service");
const QString clin_service_h = tr("e.g. “Liaison Psychiatry Service”");
FieldRefPtr clin_contact_fr = app.storedVarFieldRef(
varconst::DEFAULT_CLINICIAN_CONTACT_DETAILS, false);
const QString clin_contact_t = tr("Default clinician/researcher’s contact details");
const QString clin_contact_h = tr("e.g. “x2167”");
auto g = new QuGridContainer();
g->setColumnStretch(0, 1);
g->setColumnStretch(1, 1);
int row = 0;
const Qt::Alignment labelalign = Qt::AlignRight | Qt::AlignTop;
g->addCell(QuGridCell(
(new QuText(stringfunc::makeTitle(devicename_t, devicename_h, true)))
->setTextAlignment(labelalign),
row, 0));
g->addCell(QuGridCell(
(new QuLineEdit(devicename_fr))
->setHint(stringfunc::makeHint(devicename_t, devicename_h)),
row, 1));
++row;
g->addCell(QuGridCell(
(new QuText(stringfunc::makeTitle(username_t, "", true)))
->setTextAlignment(labelalign),
row, 0));
g->addCell(QuGridCell(
(new QuLineEdit(username_fr))
->setHint(username_t)
->setWidgetInputMethodHints(
Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText
),
row, 1));
++row;
if (storing_password) {
g->addCell(QuGridCell(
(new QuText(stringfunc::makeTitle(password_t, "", true)))
->setTextAlignment(labelalign),
row, 0));
g->addCell(QuGridCell(
(new QuLineEdit(password_fr))
->setHint(password_t)
->setEchoMode(QLineEdit::Password),
row, 1));
++row;
}
g->addCell(QuGridCell(
(new QuText(stringfunc::makeTitle(upload_after_edit_t)))
->setTextAlignment(labelalign),
row, 0));
g->addCell(QuGridCell(
(new QuMcq(upload_after_edit_fr,CommonOptions::yesNoBoolean()))
->setHorizontal(true),
row, 1));
++row;
QuPagePtr page(new QuPage{
(new QuText(label_server))->setItalic(true),
g,
new QuHorizontalLine(),
(new QuText(label_clinician))->setItalic(true),
questionnairefunc::defaultGridRawPointer({
{
stringfunc::makeTitle(clin_specialty_t, clin_specialty_h, true),
(new QuLineEdit(clin_specialty_fr))->setHint(
stringfunc::makeHint(clin_specialty_t, clin_specialty_h))
},
{
stringfunc::makeTitle(clin_name_t, clin_name_h, true),
(new QuLineEdit(clin_name_fr))->setHint(
stringfunc::makeHint(clin_name_t, clin_name_h))
},
{
stringfunc::makeTitle(clin_profreg_t, clin_profreg_h, true),
(new QuLineEdit(clin_profreg_fr))->setHint(
stringfunc::makeHint(clin_profreg_t, clin_profreg_h))
},
{
stringfunc::makeTitle(clin_post_t, clin_post_h, true),
(new QuLineEdit(clin_post_fr))->setHint(
stringfunc::makeHint(clin_post_t, clin_post_h))
},
{
stringfunc::makeTitle(clin_service_t, clin_service_h, true),
(new QuLineEdit(clin_service_fr))->setHint(
stringfunc::makeHint(clin_service_t, clin_service_h))
},
{
stringfunc::makeTitle(clin_contact_t, clin_contact_h, true),
(new QuLineEdit(clin_contact_fr))->setHint(
stringfunc::makeHint(clin_contact_t, clin_contact_h))
},
}, 1, 1),
});
page->setTitle(tr("User settings"));
page->setType(QuPage::PageType::Config);
auto questionnaire = new Questionnaire(m_app, {page});
questionnaire->setFinishButtonIconToTick();
connect(questionnaire, &Questionnaire::completed,
this, &SettingsMenu::userSettingsSaved);
connect(questionnaire, &Questionnaire::cancelled,
this, &SettingsMenu::userSettingsCancelled);
return questionnaire;
}
OpenableWidget* SettingsMenu::setQuestionnaireFontSize(CamcopsApp& app)
{
auto window = new FontSizeAndDpiWindow(app);
return window->editor();
}
void SettingsMenu::setPrivilege()
{
m_app.grantPrivilege();
}
void SettingsMenu::changeAppPassword()
{
m_app.changeAppPassword();
}
void SettingsMenu::changePrivPassword()
{
m_app.changePrivPassword();
}
QVariant SettingsMenu::serverPasswordGetter()
{
if (!m_plaintext_pw_live) {
m_temp_plaintext_password = m_app.getPlaintextServerPassword();
m_plaintext_pw_live = true;
}
return QVariant(m_temp_plaintext_password);
}
bool SettingsMenu::serverPasswordSetter(const QVariant& value)
{
const SecureQString value_str = value.toString();
const bool changed = value_str != m_temp_plaintext_password;
m_temp_plaintext_password = value_str;
m_plaintext_pw_live = true;
return changed;
}
void SettingsMenu::userSettingsSaved()
{
DbNestableTransaction trans(m_app.sysdb());
m_app.saveCachedVars();
if (m_app.storingServerPassword()) {
m_app.setEncryptedServerPassword(m_temp_plaintext_password);
} else {
m_app.setVar(varconst::SERVER_USERPASSWORD_OBSCURED, "");
}
m_temp_plaintext_password = "";
m_plaintext_pw_live = false;
}
void SettingsMenu::userSettingsCancelled()
{
m_temp_plaintext_password = "";
m_plaintext_pw_live = false;
m_app.clearCachedVars();
}
void SettingsMenu::deleteAllExtraStrings()
{
if (uifunc::confirm(tr("<b>Are you sure you want to delete all extra "
"strings?</b><br>"
"(To get them back, re-download them "
"from your server.)"),
tr("Wipe all extra strings?"),
tr("Yes, delete them"),
tr("No! Leave them alone"),
this)) {
m_app.deleteAllExtraStrings();
}
}
void SettingsMenu::registerWithServer()
{
NetworkManager* netmgr = m_app.networkManager();
netmgr->registerWithServer();
}
void SettingsMenu::fetchAllServerInfo()
{
NetworkManager* netmgr = m_app.networkManager();
netmgr->fetchAllServerInfo();
}
#ifdef SETTINGSMENU_OFFER_SPECIFIC_FETCHES
void SettingsMenu::fetchIdDescriptions()
{
NetworkManager* netmgr = m_app.networkManager();
netmgr->fetchIdDescriptions();
}
void SettingsMenu::fetchExtraStrings()
{
NetworkManager* netmgr = m_app.networkManager();
netmgr->fetchExtraStrings();
}
#endif
OpenableWidget* SettingsMenu::viewServerInformation(CamcopsApp& app)
{
const QString label_server_address = tr("Server hostname/IP address:");
const QString label_server_port = tr("Port for HTTPS:");
const QString label_server_path = tr("Path on server:");
const QString label_server_timeout = tr("Network timeout (ms):");
const QString label_last_server_registration = tr(
"Last server registration/ID info acceptance:");
const QString label_last_successful_upload = tr("Last successful upload:");
const QString label_dbtitle = tr("Database title (from the server):");
const QString label_policy_upload = tr("Server’s upload ID policy:");
const QString label_policy_finalize = tr("Server’s finalizing ID policy:");
const QString label_server_camcops_version = tr("Server CamCOPS version:");
const QString data_server_address = convert::prettyValue(
app.var(varconst::SERVER_ADDRESS));
const QString data_server_port = convert::prettyValue(
app.var(varconst::SERVER_PORT));
const QString data_server_path = convert::prettyValue(
app.var(varconst::SERVER_PATH));
const QString data_server_timeout = convert::prettyValue(
app.var(varconst::SERVER_TIMEOUT_MS));
const QString data_last_server_registration = convert::prettyValue(
app.var(varconst::LAST_SERVER_REGISTRATION));
const QString data_last_successful_upload = convert::prettyValue(
app.var(varconst::LAST_SUCCESSFUL_UPLOAD));
const QString data_dbtitle = convert::prettyValue(
app.var(varconst::SERVER_DATABASE_TITLE));
const QString data_policy_upload = convert::prettyValue(
app.var(varconst::ID_POLICY_UPLOAD));
const QString data_policy_finalize = convert::prettyValue(
app.var(varconst::ID_POLICY_FINALIZE));
const QString data_server_camcops_version = convert::prettyValue(
app.var(varconst::SERVER_CAMCOPS_VERSION));
const Qt::Alignment labelalign = Qt::AlignRight | Qt::AlignTop;
const Qt::Alignment dataalign = Qt::AlignLeft | Qt::AlignTop;
auto g1 = new QuGridContainer();
g1->setColumnStretch(0, 1);
g1->setColumnStretch(1, 1);
int row = 0;
g1->addCell(QuGridCell((new QuText(label_server_address))
->setTextAlignment(labelalign), row, 0));
g1->addCell(QuGridCell((new QuText(data_server_address))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
g1->addCell(QuGridCell((new QuText(label_server_port))
->setTextAlignment(labelalign), row, 0));
g1->addCell(QuGridCell((new QuText(data_server_port))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
g1->addCell(QuGridCell((new QuText(label_server_path))
->setTextAlignment(labelalign), row, 0));
g1->addCell(QuGridCell((new QuText(data_server_path))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
g1->addCell(QuGridCell((new QuText(label_server_timeout))
->setTextAlignment(labelalign), row, 0));
g1->addCell(QuGridCell((new QuText(data_server_timeout))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
auto g2 = new QuGridContainer();
g2->setColumnStretch(0, 1);
g2->setColumnStretch(1, 1);
row = 0;
g2->addCell(QuGridCell((new QuText(label_last_server_registration))
->setTextAlignment(labelalign), row, 0));
g2->addCell(QuGridCell((new QuText(data_last_server_registration))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
g2->addCell(QuGridCell((new QuText(label_last_successful_upload))
->setTextAlignment(labelalign), row, 0));
g2->addCell(QuGridCell((new QuText(data_last_successful_upload))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
g2->addCell(QuGridCell((new QuText(label_dbtitle))
->setTextAlignment(labelalign), row, 0));
g2->addCell(QuGridCell((new QuText(data_dbtitle))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
g2->addCell(QuGridCell((new QuText(label_policy_upload))
->setTextAlignment(labelalign), row, 0));
g2->addCell(QuGridCell((new QuText(data_policy_upload))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
g2->addCell(QuGridCell((new QuText(label_policy_finalize))
->setTextAlignment(labelalign), row, 0));
g2->addCell(QuGridCell((new QuText(data_policy_finalize))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
g2->addCell(QuGridCell((new QuText(label_server_camcops_version))
->setTextAlignment(labelalign), row, 0));
g2->addCell(QuGridCell((new QuText(data_server_camcops_version))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
auto g3 = new QuGridContainer();
g3->setColumnStretch(0, 1);
g3->setColumnStretch(1, 1);
row = 0;
#ifdef OLD_STYLE_ID_DESCRIPTIONS
for (int n = 1; n <= dbconst::NUMBER_OF_IDNUMS; ++n) {
const QString desc = convert::prettyValue(
app.var(dbconst::IDDESC_FIELD_FORMAT.arg(n)));
const QString shortdesc = convert::prettyValue(
app.var(dbconst::IDSHORTDESC_FIELD_FORMAT.arg(n)));
#else
QVector<IdNumDescriptionPtr> descriptions = m_app.getAllIdDescriptions();
for (const IdNumDescriptionPtr& description : descriptions) {
const int n = description->whichIdNum();
const QString desc = description->description();
const QString shortdesc = description->shortDescription();
#endif
g3->addCell(QuGridCell(
(new QuText(tr("Description for patient identifier ") +
QString::number(n) + ":")
)->setTextAlignment(labelalign), row, 0));
g3->addCell(QuGridCell((new QuText(desc))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
g3->addCell(QuGridCell(
(new QuText(tr("Short description for patient identifier ") +
QString::number(n) + ":")
)->setTextAlignment(labelalign), row, 0));
g3->addCell(QuGridCell((new QuText(shortdesc))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
}
ExtraString extrastring(m_app, m_app.sysdb());
QMap<QString, int> count_by_language = extrastring.getStringCountByLanguage();
auto g4 = new QuGridContainer();
g4->setColumnStretch(0, 1);
g4->setColumnStretch(1, 1);
row = 0;
g4->addCell(QuGridCell((new QuText(tr("Language")))
->setTextAlignment(labelalign)
->setItalic(), row, 0));
g4->addCell(QuGridCell((new QuText(tr("Number of strings")))
->setTextAlignment(dataalign)
->setItalic(), row, 1));
++row;
for (const QString& lang : count_by_language.keys()) {
const int count = count_by_language[lang];
g4->addCell(QuGridCell((new QuText(lang.isEmpty() ? "–" : lang))
->setTextAlignment(labelalign), row, 0));
g4->addCell(QuGridCell((new QuText(QString::number(count)))
->setTextAlignment(dataalign)->setBold(), row, 1));
++row;
}
QuPagePtr page(new QuPage{
g1,
new QuHorizontalLine(),
g2,
new QuHorizontalLine(),
new QuText(tr("ID number descriptions:")),
g3,
new QuHorizontalLine(),
new QuText(tr("Extra string counts by language:")),
g4,
});
page->setTitle(tr("Show server information"));
page->setType(QuPage::PageType::Config);
auto questionnaire = new Questionnaire(m_app, {page});
questionnaire->setFinishButtonIconToTick();
questionnaire->setReadOnly(true);
return questionnaire;
}
void SettingsMenu::dropUnknownTables()
{
const QString title = tr("Drop unknown tables?");
DatabaseManager& data_db = m_app.db();
DatabaseManager& sys_db = m_app.sysdb();
QStringList data_tables = data_db.tablesNotExplicitlyCreatedByUs();
QStringList sys_tables = sys_db.tablesNotExplicitlyCreatedByUs();
data_tables.sort();
sys_tables.sort();
if (data_tables.isEmpty() && sys_tables.isEmpty()) {
uifunc::alert(tr("All is well; no unknown tables."), title);
return;
}
QStringList lines{tr("Delete the following unknown data tables?")};
lines.append("");
lines += data_tables;
lines.append("");
lines.append(tr("... and the following unknown system tables?"));
lines.append("");
lines += sys_tables;
if (!uifunc::confirm(lines.join("\n"), title,
tr("Yes, drop"), tr("No, cancel"), this)) {
return;
}
data_db.dropTablesNotExplicitlyCreatedByUs();
sys_db.dropTablesNotExplicitlyCreatedByUs();
uifunc::alert(tr("Tables dropped."), title);
}
void SettingsMenu::viewDataDbAsSql()
{
#ifdef OFFER_VIEW_SQL
viewDbAsSql(m_app.db(), tr("Main data database"));
#endif
}
void SettingsMenu::viewSystemDbAsSql()
{
#ifdef OFFER_VIEW_SQL
viewDbAsSql(m_app.sysdb(), tr("CamCOPS system database"));
#endif
}
void SettingsMenu::viewDbAsSql(DatabaseManager& db, const QString& title)
{
QString sql;
{ // block ensures stream is flushed by the time we read the string
SlowGuiGuard guard = m_app.getSlowGuiGuard();
QTextStream os(&sql);
dumpsql::dumpDatabase(os, db);
}
LogMessageBox box(this, title, sql);
box.exec();
}
void SettingsMenu::debugDataDbAsSql()
{
debugDbAsSql(m_app.db(), tr("Data"));
}
void SettingsMenu::debugSystemDbAsSql()
{
debugDbAsSql(m_app.sysdb(), tr("System"));
}
void SettingsMenu::debugDbAsSql(DatabaseManager& db, const QString& prefix)
{
{ // guard block
SlowGuiGuard guard = m_app.getSlowGuiGuard(tr("Sending data..."),
TextConst::pleaseWait());
QString sql;
{ // block ensures stream is flushed by the time we read the string
QTextStream os(&sql);
dumpsql::dumpDatabase(os, db);
}
qInfo().noquote().nospace() << sql;
}
uifunc::alert(prefix + " " + tr("database sent to debugging stream"),
tr("Finished"));
}
void SettingsMenu::saveDataDbAsSql()
{
saveDbAsSql(m_app.db(),
tr("Save data database as..."),
tr("Data database written to:"));
}
void SettingsMenu::saveSystemDbAsSql()
{
saveDbAsSql(m_app.sysdb(),
tr("Save system database as..."),
tr("System database written to:"));
}
void SettingsMenu::saveDbAsSql(DatabaseManager& db, const QString& save_title,
const QString& finish_prefix)
{
const QString filename = QFileDialog::getSaveFileName(this, save_title);
if (filename.isEmpty()) {
return; // user cancelled
}
QFile file(filename);
file.open(QIODevice::WriteOnly | QIODevice::Text);
if (!file.isOpen()) {
uifunc::alert(tr("Unable to open file: ") + filename, tr("Failure"));
return;
}
QTextStream os(&file);
dumpsql::dumpDatabase(os, db);
uifunc::alert(finish_prefix + " " + filename + "\n" +
tr("You can import it into SQLite with a command like") +
" \"sqlite3 newdb.sqlite < mydump.sql\"",
tr("Success"));
}
void SettingsMenu::viewDataCounts()
{
viewCounts(m_app.db(), tr("Record counts for data database"));
}
void SettingsMenu::viewSystemCounts()
{
viewCounts(m_app.sysdb(), tr("Record counts for system database"));
}
void SettingsMenu::viewCounts(DatabaseManager& db, const QString& title)
{
const QStringList tables = db.getAllTables();
QStringList lines;
for (const QString& table : tables) {
const int count = db.count(table);
lines.append(QString("%1: <b>%2</b>").arg(table).arg(count));
}
const QString text = lines.join("<br>");
LogMessageBox box(this, title, text, true);
box.exec();
}
void SettingsMenu::chooseLanguage()
{
uifunc::chooseLanguage(m_app, this);
}
void SettingsMenu::changeMode()
{
m_app.setModeFromUser();
}