File indexing completed on 2025-02-02 04:11:22

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.hpp"
0008 #include "glaxnimate_window_p.hpp"
0009 
0010 #include <QDesktopServices>
0011 #include <QCloseEvent>
0012 #include <QDragEnterEvent>
0013 #include <QLocalSocket>
0014 #include <QDataStream>
0015 #include <QSharedMemory>
0016 
0017 #include "app/widgets/settings_dialog.hpp"
0018 #include "app_info.hpp"
0019 #include "settings/clipboard_settings.hpp"
0020 #include "export_image_sequence_dialog.hpp"
0021 
0022 
0023 GlaxnimateWindow::GlaxnimateWindow(bool restore_state, bool debug, QWidget *parent, Qt::WindowFlags flags)
0024     : KXmlGuiWindow(parent, flags), d(std::make_unique<Private>())
0025 {
0026     d->setupUi(restore_state, debug, this);
0027     d->setup_document_new(i18n("New Animation"));
0028     d->autosave_timer_start();
0029 }
0030 
0031 GlaxnimateWindow::~GlaxnimateWindow() = default;
0032 
0033 
0034 void GlaxnimateWindow::changeEvent(QEvent *e)
0035 {
0036     KXmlGuiWindow::changeEvent(e);
0037     switch ( e->type() )
0038     {
0039         case QEvent::LanguageChange:
0040             d->retranslateUi(this);
0041             break;
0042         default:
0043             break;
0044     }
0045 }
0046 
0047 void GlaxnimateWindow::document_new()
0048 {
0049     d->setup_document_new(i18n("New Animation"));
0050 }
0051 
0052 void GlaxnimateWindow::document_save()
0053 {
0054     if ( d->save_document(false, false) )
0055         d->status_message(i18n("File saved"));
0056     else
0057         d->status_message(i18n("Could not save file"), 0);
0058 }
0059 
0060 void GlaxnimateWindow::document_save_as()
0061 {
0062     if ( d->save_document(true, false) )
0063         d->status_message(i18n("File saved"));
0064     else
0065         d->status_message(i18n("Could not save file"), 0);
0066 }
0067 
0068 void GlaxnimateWindow::document_export()
0069 {
0070     if ( d->save_document(false, true) )
0071         d->status_message(i18n("File exported"));
0072     else
0073         d->status_message(i18n("Could not export file"), 0);
0074 }
0075 
0076 void GlaxnimateWindow::document_export_as()
0077 {
0078     if ( d->save_document(true, true) )
0079         d->status_message(i18n("File exported"));
0080     else
0081         d->status_message(i18n("Could not export file"), 0);
0082 }
0083 
0084 void GlaxnimateWindow::document_export_sequence()
0085 {
0086     ExportImageSequenceDialog(d->comp, d->export_options.path, this).exec();
0087 }
0088 
0089 void GlaxnimateWindow::document_open_dialog()
0090 {
0091     d->document_open();
0092 }
0093 
0094 void GlaxnimateWindow::document_treeview_current_changed(model::VisualNode* index)
0095 {
0096     d->set_current_object(index);
0097 }
0098 
0099 
0100 void GlaxnimateWindow::layer_new_menu()
0101 {
0102     d->layer_new_layer();
0103 }
0104 
0105 void GlaxnimateWindow::refresh_title()
0106 {
0107     d->refresh_title();
0108 }
0109 
0110 void GlaxnimateWindow::layer_delete()
0111 {
0112     d->layer_delete();
0113 }
0114 
0115 void GlaxnimateWindow::layer_duplicate()
0116 {
0117     d->layer_duplicate();
0118 }
0119 
0120 
0121 void GlaxnimateWindow::view_fit()
0122 {
0123     d->view_fit();
0124 }
0125 
0126 void GlaxnimateWindow::showEvent(QShowEvent * event)
0127 {
0128     KXmlGuiWindow::showEvent(event);
0129     if ( !d->started )
0130     {
0131         d->started = true;
0132         d->view_fit();
0133     }
0134 }
0135 
0136 void GlaxnimateWindow::preferences()
0137 {
0138     app::SettingsDialog(this).exec();
0139     d->autosave_timer_load_settings();
0140 }
0141 
0142 void GlaxnimateWindow::closeEvent ( QCloseEvent* event )
0143 {
0144     if ( !d->close_document() )
0145     {
0146         event->ignore();
0147     }
0148     else
0149     {
0150         d->shutdown();
0151         KXmlGuiWindow::closeEvent(event);
0152         event->accept();
0153         qApp->quit();
0154     }
0155 }
0156 
0157 void GlaxnimateWindow::document_open_recent(QAction* action)
0158 {
0159     d->document_open_from_filename(action->data().toString());
0160 }
0161 
0162 void GlaxnimateWindow::help_about()
0163 {
0164     d->help_about();
0165 }
0166 
0167 void GlaxnimateWindow::document_open(const QString& filename)
0168 {
0169     d->document_open_from_filename(filename);
0170 }
0171 
0172 void GlaxnimateWindow::document_open_settings(const QString& filename, const QVariantMap& settings)
0173 {
0174     d->document_open_from_filename(filename, settings);
0175 }
0176 
0177 void GlaxnimateWindow::document_reload()
0178 {
0179     d->document_reload();
0180 }
0181 
0182 model::Document * GlaxnimateWindow::document() const
0183 {
0184     return d->current_document.get();
0185 }
0186 
0187 void GlaxnimateWindow::warning ( const QString& message, const QString& title ) const
0188 {
0189     d->show_warning(title.isEmpty() ? i18n("Warning") : title, message);
0190 }
0191 
0192 void GlaxnimateWindow::status ( const QString& message ) const
0193 {
0194     d->status_message(message);
0195 }
0196 
0197 QVariant GlaxnimateWindow::choose_option(const QString& label, const QVariantMap& options, const QString& title) const
0198 {
0199     return d->choose_option(label, options, title);
0200 }
0201 
0202 void GlaxnimateWindow::save_frame_bmp()
0203 {
0204     d->save_frame_bmp();
0205 }
0206 
0207 void GlaxnimateWindow::save_frame_svg()
0208 {
0209     d->save_frame_svg();
0210 }
0211 
0212 void GlaxnimateWindow::document_treeview_selection_changed(const std::vector<model::VisualNode*>& selected, const std::vector<model::VisualNode*>& deselected)
0213 {
0214     d->selection_changed(selected, deselected);
0215 }
0216 
0217 void GlaxnimateWindow::scene_selection_changed(const std::vector<model::VisualNode*>& selected, const std::vector<model::VisualNode*>& deselected)
0218 {
0219     d->scene_selection_changed(selected, deselected);
0220 }
0221 
0222 void GlaxnimateWindow::tool_triggered(bool checked)
0223 {
0224     if ( checked )
0225         d->switch_tool_action(static_cast<QAction*>(sender()));
0226 }
0227 
0228 void GlaxnimateWindow::help_manual()
0229 {
0230     QDesktopServices::openUrl(AppInfo::instance().url_docs());
0231 }
0232 
0233 void GlaxnimateWindow::help_issue()
0234 {
0235     QDesktopServices::openUrl(AppInfo::instance().url_issues());
0236 }
0237 
0238 void GlaxnimateWindow::help_donate()
0239 {
0240     QDesktopServices::openUrl(AppInfo::instance().url_donate());
0241 }
0242 
0243 model::Composition * GlaxnimateWindow::current_composition() const
0244 {
0245     return d->current_composition();
0246 }
0247 
0248 model::VisualNode * GlaxnimateWindow::current_document_node() const
0249 {
0250     return d->current_document_node();
0251 }
0252 
0253 QColor GlaxnimateWindow::current_color() const
0254 {
0255     return d->ui.fill_style_widget->current_color();
0256 }
0257 
0258 QColor GlaxnimateWindow::secondary_color() const
0259 {
0260     return d->ui.fill_style_widget->secondary_color();
0261 }
0262 
0263 void GlaxnimateWindow::set_current_color(const QColor& c)
0264 {
0265     d->ui.fill_style_widget->set_current_color(c);
0266 }
0267 
0268 void GlaxnimateWindow::set_secondary_color(const QColor& c)
0269 {
0270     d->ui.stroke_style_widget->set_color(c);
0271 }
0272 
0273 model::Object * GlaxnimateWindow::current_shape_container_script()
0274 {
0275     auto prop = current_shape_container();
0276     return prop ? prop->object() : nullptr;
0277 }
0278 
0279 void GlaxnimateWindow::set_current_document_node(model::VisualNode* node)
0280 {
0281     d->set_current_document_node(node);
0282 }
0283 
0284 QPen GlaxnimateWindow::current_pen_style() const
0285 {
0286     return d->ui.stroke_style_widget->pen_style();
0287 }
0288 
0289 std::vector<model::VisualNode*> GlaxnimateWindow::cleaned_selection() const
0290 {
0291     return d->cleaned_selection();
0292 }
0293 
0294 void GlaxnimateWindow::copy() const
0295 {
0296     SelectionManager::copy();
0297 }
0298 
0299 void GlaxnimateWindow::paste()
0300 {
0301     SelectionManager::paste();
0302 }
0303 
0304 void GlaxnimateWindow::cut()
0305 {
0306     SelectionManager::cut();
0307 }
0308 
0309 void GlaxnimateWindow::delete_selected()
0310 {
0311     SelectionManager::delete_selected();
0312 }
0313 
0314 void GlaxnimateWindow::duplicate_selection() const
0315 {
0316     d->duplicate_selection();
0317 }
0318 
0319 void GlaxnimateWindow::layer_top()
0320 {
0321     d->move_current(command::ReorderCommand::MoveTop);
0322 }
0323 
0324 void GlaxnimateWindow::layer_raise()
0325 {
0326     d->move_current(command::ReorderCommand::MoveUp);
0327 }
0328 
0329 void GlaxnimateWindow::layer_lower()
0330 {
0331     d->move_current(command::ReorderCommand::MoveDown);
0332 }
0333 
0334 void GlaxnimateWindow::layer_bottom()
0335 {
0336     d->move_current(command::ReorderCommand::MoveBottom);
0337 }
0338 
0339 item_models::DocumentNodeModel * GlaxnimateWindow::model() const
0340 {
0341     return &d->document_node_model;
0342 }
0343 
0344 void GlaxnimateWindow::group_shapes()
0345 {
0346     d->group_shapes();
0347 }
0348 
0349 void GlaxnimateWindow::ungroup_shapes()
0350 {
0351     d->ungroup_shapes();
0352 }
0353 
0354 void GlaxnimateWindow::move_to()
0355 {
0356     d->move_to();
0357 }
0358 
0359 void GlaxnimateWindow::validate_tgs()
0360 {
0361     d->validate_tgs();
0362 }
0363 
0364 qreal GlaxnimateWindow::current_zoom() const
0365 {
0366     return d->ui.canvas->get_zoom_factor();
0367 }
0368 
0369 void GlaxnimateWindow::timerEvent(QTimerEvent*)
0370 {
0371     d->autosave(false);
0372 }
0373 
0374 void GlaxnimateWindow::dragEnterEvent(QDragEnterEvent* event)
0375 {
0376     if ( !d->drop_event_data(event).isEmpty() )
0377         event->acceptProposedAction();
0378 }
0379 
0380 void GlaxnimateWindow::dragLeaveEvent(QDragLeaveEvent* event)
0381 {
0382     event->accept();
0383 }
0384 
0385 void GlaxnimateWindow::dragMoveEvent(QDragMoveEvent* event)
0386 {
0387     if ( !d->drop_event_data(event).isEmpty() )
0388         event->acceptProposedAction();
0389 }
0390 
0391 void GlaxnimateWindow::dropEvent(QDropEvent* event)
0392 {
0393     auto str = d->drop_event_data(event);
0394     if ( !str.isEmpty() )
0395     {
0396         d->drop_file(str);
0397     }
0398 }
0399 
0400 void GlaxnimateWindow::switch_tool(tools::Tool* tool)
0401 {
0402     d->switch_tool(tool);
0403 }
0404 
0405 QString GlaxnimateWindow::get_open_image_file(const QString& title, const QString& dir) const
0406 {
0407     return d->get_open_image_files(title, dir)[0];
0408 }
0409 
0410 model::BrushStyle * GlaxnimateWindow::linked_brush_style ( bool secondary ) const
0411 {
0412     if ( secondary )
0413         return d->secondary_brush;
0414     return d->main_brush;
0415 }
0416 
0417 PluginUiDialog * GlaxnimateWindow::create_dialog(const QString& ui_file) const
0418 {
0419     return d->ui.console->create_dialog(ui_file);
0420 }
0421 
0422 void GlaxnimateWindow::trace_dialog(model::DocumentNode* object)
0423 {
0424     return d->trace_dialog(object);
0425 }
0426 
0427 void GlaxnimateWindow::shape_to_composition(model::ShapeElement* node)
0428 {
0429     return d->shape_to_composition(node);
0430 }
0431 
0432 void GlaxnimateWindow::set_current_composition(model::Composition* comp)
0433 {
0434     d->ui.tab_bar->set_current_composition(comp);
0435 }
0436 
0437 QMenu * GlaxnimateWindow::create_layer_menu() const
0438 {
0439     return d->ui.menu_new_layer;
0440 }
0441 
0442 void GlaxnimateWindow::select(const std::vector<model::VisualNode*>& nodes)
0443 {
0444     d->scene.user_select(nodes, graphics::DocumentScene::Replace);
0445 }
0446 
0447 void GlaxnimateWindow::switch_composition(model::Composition* comp, int index)
0448 {
0449     d->switch_composition(comp, index);
0450 }
0451 
0452 void GlaxnimateWindow::ipc_connect(const QString &name)
0453 {
0454     d->ipc_connect(name);
0455 }
0456 
0457 void GlaxnimateWindow::ipc_signal_connections(bool enable)
0458 {
0459     if (enable) {
0460         connect(document(), &model::Document::current_time_changed, this, &GlaxnimateWindow::ipc_write_time);
0461         connect(&d->scene, &graphics::DocumentScene::drawing_background, this, &GlaxnimateWindow::ipc_draw_background);
0462     } else {
0463         disconnect(document(), &model::Document::current_time_changed, this, &GlaxnimateWindow::ipc_write_time);
0464         disconnect(&d->scene, &graphics::DocumentScene::drawing_background, this, &GlaxnimateWindow::ipc_draw_background);
0465     }
0466 }
0467 
0468 void GlaxnimateWindow::ipc_error(QLocalSocket::LocalSocketError socketError)
0469 {
0470     auto name = d->ipc_socket? d->ipc_socket->serverName() : "???";
0471     switch (socketError) {
0472     case QLocalSocket::ServerNotFoundError:
0473         app::log::Log("ipc").stream(app::log::Warning) << "IPC server not found:" << name;
0474         break;
0475     case QLocalSocket::ConnectionRefusedError:
0476         app::log::Log("ipc").stream(app::log::Warning) << "IPC server refused connection:" << name;
0477         break;
0478     case QLocalSocket::PeerClosedError:
0479         app::log::Log("ipc").stream(app::log::Info) << "IPC server closed the connection:" << name;
0480         d->ipc_socket.reset();
0481         d->ipc_memory.reset();
0482         break;
0483     default:
0484         app::log::Log("ipc").stream(app::log::Warning) << "IPC error:" << d->ipc_socket->errorString();
0485     }
0486 }
0487 
0488 void GlaxnimateWindow::ipc_read()
0489 {
0490     while (d->ipc_stream && !d->ipc_stream->atEnd()) {
0491         QString message;
0492         *d->ipc_stream >> message;
0493 
0494         // Here is the receive/read side of the IPC protocol - a few commands:
0495 
0496         if (message == "hello") {
0497             // handshake
0498             *d->ipc_stream << QString("version 1");
0499             d->ipc_socket->flush();
0500             ipc_signal_connections(true);
0501             ipc_write_time(document()->current_time());
0502         } else if (message == "redraw") {
0503             d->ui.canvas->viewport()->update();
0504         } else if (message == "clear") {
0505             // The server sends this when video should no longer be in the background.
0506             ipc_signal_connections(false);
0507         } else if (message.startsWith("open ")) {
0508             // open a different document than command line option
0509             document_open(message.mid(5));
0510             ipc_signal_connections(true);
0511             ipc_write_time(document()->current_time());
0512         } else if (message == "bye") {
0513             d->ipc_socket->disconnectFromServer();
0514         } else {
0515             app::log::Log("ipc").stream(app::log::Info) << "IPC server sent unknown command:" << message;
0516         }
0517     }
0518 }
0519 
0520 void GlaxnimateWindow::ipc_write_time(model::FrameTime t)
0521 {
0522     // Here is the send/write side of the IPC protocol: binary time updates
0523 
0524     if (d->ipc_stream && d->ipc_socket && d->ipc_socket->isOpen() && d->ipc_stream->atEnd()) {
0525         if (d->ipc_stream->status() != QDataStream::Ok) {
0526             d->ipc_stream->resetStatus();
0527         }
0528         *d->ipc_stream << t;
0529         d->ipc_socket->flush();
0530     }
0531 }
0532 
0533 void GlaxnimateWindow::ipc_draw_background(QPainter *painter)
0534 {
0535     // This is the shared memory portion of the IPC: a single QImage
0536     if (d->ipc_memory && d->ipc_memory->attach(QSharedMemory::ReadOnly)) {
0537         d->ipc_memory->lock();
0538 
0539         uchar *from = (uchar *) d->ipc_memory->data();
0540         // Get the width of the image and move the pointer forward
0541         qint32 image_width = *(qint32 *)from;
0542         from += sizeof(image_width);
0543 
0544         // Get the height of the image and move the pointer forward
0545         qint32 image_height = *(qint32 *)from;
0546         from += sizeof(image_height);
0547 
0548         // Get the image format of the image and move the pointer forward
0549         qint32 image_format = *(qint32 *)from;
0550         from += sizeof(image_format);
0551 
0552         // Get the bytes per line of the image and move the pointer forward
0553         qint32 bytes_per_line = *(qint32 *)from;
0554         from += sizeof(bytes_per_line);
0555 
0556         // Generate an image using the raw data and move the pointer forward
0557         QImage image = QImage(from, image_width, image_height, bytes_per_line, QImage::Format(image_format)).copy();
0558 
0559         d->ipc_memory->unlock();
0560         d->ipc_memory->detach();
0561 
0562         qreal image_aspect = qreal(image.width()) / image.height();
0563         qreal document_aspect = qreal(d->comp->width.get()) / d->comp->height.get();
0564         qreal width, height;
0565         QRectF draw_rect;
0566         if (image_aspect >= document_aspect) {
0567             height = d->comp->height.get();
0568             width = image_aspect * height;
0569             draw_rect = {(d->comp->width.get() - width) / 2.0, 0.0, width, height};
0570         } else {
0571             width = d->comp->width.get();
0572             height = width / image_aspect;
0573             draw_rect = {0.0, (d->comp->height.get() - height) / 2.0, width, height};
0574         }
0575         painter->drawImage(draw_rect, image);
0576     }
0577 }
0578 
0579 std::vector<model::ShapeElement *> GlaxnimateWindow::convert_to_path(const std::vector<model::ShapeElement *>& shapes)
0580 {
0581     std::vector<model::ShapeElement *> out;
0582     d->convert_to_path(shapes, &out);
0583     return out;
0584 }
0585 
0586 model::ShapeElement * GlaxnimateWindow::convert_to_path(model::ShapeElement* shape)
0587 {
0588     return convert_to_path(std::vector<model::ShapeElement *>{shape})[0];
0589 }
0590 
0591 void GlaxnimateWindow::show_startup_dialog()
0592 {
0593     d->show_startup_dialog();
0594 }
0595 
0596 std::vector<io::mime::MimeSerializer*> GlaxnimateWindow::supported_mimes() const
0597 {
0598     std::vector<io::mime::MimeSerializer*> mimes;
0599     for ( const auto& mime : settings::ClipboardSettings::mime_types() )
0600     {
0601         if ( mime.enabled )
0602             mimes.push_back(mime.serializer);
0603     }
0604     return mimes;
0605 }
0606 
0607 void GlaxnimateWindow::set_selection(const std::vector<model::VisualNode*>& selected)
0608 {
0609     d->scene.user_select(selected, graphics::DocumentScene::Replace);
0610 }
0611 
0612 void GlaxnimateWindow::update_selection(const std::vector<model::VisualNode*>& selected, const std::vector<model::VisualNode*>& deselected)
0613 {
0614     return d->selection_changed(selected, deselected);
0615 }
0616 
0617 void GlaxnimateWindow::check_autosaves()
0618 {
0619     d->check_autosaves();
0620 }