Warning, file /graphics/glaxnimate/src/gui/settings/document_templates.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "document_templates.hpp"
0008 
0009 #include <numeric>
0010 
0011 #include <QAction>
0012 
0013 #include "app/application.hpp"
0014 
0015 #include "io/glaxnimate/glaxnimate_format.hpp"
0016 #include "model/assets/assets.hpp"
0017 
0018 using namespace glaxnimate::gui;
0019 using namespace glaxnimate;
0020 
0021 
0022 settings::DocumentTemplate::DocumentTemplate(const QString& filename, bool* loaded)
0023 : filename(filename), document(load(loaded))
0024 {}
0025 
0026 QSize settings::DocumentTemplate::size() const
0027 {
0028     if ( auto comp = main_comp() )
0029         return comp->size();
0030     return {};
0031 }
0032 
0033 model::Composition* settings::DocumentTemplate::main_comp() const
0034 {
0035     if ( document->assets()->compositions->values.empty() )
0036         return nullptr;
0037     return document->assets()->compositions->values[0];
0038 }
0039 
0040 model::FrameTime settings::DocumentTemplate::duration() const
0041 {
0042     if ( auto comp = main_comp() )
0043         return comp->animation->last_frame.get() - comp->animation->first_frame.get();
0044 
0045     return 0;
0046 }
0047 
0048 QString settings::DocumentTemplate::name() const
0049 {
0050     if ( auto comp = main_comp() )
0051         return comp->name.get();
0052     return i18n("Unnamed");
0053 }
0054 
0055 QString settings::DocumentTemplate::long_name() const
0056 {
0057     return name_template(main_comp()).subs(name()).toString();
0058 }
0059 
0060 float settings::DocumentTemplate::fps() const
0061 {
0062     if ( auto comp = main_comp() )
0063         return comp->fps.get();
0064 
0065     return 60;
0066 }
0067 
0068 std::unique_ptr<model::Document> settings::DocumentTemplate::create(bool* ok) const
0069 {
0070     std::unique_ptr<model::Document> document;
0071     document = load(ok);
0072     document->assets()->refresh_uuid();
0073     for ( const auto& comp : document->assets()->compositions->values )
0074         document->set_best_name(comp.get());
0075     return document;
0076 }
0077 
0078 std::unique_ptr<model::Document> settings::DocumentTemplate::load(bool* ok) const
0079 {
0080     std::unique_ptr<model::Document> document = std::make_unique<model::Document>("");
0081     QFile file(filename);
0082     *ok = io::glaxnimate::GlaxnimateFormat().open(file, filename, document.get(), {});
0083     return document;
0084 }
0085 
0086 bool settings::DocumentTemplate::operator<(const settings::DocumentTemplate& other) const
0087 {
0088     QString n1 = name();
0089     QString n2 = other.name();
0090     if ( n1 == n2 )
0091     {
0092         if ( size().width() == other.size().width() )
0093             return fps() > other.fps();
0094         return size().width() > other.size().width();
0095     }
0096     return n1 < n2;
0097 }
0098 
0099 QString settings::DocumentTemplate::aspect_ratio() const
0100 {
0101     return aspect_ratio(size());
0102 }
0103 
0104 QString settings::DocumentTemplate::aspect_ratio(const QSize& size)
0105 {
0106     if ( size.width() > 0 && size.height() > 0 )
0107     {
0108         int gcd = std::gcd(size.width(), size.height());
0109         return QString("%1:%2").arg(size.width()/gcd).arg(size.height()/gcd);
0110     }
0111 
0112     return {};
0113 }
0114 
0115 KLocalizedString settings::DocumentTemplate::name_template(model::Composition* comp)
0116 {
0117     if ( !comp )
0118         return ki18n("%1");
0119 
0120     QString aspect;
0121     auto w = comp->width.get();
0122     auto h = comp->height.get();
0123 
0124     //:
0125     return ki18nc("%5 is the file name, %1x%2 is the size, %3 is the aspect ratio, %4 is the frame rate", "%5 - %1x%2 %3 %4fps")
0126         .subs(w)
0127         .subs(h)
0128         .subs(aspect_ratio(QSize(w, h)))
0129         .subs(comp->fps.get())
0130     ;
0131 }
0132 
0133 QAction* settings::DocumentTemplates::create_action(const DocumentTemplate& templ, QObject *parent)
0134 {
0135     QAction* action = new QAction(QIcon::fromTheme("document-new-from-template"), templ.long_name(), parent);
0136     connect(action, &QAction::triggered, this, [&templ, this]{
0137         Q_EMIT create_from(templ);
0138     });
0139     return action;
0140 }
0141 
0142 void settings::DocumentTemplates::load()
0143 {
0144     templates_.clear();
0145     names.clear();
0146 
0147     for ( QDir dir : app::Application::instance()->data_paths("templates") )
0148     {
0149         for ( const auto& filename : dir.entryList(QDir::Files|QDir::Readable) )
0150         {
0151             bool ok = false;
0152             DocumentTemplate templ(dir.absoluteFilePath(filename), &ok);
0153             if ( ok && !names.count(templ.long_name()) )
0154             {
0155                 names.insert(templ.long_name());
0156                 templates_.push_back(std::move(templ));
0157             }
0158         }
0159     }
0160 
0161     std::sort(templates_.begin(), templates_.end());
0162 
0163     Q_EMIT loaded(templates_);
0164 }
0165 
0166 settings::DocumentTemplates::DocumentTemplates()
0167 {
0168     load();
0169 }
0170 
0171 bool settings::DocumentTemplates::save_as_template(model::Document* document)
0172 {
0173     if ( document->assets()->compositions->values.empty() )
0174         return false;
0175 
0176     model::Composition* comp = document->assets()->compositions->values[0];
0177 
0178     QString name_base = comp ? comp->name.get() : "";
0179     if ( name_base.isEmpty() )
0180         name_base = i18n("Template");
0181 
0182     // Find a nice name, avoiding duplicates
0183     auto name_full_template = DocumentTemplate::name_template(comp);
0184     KLocalizedString name_template = ki18n("%1 %2").subs(name_base);
0185     QString name = name_base;
0186     QString name_full = name_full_template.subs(name).toString();
0187     for ( int count = 1; names.count(name_full); count++ )
0188     {
0189         name = name_template.subs(count).toString();
0190         name_full = name_full_template.subs(name).toString();
0191     }
0192 
0193     // Generate a file name
0194     QString basename;
0195     for ( const QChar& ch : name )
0196     {
0197         if ( ch.isLetterOrNumber() || ch == '_' || ch == '-' )
0198             basename += ch;
0199         else
0200             basename += '_';
0201     }
0202 
0203     io::glaxnimate::GlaxnimateFormat format;
0204     QDir dirname = app::Application::instance()->writable_data_path("templates");
0205     QString path = dirname.absoluteFilePath(basename + "." + format.extensions()[0]);
0206 
0207     if ( !dirname.exists() )
0208     {
0209         dirname.cdUp();
0210         dirname.mkpath("templates");
0211     }
0212 
0213     QFile file(path);
0214     if ( !format.save(file, path, comp, {}) )
0215         return false;
0216     file.close();
0217 
0218     bool ok = false;
0219     DocumentTemplate templ(path, &ok);
0220     if ( !ok )
0221         return false;
0222 
0223     auto it = std::lower_bound(templates_.begin(), templates_.end(), templ);
0224     templates_.insert(it, std::move(templ));
0225     names.insert(name_full);
0226 
0227     Q_EMIT loaded(templates_);
0228 
0229     return true;
0230 }
0231 
0232 settings::DocumentTemplates & settings::DocumentTemplates::instance()
0233 {
0234     static settings::DocumentTemplates instance;
0235     return instance;
0236 }
0237 
0238 const std::vector<settings::DocumentTemplate> & settings::DocumentTemplates::templates() const
0239 {
0240     return templates_;
0241 }