diff options
-rw-r--r-- | ReleaseNotes/ReleaseNotes.txt | 3 | ||||
-rw-r--r-- | desktop-widgets/mainwindow.cpp | 20 | ||||
-rw-r--r-- | desktop-widgets/printoptions.cpp | 89 | ||||
-rw-r--r-- | desktop-widgets/printoptions.h | 1 | ||||
-rw-r--r-- | desktop-widgets/templatelayout.cpp | 58 | ||||
-rw-r--r-- | desktop-widgets/templatelayout.h | 3 | ||||
-rw-r--r-- | printing_templates/One Dive.html | 188 |
7 files changed, 267 insertions, 95 deletions
diff --git a/ReleaseNotes/ReleaseNotes.txt b/ReleaseNotes/ReleaseNotes.txt index 772dca088..10fdfa7d7 100644 --- a/ReleaseNotes/ReleaseNotes.txt +++ b/ReleaseNotes/ReleaseNotes.txt @@ -15,6 +15,9 @@ Some of the changes since _Subsurface_ 4.7.4 - mobile: fix black/white switch in splash screen (#531) - UI: tag editing. Comma entry shows all tags (again) (#605) - mobile: enable auto completion for dive site entry (#546) +- Printing: the bundled templates are now read-only and are always overwritten + by the application. The first time the user runs this update, backup files + of the previous templates would be created. (#847). Some of the changes since _Subsurface_ 4.7.2 diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index b0817b9f1..330ea7d05 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -256,8 +256,24 @@ MainWindow::MainWindow() : QMainWindow(), connect(geoLookup, SIGNAL(started()),information(), SLOT(disableGeoLookupEdition())); connect(geoLookup, SIGNAL(finished()), information(), SLOT(enableGeoLookupEdition())); #ifndef NO_PRINTING - // copy the bundled print templates to the user path; no overwriting occurs! - copyPath(getPrintingTemplatePathBundle(), getPrintingTemplatePathUser()); + // copy the bundled print templates to the user path + QStringList templateBackupList; + QString templatePathUser(getPrintingTemplatePathUser()); + copy_bundled_templates(getPrintingTemplatePathBundle(), templatePathUser, &templateBackupList); + if (templateBackupList.length()) { + QMessageBox msgBox(this); + templatePathUser.replace("\\", "/"); + templateBackupList.replaceInStrings(templatePathUser + "/", ""); + msgBox.setWindowTitle(tr("Template backup created")); + msgBox.setText(tr("The following backup printing templates were created:\n\n%1\n\n" + "Location:\n%2\n\n" + "Please note that as of this version of Subsurface the default templates\n" + "are read-only and should not be edited directly, since the application\n" + "can overwrite them on startup.").arg(templateBackupList.join("\n")).arg(templatePathUser)); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + } + set_bundled_templates_as_read_only(); find_all_templates(); #endif diff --git a/desktop-widgets/printoptions.cpp b/desktop-widgets/printoptions.cpp index eaeb6954b..cebb073a3 100644 --- a/desktop-widgets/printoptions.cpp +++ b/desktop-widgets/printoptions.cpp @@ -60,11 +60,13 @@ void PrintOptions::setupTemplates() int current_index = 0; ui.printTemplate->clear(); Q_FOREACH(const QString& theme, currList) { - if (theme == storedTemplate) // find the stored template in the list + // find the stored template in the list + if (theme == storedTemplate || theme == lastImportExportTemplate) current_index = currList.indexOf(theme); ui.printTemplate->addItem(theme.split('.')[0], theme); } ui.printTemplate->setCurrentIndex(current_index); + lastImportExportTemplate = ""; } // print type radio buttons @@ -121,6 +123,20 @@ void PrintOptions::on_printTemplate_currentIndexChanged(int index) void PrintOptions::on_editButton_clicked() { + QString templateName = getSelectedTemplate(); + QString prefix = (printOptions->type == print_options::STATISTICS) ? "statistics/" : ""; + QFile f(getPrintingTemplatePathUser() + QDir::separator() + prefix + templateName); + if (!f.open(QFile::ReadWrite | QFile::Text)) { + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Read-only template!")); + msgBox.setText(tr("The template '%1' is read-only and connot be edited.\n" + "Please export this template to a different file.").arg(templateName)); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + return; + } else { + f.close(); + } TemplateEdit te(this, printOptions, templateOptions); te.exec(); setup(); @@ -128,36 +144,93 @@ void PrintOptions::on_editButton_clicked() void PrintOptions::on_importButton_clicked() { - QString filename = QFileDialog::getOpenFileName(this, tr("Import template file"), "", + QString pathUser = getPrintingTemplatePathUser(); + QString filename = QFileDialog::getOpenFileName(this, tr("Import template file"), pathUser, tr("HTML files") + " (*.html)"); if (filename.isEmpty()) return; QFileInfo fileInfo(filename); - QFile::copy(filename, getPrintingTemplatePathUser() + QDir::separator() + fileInfo.fileName()); + + const QString dest = pathUser + QDir::separator() + fileInfo.fileName(); + QFile f(dest); + if (!f.open(QFile::ReadWrite | QFile::Text)) { + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Read-only template!")); + msgBox.setText(tr("The destination template '%1' is read-only and cannot be overwritten.").arg(fileInfo.fileName())); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + return; + } else { + f.close(); + if (filename != dest) + f.remove(); + } + + QFile::copy(filename, dest); printOptions->p_template = fileInfo.fileName(); + lastImportExportTemplate = fileInfo.fileName(); find_all_templates(); setup(); } void PrintOptions::on_exportButton_clicked() { - QString filename = QFileDialog::getSaveFileName(this, tr("Export template files as"), "", + QString pathUser = getPrintingTemplatePathUser(); + QString filename = QFileDialog::getSaveFileName(this, tr("Export template files as"), pathUser, tr("HTML files") + " (*.html)"); if (filename.isEmpty()) return; - QFile::copy(getPrintingTemplatePathUser() + QDir::separator() + getSelectedTemplate(), filename); + const QString ext(".html"); + if (filename.endsWith(".htm", Qt::CaseInsensitive)) + filename += "l"; + else if (!filename.endsWith(ext, Qt::CaseInsensitive)) + filename += ext; + QFileInfo fileInfo(filename); + const QString dest = pathUser + QDir::separator() + getSelectedTemplate(); + + QFile f(filename); + if (!f.open(QFile::ReadWrite | QFile::Text)) { + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Read-only template!")); + msgBox.setText(tr("The destination template '%1' is read-only and cannot be overwritten.").arg(fileInfo.fileName())); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + return; + } else { + f.close(); + if (dest != filename) + f.remove(); + } + + QFile::copy(dest, filename); + if (!f.open(QFile::ReadWrite | QFile::Text)) + f.setPermissions(QFileDevice::ReadUser | QFileDevice::ReadOwner | QFileDevice::WriteUser | QFileDevice::WriteOwner); + else + f.close(); + lastImportExportTemplate = fileInfo.fileName(); + find_all_templates(); + setup(); } void PrintOptions::on_deleteButton_clicked() { QString templateName = getSelectedTemplate(); - QMessageBox msgBox; - msgBox.setText(tr("This action cannot be undone!")); - msgBox.setInformativeText(tr("Delete template: %1?").arg(templateName)); + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("This action cannot be undone!")); + msgBox.setText(tr("Delete template '%1'?").arg(templateName)); msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Cancel); if (msgBox.exec() == QMessageBox::Ok) { QFile f(getPrintingTemplatePathUser() + QDir::separator() + templateName); + if (!f.open(QFile::ReadWrite | QFile::Text)) { + msgBox.setWindowTitle(tr("Read-only template!")); + msgBox.setText(tr("The template '%1' is read-only and cannot be deleted.").arg(templateName)); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + return; + } else { + f.close(); + } f.remove(); find_all_templates(); setup(); diff --git a/desktop-widgets/printoptions.h b/desktop-widgets/printoptions.h index 9aaf8de70..1db0dfe61 100644 --- a/desktop-widgets/printoptions.h +++ b/desktop-widgets/printoptions.h @@ -72,6 +72,7 @@ private: struct print_options *printOptions; struct template_options *templateOptions; bool hasSetupSlots; + QString lastImportExportTemplate; void setupTemplates(); private diff --git a/desktop-widgets/templatelayout.cpp b/desktop-widgets/templatelayout.cpp index 9f933e91c..f3f3c8be2 100644 --- a/desktop-widgets/templatelayout.cpp +++ b/desktop-widgets/templatelayout.cpp @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#include <QFileDevice> #include <string> #include "templatelayout.h" @@ -19,23 +20,74 @@ int getTotalWork(print_options *printOptions) void find_all_templates() { + const QString ext(".html"); grantlee_templates.clear(); grantlee_statistics_templates.clear(); QDir dir(getPrintingTemplatePathUser()); QStringList list = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); foreach (const QString& filename, list) { - if (filename.at(filename.size() - 1) != '~') { + if (filename.at(filename.size() - 1) != '~' && filename.endsWith(ext)) grantlee_templates.append(filename); - } } // find statistics templates dir.setPath(getPrintingTemplatePathUser() + QDir::separator() + "statistics"); list = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); foreach (const QString& filename, list) { - if (filename.at(filename.size() - 1) != '~') { + if (filename.at(filename.size() - 1) != '~' && filename.endsWith(ext)) grantlee_statistics_templates.append(filename); + } +} + +/* find templates which are part of the bundle in the user path + * and set them as read only. + */ +void set_bundled_templates_as_read_only() +{ + QDir dir; + const QString stats("statistics"); + QStringList list, listStats; + QString pathBundle = getPrintingTemplatePathBundle(); + QString pathUser = getPrintingTemplatePathUser(); + + dir.setPath(pathBundle); + list = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); + dir.setPath(pathBundle + QDir::separator() + stats); + listStats = dir.entryList(QDir::Files | QDir::NoDotAndDotDot); + for (int i = 0; i < listStats.length(); i++) + listStats[i] = stats + QDir::separator() + listStats.at(i); + list += listStats; + + foreach (const QString& f, list) + QFile::setPermissions(pathUser + QDir::separator() + f, QFileDevice::ReadOwner | QFileDevice::ReadUser); +} + +void copy_bundled_templates(QString src, QString dst, QStringList *templateBackupList) +{ + QDir dir(src); + if (!dir.exists()) + return; + foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QString dst_path = dst + QDir::separator() + d; + dir.mkpath(dst_path); + copy_bundled_templates(src + QDir::separator() + d, dst_path, templateBackupList); + } + foreach (QString f, dir.entryList(QDir::Files)) { + QFile fileSrc(src + QDir::separator() + f); + QFile fileDest(dst + QDir::separator() + f); + if (fileDest.exists()) { + // if open() fails the file is either locked or r/o. try to remove it and then overwrite + if (!fileDest.open(QFile::ReadWrite | QFile::Text)) { + fileDest.setPermissions(QFileDevice::WriteOwner | QFileDevice::WriteUser); + fileDest.remove(); + } else { // if the file is not read-only create a backup + fileDest.close(); + const QString targetFile = fileDest.fileName().replace(".html", "-User.html"); + fileDest.copy(targetFile); + *templateBackupList << targetFile; + } } + fileSrc.copy(fileDest.fileName()); // in all cases copy the file } } diff --git a/desktop-widgets/templatelayout.h b/desktop-widgets/templatelayout.h index 9c24d096d..cb60cc03d 100644 --- a/desktop-widgets/templatelayout.h +++ b/desktop-widgets/templatelayout.h @@ -2,6 +2,7 @@ #ifndef TEMPLATELAYOUT_H #define TEMPLATELAYOUT_H +#include <QStringList> #include <grantlee_templates.h> #include "mainwindow.h" #include "printoptions.h" @@ -12,6 +13,8 @@ int getTotalWork(print_options *printOptions); void find_all_templates(); +void set_bundled_templates_as_read_only(); +void copy_bundled_templates(QString src, QString dst, QStringList *templateBackupList); extern QList<QString> grantlee_templates, grantlee_statistics_templates; diff --git a/printing_templates/One Dive.html b/printing_templates/One Dive.html index 7f7945396..a9ee00471 100644 --- a/printing_templates/One Dive.html +++ b/printing_templates/One Dive.html @@ -4,19 +4,17 @@ body { {{ print_options.grayscale }}; padding: 0; - margin: 0; + margin: 0 0 0 6%; <!-- Provide LH margin for binding the page --> font-size: {{ template_options.font_size }}vw; line-height: {{ template_options.line_spacing }}; font-family: {{ template_options.font }}; } h1 { - float: left; font-size: {{ template_options.font_size }}vw; } p { - float: left; font-size: {{ template_options.font_size }}vw; } @@ -26,11 +24,17 @@ border-width: {{ template_options.borderwidth }}px; border-style:solid; border-color: {{ template_options.color6 }}; + border-collapse: separate; + } + + tr { + height: 4vh; } td { - padding-left: 0.5vw; - padding-right: 0.5vw; + padding: 0; + margin: 0; + padding-left: 1%; } #body_div { @@ -38,70 +42,101 @@ } .mainContainer { - width: 98%; + width: 97%; height: 100%; - margin-left: 1%; + margin-left: 0%; margin-right: 1%; margin-top: 0%; margin-bottom: 0%; - overflow: hidden; - border-width: 0; + border-width: 1px; page-break-inside: avoid; } .innerContainer { - width: 100%; + width: 99%; height: 99%; - padding-top: 1%; - overflow: hidden; + padding-top: 0%; } .diveDetails { width: 100%; - height: 98%; - float: left; + margin: 0.0%; + } + + .dataSection { + width: 100%; + margin: 0.0% 0% 0% 0%; } .diveProfile { - width: 99%; - height: 40%; - margin: 0.5%; + width: 99.5%; + height: 45%; + margin: 0.2% 0% 0.5% 0.5%; } - .dataSection { + .notesSection { width: 100%; - height: 40%; - margin: 0%; + margin: 0.0%; + min-height: 35%; } .fieldTitle { background-color: {{ template_options.color2 }}; overflow: hidden; color: {{ template_options.color4 }}; + width: 7%; + padding-left:5px; } .fieldData { background-color: {{ template_options.color3 }}; color: {{ template_options.color5 }}; + width: 13%; + padding: o$ 1% 0% 1%; } .table_class { - float: left; - margin: 0.5%; - width: 49%; + margin: 0%; + width: 100%; + } + + td.insert_column_inner { + border-left-style:solid; + border-left-color: {{ template_options.color6 }}; + background-color: {{ template_options.color2 }}; + color: {{ template_options.color4 }}; + border: 5px solid black; } + td.insert_column_outer { + background-color: {{ template_options.color2 }}; + color: {{ template_options.color4 }}; + } + .notes_table_class { overflow: hidden; - width: 99%; - margin: 0.5%; + width: 100%; + margin: 0.0% 0% 0% 0%; + max-height: 35%; } + .notes_table_class td.fieldTitle { + max-height: 0.15vh; + } + .textArea { line-height: {{ template_options.line_spacing }}; color: {{ template_options.color5 }}; - max-height: 19vh; - overflow: hidden; + font-size: {{ template_options.font_size }}vw; + padding: 1%; + } + + td.fieldTitle b { + font-size: {{ template_options.font_size }}vw; + } + + .hidden_div { + display: none; } </style> </head> @@ -111,80 +146,59 @@ {% for dive in dives %} <div class="mainContainer"> <div class="innerContainer"> - <div class="diveDetails"> - <div class="diveProfile" id="dive_{{ dive.id }}"> - </div> - <div class="dataSection"> + <div class="dataSection"> <table class="table_class"> - <tbody><tr> - <td class="fieldTitle"> - <h1> Dive No. </h1> - </td> - <td class="fieldData"> - <p> {{ dive.number }} </p> - </td> - </tr> <tr> <td class="fieldTitle"> - <h1> Date </h1> + <b> Date </b> </td> <td class="fieldData"> <p> {{ dive.date }} </p> </td> - </tr> - <tr> <td class="fieldTitle"> - <h1> Location </h1> + <b> Dive No. </b> </td> <td class="fieldData"> - <p> {{ dive.location }} </p> + <p> {{ dive.number }} </p> </td> </tr> <tr> <td class="fieldTitle"> - <h1> Max. depth </h1> + <b> Time </b> </td> <td class="fieldData"> - <p> {{ dive.depth }} </p> + <p> {{ dive.time }} </p> </td> - </tr> - <tr> <td class="fieldTitle"> - <h1> Duration </h1> + <b> Gases </b> </td> <td class="fieldData"> - <p> {{ dive.duration }} </p> + <p> {{ dive.gas }} </p> </td> </tr> - </tbody></table> - <table class="table_class"> - <tbody><tr> + <tr> <td class="fieldTitle"> - <h1> Time. </h1> + <b> Location </b> </td> <td class="fieldData"> - <p> {{ dive.time }} </p> + <p> {{ dive.location }} </p> </td> - </tr> - <tr> <td class="fieldTitle"> - <h1> Air Temp. </h1> + <b> Water Temp. </b> </td> <td class="fieldData"> - <p> {{ dive.airTemp }} </p> + <p> {{ dive.waterTemp }} </p> </td> </tr> <tr> <td class="fieldTitle"> - <h1> Water Temp. </h1> + <b> Max Depth </b> </td> <td class="fieldData"> - <p> {{ dive.waterTemp }} </p> + <p> {{ dive.depth }} </p> </td> - </tr> - <tr> <td class="fieldTitle"> - <h1> Buddy </h1> + <b> Buddy </b> </td> <td class="fieldData"> <p> {{ dive.buddy }} </p> @@ -192,34 +206,44 @@ </tr> <tr> <td class="fieldTitle"> - <h1> Divemaster </h1> + <b> Duration </b> </td> <td class="fieldData"> - <p> {{ dive.divemaster }} </p> + <p> {{ dive.duration }} </p> </td> - </tr> - </tbody> - </table> - <table class="notes_table_class"> - <tbody> - <tr> <td class="fieldTitle"> - <h1> Notes </h1> + <b> Dive Master </b> </td> - </tr> - <tr> <td class="fieldData"> - <div class="textArea"> - <p> {{ dive.notes|safe }} </p> - </div> + <p> {{ dive.divemaster }} </p> </td> </tr> - </tbody> </table> - </div> </div> - </div> - </div> + + <div class="diveProfile" id="dive_{{ dive.id }}"> + </div> + + <div class="notesSection"> + <table class="notes_table_class"> + <tbody> + <tr> + <td class="fieldTitle"> + <b> Notes </b> + </td> + </tr> + <tr> + <td class="fieldData"> + <p> {{ dive.notes|safe }} </p> + </td> + </tr> + </tbody> + </table> + </div> <!-- notesSection --> + + </div> <!-- innerContainer --> + </div> <!-- mainContainer --> + {% endfor %} {% endblock %} </div> |