Warning, file /graphics/glaxnimate/src/gui/tools/text_tool.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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 <QGraphicsTextItem> 0008 #include <QTextDocument> 0009 #include <QTextFrame> 0010 #include <QTextFrameLayoutData> 0011 #include <QAbstractTextDocumentLayout> 0012 #include <QInputDialog> 0013 0014 #include "draw_tool_base.hpp" 0015 #include "model/shapes/text.hpp" 0016 #include "widgets/tools/text_tool_widget.hpp" 0017 #include "glaxnimate_app.hpp" 0018 0019 0020 namespace glaxnimate::gui::tools { 0021 0022 class TextTool : public DrawToolBase 0023 { 0024 public: 0025 QCursor cursor() override { return Qt::IBeamCursor; } 0026 QString id() const override { return "text"; } 0027 QIcon icon() const override { return QIcon::fromTheme("draw-text"); } 0028 QString name() const override { return i18n("Draw Text"); } 0029 QKeySequence key_sequence() const override { return QKeySequence(i18n("F8"), QKeySequence::PortableText); } 0030 static int static_group() noexcept { return Registry::Shape; } 0031 int group() const noexcept override { return static_group(); } 0032 0033 void mouse_press(const MouseEvent& event) override 0034 { 0035 forward_click = editor.scene() && editor.mapToScene(editor.boundingRect()).containsPoint(event.scene_pos, Qt::WindingFill); 0036 if ( forward_click ) 0037 event.forward_to_scene(); 0038 } 0039 0040 void mouse_move(const MouseEvent& event) override 0041 { 0042 event.forward_to_scene(); 0043 } 0044 0045 void mouse_release(const MouseEvent& event) override 0046 { 0047 if ( forward_click ) 0048 { 0049 event.forward_to_scene(); 0050 forward_click = false; 0051 return; 0052 } 0053 0054 auto clicked_on = under_mouse(event, true, SelectionMode::Shape).nodes; 0055 for ( auto shape : clicked_on ) 0056 { 0057 if ( auto text = shape->node()->cast<model::TextShape>() ) 0058 { 0059 select(event, text); 0060 return; 0061 } 0062 } 0063 0064 if ( editor.scene() ) 0065 commit(event); 0066 else 0067 create(event); 0068 } 0069 0070 void mouse_double_click(const MouseEvent& event) override 0071 { 0072 if ( !editor.scene() ) 0073 return mouse_release(event); 0074 0075 if ( !editor.mapToScene(editor.boundingRect()).containsPoint(event.scene_pos, Qt::WindingFill) ) 0076 { 0077 commit(event); 0078 } 0079 else 0080 { 0081 event.forward_to_scene(); 0082 forward_click = true; 0083 } 0084 } 0085 0086 void paint(const PaintEvent& event) override 0087 { 0088 Q_UNUSED(event); 0089 } 0090 0091 void key_press(const KeyEvent& event) override 0092 { 0093 QCoreApplication::sendEvent(event.scene, event.event); 0094 } 0095 0096 void key_release(const KeyEvent& event) override 0097 { 0098 if ( event.key() == Qt::Key_Escape ) 0099 { 0100 commit(event); 0101 event.repaint(); 0102 event.accept(); 0103 event.window->switch_tool(Registry::instance().tool("select")); 0104 } 0105 else 0106 { 0107 QCoreApplication::sendEvent(event.scene, event.event); 0108 } 0109 } 0110 0111 void enable_event(const Event& event) override 0112 { 0113 widget()->set_document(event.window->document()); 0114 window = event.window; 0115 } 0116 0117 void disable_event(const Event& event) override 0118 { 0119 commit(event); 0120 window = nullptr; 0121 } 0122 0123 void close_document_event(const Event& event) override 0124 { 0125 widget()->set_document(event.window->document()); 0126 clear(); 0127 } 0128 0129 void clear() 0130 { 0131 if ( editor.scene() ) 0132 editor.scene()->removeItem(&editor); 0133 target = nullptr; 0134 editor.setPlainText(""); 0135 forward_click = false; 0136 modified = false; 0137 } 0138 0139 void commit(const Event& event) 0140 { 0141 QString text = editor.toPlainText(); 0142 0143 if ( target ) 0144 { 0145 if ( modified ) 0146 target->text.set_undoable(text, true); 0147 } 0148 else if ( !text.isEmpty() ) 0149 { 0150 auto shape = std::make_unique<model::TextShape>(event.window->document()); 0151 shape->text.set(text); 0152 shape->name.set(text); 0153 shape->position.set(editor.pos() - editor_offet()); 0154 shape->font->family.set(font.family()); 0155 shape->font->style.set(font.styleName()); 0156 shape->font->size.set(font.pointSizeF()); 0157 create_shape(i18n("Draw Text"), event, std::move(shape)); 0158 } 0159 0160 clear(); 0161 } 0162 0163 void select(const MouseEvent& event, model::TextShape* item) 0164 { 0165 select(event.scene, item); 0166 QPointF pos = editor.mapFromScene(event.scene_pos); 0167 QTextCursor cur(editor.document()); 0168 int curpos = editor.document()->documentLayout()->hitTest(pos, Qt::FuzzyHit); 0169 cur.setPosition(curpos); 0170 editor.setTextCursor(cur); 0171 } 0172 0173 void select(graphics::DocumentScene * scene, model::TextShape* item) 0174 { 0175 clear(); 0176 0177 widget()->set_font(item->font->query()); 0178 0179 scene->addItem(&editor); 0180 target = item; 0181 0182 // editor.setDefaultTextColor(Qt::transparent); 0183 editor.setPlainText(item->text.get()); 0184 0185 font = item->font->query(); 0186 editor.setFont(font); 0187 editor.setFocus(Qt::OtherFocusReason); 0188 0189 QPen pen(QColor(128, 0, 0, 100), 1); 0190 pen.setCosmetic(true); 0191 set_text_format(Qt::transparent, pen, item->font->line_spacing()); 0192 0193 editor.setPos({}); 0194 update_editor_position(); 0195 0196 widget()->set_preview_text(target->text.get()); 0197 show_keyboard(); 0198 } 0199 0200 void create(const MouseEvent& event) 0201 { 0202 clear(); 0203 0204 set_text_format(event.window->current_color(), event.window->current_pen_style(), base_line_spacing()); 0205 editor.setTransform(QTransform{}); 0206 editor.setPos(event.scene_pos + editor_offet()); 0207 event.scene->addItem(&editor); 0208 editor.setPlainText(""); 0209 editor.setFocus(Qt::OtherFocusReason); 0210 editor.setDefaultTextColor(Qt::black); 0211 editor.setFont(font); 0212 widget()->set_preview_text(""); 0213 show_keyboard(); 0214 } 0215 0216 void show_keyboard() 0217 { 0218 #ifdef Q_OS_ANDROID 0219 QGuiApplication::inputMethod()->show(); 0220 #endif 0221 } 0222 0223 QWidget* on_create_widget() override 0224 { 0225 auto widget = new TextToolWidget(); 0226 connect(widget, &TextToolWidget::checks_changed, this, &TextTool::update_format); 0227 return widget; 0228 } 0229 0230 private: 0231 void update_editor_position() 0232 { 0233 QTransform trans = target->transform_matrix(target->time()); 0234 QPointF pos = target->position.get() + editor_offet(); 0235 trans.translate(pos.x(), pos.y()); 0236 editor.setTransform(trans); 0237 } 0238 0239 TextToolWidget* widget() 0240 { 0241 return static_cast<TextToolWidget*>(get_settings_widget()); 0242 } 0243 0244 qreal base_line_spacing() 0245 { 0246 QFontMetrics metrics(editor.font()); 0247 return metrics.ascent() + metrics.descent(); 0248 } 0249 0250 void set_text_format(const QBrush& fill, const QPen& stroke, qreal line_height) 0251 { 0252 editor.document()->setUseDesignMetrics(true); 0253 QTextCursor cur = editor.textCursor(); 0254 cur.movePosition(QTextCursor::Start); 0255 cur.select(QTextCursor::Document); 0256 QTextCharFormat fmt; 0257 if ( widget()->create_stroke() ) 0258 fmt.setTextOutline(stroke); 0259 if ( widget()->create_fill() ) 0260 fmt.setForeground(fill); 0261 cur.setCharFormat(fmt); 0262 QTextBlockFormat bfmt; 0263 bfmt.setLineHeight(line_height - base_line_spacing(), QTextBlockFormat::LineDistanceHeight); 0264 cur.setBlockFormat(bfmt); 0265 editor.setTextCursor(cur); 0266 } 0267 0268 QPointF editor_offet() const 0269 { 0270 auto fmt = editor.document()->rootFrame()->frameFormat(); 0271 QFontMetrics metrics(font); 0272 auto margin = fmt.border() + fmt.padding(); 0273 return QPointF(-margin - fmt.leftMargin(), -margin - fmt.topMargin() - metrics.ascent()); 0274 } 0275 0276 model::TextShape* impl_extract_selection_recursive_item(graphics::DocumentScene * scene, model::VisualNode* node) 0277 { 0278 auto meta = node->metaObject(); 0279 if ( meta->inherits(&model::TextShape::staticMetaObject) ) 0280 return static_cast<model::TextShape*>(node); 0281 0282 if ( meta->inherits(&model::Group::staticMetaObject) ) 0283 { 0284 for ( const auto& sub : static_cast<model::Group*>(node)->shapes ) 0285 { 0286 if ( auto tn = impl_extract_selection_recursive_item(scene, sub.get()) ) 0287 return tn; 0288 } 0289 } 0290 0291 return nullptr; 0292 } 0293 0294 void on_selected(graphics::DocumentScene * scene, model::VisualNode * node) override 0295 { 0296 if ( auto text = impl_extract_selection_recursive_item(scene, node) ) 0297 select(scene, text); 0298 } 0299 0300 void on_font_changed(const QFont& f) 0301 { 0302 font = f; 0303 editor.setFont(f); 0304 0305 if ( target ) 0306 { 0307 target->font->from_qfont(f); 0308 update_editor_position(); 0309 } 0310 } 0311 0312 void custom_font_selected(int database_index) 0313 { 0314 if ( !window ) 0315 return; 0316 0317 window->document()->assets()->add_font(model::CustomFontDatabase::instance().get_font(database_index)); 0318 } 0319 0320 void initialize(const Event&) override 0321 { 0322 editor.setTextInteractionFlags(Qt::TextEditorInteraction); 0323 editor.setZValue(9001); 0324 font = widget()->font(); 0325 connect(widget(), &TextToolWidget::font_changed, this, &TextTool::on_font_changed); 0326 connect(editor.document(), &QTextDocument::contentsChanged, this, &TextTool::apply_changes); 0327 connect(widget(), &TextToolWidget::custom_font_selected, this, &TextTool::custom_font_selected); 0328 } 0329 0330 void apply_changes() 0331 { 0332 if ( target ) 0333 { 0334 QString text = editor.toPlainText(); 0335 if ( text != target->text.get() ) 0336 { 0337 target->text.set_undoable(text, false); 0338 modified = true; 0339 } 0340 } 0341 } 0342 0343 void update_format() 0344 { 0345 if ( !target ) 0346 set_text_format(window->current_color(), window->current_pen_style(), base_line_spacing()); 0347 } 0348 0349 static Autoreg<TextTool> autoreg; 0350 QGraphicsTextItem editor; 0351 model::TextShape* target = nullptr; 0352 bool forward_click = false; 0353 QFont font; 0354 bool modified = false; 0355 glaxnimate::gui::SelectionManager* window = nullptr; 0356 }; 0357 0358 tools::Autoreg<tools::TextTool> tools::TextTool::autoreg{max_priority + 3}; 0359 0360 } // namespace glaxnimate::gui::tools