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 }