File indexing completed on 2024-06-02 04:20:57

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 "sticker_pack_builder_dialog.hpp"
0008 #include "ui_sticker_pack_builder_dialog.h"
0009 
0010 #include <QUuid>
0011 #include <QMessageBox>
0012 #include <QPainter>
0013 #include <QLabel>
0014 #include <QToolButton>
0015 #include <QStandardPaths>
0016 #include <QPointer>
0017 
0018 #include "io/lottie/tgs_format.hpp"
0019 #include "io/glaxnimate/glaxnimate_format.hpp"
0020 #include "model/assets/assets.hpp"
0021 
0022 #include "emoji/emoji_set.hpp"
0023 #include "emoji/emoji_dialog.hpp"
0024 #include "android_file_picker.hpp"
0025 #include "document_opener.hpp"
0026 #include "glaxnimate_app.hpp"
0027 #include "telegram_intent.hpp"
0028 #include "style/scroll_area_event_filter.hpp"
0029 
0030 
0031 class glaxnimate::android::StickerPackBuilderDialog::Private
0032 {
0033 public:
0034     struct Item
0035     {
0036         Item(std::unique_ptr<model::Document> doc, Private* d)
0037             : document(std::move(doc)),
0038               emoji(emoji::EmojiGroup::table[0]->first().unicode),
0039               d(d)
0040         {
0041             int row = d->ui.layout_items->rowCount();
0042 
0043             d->ui.layout_items->addWidget(&preview, row, 0);
0044 
0045             button_emoji.setText(emoji);
0046             connect(&button_emoji, &QAbstractButton::clicked, d->parent, [d, this]{
0047                 d->change_emoji(this);
0048             });
0049             d->ui.layout_items->addWidget(&button_emoji, row, 1);
0050 
0051             d->ui.layout_items->addItem(&spacer, row, 2);
0052 
0053             button_delete.setIcon(QIcon::fromTheme("edit-delete"));
0054             connect(&button_delete, &QAbstractButton::clicked, d->parent, [d, this]{
0055                 d->remove(this);
0056             });
0057             d->ui.layout_items->addWidget(&button_delete, row, 3);
0058 
0059             adjust_size();
0060         }
0061 
0062         ~Item()
0063         {
0064             d->ui.layout_items->removeWidget(&preview);
0065             d->ui.layout_items->removeWidget(&button_emoji);
0066             d->ui.layout_items->removeWidget(&button_delete);
0067             d->ui.layout_items->removeItem(&spacer);
0068         }
0069 
0070         void set_emoji(const QString& e)
0071         {
0072             emoji = e;
0073             button_emoji.setText(emoji);
0074         }
0075 
0076         void rebuild_preview()
0077         {
0078             auto main = document->assets()->compositions->values[0];
0079             rebuild_preview((
0080                 main->animation->first_frame.get() +
0081                 main->animation->last_frame.get()
0082             ) / 2);
0083         }
0084 
0085         void rebuild_preview(model::FrameTime t)
0086         {
0087             auto main = document->assets()->compositions->values[0];
0088             pixmap.fill(Qt::white);
0089             QPainter painter(&pixmap);
0090             painter.setRenderHint(QPainter::Antialiasing);
0091             auto scale = qreal(pixmap.width()) / main->width.get();
0092             painter.scale(scale, scale);
0093             main->paint(&painter, t, model::VisualNode::Render);
0094             painter.end();
0095             preview.setPixmap(pixmap);
0096         }
0097 
0098         void adjust_size()
0099         {
0100             int width = d->extent;
0101 
0102             button_emoji.setFont(d->font);
0103             QSize size(width, width);
0104             preview.setFixedSize(size);
0105             button_emoji.setFixedSize(size);
0106             button_delete.setFixedSize(size);
0107             button_delete.setIconSize(QSize(width*0.8, width*0.8));
0108             pixmap = QPixmap(size);
0109             rebuild_preview();
0110         }
0111 
0112         std::unique_ptr<model::Document> document;
0113         QPixmap pixmap;
0114         QString emoji;
0115 
0116         QLabel preview;
0117         QPushButton button_emoji;
0118         QPushButton button_delete;
0119         QSpacerItem spacer{0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum};
0120         Private* d;
0121     };
0122 
0123     Private(StickerPackBuilderDialog* parent)
0124         : parent(parent), opener(parent)
0125     {
0126         ui.setupUi(parent);
0127         ui.button_add->setIcon(QIcon::fromTheme("document-open"));
0128         ui.button_export->setIcon(QIcon::fromTheme("document-send"));
0129         ui.button_clear->setIcon(QIcon::fromTheme("edit-clear-all"));
0130         ui.button_add_current->setIcon(QIcon::fromTheme("list-add"));
0131         ui.button_export->setEnabled(false);
0132 
0133         connect(&file_picker, &AndroidFilePicker::open_selected, parent, [this](const QUrl& url){
0134             add_document(url);
0135         });
0136         connect(ui.button_add, &QAbstractButton::clicked, parent, [this]{ request_add(); });
0137         connect(ui.button_export, &QAbstractButton::clicked, parent, [this]{ export_pack(); });
0138         connect(ui.button_add_current, &QAbstractButton::clicked, parent, [this]{ add_current(); });
0139         connect(ui.button_clear, &QAbstractButton::clicked, parent, [this]{ clear(); });
0140 
0141         update_extent();
0142 
0143         scroller.set_target(ui.scroll_area);
0144     }
0145 
0146     void update_extent()
0147     {
0148         extent = qMin(parent->height(), parent->width()) / 3.;
0149         font.setPixelSize(extent * 0.8);
0150     }
0151 
0152     void request_add()
0153     {
0154         if ( items.size() == 50 )
0155         {
0156             QMessageBox::warning(parent, parent->windowTitle(), i18n("Too many stickers"));
0157             return;
0158         }
0159 
0160         if ( !file_picker.select_open(false) )
0161         {
0162             QMessageBox::warning(parent, parent->windowTitle(), i18n("Could not open file"));
0163         }
0164     }
0165 
0166     void add_document(const QUrl& url)
0167     {
0168         auto doc = opener.open(url);
0169         if ( !doc )
0170         {
0171             QMessageBox::warning(parent, parent->windowTitle(), i18n("Could not open file"));
0172             return;
0173         }
0174 
0175         ui.button_export->setEnabled(true);
0176         items.emplace_back(std::make_unique<Item>(std::move(doc), this));
0177     }
0178 
0179     void remove(Item* item)
0180     {
0181         for ( auto it = items.begin(); it != items.end(); ++it )
0182         {
0183             if ( it->get() == item )
0184             {
0185                 items.erase(it);
0186                 break;
0187             }
0188         }
0189         ui.button_export->setEnabled(!items.empty());
0190     }
0191 
0192     void change_emoji(Item* item)
0193     {
0194         for ( auto it = items.begin(); it != items.end(); ++it )
0195         {
0196             if ( it->get() == item )
0197             {
0198                 if ( emoji_selector.exec() )
0199                     item->set_emoji(emoji_selector.current_unicode());
0200                 return;
0201             }
0202         }
0203     }
0204 
0205     void export_pack()
0206     {
0207         QStringList filenames;
0208         QStringList emoji;
0209 
0210         QDir path_parent = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation);
0211         QString subdir = QUuid::createUuid().toString(QUuid::WithoutBraces);
0212         path_parent.mkpath(subdir);
0213         QDir path = path_parent.absoluteFilePath(subdir);
0214 
0215         int index = 0;
0216         for ( const auto& item : items )
0217         {
0218             QString filename = path.absoluteFilePath(QString::number(index) + ".tgs");
0219             QFile file(filename);
0220             if ( !exporter.save(file, filename, item->document.get(), {}) )
0221             {
0222                 QMessageBox::warning(parent, parent->windowTitle(), i18n("Cannot save as TGS"));
0223                 continue;
0224             }
0225 
0226             filenames << filename;
0227             emoji << item->emoji;
0228 
0229             index++;
0230         }
0231 
0232         auto result = TelegramIntent().send_stickers(filenames, emoji);
0233         if ( !result )
0234         {
0235             QMessageBox::warning(parent, parent->windowTitle(), result.message());
0236         }
0237         else
0238         {
0239             parent->accept();
0240         }
0241     }
0242 
0243 
0244     void add_current()
0245     {
0246         if ( !current_file )
0247             return;
0248 
0249         QByteArray data;
0250         QBuffer buf(&data);
0251         io::glaxnimate::GlaxnimateFormat format;
0252         format.save(buf, "", current_file, {});
0253         buf.seek(0);
0254         buf.close();
0255         std::unique_ptr<model::Document> new_file = std::make_unique<model::Document>("");
0256         format.open(buf, "", new_file.get(), {});
0257         items.emplace_back(std::make_unique<Item>(std::move(new_file), this));
0258         ui.button_export->setEnabled(true);
0259     }
0260 
0261     void clear()
0262     {
0263         if ( QMessageBox::question(parent, parent->windowTitle(), i18n("Are you sure you want to remove all stickers from the pack?")) == QMessageBox::Yes )
0264         {
0265             items.clear();
0266             ui.button_export->setEnabled(false);
0267         }
0268     }
0269 
0270     qreal extent;
0271     Ui::StickerPackBuilderDialog ui;
0272     StickerPackBuilderDialog* parent;
0273     AndroidFilePicker file_picker;
0274     DocumentOpener opener;
0275     io::lottie::TgsFormat exporter;
0276     std::vector<std::unique_ptr<Item>> items;
0277     QFont font;
0278     emoji::EmojiDialog emoji_selector;
0279     QPointer<model::Document> current_file;
0280     gui::ScrollAreaEventFilter scroller;
0281     DialogFixerFilter fixer{&emoji_selector};
0282 };
0283 
0284 glaxnimate::android::StickerPackBuilderDialog::StickerPackBuilderDialog(QWidget *parent) :
0285     BaseDialog(parent),
0286     d(std::make_unique<Private>(this))
0287 {
0288     d->emoji_selector.set_image_path(QDir("assets:/share/glaxnimate/glaxnimate/emoji/png"));
0289     emoji::EmojiSetSlugFormat slug;
0290     slug.prefix = "emoji_u";
0291     d->emoji_selector.set_image_suffix(".png");
0292     d->emoji_selector.set_image_slug_format(slug);
0293     d->emoji_selector.load_emoji(emoji::EmojiDialog::Image);
0294 }
0295 
0296 glaxnimate::android::StickerPackBuilderDialog::~StickerPackBuilderDialog()
0297 {
0298 }
0299 
0300 void glaxnimate::android::StickerPackBuilderDialog::set_current_file(model::Document *current)
0301 {
0302     d->current_file = current;
0303 }
0304 
0305 void glaxnimate::android::StickerPackBuilderDialog::changeEvent(QEvent *e)
0306 {
0307     BaseDialog::changeEvent(e);
0308     switch (e->type()) {
0309         case QEvent::LanguageChange:
0310             d->ui.retranslateUi(this);
0311             break;
0312         default:
0313             break;
0314     }
0315 }
0316 
0317 void glaxnimate::android::StickerPackBuilderDialog::resizeEvent(QResizeEvent *e)
0318 {
0319     BaseDialog::resizeEvent(e);
0320     d->update_extent();
0321     for ( const auto& item : d->items )
0322         item->adjust_size();
0323 }
0324 
0325 glaxnimate::emoji::EmojiDialog & glaxnimate::android::StickerPackBuilderDialog::emoji_dialog()
0326 {
0327     return d->emoji_selector;
0328 }