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