File indexing completed on 2025-02-02 04:11:23
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 "glaxnimate_window_p.hpp" 0008 0009 #include <QTemporaryFile> 0010 #include <QDesktopServices> 0011 #include <QFileDialog> 0012 #include <QImageWriter> 0013 #include <QDropEvent> 0014 #include <QtConcurrent> 0015 #include <QEventLoop> 0016 #include <QLocalSocket> 0017 #include <QDataStream> 0018 #include <QSharedMemory> 0019 #include <QtGlobal> 0020 #include <QNetworkReply> 0021 0022 0023 #include "io/lottie/lottie_html_format.hpp" 0024 #include "io/svg/svg_renderer.hpp" 0025 #include "io/svg/svg_html_format.hpp" 0026 #include "io/glaxnimate/glaxnimate_format.hpp" 0027 #include "io/raster/raster_mime.hpp" 0028 #include "io/lottie/tgs_format.hpp" 0029 #include "io/lottie/validation.hpp" 0030 #include "io/rive/rive_html_format.hpp" 0031 #include "plugin/io.hpp" 0032 0033 #include "model/visitor.hpp" 0034 #include "widgets/font/font_loader.hpp" 0035 0036 #include "command/undo_macro_guard.hpp" 0037 #include "command/undo_macro_guard.hpp" 0038 0039 #include "tools/base.hpp" 0040 0041 #include "glaxnimate_app.hpp" 0042 #include "app_info.hpp" 0043 #include "widgets/dialogs/import_export_dialog.hpp" 0044 #include "widgets/dialogs/io_status_dialog.hpp" 0045 #include "widgets/shape_style/shape_style_preview_widget.hpp" 0046 #include "widgets/dialogs/stalefiles_dialog.hpp" 0047 0048 template<class T> 0049 static void process_events(const QFuture<T>& promise) 0050 { 0051 while ( !promise.isFinished() ) 0052 { 0053 qApp->processEvents(QEventLoop::ExcludeUserInputEvents|QEventLoop::WaitForMoreEvents, 10); 0054 } 0055 } 0056 0057 void GlaxnimateWindow::Private::setup_document_ptr(std::unique_ptr<model::Document> doc) 0058 { 0059 if ( !close_document() ) 0060 return; 0061 0062 current_document = std::move(doc); 0063 0064 do_setup_document(); 0065 0066 QDir path = app::settings::get<QString>("open_save", "path"); 0067 auto opts = current_document->io_options(); 0068 opts.path = path; 0069 current_document->set_io_options(opts); 0070 0071 view_fit(); 0072 if ( comp && !comp->shapes.empty() ) 0073 ui.view_document_node->set_current_node(comp->shapes[0]); 0074 0075 ui.timeline_widget->reset_view(); 0076 } 0077 0078 void GlaxnimateWindow::Private::do_setup_document() 0079 { 0080 current_document_has_file = false; 0081 0082 // Composition 0083 comp = nullptr; 0084 connect(current_document->assets()->compositions.get(), &model::CompositionList::docnode_child_remove_end, parent, [this](model::DocumentNode*, int index){on_remove_precomp(index);}); 0085 connect(current_document->assets()->compositions.get(), &model::CompositionList::precomp_added, parent, [this](model::Composition* node, int row){setup_composition(node, row);}); 0086 ui.menu_new_comp_layer->setEnabled(false); 0087 for ( const auto& precomp : current_document->assets()->compositions->values ) 0088 setup_composition(precomp.get()); 0089 0090 // Undo Redo 0091 parent->undo_group().addStack(¤t_document->undo_stack()); 0092 parent->undo_group().setActiveStack(¤t_document->undo_stack()); 0093 0094 // Views 0095 document_node_model.set_document(current_document.get()); 0096 ui.view_document_node->set_composition(comp); 0097 ui.timeline_widget->set_document(current_document.get()); 0098 ui.timeline_widget->set_composition(comp); 0099 ui.view_assets->setRootIndex(asset_model.mapFromSource(document_node_model.node_index(current_document->assets()).siblingAtColumn(1))); 0100 0101 property_model.set_document(current_document.get()); 0102 property_model.set_object(comp); 0103 ui.tab_bar->set_document(current_document.get()); 0104 0105 scene.set_document(current_document.get()); 0106 0107 if ( !current_document->assets()->compositions->values.empty() ) 0108 switch_composition(current_document->assets()->compositions->values[0], 0); 0109 0110 ui.document_swatch_widget->set_document(current_document.get()); 0111 ui.widget_gradients->set_document(current_document.get()); 0112 0113 // Scripting 0114 ui.console->clear_contexts(); 0115 ui.console->set_global("document", QVariant::fromValue(current_document.get())); 0116 0117 // Title 0118 QObject::connect(current_document.get(), &model::Document::filename_changed, parent, &GlaxnimateWindow::refresh_title); 0119 QObject::connect(¤t_document->undo_stack(), &QUndoStack::cleanChanged, parent, &GlaxnimateWindow::refresh_title); 0120 refresh_title(); 0121 0122 // Playback 0123 ui.play_controls->set_record_enabled(false); 0124 ui.play_controls_2->set_record_enabled(false); 0125 ///... 0126 QObject::connect(ui.play_controls, &FrameControlsWidget::frame_selected, current_document.get(), [this](int frame){current_document->set_current_time(frame);}); 0127 QObject::connect(current_document.get(), &model::Document::current_time_changed, ui.play_controls, [this](float frame){ui.play_controls->set_frame(frame);}); 0128 QObject::connect(current_document.get(), &model::Document::record_to_keyframe_changed, ui.play_controls, &FrameControlsWidget::set_record_enabled); 0129 QObject::connect(current_document.get(), &model::Document::record_to_keyframe_changed, ui.play_controls_2, &FrameControlsWidget::set_record_enabled); 0130 QObject::connect(ui.play_controls, &FrameControlsWidget::record_toggled, current_document.get(), &model::Document::set_record_to_keyframe); 0131 QObject::connect(ui.play_controls_2, &FrameControlsWidget::record_toggled, current_document.get(), &model::Document::set_record_to_keyframe); 0132 0133 widget_recording->setVisible(false); 0134 QObject::connect(current_document.get(), &model::Document::record_to_keyframe_changed, widget_recording, &QWidget::setVisible); 0135 0136 // Export 0137 export_options = {}; 0138 ui.action_export->setText(i18n("Export...")); 0139 } 0140 0141 void GlaxnimateWindow::Private::setup_document_new(const QString& filename) 0142 { 0143 if ( !close_document() ) 0144 return; 0145 0146 current_document = std::make_unique<model::Document>(filename); 0147 current_document->assets()->add_comp_no_undo(); 0148 0149 do_setup_document(); 0150 QUrl url = QUrl::fromLocalFile(i18n("Unsaved Animation")); 0151 autosave_file.setManagedFile(url); 0152 comp->name.set(comp->type_name_human()); 0153 comp->width.set(app::settings::get<int>("defaults", "width")); 0154 comp->height.set(app::settings::get<int>("defaults", "height")); 0155 comp->fps.set(app::settings::get<float>("defaults", "fps")); 0156 float duration = app::settings::get<float>("defaults", "duration"); 0157 int out_point = comp->fps.get() * duration; 0158 comp->animation->last_frame.set(out_point); 0159 0160 0161 auto layer = std::make_unique<model::Layer>(current_document.get()); 0162 layer->animation->last_frame.set(out_point); 0163 layer->name.set(layer->type_name_human()); 0164 QPointF pos( 0165 comp->width.get() / 2.0, 0166 comp->height.get() / 2.0 0167 ); 0168 layer->transform.get()->anchor_point.set(pos); 0169 layer->transform.get()->position.set(pos); 0170 model::ShapeElement* ptr = layer.get(); 0171 comp->shapes.insert(std::move(layer), 0); 0172 0173 QDir path = app::settings::get<QString>("open_save", "path"); 0174 auto opts = current_document->io_options(); 0175 opts.path = path; 0176 current_document->set_io_options(opts); 0177 0178 ui.view_document_node->set_current_node(ptr); 0179 ui.play_controls->set_range(0, out_point); 0180 view_fit(); 0181 0182 ui.timeline_widget->reset_view(); 0183 } 0184 0185 bool GlaxnimateWindow::Private::setup_document_open(QIODevice* file, const io::Options& options, bool is_file) 0186 { 0187 if ( !close_document() ) 0188 return false; 0189 0190 current_document = std::make_unique<model::Document>(options.filename); 0191 0192 dialog_import_status->reset(options.format, options.filename); 0193 /*auto promise = QtConcurrent::run( 0194 [options, current_document=current_document.get()]{ 0195 QFile file(options.filename); 0196 return options.format->open(file, options.filename, current_document, options.settings); 0197 }); 0198 0199 process_events(promise); 0200 0201 bool ok = promise.result();*/ 0202 0203 current_document->set_io_options(options); 0204 0205 bool ok = options.format->open(*file, options.filename, current_document.get(), options.settings); 0206 0207 do_setup_document(); 0208 0209 if ( is_file ) 0210 { 0211 current_document_has_file = true; 0212 0213 app::settings::set<QString>("open_save", "path", options.path.absolutePath()); 0214 0215 if ( ok && !autosave_load ) 0216 most_recent_file(options.filename); 0217 } 0218 0219 view_fit(); 0220 0221 0222 QUrl file_url = QUrl::fromLocalFile(options.filename); 0223 auto stale = KAutoSaveFile::staleFiles(file_url); 0224 autosave_file.setManagedFile(file_url); 0225 0226 if ( !autosave_load && !stale.empty() ) 0227 { 0228 // This ensures the stale file is not leaked if the user doesn't load it. 0229 stale[0]->setParent(current_document.get()); 0230 0231 WindowMessageWidget::Message msg{ 0232 i18n("Looks like this file is being edited by another Glaxnimate instance or it was being edited when Glaxnimate crashed."), 0233 KMessageWidget::Information 0234 }; 0235 0236 msg.add_action( 0237 QIcon::fromTheme("document-close"), 0238 i18n("Close Document"), 0239 parent, 0240 &GlaxnimateWindow::document_new 0241 ); 0242 0243 msg.add_action( 0244 QIcon::fromTheme("document-revert"), 0245 i18n("Load Backup"), 0246 parent, 0247 [this, file=stale[0]]{ load_backup(file, true); } 0248 ); 0249 0250 for ( int i = 1; i < stale.size(); i++ ) 0251 delete stale[i]; 0252 0253 ui.message_widget->queue_message(std::move(msg)); 0254 } 0255 0256 export_options = options; 0257 export_options.filename = ""; 0258 0259 ui.timeline_widget->reset_view(); 0260 0261 load_pending(); 0262 0263 return ok; 0264 } 0265 0266 bool GlaxnimateWindow::Private::setup_document_open(const io::Options& options) 0267 { 0268 QFile file(options.filename); 0269 return setup_document_open(&file, options, true); 0270 } 0271 0272 0273 void GlaxnimateWindow::Private::refresh_title() 0274 { 0275 QString title = current_document->filename(); 0276 if ( !current_document->undo_stack().isClean() ) 0277 title += " *"; 0278 parent->setWindowTitle(title); 0279 } 0280 0281 bool GlaxnimateWindow::Private::close_document() 0282 { 0283 if ( current_document ) 0284 { 0285 if ( !autosave_load ) { 0286 autosave_file.remove(); 0287 autosave_file.releaseLock(); 0288 } 0289 0290 if ( !current_document->undo_stack().isClean() ) 0291 { 0292 QMessageBox warning(parent); 0293 warning.setWindowTitle(i18n("Closing Animation")); 0294 warning.setText(i18n("The animation has unsaved changes.\nDo you want to save your changes?")); 0295 warning.setInformativeText(current_document->filename()); 0296 warning.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); 0297 warning.setDefaultButton(QMessageBox::Save); 0298 warning.setIcon(QMessageBox::Warning); 0299 int result = warning.exec(); 0300 0301 if ( result == QMessageBox::Save ) 0302 { 0303 if ( !save_document(false, false) ) 0304 return false; 0305 } 0306 else if ( result == QMessageBox::Cancel ) 0307 { 0308 return false; 0309 } 0310 0311 // Prevent signals on the destructor 0312 current_document->undo_stack().clear(); 0313 } 0314 } 0315 0316 if ( active_tool ) 0317 active_tool->close_document_event({ui.canvas, &scene, parent}); 0318 0319 comp = nullptr; 0320 ui.stroke_style_widget->set_targets({}); 0321 ui.stroke_style_widget->set_current(nullptr); 0322 ui.fill_style_widget->set_targets({}); 0323 ui.fill_style_widget->set_current(nullptr); 0324 document_node_model.clear_document(); 0325 property_model.clear_document(); 0326 scene.clear_document(); 0327 ui.timeline_widget->clear_document(); 0328 ui.document_swatch_widget->set_document(nullptr); 0329 ui.widget_gradients->set_document(nullptr); 0330 ui.view_document_node->set_composition(nullptr); 0331 ui.tab_bar->set_document(nullptr); 0332 0333 for ( const auto& stack : parent->undo_group().stacks() ) 0334 parent->undo_group().removeStack(stack); 0335 0336 ui.console->clear_contexts(); 0337 ui.console->clear_output(); 0338 ui.console->set_global("document", QVariant{}); 0339 0340 comp_selections.clear(); 0341 ui.menu_new_comp_layer->clear(); 0342 0343 return true; 0344 } 0345 0346 bool GlaxnimateWindow::Private::save_document(bool force_dialog, bool export_opts) 0347 { 0348 io::Options opts = export_opts ? export_options : current_document->io_options(); 0349 0350 if ( !opts.format || !opts.format->can_save() || !current_document_has_file || opts.filename.isEmpty() ) 0351 force_dialog = true; 0352 0353 if ( force_dialog ) 0354 { 0355 ImportExportDialog dialog(opts, parent); 0356 0357 if ( !dialog.export_dialog(comp) ) 0358 return false; 0359 0360 opts = dialog.io_options(); 0361 } 0362 0363 dialog_export_status->reset(opts.format, opts.filename); 0364 0365 if ( !qobject_cast<plugin::IoFormat*>(opts.format) ) 0366 { 0367 auto promise = QtConcurrent::run( 0368 [opts, comp=comp]{ 0369 QSaveFile file(opts.filename); 0370 bool result = opts.format->save(file, opts.filename, comp, opts.settings); 0371 0372 if ( result ) 0373 file.commit(); 0374 0375 return result; 0376 }); 0377 0378 process_events(promise); 0379 0380 if ( !promise.result() ) 0381 return false; 0382 } 0383 else 0384 { 0385 QSaveFile file(opts.filename); 0386 bool result = opts.format->save(file, opts.filename, comp, opts.settings); 0387 0388 if ( !result ) 0389 return false; 0390 0391 file.commit(); 0392 } 0393 0394 if ( export_opts ) 0395 { 0396 export_options = opts; 0397 ui.action_export->setText(i18n("Export to %1", QFileInfo(opts.filename).fileName())); 0398 } 0399 else 0400 { 0401 if ( opts.format->can_open() ) 0402 most_recent_file(opts.filename); 0403 current_document->undo_stack().setClean(); 0404 current_document_has_file = true; 0405 app::settings::set<QString>("open_save", "path", opts.path.absolutePath()); 0406 current_document->set_io_options(opts); 0407 0408 if ( !export_opts && !export_options.format ) 0409 export_options.path = opts.path; 0410 } 0411 0412 autosave_file.remove(); 0413 autosave_file.setManagedFile(QUrl::fromLocalFile(opts.filename)); 0414 return true; 0415 } 0416 0417 void GlaxnimateWindow::Private::document_open() 0418 { 0419 io::Options options = current_document->io_options(); 0420 0421 ImportExportDialog dialog(options, ui.centralwidget->parentWidget()); 0422 if ( dialog.import_dialog() ) 0423 setup_document_open(dialog.io_options()); 0424 } 0425 0426 0427 io::Options GlaxnimateWindow::Private::options_from_filename(const QString& filename, const QVariantMap& settings) 0428 { 0429 QFileInfo finfo(filename); 0430 if ( finfo.isFile() ) 0431 { 0432 io::Options opts; 0433 opts.format = io::IoRegistry::instance().from_filename(filename, io::ImportExport::Import); 0434 opts.path = finfo.dir(); 0435 opts.filename = filename; 0436 opts.settings = settings; 0437 0438 if ( opts.format ) 0439 { 0440 ImportExportDialog dialog(opts, ui.centralwidget->parentWidget()); 0441 if ( dialog.options_dialog(opts.format->open_settings()) ) 0442 return dialog.io_options(); 0443 0444 return {}; 0445 } 0446 else 0447 { 0448 show_warning(i18n("Open File"), i18n("No importer found for %1", filename)); 0449 } 0450 } 0451 else 0452 { 0453 show_warning(i18n("Open File"), i18n("The file might have been moved or deleted\n%1", filename)); 0454 } 0455 0456 return {}; 0457 } 0458 0459 0460 void GlaxnimateWindow::Private::document_open_from_filename(const QString& filename, const QVariantMap& settings) 0461 { 0462 io::Options opts = options_from_filename(filename, settings); 0463 if ( opts.format ) 0464 { 0465 setup_document_open(opts); 0466 most_recent_file(filename); 0467 } 0468 } 0469 0470 void GlaxnimateWindow::Private::drop_document(const QString& filename, bool as_comp) 0471 { 0472 auto options = options_from_filename(filename); 0473 if ( !options.format ) 0474 return; 0475 0476 model::Document imported(options.filename); 0477 QFile file(options.filename); 0478 bool ok = options.format->open(file, options.filename, &imported, options.settings); 0479 if ( !ok ) 0480 { 0481 show_warning(i18n("Import File"), i18n("Could not import %1", options.filename)); 0482 return; 0483 } 0484 0485 parent->paste_document(&imported, i18n("Import File"), as_comp); 0486 } 0487 0488 void GlaxnimateWindow::Private::document_reload() 0489 { 0490 if ( !current_document_has_file ) 0491 { 0492 status_message(i18n("No file to reload from")); 0493 return; 0494 } 0495 0496 auto options = current_document->io_options(); 0497 setup_document_open(options); 0498 } 0499 0500 0501 void GlaxnimateWindow::Private::preview(io::ImportExport& exporter, const QVariantMap& options) 0502 { 0503 dialog_export_status->reset(&exporter, i18n("Web Preview")); 0504 0505 auto promise = QtConcurrent::run( 0506 [&exporter, comp=comp, options]() -> QString { 0507 QTemporaryFile tempf(GlaxnimateApp::temp_path() + "/XXXXXX." + exporter.extensions()[0]); 0508 tempf.setAutoRemove(false); 0509 bool ok = tempf.open() && exporter.save( 0510 tempf, tempf.fileName(), comp, options 0511 ); 0512 if ( !ok ) 0513 return ""; 0514 return tempf.fileName(); 0515 }); 0516 0517 process_events(promise); 0518 0519 QString path = promise.result(); 0520 0521 dialog_export_status->disconnect_import_export(); 0522 0523 if ( path.isEmpty() ) 0524 { 0525 show_warning(i18n("Web Preview"), i18n("Could not create file")); 0526 return; 0527 } 0528 0529 if ( !QDesktopServices::openUrl(QUrl::fromLocalFile(path)) ) 0530 { 0531 show_warning(i18n("Web Preview"), i18n("Could not open browser")); 0532 } 0533 } 0534 0535 void GlaxnimateWindow::Private::preview_lottie(const QString& renderer) 0536 { 0537 io::lottie::LottieHtmlFormat fmt; 0538 preview(fmt, {{"renderer", renderer}}); 0539 } 0540 0541 void GlaxnimateWindow::Private::preview_svg() 0542 { 0543 io::svg::SvgHtmlFormat fmt; 0544 preview(fmt, {}); 0545 } 0546 0547 void GlaxnimateWindow::Private::preview_rive() 0548 { 0549 io::rive::RiveHtmlFormat fmt; 0550 preview(fmt, {}); 0551 } 0552 0553 void GlaxnimateWindow::Private::save_frame_bmp() 0554 { 0555 int frame = current_document->current_time(); 0556 QFileDialog fd(parent, i18n("Save Frame Image")); 0557 fd.setDirectory(app::settings::get<QString>("open_save", "render_path")); 0558 fd.setDefaultSuffix("png"); 0559 fd.selectFile(i18n("Frame%1.png", frame)); 0560 fd.setAcceptMode(QFileDialog::AcceptSave); 0561 fd.setFileMode(QFileDialog::AnyFile); 0562 fd.setOption(QFileDialog::DontUseNativeDialog, !app::settings::get<bool>("open_save", "native_dialog")); 0563 0564 QString formats; 0565 for ( const auto& fmt : QImageWriter::supportedImageFormats() ) 0566 formats += QString("*.%1 ").arg(QString::fromUtf8(fmt)); 0567 fd.setNameFilter(i18n("Image files (%1)", formats)); 0568 0569 if ( fd.exec() == QDialog::Rejected ) 0570 return; 0571 0572 app::settings::set("open_save", "render_path", fd.directory().path()); 0573 0574 QImage image = io::raster::RasterMime().to_image({comp}); 0575 if ( !image.save(fd.selectedFiles()[0]) ) 0576 show_warning(i18n("Render Frame"), i18n("Could not save image")); 0577 } 0578 0579 0580 void GlaxnimateWindow::Private::save_frame_svg() 0581 { 0582 int frame = current_document->current_time(); 0583 QFileDialog fd(parent, i18n("Save Frame Image")); 0584 fd.setDirectory(app::settings::get<QString>("open_save", "render_path")); 0585 fd.setDefaultSuffix("svg"); 0586 fd.selectFile(i18n("Frame%1.svg", frame)); 0587 fd.setAcceptMode(QFileDialog::AcceptSave); 0588 fd.setFileMode(QFileDialog::AnyFile); 0589 fd.setNameFilter(i18n("Scalable Vector Graphics (*.svg)")); 0590 fd.setOption(QFileDialog::DontUseNativeDialog, !app::settings::get<bool>("open_save", "native_dialog")); 0591 0592 if ( fd.exec() == QDialog::Rejected ) 0593 return; 0594 0595 app::settings::set("open_save", "render_path", fd.directory().path()); 0596 0597 QFile file(fd.selectedFiles()[0]); 0598 if ( !file.open(QFile::WriteOnly) ) 0599 { 0600 show_warning(i18n("Render Frame"), i18n("Could not save image")); 0601 return; 0602 } 0603 0604 io::svg::SvgRenderer rend(io::svg::NotAnimated, io::svg::CssFontType::FontFace); 0605 rend.write_main(comp); 0606 rend.write(&file, true); 0607 } 0608 0609 void GlaxnimateWindow::Private::validate_discord() 0610 { 0611 IoStatusDialog dialog(QIcon::fromTheme("discord"), "", false, parent); 0612 io::lottie::LottieFormat fmt; 0613 dialog.reset(&fmt, i18n("Validate Discord Sticker")); 0614 io::lottie::validate_discord(current_document.get(), comp, &fmt); 0615 dialog.show_errors(i18n("No issues found"), i18n("Some issues detected")); 0616 dialog.exec(); 0617 } 0618 0619 void GlaxnimateWindow::Private::validate_tgs() 0620 { 0621 IoStatusDialog dialog(QIcon::fromTheme("telegram"), "", false, parent); 0622 io::lottie::TgsFormat fmt; 0623 dialog.reset(&fmt, i18n("Validate Telegram Sticker")); 0624 fmt.validate(current_document.get(), comp); 0625 dialog.show_errors(i18n("No issues found"), i18n("Some issues detected")); 0626 dialog.exec(); 0627 } 0628 0629 void GlaxnimateWindow::Private::autosave_timer_start(int mins) 0630 { 0631 if ( mins == -1 ) 0632 mins = app::settings::get<int>("open_save", "backup_frequency"); 0633 autosave_timer_mins = mins; 0634 if ( autosave_timer_mins ) 0635 autosave_timer = parent->startTimer(autosave_timer_mins * 1000 * 60); 0636 } 0637 0638 void GlaxnimateWindow::Private::autosave(bool force) 0639 { 0640 if ( current_document && (force || (!current_document->undo_stack().isClean() && !autosave_load)) ) 0641 { 0642 autosave_file.open(QIODevice::WriteOnly); 0643 io::glaxnimate::GlaxnimateFormat().save(autosave_file, autosave_file.fileName(), comp, {}); 0644 autosave_file.close(); 0645 } 0646 } 0647 0648 void GlaxnimateWindow::Private::autosave_timer_load_settings() 0649 { 0650 int mins = app::settings::get<int>("open_save", "backup_frequency"); 0651 if ( mins != autosave_timer_mins ) 0652 { 0653 if ( autosave_timer ) 0654 parent->killTimer(autosave_timer); 0655 autosave_timer = 0; 0656 autosave_timer_start(mins); 0657 } 0658 } 0659 0660 void GlaxnimateWindow::Private::load_backup(KAutoSaveFile* file, bool io_options_from_current) 0661 { 0662 file->setParent(nullptr); 0663 std::unique_ptr<KAutoSaveFile> fptr(file); 0664 0665 if ( !file->open(QIODevice::ReadOnly) ) 0666 { 0667 show_warning(i18n("Load Backup"), i18n("Could not open the backup file")); 0668 return; 0669 } 0670 0671 QFileInfo path(file->managedFile().toString()); 0672 QDir dir = path.dir(); 0673 if ( dir.isRoot() ) 0674 dir = QDir(); 0675 0676 io::Options io_options_bak { 0677 io::glaxnimate::GlaxnimateFormat::instance(), 0678 dir, 0679 path.filePath(), 0680 {} 0681 }; 0682 0683 io::Options io_options; 0684 if ( io_options_from_current ) 0685 { 0686 io_options = current_document->io_options(); 0687 } 0688 else 0689 { 0690 io_options = io_options_bak; 0691 io_options.format = io::IoRegistry::instance().from_filename(path.filePath(), io::ImportExport::Import); 0692 } 0693 0694 auto lock = autosave_load.get_lock(); 0695 setup_document_open(file, io_options_bak, true); 0696 file->close(); 0697 autosave_file.setManagedFile(file->managedFile()); 0698 current_document->set_io_options(io_options); 0699 current_document->undo_stack().resetClean(); 0700 autosave(true); 0701 } 0702 0703 0704 QString GlaxnimateWindow::Private::drop_event_data(QDropEvent* event) 0705 { 0706 const QMimeData* data = event->mimeData(); 0707 0708 if ( !data->hasUrls() ) 0709 return {}; 0710 0711 for ( const auto& url : data->urls() ) 0712 { 0713 if ( url.isLocalFile() ) 0714 { 0715 QString filename = url.toLocalFile(); 0716 QString extension = QFileInfo(filename).completeSuffix(); 0717 if ( io::IoRegistry::instance().from_extension(extension, io::ImportExport::Import) ) 0718 return filename; 0719 } 0720 } 0721 0722 return {}; 0723 } 0724 0725 void GlaxnimateWindow::Private::set_color_def(model::BrushStyle* def, bool secondary) 0726 { 0727 model::Styler* target; 0728 QString what; 0729 if ( secondary ) 0730 { 0731 target = ui.stroke_style_widget->current(); 0732 what = i18n("Stroke"); 0733 } 0734 else 0735 { 0736 target = ui.fill_style_widget->current(); 0737 what = i18n("Fill"); 0738 } 0739 0740 if ( target ) 0741 { 0742 auto old = target->use.get(); 0743 0744 if ( !def ) 0745 { 0746 command::UndoMacroGuard macro(i18n("Unlink %1 Color", what), current_document.get()); 0747 if ( auto col = qobject_cast<model::NamedColor*>(target->use.get()) ) 0748 target->color.set_undoable(col->color.get()); 0749 target->use.set_undoable(QVariant::fromValue(def)); 0750 target->visible.set_undoable(false); 0751 if ( old ) 0752 old->remove_if_unused(false); 0753 } 0754 else 0755 { 0756 command::UndoMacroGuard macro(i18n("Link %1 Color", what), current_document.get()); 0757 target->use.set_undoable(QVariant::fromValue(def)); 0758 target->visible.set_undoable(true); 0759 if ( old ) 0760 old->remove_if_unused(false); 0761 } 0762 } 0763 0764 set_brush_reference(def, secondary); 0765 } 0766 0767 void GlaxnimateWindow::Private::set_brush_reference ( model::BrushStyle* sty, bool secondary ) 0768 { 0769 if ( secondary ) 0770 widget_current_style->set_stroke_ref(sty); 0771 else 0772 widget_current_style->set_fill_ref(sty); 0773 0774 if ( qobject_cast<model::Gradient*>(sty) ) 0775 sty = nullptr; 0776 0777 if ( secondary ) 0778 secondary_brush = sty; 0779 else 0780 main_brush = sty; 0781 0782 style_change_event(); 0783 } 0784 0785 void GlaxnimateWindow::Private::style_change_event() 0786 { 0787 if ( active_tool ) 0788 active_tool->shape_style_change_event({ui.canvas, &scene, parent}); 0789 } 0790 0791 0792 void GlaxnimateWindow::Private::import_file() 0793 { 0794 io::Options options = current_document->io_options(); 0795 QString path = app::settings::get<QString>("open_save", "import_path"); 0796 if ( !path.isEmpty() ) 0797 options.path.setPath(path); 0798 0799 ImportExportDialog dialog(options, ui.centralwidget->parentWidget()); 0800 if ( dialog.import_dialog() ) 0801 { 0802 options = dialog.io_options(); 0803 app::settings::set("open_save", "import_path", options.path.path()); 0804 import_file(options); 0805 } 0806 0807 } 0808 0809 void GlaxnimateWindow::Private::import_file(const io::Options& options) 0810 { 0811 QFile file(options.filename); 0812 import_file(&file, options); 0813 } 0814 0815 void GlaxnimateWindow::Private::import_file(QIODevice* file, const io::Options& options) 0816 { 0817 model::Document imported(options.filename); 0818 0819 dialog_export_status->reset(options.format, options.filename); 0820 auto settings = options.settings; 0821 settings["default_time"] = comp->animation->last_frame.get(); 0822 bool ok = options.format->open(*file, options.filename, &imported, settings); 0823 if ( !ok ) 0824 { 0825 show_warning(i18n("Import File"), i18n("Could not import %1", options.filename)); 0826 return; 0827 } 0828 0829 /// \todo ask if comp 0830 parent->paste_document(&imported, i18n("Import File"), true); 0831 0832 load_pending(); 0833 } 0834 0835 void GlaxnimateWindow::Private::import_file(const QString& filename, const QVariantMap& settings) 0836 { 0837 QFileInfo finfo(filename); 0838 io::Options opts; 0839 opts.settings = settings; 0840 opts.format = io::IoRegistry::instance().from_extension(finfo.suffix(), io::ImportExport::Import); 0841 if ( !opts.format ) 0842 show_warning(i18n("Import File"), i18n("Could not import %1", filename)); 0843 opts.filename = filename; 0844 opts.path = finfo.dir(); 0845 import_file(opts); 0846 } 0847 0848 void GlaxnimateWindow::Private::ipc_connect(const QString &name) 0849 { 0850 ipc_socket = qobject_make_unique<QLocalSocket>(); 0851 ipc_stream = std::make_unique<QDataStream>(ipc_socket.get()); 0852 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 0853 ipc_stream->setVersion(QDataStream::Qt_5_15); 0854 QObject::connect(ipc_socket.get(), &QLocalSocket::errorOccurred, parent, &GlaxnimateWindow::ipc_error); 0855 #endif 0856 QObject::connect(ipc_socket.get(), &QLocalSocket::readyRead, parent, &GlaxnimateWindow::ipc_read); 0857 ipc_socket->connectToServer(name); 0858 ipc_memory = std::make_unique<QSharedMemory>(name); 0859 } 0860 0861 static void on_font_loader_finished(glaxnimate::gui::font::FontLoader* loader) 0862 { 0863 if ( !loader->fonts().empty() ) 0864 { 0865 auto document = static_cast<glaxnimate::model::Document*>(loader->parent()); 0866 bool clear = document->undo_stack().count() == 0; 0867 0868 glaxnimate::command::UndoMacroGuard guard(i18n("Download fonts"), document); 0869 for ( const auto& font : loader->fonts() ) 0870 document->assets()->add_font(font); 0871 guard.finish(); 0872 0873 if ( clear ) 0874 document->undo_stack().clear(); 0875 } 0876 loader->deleteLater(); 0877 } 0878 0879 void glaxnimate::gui::GlaxnimateWindow::Private::load_pending() 0880 { 0881 auto font_loader = new glaxnimate::gui::font::FontLoader(); 0882 font_loader->setParent(current_document.get()); 0883 connect( 0884 font_loader, &gui::font::FontLoader::error, parent, 0885 [](const QString& msg){ app::log::Log("Font Loader").log(msg); } 0886 ); 0887 font_loader->queue_pending(current_document.get()); 0888 connect( 0889 font_loader, &gui::font::FontLoader::finished, current_document.get(), 0890 [font_loader]{on_font_loader_finished(font_loader);} 0891 ); 0892 font_loader->load_queue(); 0893 } 0894 0895 void glaxnimate::gui::GlaxnimateWindow::Private::load_remote_document(const QUrl& url, io::Options options, bool open) 0896 { 0897 auto reply = http.get(QNetworkRequest(url)); 0898 connect(reply, &QNetworkReply::finished, parent, [this, options, open, reply]() { 0899 if ( reply->error() ) 0900 { 0901 auto code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); 0902 QString message; 0903 if ( code.isValid() ) 0904 { 0905 auto reason = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); 0906 message = i18n("HTTP Error %1 %2", code.toInt(), reason); 0907 } 0908 else 0909 { 0910 message = i18n("Network Error"); 0911 } 0912 0913 show_warning(i18n("Load URL"), i18n("Could not load %1: %2", reply->url().toString(), message)); 0914 return; 0915 } 0916 0917 if ( open ) 0918 setup_document_open(reply, options, false); 0919 else 0920 import_file(reply, options); 0921 }); 0922 } 0923 0924 void glaxnimate::gui::GlaxnimateWindow::Private::check_autosaves() 0925 { 0926 auto stale = KAutoSaveFile::allStaleFiles(); 0927 if ( stale.empty() ) 0928 return; 0929 0930 WindowMessageWidget::Message msg{ 0931 i18n("There are %1 auto-save files that can be restored.", stale.size()), 0932 KMessageWidget::Information 0933 }; 0934 0935 msg.add_action(QIcon::fromTheme("dialog-cancel"), i18n("Ignore")); 0936 0937 msg.add_action( 0938 QIcon::fromTheme("document-revert"), 0939 i18n("Load Backup..."), 0940 parent, 0941 [this, stale]{ show_stale_autosave_list(stale); } 0942 ); 0943 0944 ui.message_widget->queue_message(std::move(msg)); 0945 } 0946 0947 void glaxnimate::gui::GlaxnimateWindow::Private::show_stale_autosave_list(const QList<KAutoSaveFile*>& stale) 0948 { 0949 StalefilesDialog dialog(stale, parent); 0950 if ( dialog.exec() == QDialog::Accepted && dialog.selected() ) 0951 { 0952 dialog.cleanup(dialog.selected()); 0953 load_backup(dialog.selected(), true); 0954 } 0955 else 0956 { 0957 dialog.cleanup(nullptr); 0958 } 0959 }