File indexing completed on 2025-02-02 04:11:25
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 <QComboBox> 0010 #include <QLabel> 0011 #include <QScrollBar> 0012 #include <QInputDialog> 0013 #include <QLabel> 0014 #include <QPushButton> 0015 #include <QActionGroup> 0016 #include <QScreen> 0017 #include <QDialogButtonBox> 0018 0019 #include <KHelpMenu> 0020 #include <KActionCollection> 0021 #include <KShortcutsDialog> 0022 0023 #include "app/settings/keyboard_shortcuts.hpp" 0024 0025 #include "tools/base.hpp" 0026 #include "model/shapes/group.hpp" 0027 #include "model/shapes/image.hpp" 0028 #include "model/shapes/repeater.hpp" 0029 #include "model/shapes/trim.hpp" 0030 #include "model/shapes/inflate_deflate.hpp" 0031 #include "model/shapes/round_corners.hpp" 0032 #include "model/shapes/offset_path.hpp" 0033 #include "model/shapes/zig_zag.hpp" 0034 #include "io/io_registry.hpp" 0035 0036 #include "widgets/dialogs/io_status_dialog.hpp" 0037 #include "widgets/dialogs/about_dialog.hpp" 0038 #include "widgets/dialogs/resize_dialog.hpp" 0039 #include "widgets/dialogs/timing_dialog.hpp" 0040 #include "widgets/dialogs/document_metadata_dialog.hpp" 0041 #include "widgets/dialogs/trace_dialog.hpp" 0042 #include "widgets/dialogs/startup_dialog.hpp" 0043 #include "widgets/lottiefiles/lottiefiles_search_dialog.hpp" 0044 0045 #include "widgets/view_transform_widget.hpp" 0046 #include "widgets/flow_layout.hpp" 0047 #include "widgets/menus/node_menu.hpp" 0048 #include "widgets/shape_style/shape_style_preview_widget.hpp" 0049 0050 #include "style/better_elide_delegate.hpp" 0051 #include "tools/edit_tool.hpp" 0052 #include "plugin/action.hpp" 0053 #include "glaxnimate_app.hpp" 0054 #include "settings/toolbar_settings.hpp" 0055 #include "settings/document_templates.hpp" 0056 #include "emoji/emoji_set_dialog.hpp" 0057 0058 using namespace glaxnimate::gui; 0059 0060 0061 static QToolButton* action_button(QAction* action, QWidget* parent) 0062 { 0063 auto button = new ScalableButton(parent); 0064 button->setDefaultAction(action); 0065 button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 0066 button->resize(16, 16); 0067 button->setMaximumSize(64, 64); 0068 return button; 0069 } 0070 0071 static void action_combo(QComboBox* box, QAction* action) 0072 { 0073 int index = box->count(); 0074 box->addItem(action->icon(), action->text(), QVariant::fromValue(action)); 0075 QObject::connect(action, &QAction::triggered, box, [index, box]{ 0076 box->setCurrentIndex(index); 0077 }); 0078 QObject::connect(action, &QAction::changed, box, [index, box, action]{ 0079 box->setItemIcon(index, action->icon()); 0080 box->setItemText(index, action->text()); 0081 }); 0082 } 0083 0084 void GlaxnimateWindow::Private::setupUi(bool restore_state, bool debug, GlaxnimateWindow* parent) 0085 { 0086 this->parent = parent; 0087 parent->createGUI(); 0088 ui.setupUi(parent); 0089 0090 init_actions(); 0091 0092 tools::Tool* to_activate = init_tools_ui(); 0093 0094 init_item_views(); 0095 0096 // Tool buttons 0097 ui.btn_layer_add->setMenu(ui.menu_new_layer); 0098 0099 init_status_bar(); 0100 0101 // Graphics scene 0102 ui.canvas->setScene(&scene); 0103 ui.canvas->set_tool_target(parent); 0104 connect(&scene, &graphics::DocumentScene::node_user_selected, parent, &GlaxnimateWindow::scene_selection_changed); 0105 connect(ui.canvas, &Canvas::dropped, parent, [this](const QMimeData* d){dropped(d);}); 0106 0107 // Dialogs 0108 dialog_import_status = new IoStatusDialog(QIcon::fromTheme("document-open"), i18n("Open File"), false, parent); 0109 dialog_export_status = new IoStatusDialog(QIcon::fromTheme("document-save"), i18n("Save File"), false, parent); 0110 about_dialog = new AboutDialog(parent); 0111 0112 // Only enabled on debug because it isn't fully integrated yet 0113 if ( debug ) 0114 { 0115 KHelpMenu *help_menu = new KHelpMenu(parent); 0116 ui.menu_bar->addMenu(help_menu->menu()); 0117 connect(help_menu, &KHelpMenu::showAboutApplication, about_dialog, &QWidget::show); 0118 } 0119 0120 // Recent files 0121 recent_files = app::settings::get<QStringList>("open_save", "recent_files"); 0122 ui.action_open_last->setEnabled(!recent_files.isEmpty()); 0123 reload_recent_menu(); 0124 connect(ui.menu_open_recent, &QMenu::triggered, parent, &GlaxnimateWindow::document_open_recent); 0125 0126 // Docks 0127 init_docks(); 0128 0129 // Menus 0130 init_menus(); 0131 0132 // Debug menu 0133 if ( debug ) 0134 init_debug(); 0135 0136 // Initialize tools 0137 init_tools(to_activate); 0138 0139 // Restore state 0140 // NOTE: keep at the end so we do this once all the widgets are in their default spots 0141 if ( restore_state ) 0142 init_restore_state(); 0143 0144 0145 // Toolbar settings 0146 parent->setToolButtonStyle(settings::ToolbarSettingsGroup::button_style); 0147 parent->setIconSize(settings::ToolbarSettingsGroup::icon_size()); 0148 ui.toolbar_tools->setIconSize(settings::ToolbarSettingsGroup::tool_icon_size()); 0149 } 0150 0151 template<class T> 0152 void GlaxnimateWindow::Private::add_modifier_menu_action(QMenu* menu) 0153 { 0154 menu->addAction(T::static_tree_icon(), T::static_type_name_human(), [this]{ 0155 auto layer = std::make_unique<T>(current_document.get()); 0156 parent->layer_new_impl(std::move(layer)); 0157 })->setObjectName("action_new_" + T::static_class_name().toLower()); 0158 } 0159 0160 void GlaxnimateWindow::Private::init_actions() 0161 { 0162 // Standard Shortcuts 0163 ui.action_new->setShortcut(QKeySequence::New); 0164 ui.action_open->setShortcut(QKeySequence::Open); 0165 ui.action_close->setShortcut(QKeySequence::Close); 0166 ui.action_reload->setShortcut(QKeySequence("Ctrl+F5", QKeySequence::PortableText)); 0167 ui.action_save->setShortcut(QKeySequence::Save); 0168 ui.action_save_as->setShortcut(QKeySequence::SaveAs); 0169 ui.action_quit->setShortcut(QKeySequence::Quit); 0170 ui.action_copy->setShortcut(QKeySequence::Copy); 0171 ui.action_cut->setShortcut(QKeySequence::Cut); 0172 ui.action_paste->setShortcut(QKeySequence::Paste); 0173 ui.action_paste_as_composition->setShortcut(QKeySequence("Ctrl+Shift+V", QKeySequence::PortableText)); 0174 ui.action_select_all->setShortcut(QKeySequence::SelectAll); 0175 ui.action_undo->setShortcut(QKeySequence::Undo); 0176 ui.action_redo->setShortcut(QKeySequence::Redo); 0177 ui.action_group->setShortcut(QKeySequence("Ctrl+G", QKeySequence::PortableText)); 0178 ui.action_ungroup->setShortcut(QKeySequence("Ctrl+Shift+G", QKeySequence::PortableText)); 0179 ui.action_open_last->setShortcut(QKeySequence("Ctrl+Shift+O", QKeySequence::PortableText)); 0180 ui.action_import_image->setShortcut(QKeySequence("Ctrl+I", QKeySequence::PortableText)); 0181 ui.action_import->setShortcut(QKeySequence("Ctrl+Shift+I", QKeySequence::PortableText)); 0182 ui.action_node_remove->setShortcut(QKeySequence("Del", QKeySequence::PortableText)); 0183 ui.action_delete->setShortcut(QKeySequence("Del", QKeySequence::PortableText)); 0184 ui.action_export->setShortcut(QKeySequence("Ctrl+E", QKeySequence::PortableText)); 0185 ui.action_export_as->setShortcut(QKeySequence("Ctrl+Shift+E", QKeySequence::PortableText)); 0186 ui.action_frame_prev->setShortcut(QKeySequence("Left", QKeySequence::PortableText)); 0187 ui.action_frame_next->setShortcut(QKeySequence("Right", QKeySequence::PortableText)); 0188 ui.action_duplicate->setShortcut(QKeySequence("Ctrl+D", QKeySequence::PortableText)); 0189 ui.action_play->setShortcut(QKeySequence("Space", QKeySequence::PortableText)); 0190 0191 // Actions 0192 connect(ui.action_copy, &QAction::triggered, parent, &GlaxnimateWindow::copy); 0193 connect(ui.action_paste, &QAction::triggered, parent, &GlaxnimateWindow::paste); 0194 connect(ui.action_paste_as_composition, &QAction::triggered, parent, [this]{parent->paste_as_composition();}); 0195 connect(ui.action_cut, &QAction::triggered, parent, &GlaxnimateWindow::cut); 0196 connect(ui.action_duplicate, &QAction::triggered, parent, &GlaxnimateWindow::duplicate_selection); 0197 connect(ui.action_reload, &QAction::triggered, parent, &GlaxnimateWindow::document_reload); 0198 connect(ui.action_raise_to_top, &QAction::triggered, parent, &GlaxnimateWindow::layer_top); 0199 connect(ui.action_raise, &QAction::triggered, parent, &GlaxnimateWindow::layer_raise); 0200 connect(ui.action_lower, &QAction::triggered, parent, &GlaxnimateWindow::layer_lower); 0201 connect(ui.action_lower_to_bottom, &QAction::triggered, parent, &GlaxnimateWindow::layer_bottom); 0202 connect(ui.action_group, &QAction::triggered, parent, &GlaxnimateWindow::group_shapes); 0203 connect(ui.action_ungroup, &QAction::triggered, parent, &GlaxnimateWindow::ungroup_shapes); 0204 connect(ui.action_quit, &QAction::triggered, parent, &GlaxnimateWindow::close); 0205 connect(ui.action_move_to, &QAction::triggered, parent, &GlaxnimateWindow::move_to); 0206 connect(ui.action_validate_tgs, &QAction::triggered, parent, &GlaxnimateWindow::validate_tgs); 0207 connect(ui.action_validate_discord, &QAction::triggered, parent, [this]{validate_discord();}); 0208 connect(ui.action_resize, &QAction::triggered, parent, [this]{ ResizeDialog(this->parent).resize_composition(comp); }); 0209 connect(ui.action_donate, &QAction::triggered, parent, &GlaxnimateWindow::help_donate); 0210 connect(ui.action_new_layer, &QAction::triggered, parent, [this]{layer_new_layer();}); 0211 connect(ui.action_new_group, &QAction::triggered, parent, [this]{layer_new_group();}); 0212 connect(ui.action_new_fill, &QAction::triggered, parent, [this]{layer_new_fill();}); 0213 connect(ui.action_new_stroke, &QAction::triggered, parent, [this]{layer_new_stroke();}); 0214 connect(ui.action_open_last, &QAction::triggered, parent, [this]{ 0215 if ( !recent_files.isEmpty() ) 0216 { 0217 // Avoid references to recent_files 0218 QString filename = recent_files[0]; 0219 document_open_from_filename(filename); 0220 } 0221 }); 0222 add_modifier_menu_action<model::Repeater>(ui.menu_new_layer); 0223 add_modifier_menu_action<model::Trim>(ui.menu_new_layer); 0224 add_modifier_menu_action<model::InflateDeflate>(ui.menu_new_layer); 0225 add_modifier_menu_action<model::RoundCorners>(ui.menu_new_layer); 0226 add_modifier_menu_action<model::OffsetPath>(ui.menu_new_layer); 0227 add_modifier_menu_action<model::ZigZag>(ui.menu_new_layer); 0228 connect(ui.action_import_image, &QAction::triggered, parent, [this]{import_image();}); 0229 connect(ui.action_delete, &QAction::triggered, parent, &GlaxnimateWindow::delete_selected); 0230 connect(ui.action_export, &QAction::triggered, parent, &GlaxnimateWindow::document_export); 0231 connect(ui.action_export_as, &QAction::triggered, parent, &GlaxnimateWindow::document_export_as); 0232 connect(ui.action_export_sequence, &QAction::triggered, parent, &GlaxnimateWindow::document_export_sequence); 0233 connect(ui.action_document_cleanup, &QAction::triggered, parent, [this]{cleanup_document();}); 0234 connect(ui.action_timing, &QAction::triggered, parent, [this]{ 0235 TimingDialog(comp, this->parent).exec(); 0236 }); 0237 0238 connect(ui.action_play, &QAction::triggered, ui.play_controls, &FrameControlsWidget::toggle_play); 0239 connect(ui.play_controls, &FrameControlsWidget::play_started, parent, [this]{ 0240 ui.action_play->setText(i18n("Pause")); 0241 ui.action_play->setIcon(QIcon::fromTheme("media-playback-pause")); 0242 }); 0243 connect(ui.play_controls, &FrameControlsWidget::play_stopped, parent, [this]{ 0244 ui.action_play->setText(i18n("Play")); 0245 ui.action_play->setIcon(QIcon::fromTheme("media-playback-start")); 0246 }); 0247 connect(ui.play_controls, &FrameControlsWidget::record_toggled, ui.action_record, &QAction::setChecked); 0248 connect(ui.action_record, &QAction::triggered, ui.play_controls, &FrameControlsWidget::set_record_enabled); 0249 connect(ui.action_frame_first, &QAction::triggered, ui.play_controls, &FrameControlsWidget::go_first); 0250 connect(ui.action_frame_prev, &QAction::triggered, ui.play_controls, &FrameControlsWidget::go_prev); 0251 connect(ui.action_frame_next, &QAction::triggered, ui.play_controls, &FrameControlsWidget::go_next); 0252 connect(ui.action_frame_last, &QAction::triggered, ui.play_controls, &FrameControlsWidget::go_last); 0253 connect(ui.play_controls, &FrameControlsWidget::loop_changed, ui.action_play_loop, &QAction::setChecked); 0254 connect(ui.action_play_loop, &QAction::triggered, ui.play_controls, &FrameControlsWidget::set_loop); 0255 0256 connect(ui.play_controls, &FrameControlsWidget::min_changed, ui.play_controls_2, &FrameControlsWidget::set_min); 0257 connect(ui.play_controls, &FrameControlsWidget::max_changed, ui.play_controls_2, &FrameControlsWidget::set_max); 0258 connect(ui.play_controls, &FrameControlsWidget::fps_changed, ui.play_controls_2, &FrameControlsWidget::set_fps); 0259 connect(ui.play_controls, &FrameControlsWidget::play_started, ui.play_controls_2, &FrameControlsWidget::play); 0260 connect(ui.play_controls, &FrameControlsWidget::play_stopped, ui.play_controls_2, &FrameControlsWidget::pause); 0261 connect(ui.play_controls, &FrameControlsWidget::loop_changed, ui.play_controls_2, &FrameControlsWidget::set_loop); 0262 connect(ui.play_controls, &FrameControlsWidget::frame_selected, ui.play_controls_2, &FrameControlsWidget::set_frame); 0263 connect(ui.play_controls_2, &FrameControlsWidget::play_started, ui.play_controls, &FrameControlsWidget::play); 0264 connect(ui.play_controls_2, &FrameControlsWidget::play_stopped, ui.play_controls, &FrameControlsWidget::pause); 0265 connect(ui.play_controls_2, &FrameControlsWidget::loop_changed, ui.play_controls, &FrameControlsWidget::set_loop); 0266 connect(ui.play_controls_2, &FrameControlsWidget::frame_selected, ui.play_controls, &FrameControlsWidget::set_frame); 0267 connect(ui.play_controls, &FrameControlsWidget::min_changed, ui.scroll_time, &QSlider::setMinimum); 0268 connect(ui.play_controls, &FrameControlsWidget::max_changed, ui.scroll_time, &QSlider::setMaximum); 0269 connect(ui.play_controls, &FrameControlsWidget::frame_selected, ui.scroll_time, &QSlider::setValue); 0270 connect(ui.scroll_time, &QSlider::valueChanged, ui.play_controls_2, &FrameControlsWidget::frame_selected); 0271 0272 connect(ui.action_metadata, &QAction::triggered, parent, [this]{ 0273 DocumentMetadataDialog(current_document.get(), this->parent).exec(); 0274 }); 0275 connect(ui.action_trace_bitmap, &QAction::triggered, parent, [this]{ 0276 trace_dialog(parent->current_shape()); 0277 }); 0278 connect(ui.action_object_to_path, &QAction::triggered, parent, [this]{to_path();}); 0279 connect(ui.action_lottie_preview, &QAction::triggered, parent, [this]{preview_lottie("svg");}); 0280 connect(ui.action_lottie_canvas_preview, &QAction::triggered, parent, [this]{preview_lottie("canvas");}); 0281 connect(ui.action_svg_preview, &QAction::triggered, parent, [this]{preview_svg();}); 0282 connect(ui.action_rive_preview, &QAction::triggered, parent, [this]{preview_rive();}); 0283 connect(ui.action_new_precomp, &QAction::triggered, parent, [this]{add_composition();}); 0284 connect(ui.action_new_precomp_selection, &QAction::triggered, parent, [this]{ 0285 objects_to_new_composition(comp, cleaned_selection(), &comp->shapes, -1); 0286 }); 0287 connect(ui.menu_new_comp_layer, &QMenu::triggered, parent, [this](QAction* act){layer_new_comp_action(act);}); 0288 connect(ui.action_align_hor_left_out, &QAction::triggered, parent, [this]{align(AlignDirection::Horizontal, AlignPosition::Begin, true);}); 0289 connect(ui.action_align_hor_left, &QAction::triggered, parent, [this]{align(AlignDirection::Horizontal, AlignPosition::Begin, false);}); 0290 connect(ui.action_align_hor_center, &QAction::triggered, parent, [this]{align(AlignDirection::Horizontal, AlignPosition::Center, false);}); 0291 connect(ui.action_align_hor_right, &QAction::triggered, parent, [this]{align(AlignDirection::Horizontal, AlignPosition::End, false);}); 0292 connect(ui.action_align_hor_right_out, &QAction::triggered, parent, [this]{align(AlignDirection::Horizontal, AlignPosition::End, true);}); 0293 connect(ui.action_align_vert_top_out, &QAction::triggered, parent, [this]{align(AlignDirection::Vertical, AlignPosition::Begin, true);}); 0294 connect(ui.action_align_vert_top, &QAction::triggered, parent, [this]{align(AlignDirection::Vertical, AlignPosition::Begin, false);}); 0295 connect(ui.action_align_vert_center, &QAction::triggered, parent, [this]{align(AlignDirection::Vertical, AlignPosition::Center, false);}); 0296 connect(ui.action_align_vert_bottom, &QAction::triggered, parent, [this]{align(AlignDirection::Vertical, AlignPosition::End, false);}); 0297 connect(ui.action_align_vert_bottom_out,&QAction::triggered, parent, [this]{align(AlignDirection::Vertical, AlignPosition::End, true);}); 0298 connect(ui.action_import, &QAction::triggered, parent, [this]{import_file();}); 0299 connect(ui.action_flip_view, &QAction::triggered, ui.canvas, &Canvas::flip_horizontal); 0300 connect(ui.action_text_put_on_path, &QAction::triggered, parent, [this]{text_put_on_path();}); 0301 connect(ui.action_text_remove_from_path, &QAction::triggered, parent, [this]{text_remove_from_path();}); 0302 connect(ui.action_insert_emoji, &QAction::triggered, parent, [this]{insert_emoji();}); 0303 connect(ui.action_open_lottiefiles, &QAction::triggered, parent, [this]{import_from_lottiefiles();}); 0304 connect(ui.action_shortcuts, &QAction::triggered, parent, [this]{ 0305 KShortcutsDialog::showDialog(parent->actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this->parent); 0306 }); 0307 0308 // Undo Redo 0309 QObject::connect(ui.action_redo, &QAction::triggered, &parent->undo_group(), &QUndoGroup::redo); 0310 QObject::connect(&parent->undo_group(), &QUndoGroup::canRedoChanged, ui.action_redo, &QAction::setEnabled); 0311 QObject::connect(&parent->undo_group(), &QUndoGroup::redoTextChanged, ui.action_redo, [this](const QString& s){ 0312 ui.action_redo->setText(i18n("Redo %1", s)); 0313 }); 0314 QObject::connect(ui.action_undo, &QAction::triggered, &parent->undo_group(), &QUndoGroup::undo); 0315 QObject::connect(&parent->undo_group(), &QUndoGroup::canUndoChanged, ui.action_undo, &QAction::setEnabled); 0316 QObject::connect(&parent->undo_group(), &QUndoGroup::undoTextChanged, ui.action_undo, [this](const QString& s){ 0317 ui.action_undo->setText(i18n("Undo %1", s)); 0318 }); 0319 ui.view_undo->setGroup(&parent->undo_group()); 0320 0321 #ifdef Q_OS_WIN32 0322 // Can't get emoji_data.cpp to compile on windows for qt6 for some reason 0323 // the compiler errors out without message 0324 ui.action_insert_emoji->setEnabled(false); 0325 #endif 0326 0327 parent->actionCollection()->addAssociatedWidget(parent); 0328 for ( auto action : parent->findChildren<QAction*>() ) 0329 { 0330 if ( !action->isSeparator() && !action->objectName().isEmpty() ) 0331 { 0332 parent->actionCollection()->addAction(action->objectName(), action); 0333 parent->actionCollection()->setDefaultShortcut(action, action->shortcut()); 0334 } 0335 } 0336 } 0337 0338 tools::Tool* GlaxnimateWindow::Private::init_tools_ui() 0339 { 0340 // Tool Actions 0341 QActionGroup *tool_actions = new QActionGroup(parent); 0342 tool_actions->setExclusive(true); 0343 0344 dock_tools_layout = new FlowLayout(); 0345 ui.dock_tools_layout_parent->insertLayout(0, dock_tools_layout); 0346 tools::Tool* to_activate = nullptr; 0347 for ( const auto& grp : tools::Registry::instance() ) 0348 { 0349 for ( const auto& tool : grp.second ) 0350 { 0351 QAction* action = tool.second->get_action(); 0352 action->setParent(parent); 0353 ui.menu_tools->addAction(action); 0354 action->setActionGroup(tool_actions); 0355 ScalableButton *button = tool.second->get_button(); 0356 connect(action, &QAction::triggered, parent, &GlaxnimateWindow::tool_triggered); 0357 0358 ui.toolbar_tools->addAction(action); 0359 0360 button->resize(16, 16); 0361 dock_tools_layout->addWidget(button); 0362 0363 QWidget* widget = tool.second->get_settings_widget(); 0364 widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); 0365 ui.tool_settings_widget->addWidget(widget); 0366 0367 if ( !to_activate ) 0368 { 0369 to_activate = tool.second.get(); 0370 action->setChecked(true); 0371 } 0372 } 0373 ui.menu_tools->addSeparator(); 0374 ui.toolbar_tools->addSeparator(); 0375 } 0376 0377 ui.toolbar_node->setVisible(false); 0378 ui.toolbar_node->setEnabled(false); 0379 0380 /// \todo Have some way of creating/connecting actions from the tools 0381 this->tool_actions["select"] = { 0382 ui.action_delete, 0383 }; 0384 tool_widgets["edit"] = { 0385 ui.toolbar_node 0386 }; 0387 this->tool_actions["edit"] = { 0388 ui.action_node_remove, 0389 ui.action_node_type_corner, 0390 ui.action_node_type_smooth, 0391 ui.action_node_type_symmetric, 0392 ui.action_segment_lines, 0393 ui.action_segment_curve, 0394 ui.action_node_add, 0395 ui.action_node_dissolve, 0396 }; 0397 tools::EditTool* edit_tool = static_cast<tools::EditTool*>(tools::Registry::instance().tool("edit")); 0398 connect(ui.action_node_type_corner, &QAction::triggered, parent, [edit_tool]{ 0399 edit_tool->selection_set_vertex_type(math::bezier::Corner); 0400 }); 0401 connect(ui.action_node_type_smooth, &QAction::triggered, parent, [edit_tool]{ 0402 edit_tool->selection_set_vertex_type(math::bezier::Smooth); 0403 }); 0404 connect(ui.action_node_type_symmetric, &QAction::triggered, parent, [edit_tool]{ 0405 edit_tool->selection_set_vertex_type(math::bezier::Symmetrical); 0406 }); 0407 connect(ui.action_node_remove, &QAction::triggered, parent, [edit_tool]{ 0408 edit_tool->selection_delete(); 0409 }); 0410 connect(ui.action_segment_lines, &QAction::triggered, parent, [edit_tool]{ 0411 edit_tool->selection_straighten(); 0412 }); 0413 connect(ui.action_segment_curve, &QAction::triggered, parent, [edit_tool]{ 0414 edit_tool->selection_curve(); 0415 }); 0416 connect(ui.action_node_add, &QAction::triggered, parent, [edit_tool]{ 0417 edit_tool->add_point_mode(); 0418 }); 0419 connect(ui.action_node_dissolve, &QAction::triggered, parent, [edit_tool]{ 0420 edit_tool->selection_dissolve(); 0421 }); 0422 connect(edit_tool, &tools::EditTool::gradient_stop_changed, ui.fill_style_widget, &FillStyleWidget::set_gradient_stop); 0423 connect(edit_tool, &tools::EditTool::gradient_stop_changed, ui.stroke_style_widget, &StrokeStyleWidget::set_gradient_stop); 0424 0425 return to_activate; 0426 } 0427 0428 void GlaxnimateWindow::Private::init_item_views() 0429 { 0430 // Item views 0431 ui.view_document_node->set_base_model(&document_node_model); 0432 QObject::connect(ui.view_document_node, &LayerView::current_node_changed, 0433 parent, &GlaxnimateWindow::document_treeview_current_changed); 0434 0435 ui.view_document_node->setContextMenuPolicy(Qt::CustomContextMenu); 0436 QObject::connect(ui.view_document_node, &QWidget::customContextMenuRequested, parent, 0437 [this](const QPoint& pos){ 0438 auto index = ui.view_document_node->indexAt(pos); 0439 if ( auto node = ui.view_document_node->node(index) ) 0440 NodeMenu(node, this->parent, this->parent).exec(ui.view_document_node->mapToGlobal(pos)); 0441 } 0442 ); 0443 0444 ui.view_properties->setModel(&property_model); 0445 ui.view_properties->setItemDelegateForColumn(item_models::PropertyModelSingle::ColumnValue, &property_delegate); 0446 ui.view_properties->header()->setSectionResizeMode(item_models::PropertyModelSingle::ColumnName, QHeaderView::ResizeToContents); 0447 ui.view_properties->header()->setSectionResizeMode(item_models::PropertyModelSingle::ColumnValue, QHeaderView::Stretch); 0448 0449 connect(ui.view_document_node, &LayerView::selection_changed, parent, &GlaxnimateWindow::document_treeview_selection_changed); 0450 0451 ui.timeline_widget->set_controller(parent); 0452 0453 asset_model.setSourceModel(&document_node_model); 0454 ui.view_assets->setModel(&asset_model); 0455 ui.view_assets->header()->setSectionResizeMode(item_models::DocumentNodeModel::ColumnName-1, QHeaderView::Stretch); 0456 ui.view_assets->header()->hideSection(item_models::DocumentNodeModel::ColumnVisible-1); 0457 ui.view_assets->header()->hideSection(item_models::DocumentNodeModel::ColumnLocked-1); 0458 ui.view_assets->header()->setSectionResizeMode(item_models::DocumentNodeModel::ColumnUsers-1, QHeaderView::ResizeToContents); 0459 connect(ui.view_assets, &CustomTreeView::customContextMenuRequested, parent, [this](const QPoint& pos){ 0460 auto node = document_node_model.node(asset_model.mapToSource(ui.view_assets->indexAt(pos))); 0461 if ( node ) 0462 NodeMenu(node, this->parent, this->parent).exec(ui.view_assets->mapToGlobal(pos)); 0463 }); 0464 0465 0466 connect(ui.timeline_widget, &CompoundTimelineWidget::current_node_changed, parent, [this](model::VisualNode* node){ 0467 timeline_current_node_changed(node); 0468 }); 0469 connect(ui.timeline_widget, &CompoundTimelineWidget::selection_changed, parent, [this](const std::vector<model::VisualNode*>& selected, const std::vector<model::VisualNode*>& deselected){ 0470 selection_changed(selected, deselected); 0471 }); 0472 } 0473 0474 static QWidget* status_bar_separator() 0475 { 0476 QFrame *line = new QFrame(); 0477 line->setFrameShape(QFrame::VLine); 0478 line->setFrameShadow(QFrame::Sunken); 0479 return line; 0480 } 0481 0482 void GlaxnimateWindow::Private::init_status_bar() 0483 { 0484 // Recording 0485 widget_recording = new QWidget(); 0486 ui.status_bar->addPermanentWidget(widget_recording); 0487 QHBoxLayout* lay = new QHBoxLayout; 0488 widget_recording->setLayout(lay); 0489 lay->setContentsMargins(0, 0, 0, 0); 0490 widget_recording->setVisible(false); 0491 0492 QLabel* label_recording_icon = new QLabel(); 0493 label_recording_icon->setPixmap(QIcon::fromTheme("media-record").pixmap(ui.status_bar->height())); 0494 lay->addWidget(label_recording_icon); 0495 0496 label_recording = new QLabel(); 0497 label_recording->setText(i18n("Recording Keyframes")); 0498 lay->addWidget(label_recording); 0499 0500 lay->addWidget(status_bar_separator()); 0501 0502 // X: ... Y: ... 0503 label_mouse_pos = new QLabel(); 0504 ui.status_bar->addPermanentWidget(label_mouse_pos); 0505 QFont font; 0506 font.setFamily("monospace"); 0507 font.setStyleHint(QFont::Monospace); 0508 label_mouse_pos->setFont(font); 0509 mouse_moved(QPointF(0, 0)); 0510 connect(ui.canvas, &Canvas::mouse_moved, parent, [this](const QPointF& p){mouse_moved(p);}); 0511 0512 ui.status_bar->addPermanentWidget(status_bar_separator()); 0513 0514 // Current Style 0515 widget_current_style = new ShapeStylePreviewWidget(); 0516 ui.status_bar->addPermanentWidget(widget_current_style); 0517 widget_current_style->setFixedSize(ui.status_bar->height(), ui.status_bar->height()); 0518 connect(ui.fill_style_widget, &FillStyleWidget::current_color_changed, 0519 widget_current_style, &ShapeStylePreviewWidget::set_fill_color); 0520 connect(ui.stroke_style_widget, &StrokeStyleWidget::color_changed, 0521 widget_current_style, &ShapeStylePreviewWidget::set_stroke_color); 0522 ui.status_bar->addPermanentWidget(status_bar_separator()); 0523 widget_current_style->set_fill_color(ui.fill_style_widget->current_color()); 0524 widget_current_style->set_stroke_color(ui.stroke_style_widget->current_color()); 0525 0526 // Transform widget 0527 view_trans_widget = new ViewTransformWidget(ui.status_bar); 0528 ui.status_bar->addPermanentWidget(view_trans_widget); 0529 connect(view_trans_widget, &ViewTransformWidget::zoom_changed, ui.canvas, &Canvas::set_zoom); 0530 connect(ui.canvas, &Canvas::zoomed, view_trans_widget, &ViewTransformWidget::set_zoom); 0531 connect(view_trans_widget, &ViewTransformWidget::zoom_in, ui.canvas, &Canvas::zoom_in); 0532 connect(view_trans_widget, &ViewTransformWidget::zoom_out, ui.canvas, &Canvas::zoom_out); 0533 connect(view_trans_widget, &ViewTransformWidget::angle_changed, ui.canvas, &Canvas::set_rotation); 0534 connect(ui.canvas, &Canvas::rotated, view_trans_widget, &ViewTransformWidget::set_angle); 0535 connect(view_trans_widget, &ViewTransformWidget::view_fit, parent, &GlaxnimateWindow::view_fit); 0536 connect(view_trans_widget, &ViewTransformWidget::flip_view, ui.action_flip_view, &QAction::trigger); 0537 } 0538 0539 void GlaxnimateWindow::Private::init_docks() 0540 { 0541 // Scripting 0542 connect(ui.console, &ScriptConsole::error, parent, [this](const QString& plugin, const QString& message){ 0543 show_warning(plugin, message, app::log::Error); 0544 }); 0545 ui.console->set_global("window", QVariant::fromValue(parent)); 0546 init_plugins(); 0547 0548 // Logs 0549 log_model.populate(GlaxnimateApp::instance()->log_lines()); 0550 ui.view_logs->setModel(&log_model); 0551 ui.view_logs->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); 0552 ui.view_logs->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); 0553 ui.view_logs->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); 0554 auto del = new style::BetterElideDelegate(Qt::ElideLeft, ui.view_logs); 0555 ui.view_logs->setItemDelegateForColumn(2, del); 0556 0557 // Swatches 0558 palette_model.setSearchPaths(app::Application::instance()->data_paths_unchecked("palettes")); 0559 palette_model.setSavePath(app::Application::instance()->writable_data_path("palettes")); 0560 palette_model.load(); 0561 ui.fill_style_widget->set_palette_model(&palette_model); 0562 ui.stroke_style_widget->set_palette_model(&palette_model); 0563 ui.document_swatch_widget->set_palette_model(&palette_model); 0564 0565 connect(ui.document_swatch_widget, &DocumentSwatchWidget::needs_new_color, [this]{ 0566 ui.document_swatch_widget->add_new_color(ui.fill_style_widget->current_color()); 0567 }); 0568 connect(ui.document_swatch_widget, &DocumentSwatchWidget::current_color_def, [this](model::BrushStyle* sty){ 0569 set_color_def(sty, false); 0570 }); 0571 connect(ui.document_swatch_widget, &DocumentSwatchWidget::secondary_color_def, [this](model::BrushStyle* sty){ 0572 set_color_def(sty, true); 0573 }); 0574 0575 // Gradients 0576 ui.widget_gradients->set_window(parent); 0577 connect(ui.widget_gradients, &GradientListWidget::selected, parent, [this](model::BrushStyle* sty, bool secondary){ 0578 set_brush_reference(sty, secondary); 0579 }); 0580 0581 // Fill/Stroke 0582 connect(ui.fill_style_widget, &FillStyleWidget::current_color_changed, parent, [this]{style_change_event();}); 0583 connect(ui.fill_style_widget, &FillStyleWidget::secondary_color_changed, parent, [this]{style_change_event();}); 0584 connect(ui.stroke_style_widget, &StrokeStyleWidget::pen_style_changed, parent, [this]{style_change_event();}); 0585 0586 // Tab bar 0587 connect(ui.tab_bar, &CompositionTabBar::switch_composition, parent, &GlaxnimateWindow::switch_composition); 0588 connect(ui.timeline_widget, &CompoundTimelineWidget::switch_composition, parent, &GlaxnimateWindow::switch_composition); 0589 0590 // Align 0591 ui.separator_align_relative_to->setSeparator(true); 0592 ui.separator_align_horizontal->setSeparator(true); 0593 ui.separator_align_vertical->setSeparator(true); 0594 QActionGroup *align_relative = new QActionGroup(parent); 0595 align_relative->setExclusive(true); 0596 ui.action_align_to_canvas->setActionGroup(align_relative); 0597 ui.action_align_to_selection->setActionGroup(align_relative); 0598 ui.action_align_to_canvas_group->setActionGroup(align_relative); 0599 0600 auto combo_align_to = new QComboBox(ui.dock_align->widget()); 0601 combo_align_to->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); 0602 ui.dock_align_grid->addWidget(combo_align_to, 0, 0, 1, 3); 0603 0604 action_combo(combo_align_to, ui.action_align_to_selection); 0605 action_combo(combo_align_to, ui.action_align_to_canvas); 0606 action_combo(combo_align_to, ui.action_align_to_canvas_group); 0607 connect(combo_align_to, qOverload<int>(&QComboBox::currentIndexChanged), parent, [combo_align_to](int i){ 0608 combo_align_to->itemData(i).value<QAction*>()->setChecked(true); 0609 }); 0610 0611 int row = 1; 0612 ui.dock_align_grid->addWidget(action_button(ui.action_align_hor_left, ui.dock_align->widget()), row, 0); 0613 ui.dock_align_grid->addWidget(action_button(ui.action_align_hor_center, ui.dock_align->widget()), row, 1); 0614 ui.dock_align_grid->addWidget(action_button(ui.action_align_hor_right, ui.dock_align->widget()), row, 2); 0615 row++; 0616 ui.dock_align_grid->addWidget(action_button(ui.action_align_hor_left_out, ui.dock_align->widget()), row, 0); 0617 ui.dock_align_grid->addWidget(action_button(ui.action_align_hor_right_out, ui.dock_align->widget()), row, 2); 0618 row++; 0619 ui.dock_align_grid->addWidget(action_button(ui.action_align_vert_top, ui.dock_align->widget()), row, 0); 0620 ui.dock_align_grid->addWidget(action_button(ui.action_align_vert_center, ui.dock_align->widget()), row, 1); 0621 ui.dock_align_grid->addWidget(action_button(ui.action_align_vert_bottom, ui.dock_align->widget()), row, 2); 0622 row++; 0623 ui.dock_align_grid->addWidget(action_button(ui.action_align_vert_top_out, ui.dock_align->widget()), row, 0); 0624 ui.dock_align_grid->addWidget(action_button(ui.action_align_vert_bottom_out, ui.dock_align->widget()), row, 2); 0625 row++; 0626 ui.dock_align_grid->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding), row, 0); 0627 ui.dock_align->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 0628 0629 // Layout 0630 QActionGroup *layout_actions = new QActionGroup(parent); 0631 layout_actions->setExclusive(true); 0632 for ( const auto& action : ui.menu_layout->actions() ) 0633 { 0634 action->setActionGroup(layout_actions); 0635 connect(action, &QAction::triggered, parent, [this]{layout_update();}); 0636 } 0637 0638 auto preset = LayoutPreset(app::settings::get<int>("ui", "layout")); 0639 0640 switch ( preset ) 0641 { 0642 case LayoutPreset::Unknown: 0643 case LayoutPreset::Auto: 0644 layout_auto(); 0645 break; 0646 case LayoutPreset::Wide: 0647 layout_wide(); 0648 break; 0649 case LayoutPreset::Compact: 0650 layout_compact(); 0651 break; 0652 case LayoutPreset::Medium: 0653 layout_medium(); 0654 break; 0655 case LayoutPreset::Custom: 0656 layout_auto(); 0657 app::settings::set("ui", "layout", int(LayoutPreset::Custom)); 0658 ui.action_layout_custom->setChecked(true); 0659 break; 0660 } 0661 } 0662 0663 void GlaxnimateWindow::Private::layout_update() 0664 { 0665 if ( ui.action_layout_wide->isChecked() ) 0666 layout_wide(); 0667 else if ( ui.action_layout_compact->isChecked() ) 0668 layout_compact(); 0669 else if ( ui.action_layout_custom->isChecked() ) 0670 layout_custom(); 0671 else if ( ui.action_layout_medium->isChecked() ) 0672 layout_medium(); 0673 else 0674 layout_auto(); 0675 } 0676 0677 void GlaxnimateWindow::Private::layout_medium() 0678 { 0679 // Bottom 0680 parent->addDockWidget(Qt::BottomDockWidgetArea, ui.dock_timeline); 0681 parent->tabifyDockWidget(ui.dock_timeline, ui.dock_properties); 0682 parent->tabifyDockWidget(ui.dock_properties, ui.dock_script_console); 0683 parent->tabifyDockWidget(ui.dock_script_console, ui.dock_logs); 0684 parent->tabifyDockWidget(ui.dock_logs, ui.dock_time_slider); 0685 ui.dock_timeline->raise(); 0686 ui.dock_time_slider->setVisible(false); 0687 ui.dock_timeline->setVisible(true); 0688 ui.dock_properties->setVisible(true); 0689 0690 parent->addDockWidget(Qt::BottomDockWidgetArea, ui.dock_snippets); 0691 0692 // Bottom Right 0693 parent->addDockWidget(Qt::BottomDockWidgetArea, ui.dock_layers); 0694 parent->tabifyDockWidget(ui.dock_layers, ui.dock_gradients); 0695 parent->tabifyDockWidget(ui.dock_gradients, ui.dock_swatches); 0696 parent->tabifyDockWidget(ui.dock_swatches, ui.dock_assets); 0697 ui.dock_gradients->raise(); 0698 ui.dock_gradients->setVisible(true); 0699 ui.dock_assets->setVisible(false); 0700 ui.dock_swatches->setVisible(false); 0701 ui.dock_layers->setVisible(true); 0702 0703 0704 // Right 0705 parent->addDockWidget(Qt::RightDockWidgetArea, ui.dock_colors); 0706 parent->tabifyDockWidget(ui.dock_colors, ui.dock_stroke); 0707 parent->tabifyDockWidget(ui.dock_stroke, ui.dock_undo); 0708 ui.dock_colors->raise(); 0709 ui.dock_colors->setVisible(true); 0710 ui.dock_stroke->setVisible(true); 0711 ui.dock_undo->setVisible(true); 0712 0713 0714 // Left 0715 parent->addDockWidget(Qt::LeftDockWidgetArea, ui.dock_tools); 0716 0717 parent->addDockWidget(Qt::LeftDockWidgetArea, ui.dock_tool_options); 0718 parent->tabifyDockWidget(ui.dock_tool_options, ui.dock_align); 0719 ui.dock_tool_options->raise(); 0720 ui.dock_tool_options->setVisible(true); 0721 ui.dock_align->setVisible(true); 0722 0723 // Resize 0724 parent->resizeDocks({ui.dock_snippets}, {1}, Qt::Horizontal); 0725 parent->resizeDocks({ui.dock_layers}, {1}, Qt::Horizontal); 0726 parent->resizeDocks({ui.dock_tools}, {200}, Qt::Horizontal); 0727 parent->resizeDocks({ui.dock_tool_options, ui.dock_align, ui.dock_tools}, {1, 1, 4000}, Qt::Vertical); 0728 parent->resizeDocks({ui.dock_timeline}, {300}, Qt::Vertical); 0729 ui.dock_script_console->setVisible(false); 0730 ui.dock_logs->setVisible(false); 0731 ui.dock_tools->setVisible(false); 0732 ui.dock_snippets->setVisible(false); 0733 0734 // Resize parent to have a reasonable default size 0735 parent->resize(1440, 900); 0736 0737 app::settings::set("ui", "layout", int(LayoutPreset::Medium)); 0738 ui.action_layout_medium->setChecked(true); 0739 } 0740 0741 void GlaxnimateWindow::Private::layout_wide() 0742 { 0743 // Bottom 0744 parent->addDockWidget(Qt::BottomDockWidgetArea, ui.dock_timeline); 0745 parent->tabifyDockWidget(ui.dock_timeline, ui.dock_properties); 0746 parent->tabifyDockWidget(ui.dock_properties, ui.dock_script_console); 0747 parent->tabifyDockWidget(ui.dock_script_console, ui.dock_logs); 0748 parent->tabifyDockWidget(ui.dock_logs, ui.dock_time_slider); 0749 ui.dock_timeline->raise(); 0750 ui.dock_time_slider->setVisible(false); 0751 ui.dock_timeline->setVisible(true); 0752 ui.dock_properties->setVisible(true); 0753 0754 parent->addDockWidget(Qt::BottomDockWidgetArea, ui.dock_snippets); 0755 0756 // Bottom Right 0757 parent->addDockWidget(Qt::BottomDockWidgetArea, ui.dock_layers); 0758 parent->tabifyDockWidget(ui.dock_layers, ui.dock_gradients); 0759 parent->tabifyDockWidget(ui.dock_gradients, ui.dock_swatches); 0760 parent->tabifyDockWidget(ui.dock_swatches, ui.dock_assets); 0761 ui.dock_gradients->raise(); 0762 ui.dock_gradients->setVisible(true); 0763 ui.dock_assets->setVisible(false); 0764 ui.dock_swatches->setVisible(false); 0765 ui.dock_layers->setVisible(true); 0766 0767 0768 // Right 0769 parent->addDockWidget(Qt::RightDockWidgetArea, ui.dock_colors); 0770 parent->tabifyDockWidget(ui.dock_colors, ui.dock_stroke); 0771 parent->tabifyDockWidget(ui.dock_stroke, ui.dock_undo); 0772 ui.dock_colors->raise(); 0773 ui.dock_colors->setVisible(true); 0774 ui.dock_stroke->setVisible(true); 0775 ui.dock_undo->setVisible(true); 0776 0777 0778 // Left 0779 parent->addDockWidget(Qt::LeftDockWidgetArea, ui.dock_tools); 0780 0781 parent->addDockWidget(Qt::LeftDockWidgetArea, ui.dock_tool_options); 0782 parent->addDockWidget(Qt::LeftDockWidgetArea, ui.dock_align); 0783 ui.dock_tool_options->setVisible(true); 0784 ui.dock_align->setVisible(true); 0785 0786 // Resize 0787 parent->resizeDocks({ui.dock_snippets}, {1}, Qt::Horizontal); 0788 parent->resizeDocks({ui.dock_layers}, {1}, Qt::Horizontal); 0789 parent->resizeDocks({ui.dock_tools}, {200}, Qt::Horizontal); 0790 parent->resizeDocks({ui.dock_tool_options, ui.dock_align, ui.dock_tools}, {1, 1, 4000}, Qt::Vertical); 0791 parent->resizeDocks({ui.dock_timeline}, {1080/3}, Qt::Vertical); 0792 ui.dock_script_console->setVisible(false); 0793 ui.dock_logs->setVisible(false); 0794 ui.dock_tools->setVisible(false); 0795 ui.dock_snippets->setVisible(false); 0796 0797 // Resize parent to have a reasonable default size 0798 parent->resize(1920, 1080); 0799 0800 app::settings::set("ui", "layout", int(LayoutPreset::Wide)); 0801 ui.action_layout_wide->setChecked(true); 0802 } 0803 0804 void GlaxnimateWindow::Private::layout_compact() 0805 { 0806 // Bottom 0807 parent->addDockWidget(Qt::BottomDockWidgetArea, ui.dock_time_slider); 0808 parent->tabifyDockWidget(ui.dock_time_slider, ui.dock_timeline); 0809 parent->tabifyDockWidget(ui.dock_timeline, ui.dock_script_console); 0810 parent->tabifyDockWidget(ui.dock_script_console, ui.dock_logs); 0811 ui.dock_time_slider->raise(); 0812 ui.dock_time_slider->setVisible(true); 0813 ui.dock_timeline->setVisible(false); 0814 ui.dock_logs->setVisible(false); 0815 0816 parent->addDockWidget(Qt::BottomDockWidgetArea, ui.dock_snippets); 0817 0818 // Right 0819 parent->addDockWidget(Qt::RightDockWidgetArea, ui.dock_colors); 0820 parent->tabifyDockWidget(ui.dock_colors, ui.dock_stroke); 0821 parent->tabifyDockWidget(ui.dock_stroke, ui.dock_layers); 0822 parent->tabifyDockWidget(ui.dock_layers, ui.dock_properties); 0823 parent->tabifyDockWidget(ui.dock_properties, ui.dock_undo); 0824 parent->tabifyDockWidget(ui.dock_undo, ui.dock_gradients); 0825 parent->tabifyDockWidget(ui.dock_gradients, ui.dock_swatches); 0826 parent->tabifyDockWidget(ui.dock_swatches, ui.dock_assets); 0827 ui.dock_colors->raise(); 0828 ui.dock_colors->setVisible(true); 0829 ui.dock_stroke->setVisible(true); 0830 ui.dock_gradients->setVisible(false); 0831 ui.dock_assets->setVisible(false); 0832 ui.dock_swatches->setVisible(false); 0833 ui.dock_undo->setVisible(false); 0834 ui.dock_properties->setVisible(true); 0835 ui.dock_layers->setVisible(true); 0836 0837 // Left 0838 parent->addDockWidget(Qt::LeftDockWidgetArea, ui.dock_tools); 0839 0840 parent->addDockWidget(Qt::LeftDockWidgetArea, ui.dock_tool_options); 0841 parent->tabifyDockWidget(ui.dock_tool_options, ui.dock_align); 0842 ui.dock_tool_options->raise(); 0843 ui.dock_tool_options->setVisible(true); 0844 ui.dock_align->setVisible(true); 0845 0846 // Resize 0847 parent->resizeDocks({ui.dock_tool_options, ui.dock_align, ui.dock_tools}, {1, 1, 4000}, Qt::Vertical); 0848 parent->resizeDocks({ui.dock_time_slider}, {64}, Qt::Vertical); 0849 ui.dock_script_console->setVisible(false); 0850 ui.dock_logs->setVisible(false); 0851 ui.dock_tools->setVisible(false); 0852 ui.dock_snippets->setVisible(false); 0853 0854 // Resize parent to have a reasonable default size 0855 parent->resize(1366, 768); 0856 0857 app::settings::set("ui", "layout", int(LayoutPreset::Compact)); 0858 ui.action_layout_compact->setChecked(true); 0859 } 0860 0861 void GlaxnimateWindow::Private::layout_auto() 0862 { 0863 auto real_estate = qApp->primaryScreen()->availableSize(); 0864 if ( real_estate.width() >= 1920 && real_estate.height() >= 1080 ) 0865 layout_wide(); 0866 else if ( real_estate.width() >= 1440 && real_estate.height() >= 900 ) 0867 layout_medium(); 0868 else 0869 layout_compact(); 0870 0871 app::settings::set("ui", "layout", int(LayoutPreset::Auto)); 0872 ui.action_layout_automatic->setChecked(true); 0873 } 0874 0875 void GlaxnimateWindow::Private::layout_custom() 0876 { 0877 init_restore_state(); 0878 app::settings::set("ui", "layout", int(LayoutPreset::Custom)); 0879 ui.action_layout_custom->setChecked(true); 0880 } 0881 0882 void GlaxnimateWindow::Private::init_template_menu() 0883 { 0884 ui.menu_new_from_template->clear(); 0885 0886 for ( const auto& templ : settings::DocumentTemplates::instance().templates() ) 0887 ui.menu_new_from_template->addAction(settings::DocumentTemplates::instance().create_action(templ, ui.menu_new_from_template)); 0888 } 0889 0890 void GlaxnimateWindow::Private::init_menus() 0891 { 0892 // Menu Views 0893 for ( QDockWidget* wid : parent->findChildren<QDockWidget*>() ) 0894 { 0895 QAction* act = wid->toggleViewAction(); 0896 act->setIcon(wid->windowIcon()); 0897 ui.menu_views->addAction(act); 0898 wid->setStyle(&dock_style); 0899 } 0900 0901 // Menu Toolbars 0902 for ( QToolBar* wid : parent->findChildren<QToolBar*>() ) 0903 { 0904 QAction* act = wid->toggleViewAction(); 0905 ui.menu_toolbars->addAction(act); 0906 wid->setStyle(&dock_style); 0907 } 0908 0909 // Load keyboard shortcuts 0910 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_file); 0911 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_edit); 0912 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_tools); 0913 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_view); 0914 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_views); 0915 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_document); 0916 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_render_single_frame); 0917 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_playback); 0918 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_layers); 0919 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_new_layer); 0920 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_object); 0921 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_path); 0922 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_text); 0923 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_plugins); 0924 GlaxnimateApp::instance()->shortcuts()->add_menu(ui.menu_help); 0925 0926 // Menu Templates 0927 init_template_menu(); 0928 0929 connect(&settings::DocumentTemplates::instance(), &settings::DocumentTemplates::loaded, parent, [this]{ 0930 init_template_menu(); 0931 }); 0932 0933 connect(&settings::DocumentTemplates::instance(), &settings::DocumentTemplates::create_from, parent, 0934 [this](const settings::DocumentTemplate& templ){ 0935 if ( !close_document() ) 0936 return; 0937 0938 bool ok = false; 0939 setup_document_ptr(templ.create(&ok)); 0940 if ( !ok ) 0941 show_warning(i18n("New from Template"), i18n("Could not load template")); 0942 } 0943 ); 0944 0945 connect(ui.action_save_as_template, &QAction::triggered, parent, [this]{ 0946 bool ok = true; 0947 0948 QString old_name = comp->name.get(); 0949 QString name = QInputDialog::getText(parent, i18n("Save as Template"), i18n("Name"), QLineEdit::Normal, old_name, &ok); 0950 if ( !ok ) 0951 return; 0952 0953 comp->name.set(name); 0954 if ( !settings::DocumentTemplates::instance().save_as_template(current_document.get()) ) 0955 show_warning(i18n("Save as Template"), i18n("Could not save template")); 0956 comp->name.set(old_name); 0957 }); 0958 } 0959 0960 0961 void GlaxnimateWindow::Private::init_tools(tools::Tool* to_activate) 0962 { 0963 tools::Event event{ui.canvas, &scene, parent}; 0964 for ( const auto& grp : tools::Registry::instance() ) 0965 { 0966 for ( const auto& tool : grp.second ) 0967 { 0968 tool.second->retranslate(); 0969 tool.second->initialize(event); 0970 0971 if ( to_activate == tool.second.get() ) 0972 switch_tool(tool.second.get()); 0973 } 0974 } 0975 } 0976 0977 void GlaxnimateWindow::Private::init_restore_state() 0978 { 0979 parent->restoreState(app::settings::get<QByteArray>("ui", "window_state")); 0980 ui.timeline_widget->load_state(app::settings::get<QByteArray>("ui", "timeline_splitter")); 0981 parent->restoreGeometry(app::settings::get<QByteArray>("ui", "window_geometry")); 0982 0983 // Hide tool widgets, as they might get shown by restoreState 0984 ui.toolbar_node->setVisible(false); 0985 ui.toolbar_node->setEnabled(false); 0986 } 0987 0988 void GlaxnimateWindow::Private::retranslateUi(QMainWindow* parent) 0989 { 0990 ui.retranslateUi(parent); 0991 label_recording->setText(i18n("Recording Keyframes")); 0992 0993 ui.action_undo->setText(i18n("Undo %1", current_document->undo_stack().undoText())); 0994 ui.action_redo->setText(i18n("Redo %1", current_document->undo_stack().redoText())); 0995 0996 for ( const auto& grp : tools::Registry::instance() ) 0997 for ( const auto& tool : grp.second ) 0998 tool.second->retranslate(); 0999 } 1000 1001 void GlaxnimateWindow::Private::view_fit() 1002 { 1003 ui.canvas->view_fit(); 1004 } 1005 1006 void GlaxnimateWindow::Private::reload_recent_menu() 1007 { 1008 QIcon icon(GlaxnimateApp::instance()->data_file("images/glaxnimate-file.svg")); 1009 ui.menu_open_recent->clear(); 1010 for ( const auto& recent : recent_files ) 1011 { 1012 QAction* act = new QAction(icon, QFileInfo(recent).fileName(), ui.menu_open_recent); 1013 act->setToolTip(recent); 1014 act->setData(recent); 1015 ui.menu_open_recent->addAction(act); 1016 } 1017 } 1018 1019 void GlaxnimateWindow::Private::most_recent_file(const QString& s) 1020 { 1021 recent_files.removeAll(s); 1022 recent_files.push_front(s); 1023 ui.action_open_last->setEnabled(true); 1024 1025 int max = app::settings::get<int>("open_save", "max_recent_files"); 1026 if ( recent_files.size() > max ) 1027 recent_files.erase(recent_files.begin() + max, recent_files.end()); 1028 1029 reload_recent_menu(); 1030 } 1031 1032 void GlaxnimateWindow::Private::show_warning(const QString& title, const QString& message, app::log::Severity icon) 1033 { 1034 KMessageWidget::MessageType message_type; 1035 switch ( icon ) 1036 { 1037 case app::log::Severity::Info: message_type = KMessageWidget::Information; break; 1038 case app::log::Severity::Warning: message_type = KMessageWidget::Warning; break; 1039 case app::log::Severity::Error: message_type = KMessageWidget::Error; break; 1040 } 1041 ui.message_widget->queue_message({message, message_type}); 1042 app::log::Log(title).log(message, icon); 1043 } 1044 1045 void GlaxnimateWindow::Private::help_about() 1046 { 1047 about_dialog->show(); 1048 } 1049 1050 void GlaxnimateWindow::Private::shutdown() 1051 { 1052 app::settings::set("ui", "window_geometry", parent->saveGeometry()); 1053 app::settings::set("ui", "window_state", parent->saveState()); 1054 app::settings::set("ui", "timeline_splitter", ui.timeline_widget->save_state()); 1055 app::settings::set("open_save", "recent_files", recent_files); 1056 1057 ui.fill_style_widget->save_settings(); 1058 ui.stroke_style_widget->save_settings(); 1059 1060 ui.console->save_settings(); 1061 ui.console->clear_contexts(); 1062 } 1063 1064 1065 void GlaxnimateWindow::Private::switch_tool(tools::Tool* tool) 1066 { 1067 if ( !tool || tool == active_tool ) 1068 return; 1069 1070 if ( !tool->get_action()->isChecked() ) 1071 tool->get_action()->setChecked(true); 1072 1073 if ( active_tool ) 1074 { 1075 for ( const auto& widget : tool_widgets[active_tool->id()] ) 1076 { 1077 widget->setVisible(false); 1078 widget->setEnabled(false); 1079 } 1080 1081 for ( const auto& action : tool_actions[active_tool->id()] ) 1082 { 1083 action->setEnabled(false); 1084 } 1085 } 1086 1087 1088 for ( const auto& widget : tool_widgets[tool->id()] ) 1089 { 1090 widget->setVisible(true); 1091 widget->setEnabled(true); 1092 } 1093 1094 for ( const auto& action : tool_actions[tool->id()] ) 1095 { 1096 action->setEnabled(true); 1097 } 1098 1099 active_tool = tool; 1100 scene.set_active_tool(tool); 1101 ui.canvas->set_active_tool(tool); 1102 1103 if ( auto old_wid = ui.tool_settings_widget->currentWidget() ) 1104 old_wid->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); 1105 1106 auto new_wid = tool->get_settings_widget(); 1107 new_wid->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 1108 ui.tool_settings_widget->setCurrentWidget(new_wid); 1109 if ( active_tool->group() == tools::Registry::Draw || active_tool->group() == tools::Registry::Shape ) 1110 widget_current_style->clear_gradients(); 1111 } 1112 1113 void GlaxnimateWindow::Private::switch_tool_action(QAction* action) 1114 { 1115 switch_tool(action->data().value<tools::Tool*>()); 1116 } 1117 1118 void GlaxnimateWindow::Private::status_message(const QString& message, int duration) 1119 { 1120 ui.status_bar->showMessage(message, duration); 1121 } 1122 1123 void GlaxnimateWindow::Private::trace_dialog(model::DocumentNode* object) 1124 { 1125 model::Image* bmp = nullptr; 1126 if ( object ) 1127 { 1128 bmp = object->cast<model::Image>(); 1129 } 1130 1131 if ( !bmp ) 1132 { 1133 for ( const auto& sel : scene.selection() ) 1134 { 1135 if ( auto image = sel->cast<model::Image>() ) 1136 { 1137 if ( bmp ) 1138 { 1139 show_warning(i18n("Trace Bitmap"), i18n("Only select one image"), app::log::Info); 1140 return; 1141 } 1142 bmp = image; 1143 } 1144 } 1145 1146 if ( !bmp ) 1147 { 1148 show_warning(i18n("Trace Bitmap"), i18n("You need to select an image to trace"), app::log::Info); 1149 return; 1150 } 1151 } 1152 1153 if ( !bmp->image.get() ) 1154 { 1155 show_warning(i18n("Trace Bitmap"), i18n("You selected an image with no data"), app::log::Info); 1156 return; 1157 } 1158 1159 TraceDialog dialog(bmp, parent); 1160 dialog.exec(); 1161 if ( auto created = dialog.created() ) 1162 ui.view_document_node->set_current_node(created); 1163 } 1164 1165 1166 void GlaxnimateWindow::Private::init_plugins() 1167 { 1168 auto& par = plugin::PluginActionRegistry::instance(); 1169 for ( auto act : par.enabled() ) 1170 { 1171 ui.menu_plugins->addAction(par.make_qaction(act)); 1172 } 1173 1174 connect(&par, &plugin::PluginActionRegistry::action_added, parent, [this](plugin::ActionService* action, plugin::ActionService* before) { 1175 QAction* insert = nullptr; 1176 for ( auto act : ui.menu_plugins->actions() ) 1177 { 1178 if ( act->data().value<plugin::ActionService*>() == before ) 1179 { 1180 insert = act; 1181 break; 1182 } 1183 } 1184 QAction* qaction = plugin::PluginActionRegistry::instance().make_qaction(action); 1185 ui.menu_plugins->insertAction(insert, qaction); 1186 1187 app::settings::ShortcutGroup* group = GlaxnimateApp::instance()->shortcuts()->find_group(ui.menu_plugins->menuAction()->iconText()); 1188 if (group) 1189 group->actions.push_back(GlaxnimateApp::instance()->shortcuts()->add_action(qaction)); 1190 }); 1191 1192 connect(&par, &plugin::PluginActionRegistry::action_removed, parent, [](plugin::ActionService* action) { 1193 QString slug = "action_plugin_" + action->plugin()->data().name.toLower() + "_" + action->label.toLower(); 1194 app::settings::ShortcutAction* act = GlaxnimateApp::instance()->shortcuts()->action(slug); 1195 GlaxnimateApp::instance()->shortcuts()->remove_action(act); 1196 }); 1197 1198 connect( 1199 &plugin::PluginRegistry::instance(), 1200 &plugin::PluginRegistry::loaded, 1201 ui.console, 1202 &ScriptConsole::clear_contexts 1203 ); 1204 1205 plugin::PluginRegistry::instance().set_executor(ui.console); 1206 } 1207 1208 void GlaxnimateWindow::Private::mouse_moved(const QPointF& pos) 1209 { 1210 label_mouse_pos->setText(ki18n("X: %1 Y: %2").subs(pos.x(), 8, 'f', 3).subs(pos.y(), 8, 'f', 3).toString()); 1211 } 1212 1213 void GlaxnimateWindow::Private::show_startup_dialog() 1214 { 1215 if ( !app::settings::get<bool>("ui", "startup_dialog") ) 1216 return; 1217 1218 StartupDialog dialog(parent); 1219 connect(&dialog, &StartupDialog::open_recent, parent, &GlaxnimateWindow::document_open); 1220 connect(&dialog, &StartupDialog::open_browse, parent, &GlaxnimateWindow::document_open_dialog); 1221 if ( dialog.exec() ) 1222 setup_document_ptr(dialog.create()); 1223 } 1224 1225 1226 void GlaxnimateWindow::Private::drop_file(const QString& file) 1227 { 1228 QDialog dialog(parent); 1229 dialog.setWindowTitle(i18n("Drop File")); 1230 QIcon icon = QIcon::fromTheme("dialog-question"); 1231 dialog.setWindowIcon(icon); 1232 QVBoxLayout lay; 1233 dialog.setLayout(&lay); 1234 1235 QHBoxLayout lay1; 1236 QLabel label_icon; 1237 label_icon.setPixmap(icon.pixmap(64)); 1238 lay1.addWidget(&label_icon); 1239 QLabel label_text; 1240 label_text.setText(i18n("Add to current file?")); 1241 label_text.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); 1242 lay1.addWidget(&label_text); 1243 lay.addLayout(&lay1); 1244 1245 QHBoxLayout lay2; 1246 QPushButton btn1(i18n("Add as Object")); 1247 lay2.addWidget(&btn1); 1248 connect(&btn1, &QPushButton::clicked, &dialog, [&dialog, this, &file]{ 1249 drop_document(file, false); 1250 dialog.accept(); 1251 }); 1252 QPushButton btn2(i18n("Add as Composition")); 1253 lay2.addWidget(&btn2); 1254 connect(&btn2, &QPushButton::clicked, &dialog, [&dialog, this, &file]{ 1255 drop_document(file, true); 1256 dialog.accept(); 1257 }); 1258 QPushButton btn3(i18n("Open")); 1259 lay2.addWidget(&btn3); 1260 connect(&btn3, &QPushButton::clicked, &dialog, [&dialog, this, &file]{ 1261 document_open_from_filename(file); 1262 dialog.accept(); 1263 }); 1264 lay.addLayout(&lay2); 1265 1266 dialog.exec(); 1267 } 1268 1269 void GlaxnimateWindow::Private::insert_emoji() 1270 { 1271 emoji::EmojiSetDialog dialog; 1272 if ( !dialog.exec() || dialog.selected_svg().isEmpty() ) 1273 return; 1274 import_file(dialog.selected_svg(), {{"forced_size", comp->size()}}); 1275 } 1276 1277 void GlaxnimateWindow::Private::import_from_lottiefiles() 1278 { 1279 LottieFilesSearchDialog dialog; 1280 if ( !dialog.exec() ) 1281 return; 1282 1283 io::Options options; 1284 options.format = io::IoRegistry::instance().from_slug("lottie"); 1285 options.filename = dialog.selected_name() + ".json"; 1286 1287 load_remote_document(dialog.selected_url(), options, dialog.result() == LottieFilesSearchDialog::Open); 1288 } 1289 1290 QVariant GlaxnimateWindow::Private::choose_option(const QString& label, const QVariantMap& options, const QString& title) const 1291 { 1292 QDialog dialog(parent); 1293 if ( !title.isEmpty() ) 1294 dialog.setWindowTitle(title); 1295 1296 QVBoxLayout* layout = new QVBoxLayout(); 1297 dialog.setLayout(layout); 1298 1299 QLabel qlabel; 1300 qlabel.setText(label); 1301 layout->addWidget(&qlabel); 1302 1303 QComboBox box; 1304 layout->addWidget(&box); 1305 int count = 0; 1306 for ( auto it = options.begin(); it != options.end(); ++it ) 1307 { 1308 box.insertItem(count++, it.key(), *it); 1309 } 1310 1311 layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); 1312 1313 QDialogButtonBox buttons; 1314 buttons.addButton(QDialogButtonBox::Ok); 1315 buttons.addButton(QDialogButtonBox::Cancel); 1316 connect(&buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); 1317 connect(&buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); 1318 layout->addWidget(&buttons); 1319 1320 if ( !dialog.exec() ) 1321 return {}; 1322 1323 return box.currentData(); 1324 }