This commit is contained in:
Martin Rotter 2017-01-20 08:47:42 +01:00
parent e99b66f9eb
commit 7bc8caa6c4
9 changed files with 161 additions and 102 deletions

View File

@ -2,6 +2,7 @@
—————
Added:
▪ User now can place custom user skins in user "data" folder. (issue #81) See "About" dialog for the exact path.
▪ User now can select if DB transactions are enabled or not. (bug #70) By default, DB transactions are now switched OFF, therefore anyone who wants better performance, must turn them ON in app settings.
▪ MySQL database backend now requires at least version 5.5, DB encoding is now changed to utf8mb4 character set. (bug #74)
▪ Height if message attachment image is now configurable, defaults to 36. (issue #69)

View File

@ -105,24 +105,21 @@
#define APP_DB_SQLITE_DRIVER "QSQLITE"
#define APP_DB_SQLITE_INIT "db_init_sqlite.sql"
#define APP_DB_SQLITE_PATH "data/database/local"
#define APP_DB_SQLITE_PATH "database/local"
#define APP_DB_SQLITE_FILE "database.db"
// Keep this in sync with schema versions declared in SQL initialization code.
#define APP_DB_SCHEMA_VERSION "7"
#define APP_DB_UPDATE_FILE_PATTERN "db_update_%1_%2_%3.sql"
#define APP_DB_COMMENT_SPLIT "-- !\n"
#define APP_DB_WEB_PATH "data/database/web"
#define APP_DB_NAME_PLACEHOLDER "##"
#define APP_CFG_PATH "data/config"
#define APP_CFG_PATH "config"
#define APP_CFG_FILE "config.ini"
#define APP_LOG_PATH "data/log"
#define APP_LOG_FILE "log.txt"
#define APP_QUIT_INSTANCE "-q"
#define APP_IS_RUNNING "app_is_running"
#define APP_SKIN_USER_FOLDER "skins"
#define APP_SKIN_DEFAULT "vergilius"
#define APP_SKIN_METADATA_FILE "metadata.xml"
#define APP_STYLE_DEFAULT "Fusion"

View File

@ -51,20 +51,16 @@ FormAbout::~FormAbout() {
void FormAbout::loadSettingsAndPaths() {
if (qApp->settings()->type() == SettingsProperties::Portable) {
m_ui->m_txtPathsSettingsType->setText(tr("FULLY portable"));
m_ui->m_txtPathsDatabaseRoot->setText(QDir::toNativeSeparators(qApp->applicationDirPath() +
QDir::separator() +
QString(APP_DB_SQLITE_PATH)));
}
else {
m_ui->m_txtPathsSettingsType->setText(tr("PARTIALLY portable"));
m_ui->m_txtPathsDatabaseRoot->setText(QDir::toNativeSeparators(qApp->homeFolderPath() +
QDir::separator() +
QString(APP_LOW_H_NAME) +
QDir::separator() +
QString(APP_DB_SQLITE_PATH)));
}
m_ui->m_txtPathsDatabaseRoot->setText(QDir::toNativeSeparators(qApp->settings()->userSettingsRootFolder() +
QDir::separator() +
QString(APP_DB_SQLITE_PATH)));
m_ui->m_txtPathsSettingsFile->setText(QDir::toNativeSeparators(qApp->settings()->fileName()));
m_ui->m_txtPathsSkinsRoot->setText(QDir::toNativeSeparators(qApp->skins()->getUserSkinBaseFolder()));
}
void FormAbout::loadLicenseAndInformation() {

View File

@ -92,7 +92,7 @@
<locale language="English" country="UnitedStates"/>
</property>
<property name="currentIndex">
<number>0</number>
<number>3</number>
</property>
<widget class="QWidget" name="m_tabInfo">
<attribute name="title">
@ -125,7 +125,7 @@
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="acceptRichText">
@ -159,8 +159,8 @@ p, li { white-space: pre-wrap; }
<rect>
<x>0</x>
<y>0</y>
<width>98</width>
<height>73</height>
<width>685</width>
<height>184</height>
</rect>
</property>
<property name="autoFillBackground">
@ -217,7 +217,7 @@ p, li { white-space: pre-wrap; }
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
@ -235,8 +235,8 @@ p, li { white-space: pre-wrap; }
<rect>
<x>0</x>
<y>0</y>
<width>98</width>
<height>73</height>
<width>83</width>
<height>69</height>
</rect>
</property>
<attribute name="label">
@ -287,7 +287,7 @@ p, li { white-space: pre-wrap; }
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
@ -333,7 +333,7 @@ p, li { white-space: pre-wrap; }
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="acceptRichText">
@ -389,20 +389,34 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="m_txtPathsDatabaseRoot">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Database root path</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="m_txtPathsDatabaseRoot">
<item row="3" column="1">
<widget class="QLineEdit" name="m_txtPathsSkinsRoot">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>User skins root path</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
@ -416,6 +430,17 @@ p, li { white-space: pre-wrap; }
</item>
</layout>
</widget>
<tabstops>
<tabstop>m_tabAbout</tabstop>
<tabstop>m_txtPathsSettingsType</tabstop>
<tabstop>m_txtPathsSettingsFile</tabstop>
<tabstop>m_txtPathsDatabaseRoot</tabstop>
<tabstop>m_txtPathsSkinsRoot</tabstop>
<tabstop>m_txtInfo</tabstop>
<tabstop>m_txtLicenseGnu</tabstop>
<tabstop>m_txtChangelog</tabstop>
<tabstop>m_txtLicenseBsd</tabstop>
</tabstops>
<resources/>
<connections>
<connection>

View File

@ -187,14 +187,7 @@ void DatabaseFactory::finishRestoration() {
}
void DatabaseFactory::sqliteAssemblyDatabaseFilePath() {
if (qApp->settings()->type() == SettingsProperties::Portable) {
m_sqliteDatabaseFilePath = qApp->applicationDirPath() + QDir::separator() + QString(APP_DB_SQLITE_PATH);
}
else {
m_sqliteDatabaseFilePath = qApp->homeFolderPath() + QDir::separator() +
QString(APP_LOW_H_NAME) + QDir::separator() +
QString(APP_DB_SQLITE_PATH);
}
m_sqliteDatabaseFilePath = qApp->settings()->userSettingsRootFolder() + QDir::separator() + QString(APP_DB_SQLITE_PATH);
}
QSqlDatabase DatabaseFactory::sqliteInitializeInMemoryDatabase() {

View File

@ -275,6 +275,15 @@ Settings::Settings(const QString &file_name, Format format, const SettingsProper
Settings::~Settings() {
}
QString Settings::userSettingsRootFolder() const {
if (qApp->settings()->type() == SettingsProperties::Portable) {
return getAppPathUserFolder();
}
else {
return getHomeUserFolder();
}
}
QString Settings::pathName() const {
return QFileInfo(fileName()).absolutePath();
}
@ -309,6 +318,14 @@ void Settings::finishRestoration(const QString &desired_settings_file_path) {
}
}
QString Settings::getAppPathUserFolder() {
return qApp->applicationDirPath() + QDir::separator() + QSL("data");
}
QString Settings::getHomeUserFolder() {
return qApp->homeFolderPath() + QDir::separator() + QString(APP_LOW_H_NAME) + QDir::separator() + QSL("data");
}
Settings *Settings::setupSettings(QObject *parent) {
Settings *new_settings;
@ -338,11 +355,12 @@ SettingsProperties Settings::determineProperties() {
properties.m_settingsSuffix = QDir::separator() + QString(APP_CFG_PATH) + QDir::separator() + QString(APP_CFG_FILE);
const QString app_path = qApp->applicationDirPath();
const QString home_path = qApp->homeFolderPath() + QDir::separator() + QString(APP_LOW_H_NAME);
const QString exe_path = qApp->applicationDirPath();
const QString app_path = getAppPathUserFolder();
const QString home_path = getHomeUserFolder();
const QString home_path_file = home_path + properties.m_settingsSuffix;
const bool portable_settings_available = IOFactory::isFolderWritable(app_path);
const bool portable_settings_available = IOFactory::isFolderWritable(exe_path);
const bool non_portable_settings_exist = QFile::exists(home_path_file);
// We will use PORTABLE settings only and only if it is available and NON-PORTABLE

View File

@ -312,6 +312,9 @@ class Settings : public QSettings {
return m_initializationStatus;
}
// Returns the base folder to which store user data, the "data" folder.
QString userSettingsRootFolder() const;
// Getters/setters for settings values.
inline QVariant value(const QString &section, const QString &key, const QVariant &default_value = QVariant()) const {
return QSettings::value(QString("%1/%2").arg(section, key), default_value);
@ -342,6 +345,9 @@ class Settings : public QSettings {
bool initiateRestoration(const QString &settings_backup_file_path);
static void finishRestoration(const QString &desired_settings_file_path);
static QString getAppPathUserFolder();
static QString getHomeUserFolder();
// Creates settings file in correct location.
static Settings *setupSettings(QObject *parent);

View File

@ -65,7 +65,6 @@ void SkinFactory::loadCurrentSkin() {
void SkinFactory::loadSkinFromData(const Skin &skin) {
if (!skin.m_rawData.isEmpty()) {
qApp->setStyleSheet(skin.m_rawData);
//qApp->setStyleSheet("QWidget {rgb(39, 43, 48);}");
}
qApp->setStyle(qApp->settings()->value(GROUP(GUI), SETTING(GUI::Style)).toString());
@ -75,74 +74,91 @@ void SkinFactory::setCurrentSkinName(const QString &skin_name) {
qApp->settings()->setValue(GROUP(GUI), GUI::Skin, skin_name);
}
QString SkinFactory::getUserSkinBaseFolder() const {
return qApp->settings()->userSettingsRootFolder() + QDir::separator() + APP_SKIN_USER_FOLDER;
}
QString SkinFactory::selectedSkinName() const {
return qApp->settings()->value(GROUP(GUI), SETTING(GUI::Skin)).toString();
}
Skin SkinFactory::skinInfo(const QString &skin_name, bool *ok) const {
Skin skin;
QFile skin_file(APP_SKIN_PATH + QDir::separator() + skin_name + QDir::separator() + APP_SKIN_METADATA_FILE);
QDomDocument dokument;
QStringList base_skin_folders;
if (!skin_file.open(QIODevice::Text | QIODevice::ReadOnly) || !dokument.setContent(&skin_file, true)) {
if (ok) {
*ok = false;
base_skin_folders.append(APP_SKIN_PATH);
base_skin_folders.append(getUserSkinBaseFolder());
while (!base_skin_folders.isEmpty()) {
const QString skin_folder = base_skin_folders.takeAt(0) + QDir::separator() + skin_name + QDir::separator();
const QString metadata_file = skin_folder + APP_SKIN_METADATA_FILE;
if (QFile::exists(metadata_file)) {
QFile skin_file(metadata_file);
QDomDocument dokument;
if (!skin_file.open(QIODevice::Text | QIODevice::ReadOnly) || !dokument.setContent(&skin_file, true)) {
if (ok) {
*ok = false;
}
return skin;
}
const QDomNode skin_node = dokument.namedItem(QSL("skin"));
// Obtain visible skin name.
skin.m_visibleName = skin_name;
// Obtain author.
skin.m_author = skin_node.namedItem(QSL("author")).namedItem(QSL("name")).toElement().text();
// Obtain email.
skin.m_email = skin_node.namedItem(QSL("author")).namedItem(QSL("email")).toElement().text();
// Obtain version.
skin.m_version = skin_node.attributes().namedItem(QSL("version")).toAttr().value();
// Obtain other information.
skin.m_baseName = skin_name;
// Free resources.
skin_file.close();
skin_file.deleteLater();
// Here we use "/" instead of QDir::separator() because CSS2.1 url field
// accepts '/' as path elements separator.
//
// "##" is placeholder for the actual path to skin file. This is needed for using
// images within the QSS file.
// So if one uses "##/images/border.png" in QSS then it is
// replaced by fully absolute path and target file can
// be safely loaded.
skin.m_layoutMarkupWrapper = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("html_wrapper.html")));
skin.m_layoutMarkupWrapper = skin.m_layoutMarkupWrapper.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_enclosureImageMarkup = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("html_enclosure_image.html")));
skin.m_enclosureImageMarkup = skin.m_enclosureImageMarkup.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_layoutMarkup = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("html_single_message.html")));
skin.m_layoutMarkup = skin.m_layoutMarkup.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_enclosureMarkup = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("html_enclosure_every.html")));
skin.m_enclosureMarkup = skin.m_enclosureMarkup.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_rawData = QString::fromUtf8(IOFactory::readTextFile(skin_folder + QL1S("theme.css")));
skin.m_rawData = skin.m_rawData.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
if (ok != nullptr) {
*ok = !skin.m_author.isEmpty() && !skin.m_version.isEmpty() &&
!skin.m_baseName.isEmpty() && !skin.m_email.isEmpty() &&
!skin.m_layoutMarkup.isEmpty();
}
break;
}
return skin;
}
const QDomNode skin_node = dokument.namedItem(QSL("skin"));
// Obtain visible skin name.
skin.m_visibleName = skin_name;
// Obtain author.
skin.m_author = skin_node.namedItem(QSL("author")).namedItem(QSL("name")).toElement().text();
// Obtain email.
skin.m_email = skin_node.namedItem(QSL("author")).namedItem(QSL("email")).toElement().text();
// Obtain version.
skin.m_version = skin_node.attributes().namedItem(QSL("version")).toAttr().value();
// Obtain other information.
skin.m_baseName = skin_name;
// Free resources.
skin_file.close();
skin_file.deleteLater();
// Here we use "/" instead of QDir::separator() because CSS2.1 url field
// accepts '/' as path elements separator.
//
// "##" is placeholder for the actual path to skin file. This is needed for using
// images within the QSS file.
// So if one uses "##/images/border.png" in QSS then it is
// replaced by fully absolute path and target file can
// be safely loaded.
const QString base_folder = APP_SKIN_PATH + QDir::separator() + skin_name + QDir::separator();
skin.m_layoutMarkupWrapper = QString::fromUtf8(IOFactory::readTextFile(base_folder + QL1S("html_wrapper.html")));
skin.m_layoutMarkupWrapper = skin.m_layoutMarkupWrapper.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_enclosureImageMarkup = QString::fromUtf8(IOFactory::readTextFile(base_folder + QL1S("html_enclosure_image.html")));
skin.m_enclosureImageMarkup = skin.m_enclosureImageMarkup.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_layoutMarkup = QString::fromUtf8(IOFactory::readTextFile(base_folder + QL1S("html_single_message.html")));
skin.m_layoutMarkup = skin.m_layoutMarkup.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_enclosureMarkup = QString::fromUtf8(IOFactory::readTextFile(base_folder + QL1S("html_enclosure_every.html")));
skin.m_enclosureMarkup = skin.m_enclosureMarkup.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
skin.m_rawData = QString::fromUtf8(IOFactory::readTextFile(base_folder + QL1S("theme.css")));
skin.m_rawData = skin.m_rawData.replace(QSL("##"), APP_SKIN_PATH + QL1S("/") + skin_name);
if (ok != nullptr) {
*ok = !skin.m_author.isEmpty() && !skin.m_version.isEmpty() &&
!skin.m_baseName.isEmpty() && !skin.m_email.isEmpty() &&
!skin.m_layoutMarkup.isEmpty();
}
return skin;
@ -151,10 +167,14 @@ Skin SkinFactory::skinInfo(const QString &skin_name, bool *ok) const {
QList<Skin> SkinFactory::installedSkins() const {
QList<Skin> skins;
bool skin_load_ok;
const QStringList skin_directories = QDir(APP_SKIN_PATH).entryList(QDir::Dirs |
QDir::NoDotAndDotDot |
QDir::NoSymLinks |
QDir::Readable);
QStringList skin_directories = QDir(APP_SKIN_PATH).entryList(QDir::Dirs |
QDir::NoDotAndDotDot |
QDir::NoSymLinks |
QDir::Readable);
skin_directories.append(QDir(getUserSkinBaseFolder()).entryList(QDir::Dirs |
QDir::NoDotAndDotDot |
QDir::NoSymLinks |
QDir::Readable));
foreach (const QString &base_directory, skin_directories) {
const Skin skin_info = skinInfo(base_directory, &skin_load_ok);

View File

@ -69,7 +69,10 @@ class SkinFactory : public QObject {
// Sets the desired skin as the active one if it exists.
void setCurrentSkinName(const QString &skin_name);
QString getUserSkinBaseFolder() const;
private:
// Loads the skin from give skin_data.
void loadSkinFromData(const Skin &skin);