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 }