1 /* This file is part of the KDE project
2  * Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
3  * Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
4  * Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
5  * Copyright (C) 2008, 2012 Pierre Stirnweiss <pstirnweiss@googlemail.org>
6  * Copyright (C) 2009 KO GmbH <cbo@kogmbh.com>
7  * Copyright (C) 2011 Mojtaba Shahi Senobari <mojtaba.shahi3000@gmail.com>
8  * Copyright (C) 2014 Denis Kuplyakov <dener.kup@gmail.com>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public License
21  * along with this library; see the file COPYING.LIB.  If not, write to
22  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  */
25 
26 #include "TextTool.h"
27 
28 #include "TextEditingPluginContainer.h"
29 #include "dialogs/SimpleCharacterWidget.h"
30 #include "dialogs/SimpleParagraphWidget.h"
31 #include "dialogs/SimpleTableWidget.h"
32 #include "dialogs/SimpleInsertWidget.h"
33 #include "dialogs/ParagraphSettingsDialog.h"
34 #include "dialogs/StyleManagerDialog.h"
35 #include "dialogs/InsertCharacter.h"
36 #include "dialogs/FontDia.h"
37 #include "dialogs/TableDialog.h"
38 #include "dialogs/SectionFormatDialog.h"
39 #include "dialogs/SectionsSplitDialog.h"
40 #include "commands/AutoResizeCommand.h"
41 #include "commands/ChangeListLevelCommand.h"
42 #include "FontSizeAction.h"
43 #include "FontFamilyAction.h"
44 #include "TextShapeDebug.h"
45 
46 #include <KoOdf.h>
47 #include <KoCanvasBase.h>
48 #include <KoShapeController.h>
49 #include <KoCanvasController.h>
50 #include <KoCanvasResourceManager.h>
51 #include <KoSelection.h>
52 #include <KoShapeManager.h>
53 #include <KoPointerEvent.h>
54 #include <KoColor.h>
55 #include <KoColorBackground.h>
56 #include <KoColorPopupAction.h>
57 #include <KoTextDocumentLayout.h>
58 #include <KoParagraphStyle.h>
59 #include <KoToolSelection.h>
60 #include <KoTextEditingPlugin.h>
61 #include <KoTextEditingRegistry.h>
62 #include <KoInlineTextObjectManager.h>
63 #include <KoTextRangeManager.h>
64 #include <KoStyleManager.h>
65 #include <KoTextOdfSaveHelper.h>
66 #include <KoTextDrag.h>
67 #include <KoTextDocument.h>
68 #include <KoTextEditor.h>
69 #include <KoChangeTracker.h>
70 #include <KoChangeTrackerElement.h>
71 #include <KoInlineNote.h>
72 #include <KoBookmark.h>
73 #include <KoBookmarkManager.h>
74 #include <KoListLevelProperties.h>
75 #include <KoTextLayoutRootArea.h>
76 //#include <ResizeTableCommand.h>
77 #include <KoIcon.h>
78 
79 #include <krun.h>
80 #include <kstandardshortcut.h>
81 #include <kactionmenu.h>
82 #include <kstandardaction.h>
83 #include <ksharedconfig.h>
84 #include <kmessagebox.h>
85 
86 #include <QMenu>
87 #include <QMenuBar>
88 #include <QAction>
89 #include <QTextTable>
90 #include <QTextList>
91 #include <QTabWidget>
92 #include <QTextDocumentFragment>
93 #include <QToolTip>
94 #include <QGraphicsObject>
95 #include <QLinearGradient>
96 #include <QBitmap>
97 #include <QDrag>
98 #include <QDragLeaveEvent>
99 #include <QDragMoveEvent>
100 #include <QDropEvent>
101 
102 #include "AnnotationTextShape.h"
103 #define AnnotationShape_SHAPEID "AnnotationTextShapeID"
104 #include "KoShapeBasedDocumentBase.h"
105 #include <KoAnnotation.h>
106 #include <KoShapeRegistry.h>
107 #include <kuser.h>
108 
109 #include <KoDocumentRdfBase.h>
110 
111 #include <algorithm>
112 
113 class TextToolSelection : public KoToolSelection
114 {
115 public:
116 
TextToolSelection(QPointer<KoTextEditor> editor)117     TextToolSelection(QPointer<KoTextEditor> editor)
118         : KoToolSelection(0)
119         , m_editor(editor)
120     {
121     }
122 
hasSelection()123     bool hasSelection() override
124     {
125         if (!m_editor.isNull()) {
126             return m_editor.data()->hasSelection();
127         }
128         return false;
129     }
130 
131     QPointer<KoTextEditor> m_editor;
132 };
133 
hit(const QKeySequence & input,KStandardShortcut::StandardShortcut shortcut)134 static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut)
135 {
136     foreach (const QKeySequence & ks, KStandardShortcut::shortcut(shortcut)) {
137         if (input == ks)
138             return true;
139     }
140     return false;
141 }
142 
TextTool(KoCanvasBase * canvas)143 TextTool::TextTool(KoCanvasBase *canvas)
144         : KoToolBase(canvas)
145         , m_textShape(0)
146         , m_textShapeData(0)
147         , m_changeTracker(0)
148         , m_allowActions(true)
149         , m_allowAddUndoCommand(true)
150         , m_allowResourceManagerUpdates(true)
151         , m_prevCursorPosition(-1)
152         , m_caretTimer(this)
153         , m_caretTimerState(true)
154         , m_currentCommand(0)
155         , m_currentCommandHasChildren(false)
156         , m_specialCharacterDocker(0)
157         , m_textTyping(false)
158         , m_textDeleting(false)
159         , m_editTipTimer(this)
160         , m_delayedEnsureVisible(false)
161         , m_toolSelection(0)
162         , m_tableDraggedOnce(false)
163         , m_tablePenMode(false)
164         , m_lastImMicroFocus(QRectF(0,0,0,0))
165         , m_drag(0)
166 {
167     setTextMode(true);
168 
169     createActions();
170 
171     m_unit = canvas->resourceManager()->unitResource(KoCanvasResourceManager::Unit);
172 
173     foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) {
174         connect(plugin, SIGNAL(startMacro(QString)),
175                 this, SLOT(startMacro(QString)));
176         connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro()));
177         const QHash<QString, QAction*> actions = plugin->actions();
178         QHash<QString, QAction*>::ConstIterator i = actions.begin();
179         while (i != actions.end()) {
180             addAction(i.key(), i.value());
181             ++i;
182         }
183     }
184 
185     // setup the context list.
186     QList<QAction*> list;
187     list.append(this->action("format_font"));
188     foreach (const QString &key, KoTextEditingRegistry::instance()->keys()) {
189         KoTextEditingFactory *factory =  KoTextEditingRegistry::instance()->value(key);
190         if (factory && factory->showInMenu()) {
191             QAction *a = new QAction(factory->title(), this);
192             connect(a, &QAction::triggered, [this, factory] { startTextEditingPlugin(factory->id()); });
193             list.append(a);
194             addAction(QString("apply_%1").arg(factory->id()), a);
195         }
196     }
197     setPopupActionList(list);
198 
199     connect(canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(shapeAddedToCanvas()));
200 
201     m_caretTimer.setInterval(500);
202     connect(&m_caretTimer, SIGNAL(timeout()), this, SLOT(blinkCaret()));
203 
204     m_editTipTimer.setInterval(500);
205     m_editTipTimer.setSingleShot(true);
206     connect(&m_editTipTimer, SIGNAL(timeout()), this, SLOT(showEditTip()));
207 }
208 
createActions()209 void TextTool::createActions()
210 {
211     bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality)
212                              & KoCanvasResourceManager::NoAdvancedText);
213 
214     m_actionConfigureSection = new QAction(koIconNeededWithSubs("", "configure-text-section", "configure"), i18n("Configure current section"), this);
215     addAction("configure_section", m_actionConfigureSection);
216     connect(m_actionConfigureSection, SIGNAL(triggered(bool)), this, SLOT(configureSection()));
217 
218     m_actionInsertSection = new QAction(koIconNeededWithSubs("", "insert-text-section", "insert-text"), i18n("Insert new section"), this);
219     addAction("insert_section", m_actionInsertSection);
220     connect(m_actionInsertSection, SIGNAL(triggered(bool)), this, SLOT(insertNewSection()));
221 
222     m_actionSplitSections = new QAction(koIconNeededWithSubs("", "text-section-split", "split"), i18n("Insert paragraph between sections"), this);
223     addAction("split_sections", m_actionSplitSections);
224     connect(m_actionSplitSections, SIGNAL(triggered(bool)), this, SLOT(splitSections()));
225 
226     m_actionPasteAsText  = new QAction(koIcon("edit-paste"), i18n("Paste As Text"), this);
227     addAction("edit_paste_text", m_actionPasteAsText);
228     m_actionPasteAsText->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V);
229     connect(m_actionPasteAsText, SIGNAL(triggered(bool)), this, SLOT(pasteAsText()));
230 
231     m_actionFormatBold  = new QAction(koIcon("format-text-bold"), i18n("Bold"), this);
232     addAction("format_bold", m_actionFormatBold);
233     m_actionFormatBold->setShortcut(Qt::CTRL + Qt::Key_B);
234     m_actionFormatBold->setCheckable(true);
235     connect(m_actionFormatBold, SIGNAL(triggered(bool)), this, SLOT(bold(bool)));
236 
237     m_actionFormatItalic  = new QAction(koIcon("format-text-italic"), i18n("Italic"), this);
238     addAction("format_italic", m_actionFormatItalic);
239     m_actionFormatItalic->setShortcut(Qt::CTRL + Qt::Key_I);
240     m_actionFormatItalic->setCheckable(true);
241     connect(m_actionFormatItalic, SIGNAL(triggered(bool)), this, SLOT(italic(bool)));
242 
243     m_actionFormatUnderline  = new QAction(koIcon("format-text-underline"), i18nc("Text formatting", "Underline"), this);
244     addAction("format_underline", m_actionFormatUnderline);
245     m_actionFormatUnderline->setShortcut(Qt::CTRL + Qt::Key_U);
246     m_actionFormatUnderline->setCheckable(true);
247     connect(m_actionFormatUnderline, SIGNAL(triggered(bool)), this, SLOT(underline(bool)));
248 
249     m_actionFormatStrikeOut  = new QAction(koIcon("format-text-strikethrough"), i18n("Strikethrough"), this);
250     addAction("format_strike", m_actionFormatStrikeOut);
251     m_actionFormatStrikeOut->setCheckable(true);
252     connect(m_actionFormatStrikeOut, SIGNAL(triggered(bool)), this, SLOT(strikeOut(bool)));
253 
254     QActionGroup *alignmentGroup = new QActionGroup(this);
255     m_actionAlignLeft  = new QAction(koIcon("format-justify-left"), i18n("Align Left"), this);
256     addAction("format_alignleft", m_actionAlignLeft);
257     m_actionAlignLeft->setShortcut(Qt::CTRL + Qt::Key_L);
258     m_actionAlignLeft->setCheckable(true);
259     alignmentGroup->addAction(m_actionAlignLeft);
260     connect(m_actionAlignLeft, SIGNAL(triggered(bool)), this, SLOT(alignLeft()));
261 
262     m_actionAlignRight  = new QAction(koIcon("format-justify-right"), i18n("Align Right"), this);
263     addAction("format_alignright", m_actionAlignRight);
264     m_actionAlignRight->setShortcut(Qt::CTRL + Qt::Key_R);
265     m_actionAlignRight->setCheckable(true);
266     alignmentGroup->addAction(m_actionAlignRight);
267     connect(m_actionAlignRight, SIGNAL(triggered(bool)), this, SLOT(alignRight()));
268 
269     m_actionAlignCenter  = new QAction(koIcon("format-justify-center"), i18n("Align Center"), this);
270     addAction("format_aligncenter", m_actionAlignCenter);
271     m_actionAlignCenter->setShortcut(Qt::CTRL + Qt::Key_E);
272     m_actionAlignCenter->setCheckable(true);
273 
274     alignmentGroup->addAction(m_actionAlignCenter);
275     connect(m_actionAlignCenter, SIGNAL(triggered(bool)), this, SLOT(alignCenter()));
276 
277     m_actionAlignBlock  = new QAction(koIcon("format-justify-fill"), i18n("Align Block"), this);
278     addAction("format_alignblock", m_actionAlignBlock);
279     m_actionAlignBlock->setShortcut(Qt::CTRL + Qt::Key_J);
280     m_actionAlignBlock->setCheckable(true);
281     alignmentGroup->addAction(m_actionAlignBlock);
282     connect(m_actionAlignBlock, SIGNAL(triggered(bool)), this, SLOT(alignBlock()));
283 
284     m_actionChangeDirection = new QAction(koIcon("format-text-direction-rtl"), i18n("Change text direction"), this);
285     addAction("change_text_direction", m_actionChangeDirection);
286     m_actionChangeDirection->setToolTip(i18n("Change writing direction"));
287     m_actionChangeDirection->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_D);
288     m_actionChangeDirection->setCheckable(true);
289     connect(m_actionChangeDirection, SIGNAL(triggered()), this, SLOT(textDirectionChanged()));
290 
291 
292     m_actionFormatSuper = new QAction(koIcon("format-text-superscript"), i18n("Superscript"), this);
293     m_actionFormatSuper->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P);
294     addAction("format_super", m_actionFormatSuper);
295     m_actionFormatSuper->setCheckable(true);
296     connect(m_actionFormatSuper, SIGNAL(triggered(bool)), this, SLOT(superScript(bool)));
297 
298     m_actionFormatSub = new QAction(koIcon("format-text-subscript"), i18n("Subscript"), this);
299     m_actionFormatSub->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_B);
300     addAction("format_sub", m_actionFormatSub);
301     m_actionFormatSub->setCheckable(true);
302     connect(m_actionFormatSub, SIGNAL(triggered(bool)), this, SLOT(subScript(bool)));
303 
304     const char* const increaseIndentActionIconName =
305         QApplication::isRightToLeft() ? koIconNameCStr("format-indent-less") : koIconNameCStr("format-indent-more");
306     m_actionFormatIncreaseIndent = new QAction(
307         QIcon::fromTheme(QLatin1String(increaseIndentActionIconName)),
308         i18n("Increase Indent"), this);
309     addAction("format_increaseindent", m_actionFormatIncreaseIndent);
310     connect(m_actionFormatIncreaseIndent, SIGNAL(triggered()), this, SLOT(increaseIndent()));
311 
312     const char* const decreaseIndentActionIconName =
313         QApplication::isRightToLeft() ? koIconNameCStr("format-indent-more") : koIconNameCStr("format-indent-less");
314     m_actionFormatDecreaseIndent = new QAction(QIcon::fromTheme(QLatin1String(decreaseIndentActionIconName)),
315                                                i18n("Decrease Indent"), this);
316     addAction("format_decreaseindent", m_actionFormatDecreaseIndent);
317     connect(m_actionFormatDecreaseIndent, SIGNAL(triggered()), this, SLOT(decreaseIndent()));
318 
319     QAction *action = new QAction(koIcon("format-list-unordered"),  i18n("Toggle List or List Level Formatting"), this);
320     action->setToolTip(i18n("Toggle list on/off, or change format of current level"));
321     addAction("format_list", action);
322 
323     action = new QAction(i18n("Increase Font Size"), this);
324     action->setShortcut(Qt::CTRL + Qt::Key_Greater);
325     addAction("fontsizeup", action);
326     connect(action, SIGNAL(triggered()), this, SLOT(increaseFontSize()));
327 
328     action = new QAction(i18n("Decrease Font Size"), this);
329     action->setShortcut(Qt::CTRL + Qt::Key_Less);
330     addAction("fontsizedown", action);
331     connect(action, SIGNAL(triggered()), this, SLOT(decreaseFontSize()));
332 
333     m_actionFormatFontFamily = new KoFontFamilyAction(this);
334     m_actionFormatFontFamily->setText(i18n("Font Family"));
335     addAction("format_fontfamily", m_actionFormatFontFamily);
336     connect(m_actionFormatFontFamily, SIGNAL(triggered(QString)),
337             this, SLOT(setFontFamily(QString)));
338 
339     m_variableMenu = new KActionMenu(i18n("Variable"), this);
340     addAction("insert_variable", m_variableMenu);
341 
342     // ------------------- Actions with a key binding and no GUI item
343     action  = new QAction(i18n("Insert Non-Breaking Space"), this);
344     addAction("nonbreaking_space", action);
345     action->setShortcut(Qt::CTRL + Qt::Key_Space);
346     connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingSpace()));
347 
348     action  = new QAction(i18n("Insert Non-Breaking Hyphen"), this);
349     addAction("nonbreaking_hyphen", action);
350     action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus);
351     connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingHyphen()));
352 
353     action  = new QAction(i18n("Insert Index"), this);
354     action->setShortcut(Qt::CTRL + Qt::Key_T);
355     addAction("insert_index", action);
356     connect(action, SIGNAL(triggered()), this, SLOT(insertIndexMarker()));
357 
358     action  = new QAction(i18n("Insert Soft Hyphen"), this);
359     addAction("soft_hyphen", action);
360     //action->setShortcut(Qt::CTRL + Qt::Key_Minus); // TODO this one is also used for the kde-global zoom-out :(
361     connect(action, SIGNAL(triggered()), this, SLOT(softHyphen()));
362 
363     if (useAdvancedText) {
364         action  = new QAction(i18n("Line Break"), this);
365         addAction("line_break", action);
366         action->setShortcut(Qt::SHIFT + Qt::Key_Return);
367         connect(action, SIGNAL(triggered()), this, SLOT(lineBreak()));
368 
369         action  = new QAction(koIcon("insert-page-break"), i18n("Page Break"), this);
370         addAction("insert_framebreak", action);
371         action->setShortcut(Qt::CTRL + Qt::Key_Return);
372         connect(action, SIGNAL(triggered()), this, SLOT(insertFrameBreak()));
373         action->setToolTip(i18n("Insert a page break"));
374         action->setWhatsThis(i18n("All text after this point will be moved into the next page."));
375     }
376 
377     action  = new QAction(i18n("Font..."), this);
378     addAction("format_font", action);
379     action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_F);
380     action->setToolTip(i18n("Change character size, font, boldface, italics etc."));
381     action->setWhatsThis(i18n("Change the attributes of the currently selected characters."));
382     connect(action, SIGNAL(triggered()), this, SLOT(selectFont()));
383 
384     m_actionFormatFontSize = new FontSizeAction(i18n("Font Size"), this);
385     addAction("format_fontsize", m_actionFormatFontSize);
386     connect(m_actionFormatFontSize, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal)));
387 
388     m_actionFormatTextColor = new KoColorPopupAction(this);
389     m_actionFormatTextColor->setIcon(koIcon("format-text-color"));
390     m_actionFormatTextColor->setToolTip(i18n("Text Color..."));
391     m_actionFormatTextColor->setText(i18n("Text Color"));
392     addAction("format_textcolor", m_actionFormatTextColor);
393     connect(m_actionFormatTextColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setTextColor(KoColor)));
394 
395     m_actionFormatBackgroundColor = new KoColorPopupAction(this);
396     m_actionFormatBackgroundColor->setIcon(koIcon("format-fill-color"));
397     m_actionFormatBackgroundColor->setToolTip(i18n("Background Color..."));
398     m_actionFormatBackgroundColor->setText(i18n("Background"));
399     addAction("format_backgroundcolor", m_actionFormatBackgroundColor);
400     connect(m_actionFormatBackgroundColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setBackgroundColor(KoColor)));
401 
402     m_autoResizeAction = new QAction(koIcon("zoom-fit-best"), i18n("Auto Resize To Content"), this);
403     addAction("auto_resize", m_autoResizeAction);
404     m_autoResizeAction->setCheckable(true);
405     connect(m_autoResizeAction, SIGNAL(triggered(bool)), this, SLOT(setAutoResize(bool)));
406 
407     m_growWidthAction = new QAction(koIcon("zoom-fit-best"), i18n("Grow To Fit Width"), this);
408     addAction("grow_to_fit_width", m_growWidthAction);
409     m_growWidthAction->setCheckable(true);
410     connect(m_growWidthAction, SIGNAL(triggered(bool)), this, SLOT(setGrowWidthToFit(bool)));
411 
412     m_growHeightAction = new QAction(koIcon("zoom-fit-best"), i18n("Grow To Fit Height"), this);
413     addAction("grow_to_fit_height", m_growHeightAction);
414     m_growHeightAction->setCheckable(true);
415     connect(m_growHeightAction, SIGNAL(triggered(bool)), this, SLOT(setGrowHeightToFit(bool)));
416 
417     m_shrinkToFitAction = new QAction(koIcon("zoom-fit-best"), i18n("Shrink To Fit"), this);
418     addAction("shrink_to_fit", m_shrinkToFitAction);
419     m_shrinkToFitAction->setCheckable(true);
420     connect(m_shrinkToFitAction, SIGNAL(triggered(bool)), this, SLOT(setShrinkToFit(bool)));
421 
422     if (useAdvancedText) {
423         action = new QAction(koIcon("insert-table"), i18n("Insert Custom..."), this);
424         addAction("insert_table", action);
425         action->setToolTip(i18n("Insert a table into the document."));
426         connect(action, SIGNAL(triggered()), this, SLOT(insertTable()));
427 
428         action  = new QAction(koIcon("edit-table-insert-row-above"), i18n("Row Above"), this);
429         action->setToolTip(i18n("Insert Row Above"));
430         addAction("insert_tablerow_above", action);
431         connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowAbove()));
432 
433         action  = new QAction(koIcon("edit-table-insert-row-below"), i18n("Row Below"), this);
434         action->setToolTip(i18n("Insert Row Below"));
435         addAction("insert_tablerow_below", action);
436         connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowBelow()));
437 
438         action  = new QAction(koIcon("edit-table-insert-column-left"), i18n("Column Left"), this);
439         action->setToolTip(i18n("Insert Column Left"));
440         addAction("insert_tablecolumn_left", action);
441         connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnLeft()));
442 
443         action  = new QAction(koIcon("edit-table-insert-column-right"), i18n("Column Right"), this);
444         action->setToolTip(i18n("Insert Column Right"));
445         addAction("insert_tablecolumn_right", action);
446         connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnRight()));
447 
448         action  = new QAction(koIcon("edit-table-delete-column"), i18n("Column"), this);
449         action->setToolTip(i18n("Delete Column"));
450         addAction("delete_tablecolumn", action);
451         connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableColumn()));
452 
453         action  = new QAction(koIcon("edit-table-delete-row"), i18n("Row"), this);
454         action->setToolTip(i18n("Delete Row"));
455         addAction("delete_tablerow", action);
456         connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableRow()));
457 
458         action  = new QAction(koIcon("edit-table-cell-merge"), i18n("Merge Cells"), this);
459         addAction("merge_tablecells", action);
460         connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeTableCells()));
461 
462         action  = new QAction(koIcon("edit-table-cell-split"), i18n("Split Cells"), this);
463         addAction("split_tablecells", action);
464         connect(action, SIGNAL(triggered(bool)), this, SLOT(splitTableCells()));
465 
466         action = new QAction(koIcon("borderpainter"), "", this);
467         action->setToolTip(i18n("Select a border style and paint that style onto a table"));
468         addAction("activate_borderpainter", action);
469     }
470 
471     action = new QAction(i18n("Paragraph..."), this);
472     addAction("format_paragraph", action);
473     action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_P);
474     action->setToolTip(i18n("Change paragraph margins, text flow, borders, bullets, numbering etc."));
475     action->setWhatsThis(i18n("<p>Change paragraph margins, text flow, borders, bullets, numbering etc.</p><p>Select text in multiple paragraphs to change the formatting of all selected paragraphs.</p><p>If no text is selected, the paragraph where the cursor is located will be changed.</p>"));
476     connect(action, SIGNAL(triggered()), this, SLOT(formatParagraph()));
477 
478     action = new QAction(i18n("Style Manager..."), this);
479     action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_S);
480     action->setToolTip(i18n("Change attributes of styles"));
481     action->setWhatsThis(i18n("<p>Change font and paragraph attributes of styles.</p><p>Multiple styles can be changed using the dialog box.</p>"));
482     addAction("format_stylist", action);
483     connect(action, SIGNAL(triggered()), this, SLOT(showStyleManager()));
484 
485     action = KStandardAction::selectAll(this, SLOT(selectAll()), this);
486     addAction("edit_select_all", action);
487 
488     action = new QAction(i18n("Special Character..."), this);
489     action->setIcon(koIcon("character-set"));
490     action->setShortcut(Qt::ALT + Qt::SHIFT + Qt::Key_C);
491     addAction("insert_specialchar", action);
492     action->setToolTip(i18n("Insert one or more symbols or characters not found on the keyboard"));
493     action->setWhatsThis(i18n("Insert one or more symbols or characters not found on the keyboard."));
494     connect(action, SIGNAL(triggered()), this, SLOT(insertSpecialCharacter()));
495 
496     action = new QAction(i18n("Repaint"), this);
497     action->setIcon(koIcon("view-refresh"));
498     addAction("repaint", action);
499     connect(action, SIGNAL(triggered()), this, SLOT(relayoutContent()));
500 
501     action = new QAction(i18n("Insert Comment"), this);
502     addAction("insert_annotation", action);
503     action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C);
504     connect(action, SIGNAL(triggered()), this, SLOT(insertAnnotation()));
505 
506 #ifndef NDEBUG
507     action = new QAction("Paragraph Debug", this); // do NOT add i18n!
508     action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_P);
509     addAction("detailed_debug_paragraphs", action);
510     connect(action, SIGNAL(triggered()), this, SLOT(debugTextDocument()));
511     action = new QAction("Styles Debug", this); // do NOT add i18n!
512     action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_S);
513     addAction("detailed_debug_styles", action);
514     connect(action, SIGNAL(triggered()), this, SLOT(debugTextStyles()));
515 #endif
516 }
517 
518 
519 #ifndef NDEBUG
520 #include "tests/MockShapes.h"
521 #include <kundo2stack.h>
522 #include <QMimeDatabase>
523 #include <QMimeType>
524 
TextTool(MockCanvas * canvas)525 TextTool::TextTool(MockCanvas *canvas)  // constructor for our unit tests;
526     : KoToolBase(canvas),
527     m_textShape(0),
528     m_textShapeData(0),
529     m_changeTracker(0),
530     m_allowActions(true),
531     m_allowAddUndoCommand(true),
532     m_allowResourceManagerUpdates(true),
533     m_prevCursorPosition(-1),
534     m_caretTimer(this),
535     m_caretTimerState(true),
536     m_currentCommand(0),
537     m_currentCommandHasChildren(false),
538     m_specialCharacterDocker(0),
539     m_textEditingPlugins(0)
540     , m_editTipTimer(this)
541     , m_delayedEnsureVisible(false)
542     , m_tableDraggedOnce(false)
543     , m_tablePenMode(false)
544 {
545     // we could init some vars here, but we probably don't have to
546     QLocale::setDefault(QLocale("en"));
547     QTextDocument *document = new QTextDocument(); // this document is leaked
548 
549     KoInlineTextObjectManager *inlineManager = new KoInlineTextObjectManager();
550     KoTextDocument(document).setInlineTextObjectManager(inlineManager);
551 
552     KoTextRangeManager *locationManager = new KoTextRangeManager();
553     KoTextDocument(document).setTextRangeManager(locationManager);
554 
555     m_textEditor = new KoTextEditor(document);
556     KoTextDocument(document).setTextEditor(m_textEditor.data());
557     m_toolSelection = new TextToolSelection(m_textEditor);
558 
559     m_changeTracker = new KoChangeTracker();
560     KoTextDocument(document).setChangeTracker(m_changeTracker);
561 
562     KoTextDocument(document).setUndoStack(new KUndo2Stack());
563 }
564 #endif
565 
~TextTool()566 TextTool::~TextTool()
567 {
568     delete m_toolSelection;
569 }
570 
showEditTip()571 void TextTool::showEditTip()
572 {
573     if (!m_textShapeData || m_editTipPointedAt.position == -1)
574         return;
575 
576     QTextCursor c(m_textShapeData->document());
577     c.setPosition(m_editTipPointedAt.position);
578     QString text = "<p align=center style=\'white-space:pre\' >";
579     int toolTipWidth = 0;
580 
581     if (m_changeTracker && m_changeTracker->containsInlineChanges(c.charFormat())
582         && m_changeTracker->displayChanges()) {
583         KoChangeTrackerElement *element = m_changeTracker->elementById(c.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt());
584         if (element->isEnabled()) {
585             QString changeType;
586             if (element->getChangeType() == KoGenChange::InsertChange)
587                 changeType = i18n("Insertion");
588             else if (element->getChangeType() == KoGenChange::DeleteChange)
589                 changeType = i18n("Deletion");
590             else
591                 changeType = i18n("Formatting");
592 
593             text += "<b>" + changeType + "</b><br/>";
594 
595             QString date = element->getDate();
596             //Remove the T which separates the Data and Time.
597             date[10] = QLatin1Char(' ');
598             date = element->getCreator() + QLatin1Char(' ') + date;
599             text += date + "</p>";
600 
601             toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(date).width();
602         }
603     }
604 
605     if (m_editTipPointedAt.bookmark || !m_editTipPointedAt.externalHRef.isEmpty()) {
606             QString help = i18n("Ctrl+click to go to link ");
607             help += m_editTipPointedAt.externalHRef;
608             text += help + "</p>";
609             toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width();
610     }
611 
612     if (m_editTipPointedAt.note) {
613             QString help = i18n("Ctrl+click to go to the note ");
614             text += help + "</p>";
615             toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width();
616     }
617 
618     if (m_editTipPointedAt.noteReference>0) {
619             QString help = i18n("Ctrl+click to go to the note reference");
620             text += help + "</p>";
621             toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width();
622     }
623 
624     QToolTip::hideText();
625 
626     if (toolTipWidth) {
627         QRect keepRect(m_editTipPos - QPoint(3,3), QSize(6,6));
628         QToolTip::showText(m_editTipPos - QPoint(toolTipWidth/2, 0), text, canvas()->canvasWidget(), keepRect);
629     }
630 
631 }
632 
blinkCaret()633 void TextTool::blinkCaret()
634 {
635     if (!(canvas()->canvasWidget() ? canvas()->canvasWidget()->hasFocus() : canvas()->canvasItem()->hasFocus())) {
636         m_caretTimer.stop();
637         m_caretTimerState = false; // not visible.
638     }
639     else {
640         m_caretTimerState = !m_caretTimerState;
641     }
642     repaintCaret();
643 }
644 
relayoutContent()645 void TextTool::relayoutContent()
646 {
647     KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(m_textShapeData->document()->documentLayout());
648     Q_ASSERT(lay);
649     foreach (KoTextLayoutRootArea *rootArea, lay->rootAreas()) {
650         rootArea->setDirty();
651     }
652     lay->emitLayoutIsDirty();
653 }
654 
paint(QPainter & painter,const KoViewConverter & converter)655 void TextTool::paint(QPainter &painter, const KoViewConverter &converter)
656 {
657     if (m_textEditor.isNull())
658         return;
659     if (canvas()
660             && (( canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus())
661                   || (canvas()->canvasItem() && canvas()->canvasItem()->hasFocus())
662                )
663             && !m_caretTimer.isActive()) { // make sure we blink
664         m_caretTimer.start();
665         m_caretTimerState = true;
666     }
667     if (!m_caretTimerState)
668         m_caretTimer.setInterval(500); // we set it lower during typing, so set it back to normal
669 
670     if (!m_textShapeData)
671         return;
672     if (m_textShapeData->isDirty())
673         return;
674 
675     qreal zoomX, zoomY;
676     converter.zoom(&zoomX, &zoomY);
677 
678     painter.save();
679     QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter);
680     shapeMatrix.scale(zoomX, zoomY);
681     shapeMatrix.translate(0, -m_textShapeData->documentOffset());
682 
683     // Possibly draw table dragging visual cues
684     const qreal boxHeight = 20;
685     if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) {
686         QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(m_dx, 0.0);
687         if (m_tableDragInfo.tableColumnDivider > 0) {
688             //let's draw left
689             qreal w = m_tableDragInfo.tableLeadSize - m_dx;
690             QRectF rect(anchorPos - QPointF(w, 0.0), QSizeF(w, 0.0));
691             QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight()));
692             drawRect.setHeight(boxHeight);
693             drawRect.moveTop(drawRect.top() - 1.5 * boxHeight);
694             QString label = m_unit.toUserStringValue(w);
695             int labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width();
696             painter.fillRect(drawRect, QColor(64, 255, 64, 196));
697             painter.setPen(QPen(QColor(0, 0, 0, 196), 0));
698             if (labelWidth + 10 < drawRect.width()) {
699                 QPointF centerLeft(drawRect.left(), drawRect.center().y());
700                 QPointF centerRight(drawRect.right(), drawRect.center().y());
701                 painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth/2+5, 0.0));
702                 painter.drawLine(centerLeft, centerLeft + QPointF(7, -5));
703                 painter.drawLine(centerLeft, centerLeft + QPointF(7, 5));
704                 painter.drawLine(drawRect.center() + QPointF(labelWidth/2+5, 0.0), centerRight);
705                 painter.drawLine(centerRight, centerRight + QPointF(-7, -5));
706                 painter.drawLine(centerRight, centerRight + QPointF(-7, 5));
707                 painter.drawText(drawRect, Qt::AlignCenter, label);
708             }
709         }
710         if (m_tableDragInfo.tableColumnDivider <  m_tableDragInfo.table->columns()) {
711             //let's draw right
712             qreal w = m_tableDragInfo.tableTrailSize + m_dx;
713             QRectF rect(anchorPos, QSizeF(w, 0.0));
714             QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight()));
715             drawRect.setHeight(boxHeight);
716             drawRect.moveTop(drawRect.top() - 1.5 * boxHeight);
717             QString label;
718             int labelWidth;
719             if (m_tableDragWithShift) {
720                 label = i18n("follows along");
721                 labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width();
722                 drawRect.setWidth(2 * labelWidth);
723                 QLinearGradient g(drawRect.topLeft(), drawRect.topRight());
724                 g.setColorAt(0.6, QColor(255, 64, 64, 196));
725                 g.setColorAt(1.0, QColor(255, 64, 64, 0));
726                 QBrush brush(g);
727                 painter.fillRect(drawRect, brush);
728             } else {
729                 label = m_unit.toUserStringValue(w);
730                 labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width();
731                 drawRect.setHeight(boxHeight);
732                 painter.fillRect(drawRect, QColor(64, 255, 64, 196));
733             }
734             painter.setPen(QPen(QColor(0, 0, 0, 196), 0));
735             if (labelWidth + 10 < drawRect.width()) {
736                 QPointF centerLeft(drawRect.left(), drawRect.center().y());
737                 QPointF centerRight(drawRect.right(), drawRect.center().y());
738                 painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth/2+5, 0.0));
739                 painter.drawLine(centerLeft, centerLeft + QPointF(7, -5));
740                 painter.drawLine(centerLeft, centerLeft + QPointF(7, 5));
741                 if (!m_tableDragWithShift) {
742                     painter.drawLine(drawRect.center() + QPointF(labelWidth/2+5, 0.0), centerRight);
743                     painter.drawLine(centerRight, centerRight + QPointF(-7, -5));
744                     painter.drawLine(centerRight, centerRight + QPointF(-7, 5));
745                 }
746                 painter.drawText(drawRect, Qt::AlignCenter, label);
747             }
748             if (!m_tableDragWithShift) {
749                 // let's draw a helper text too
750                 label = i18n("Press shift to not resize this");
751                 labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width();
752                 labelWidth += 10;
753                 //if (labelWidth < drawRect.width())
754                 {
755                     drawRect.moveTop(drawRect.top() + boxHeight);
756                     drawRect.moveLeft(drawRect.left() + (drawRect.width() - labelWidth)/2);
757                     drawRect.setWidth(labelWidth);
758                     painter.fillRect(drawRect, QColor(64, 255, 64, 196));
759                     painter.drawText(drawRect, Qt::AlignCenter, label);
760                 }
761             }
762         }
763     }
764     // Possibly draw table dragging visual cues
765     if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) {
766         QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(0.0, m_dy);
767         if (m_tableDragInfo.tableRowDivider > 0) {
768             qreal h = m_tableDragInfo.tableLeadSize - m_dy;
769             QRectF rect(anchorPos - QPointF(0.0, h), QSizeF(0.0, h));
770             QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight()));
771             drawRect.setWidth(boxHeight);
772             drawRect.moveLeft(drawRect.left() - 1.5 * boxHeight);
773             QString label = m_unit.toUserStringValue(h);
774             QRectF labelRect = QFontMetrics(QToolTip::font()).boundingRect(label);
775             labelRect.setHeight(boxHeight);
776             labelRect.setWidth(labelRect.width() + 10);
777             labelRect.moveTopLeft(drawRect.center() - QPointF(labelRect.width(), labelRect.height())/2);
778             painter.fillRect(drawRect, QColor(64, 255, 64, 196));
779             painter.fillRect(labelRect, QColor(64, 255, 64, 196));
780             painter.setPen(QPen(QColor(0, 0, 0, 196), 0));
781             if (labelRect.height() + 10 < drawRect.height()) {
782                 QPointF centerTop(drawRect.center().x(), drawRect.top());
783                 QPointF centerBottom(drawRect.center().x(), drawRect.bottom());
784                 painter.drawLine(centerTop, drawRect.center() - QPointF(0.0, labelRect.height()/2+5));
785                 painter.drawLine(centerTop, centerTop + QPointF(-5, 7));
786                 painter.drawLine(centerTop, centerTop + QPointF(5, 7));
787                 painter.drawLine(drawRect.center() + QPointF(0.0, labelRect.height()/2+5), centerBottom);
788                 painter.drawLine(centerBottom, centerBottom + QPointF(-5, -7));
789                 painter.drawLine(centerBottom, centerBottom + QPointF(5, -7));
790             }
791             painter.drawText(labelRect, Qt::AlignCenter, label);
792         }
793     }
794     if (m_caretTimerState) {
795         // Lets draw the caret ourselves, as the Qt method doesn't take cursor
796         // charFormat into consideration.
797         QTextBlock block = m_textEditor.data()->block();
798         if (block.isValid()) {
799             int posInParag = m_textEditor.data()->position() - block.position();
800             if (posInParag <= block.layout()->preeditAreaPosition())
801                 posInParag += block.layout()->preeditAreaText().length();
802 
803             QTextLine tl = block.layout()->lineForTextPosition(m_textEditor.data()->position() - block.position());
804             if (tl.isValid()) {
805                 painter.setRenderHint(QPainter::Antialiasing, false);
806                 QRectF rect = caretRect(m_textEditor.data()->cursor());
807                 QPointF baselinePoint;
808                 if (tl.ascent() > 0) {
809                     QFontMetricsF fm(m_textEditor.data()->charFormat().font(), painter.device());
810                     rect.setY(rect.y() + tl.ascent() - qMin(tl.ascent(), fm.ascent()));
811                     rect.setHeight(qMin(tl.ascent(), fm.ascent()) + qMin(tl.descent(), fm.descent()));
812                     baselinePoint = QPoint(rect.x(), rect.y() + tl.ascent());
813                 } else {
814                     //line only filled with characters-without-size (eg anchors)
815                     // layout will make sure line has height of block font
816                     QFontMetricsF fm(block.charFormat().font(), painter.device());
817                     rect.setHeight(fm.ascent() + fm.descent());
818                     baselinePoint = QPoint(rect.x(), rect.y() + fm.ascent());
819                 }
820                 QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomLeft()));
821                 drawRect.setWidth(2);
822                 painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
823                 if (m_textEditor.data()->isEditProtected(true)) {
824                     QRectF circleRect(shapeMatrix.map(baselinePoint),QSizeF(14, 14));
825                     circleRect.translate(-6.5, -6.5);
826                     QPen pen(QColor(16, 255, 255));
827                     pen.setWidthF(2.0);
828                     painter.setPen(pen);
829                     painter.setRenderHint(QPainter::Antialiasing, true);
830                     painter.drawEllipse(circleRect);
831                     painter.drawLine(circleRect.topLeft() + QPointF(4.5,4.5),
832                                     circleRect.bottomRight() - QPointF(4.5,4.5));
833                 } else {
834                     painter.fillRect(drawRect, QColor(128, 255, 128));
835                 }
836             }
837         }
838     }
839 
840     painter.restore();
841 }
842 
updateSelectedShape(const QPointF & point,bool noDocumentChange)843 void TextTool::updateSelectedShape(const QPointF &point, bool noDocumentChange)
844 {
845     QRectF area(point, QSizeF(1, 1));
846     if (m_textEditor.data()->hasSelection())
847         repaintSelection();
848     else
849         repaintCaret();
850     QList<KoShape*> sortedShapes = canvas()->shapeManager()->shapesAt(area, true);
851     std::sort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
852     for (int count = sortedShapes.count() - 1; count >= 0; count--) {
853         KoShape *shape = sortedShapes.at(count);
854 
855         if (shape->isContentProtected())
856             continue;
857         TextShape *textShape = dynamic_cast<TextShape*>(shape);
858         if (textShape) {
859             if (textShape != m_textShape) {
860                 if (static_cast<KoTextShapeData*>(textShape->userData())->document() != m_textShapeData->document()) {
861                     //we should only change to another document if allowed
862                     if (noDocumentChange) {
863                         return;
864                     }
865 
866                     // if we change to another textdocument we need to remove selection in old document
867                     // or it would continue to be painted etc
868 
869                     m_textEditor.data()->setPosition(m_textEditor.data()->position());
870                 }
871                 m_textShape = textShape;
872 
873                 setShapeData(static_cast<KoTextShapeData*>(m_textShape->userData()));
874 
875                 // This is how we inform the rulers of the active range
876                 // For now we will not consider table cells, but just give the shape dimensions
877                 QVariant v;
878                 QRectF rect(QPoint(), m_textShape->size());
879                 rect = m_textShape->absoluteTransformation(0).mapRect(rect);
880                 v.setValue(rect);
881                 canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v);
882             }
883             return;
884         }
885     }
886 }
887 
mousePressEvent(KoPointerEvent * event)888 void TextTool::mousePressEvent(KoPointerEvent *event)
889 {
890     if (m_textEditor.isNull())
891         return;
892 
893     // request the software keyboard, if any
894     if (event->button() == Qt::LeftButton && qApp->autoSipEnabled()) {
895         QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(qApp->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel));
896         // the two following bools just make it all a lot easier to read in the following if()
897         // basically, we require a widget for this to work (passing nullptr to QApplication::sendEvent
898         // crashes) and there are three tests any one of which can be true to trigger the event
899         const bool hasWidget = canvas()->canvasWidget();
900         const bool hasItem = canvas()->canvasItem();
901         if ((behavior == QStyle::RSIP_OnMouseClick && (hasWidget || hasItem)) ||
902             (hasWidget && canvas()->canvasWidget()->hasFocus()) ||
903             (hasItem && canvas()->canvasItem()->hasFocus())) {
904             QEvent event(QEvent::RequestSoftwareInputPanel);
905             if (hasWidget) {
906                 QApplication::sendEvent(canvas()->canvasWidget(), &event);
907             } else {
908                 QApplication::sendEvent(canvas()->canvasItem(), &event);
909             }
910         }
911     }
912 
913     bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
914 
915     updateSelectedShape(event->point, shiftPressed);
916 
917     KoSelection *selection = canvas()->shapeManager()->selection();
918     if (m_textShape && !selection->isSelected(m_textShape) && m_textShape->isSelectable()) {
919         selection->deselectAll();
920         selection->select(m_textShape);
921     }
922 
923     KoPointedAt pointedAt = hitTest(event->point);
924     m_tableDraggedOnce = false;
925     m_clickWithinSelection = false;
926     if (pointedAt.position != -1) {
927         m_tablePenMode = false;
928 
929         if ((event->button() == Qt::LeftButton) && !shiftPressed && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position)) {
930             m_clickWithinSelection = true;
931             m_draggingOrigin = event->pos(); //we store the pixel pos
932         } else if (! (event->button() == Qt::RightButton && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position))) {
933             m_textEditor.data()->setPosition(pointedAt.position, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
934             useCursor(Qt::IBeamCursor);
935         }
936         m_tableDragInfo.tableHit = KoPointedAt::None;
937         if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw)
938             m_caretTimer.stop();
939             m_caretTimer.setInterval(50);
940             m_caretTimer.start();
941             m_caretTimerState = true; // turn caret instantly on on click
942         }
943     } else {
944         if (event->button() == Qt::RightButton) {
945             m_tablePenMode = false;
946             KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck();
947             if (plugin)
948                 plugin->setCurrentCursorPosition(m_textShapeData->document(), -1);
949 
950             event->ignore();
951         } else if (m_tablePenMode) {
952             m_textEditor.data()->beginEditBlock(kundo2_i18n("Change Border Formatting"));
953             if (pointedAt.tableHit == KoPointedAt::ColumnDivider) {
954                 if (pointedAt.tableColumnDivider < pointedAt.table->columns()) {
955                     m_textEditor.data()->setTableBorderData(pointedAt.table,
956                         pointedAt.tableRowDivider, pointedAt.tableColumnDivider,
957                         KoBorder::LeftBorder, m_tablePenBorderData);
958                 }
959                 if (pointedAt.tableColumnDivider > 0) {
960                     m_textEditor.data()->setTableBorderData(pointedAt.table,
961                         pointedAt.tableRowDivider, pointedAt.tableColumnDivider - 1,
962                         KoBorder::RightBorder, m_tablePenBorderData);
963                 }
964             } else if (pointedAt.tableHit == KoPointedAt::RowDivider) {
965                 if (pointedAt.tableRowDivider < pointedAt.table->rows()) {
966                     m_textEditor.data()->setTableBorderData(pointedAt.table,
967                         pointedAt.tableRowDivider, pointedAt.tableColumnDivider,
968                         KoBorder::TopBorder, m_tablePenBorderData);
969                 }
970                 if (pointedAt.tableRowDivider > 0) {
971                     m_textEditor.data()->setTableBorderData(pointedAt.table,
972                         pointedAt.tableRowDivider-1, pointedAt.tableColumnDivider,
973                         KoBorder::BottomBorder, m_tablePenBorderData);
974                 }
975             }
976             m_textEditor.data()->endEditBlock();
977         } else {
978             m_tableDragInfo = pointedAt;
979             m_tablePenMode = false;
980         }
981         return;
982     }
983     if (shiftPressed) // altered selection.
984         repaintSelection();
985     else
986         repaintCaret();
987 
988     updateSelectionHandler();
989     updateStyleManager();
990 
991     updateActions();
992 
993     //activate context-menu for spelling-suggestions
994     if (event->button() == Qt::RightButton) {
995         KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck();
996         if (plugin)
997             plugin->setCurrentCursorPosition(m_textShapeData->document(), m_textEditor.data()->position());
998 
999         event->ignore();
1000     }
1001 
1002     if (event->button() ==  Qt::MidButton) { // Paste
1003         const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Selection);
1004 
1005         // on windows we do not have data if we try to paste this selection
1006         if (data) {
1007             m_prevCursorPosition = m_textEditor.data()->position();
1008             m_textEditor.data()->paste(canvas(), data, canvas()->resourceManager());
1009             editingPluginEvents();
1010         }
1011     }
1012 }
1013 
setShapeData(KoTextShapeData * data)1014 void TextTool::setShapeData(KoTextShapeData *data)
1015 {
1016     bool docChanged = !data || !m_textShapeData || m_textShapeData->document() != data->document();
1017     if (m_textShapeData) {
1018         disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
1019     }
1020     m_textShapeData = data;
1021     if (!m_textShapeData)
1022         return;
1023     connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
1024     if (docChanged) {
1025         if (!m_textEditor.isNull()) {
1026             disconnect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions()));
1027         }
1028         m_textEditor = KoTextDocument(m_textShapeData->document()).textEditor();
1029         Q_ASSERT(m_textEditor.data());
1030         if (!m_toolSelection) {
1031             m_toolSelection = new TextToolSelection(m_textEditor.data());
1032         }
1033         else {
1034             m_toolSelection->m_editor = m_textEditor.data();
1035         }
1036 
1037         m_variableMenu->menu()->clear();
1038         KoTextDocument document(m_textShapeData->document());
1039         foreach (QAction *action, document.inlineTextObjectManager()->createInsertVariableActions(canvas())) {
1040             m_variableMenu->addAction(action);
1041             connect(action, SIGNAL(triggered()), this, SLOT(returnFocusToCanvas()));
1042         }
1043 
1044         connect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions()));
1045         updateActions();
1046     }
1047 }
1048 
updateSelectionHandler()1049 void TextTool::updateSelectionHandler()
1050 {
1051     if (m_textEditor) {
1052         emit selectionChanged(m_textEditor.data()->hasSelection());
1053         if (m_textEditor.data()->hasSelection()) {
1054             QClipboard *clipboard = QApplication::clipboard();
1055             if (clipboard->supportsSelection())
1056                 clipboard->setText(m_textEditor.data()->selectedText(), QClipboard::Selection);
1057         }
1058     }
1059 
1060     KoCanvasResourceManager *p = canvas()->resourceManager();
1061     m_allowResourceManagerUpdates = false;
1062     if (m_textEditor && m_textShapeData) {
1063         p->setResource(KoText::CurrentTextPosition, m_textEditor.data()->position());
1064         p->setResource(KoText::CurrentTextAnchor, m_textEditor.data()->anchor());
1065         QVariant variant;
1066         variant.setValue<void*>(m_textShapeData->document());
1067         p->setResource(KoText::CurrentTextDocument, variant);
1068     } else {
1069         p->clearResource(KoText::CurrentTextPosition);
1070         p->clearResource(KoText::CurrentTextAnchor);
1071         p->clearResource(KoText::CurrentTextDocument);
1072     }
1073     m_allowResourceManagerUpdates = true;
1074 }
1075 
generateMimeData() const1076 QMimeData *TextTool::generateMimeData() const
1077 {
1078     if (!m_textShapeData || m_textEditor.isNull() || !m_textEditor.data()->hasSelection())
1079         return 0;
1080     int from = m_textEditor.data()->position();
1081     int to = m_textEditor.data()->anchor();
1082     KoTextOdfSaveHelper saveHelper(m_textShapeData->document(), from, to);
1083     KoTextDrag drag;
1084 
1085 #ifdef SHOULD_BUILD_RDF
1086     KoDocumentResourceManager *rm = 0;
1087     if (canvas()->shapeController()) {
1088         rm = canvas()->shapeController()->resourceManager();
1089     }
1090 
1091     if (rm && rm->hasResource(KoText::DocumentRdf)) {
1092         KoDocumentRdfBase *rdf = qobject_cast<KoDocumentRdfBase*>(rm->resource(KoText::DocumentRdf).value<QObject*>());
1093         if (rdf) {
1094             saveHelper.setRdfModel(rdf->model());
1095         }
1096     }
1097 #endif
1098     drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
1099     QTextDocumentFragment fragment = m_textEditor.data()->selection();
1100     drag.setData("text/plain", fragment.toPlainText().toUtf8());
1101 
1102     return drag.takeMimeData();
1103 }
1104 
textEditingPluginContainer()1105 TextEditingPluginContainer *TextTool::textEditingPluginContainer()
1106 {
1107     m_textEditingPlugins = canvas()->resourceManager()->
1108         resource(TextEditingPluginContainer::ResourceId).value<TextEditingPluginContainer*>();
1109 
1110     if (m_textEditingPlugins == 0) {
1111         m_textEditingPlugins = new TextEditingPluginContainer(canvas()->resourceManager());
1112         QVariant variant;
1113         variant.setValue(m_textEditingPlugins.data());
1114         canvas()->resourceManager()->setResource(TextEditingPluginContainer::ResourceId, variant);
1115 
1116         foreach (KoTextEditingPlugin* plugin, m_textEditingPlugins->values()) {
1117             connect(plugin, SIGNAL(startMacro(QString)),
1118                     this, SLOT(startMacro(QString)));
1119             connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro()));
1120             const QHash<QString, QAction*> actions = plugin->actions();
1121             QHash<QString, QAction*>::ConstIterator i = actions.begin();
1122             while (i != actions.end()) {
1123                 addAction(i.key(), i.value());
1124                 ++i;
1125             }
1126         }
1127 
1128     }
1129     return m_textEditingPlugins;
1130 }
1131 
copy() const1132 void TextTool::copy() const
1133 {
1134     QMimeData *mimeData = generateMimeData();
1135     if (mimeData) {
1136         QApplication::clipboard()->setMimeData(mimeData);
1137     }
1138 }
1139 
deleteSelection()1140 void TextTool::deleteSelection()
1141 {
1142     m_textEditor.data()->deleteChar();
1143     editingPluginEvents();
1144 }
1145 
paste()1146 bool TextTool::paste()
1147 {
1148     const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard);
1149 
1150     // on windows we do not have data if we try to paste the selection
1151     if (!data) return false;
1152 
1153     // since this is not paste-as-text we will not paste in urls, but instead let KoToolProxy solve it
1154     if (data->hasUrls()) return false;
1155 
1156     if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))
1157         ||  data->hasText()) {
1158         m_prevCursorPosition = m_textEditor.data()->position();
1159         m_textEditor.data()->paste(canvas(), data);
1160         editingPluginEvents();
1161         return true;
1162     }
1163 
1164     return false;
1165 }
1166 
cut()1167 void TextTool::cut()
1168 {
1169     if (m_textEditor.data()->hasSelection()) {
1170         copy();
1171         KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Cut"));
1172         m_textEditor.data()->deleteChar(false, topCmd);
1173         m_textEditor.data()->endEditBlock();
1174     }
1175 }
1176 
supportedPasteMimeTypes() const1177 QStringList TextTool::supportedPasteMimeTypes() const
1178 {
1179     QStringList list;
1180     list << "text/plain" << "application/vnd.oasis.opendocument.text";
1181     return list;
1182 }
1183 
dragMoveEvent(QDragMoveEvent * event,const QPointF & point)1184 void TextTool::dragMoveEvent(QDragMoveEvent *event, const QPointF &point)
1185 {
1186     if (event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::Text))
1187                     || event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::OpenOfficeClipboard))
1188                     || event->mimeData()->hasText()) {
1189         if (m_drag) {
1190             event->setDropAction(Qt::MoveAction);
1191             event->accept();
1192         } else if (event->proposedAction() == Qt::CopyAction) {
1193             event->acceptProposedAction();
1194         } else {
1195             event->ignore();
1196             return;
1197         }
1198         KoPointedAt pointedAt = hitTest(point);
1199 
1200         if (pointedAt.position == -1) {
1201             event->ignore();
1202         }
1203         if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw)
1204             m_caretTimer.stop();
1205             m_caretTimer.setInterval(50);
1206             m_caretTimer.start();
1207             m_caretTimerState = true; // turn caret instantly on on click
1208         }
1209 
1210         if (m_preDragSelection.cursor.isNull()) {
1211             repaintSelection();
1212 
1213             m_preDragSelection.cursor = QTextCursor(*m_textEditor.data()->cursor());
1214 
1215             if (m_drag) {
1216                 // Make a selection that looks like the current cursor selection
1217                 // so we can move the real caret around freely
1218                 QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections();
1219 
1220                 m_preDragSelection.format = QTextCharFormat();
1221                 m_preDragSelection.format.setBackground(qApp->palette().brush(QPalette::Highlight));
1222                 m_preDragSelection.format.setForeground(qApp->palette().brush(QPalette::HighlightedText));
1223                 sels.append(m_preDragSelection);
1224                 KoTextDocument(m_textShapeData->document()).setSelections(sels);
1225             } // else we want the selection to disappear
1226         }
1227         repaintCaret(); // will erase caret
1228         m_textEditor.data()->setPosition(pointedAt.position);
1229         repaintCaret(); // will paint caret in new spot
1230 
1231         // Selection has visually not appeared at a new spot so no need to repaint it
1232     }
1233 }
1234 
dragLeaveEvent(QDragLeaveEvent * event)1235 void TextTool::dragLeaveEvent(QDragLeaveEvent *event)
1236 {
1237     if (m_drag) {
1238         // restore the old selections
1239         QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections();
1240         sels.pop_back();
1241         KoTextDocument(m_textShapeData->document()).setSelections(sels);
1242     }
1243 
1244     repaintCaret(); // will erase caret in old spot
1245     m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor());
1246     m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor);
1247     repaintCaret(); // will paint caret in new spot
1248 
1249     if (!m_drag) {
1250         repaintSelection(); // will paint selection again
1251     }
1252 
1253     // mark that we now are back to normal selection
1254     m_preDragSelection.cursor = QTextCursor();
1255     event->accept();
1256 }
1257 
dropEvent(QDropEvent * event,const QPointF &)1258 void TextTool::dropEvent(QDropEvent *event, const QPointF &)
1259 {
1260     if (m_drag) {
1261         // restore the old selections
1262         QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections();
1263         sels.pop_back();
1264         KoTextDocument(m_textShapeData->document()).setSelections(sels);
1265     }
1266 
1267     QTextCursor insertCursor(*m_textEditor.data()->cursor());
1268 
1269     m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor());
1270     m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor);
1271     repaintSelection(); // will erase the selection in new spot
1272     if (m_drag) {
1273         m_textEditor.data()->deleteChar();
1274     }
1275     m_prevCursorPosition = insertCursor.position();
1276     m_textEditor.data()->setPosition(m_prevCursorPosition);
1277     m_textEditor.data()->paste(canvas(), event->mimeData());
1278     m_textEditor.data()->setPosition(m_prevCursorPosition);
1279     //since the paste made insertCursor we can now use that for the end position
1280     m_textEditor.data()->setPosition(insertCursor.position(), QTextCursor::KeepAnchor);
1281 
1282     // mark that we no are back to normal selection
1283     m_preDragSelection.cursor = QTextCursor();
1284     event->accept();
1285 }
1286 
hitTest(const QPointF & point) const1287 KoPointedAt TextTool::hitTest(const QPointF & point) const
1288 {
1289     if (!m_textShape || !m_textShapeData) {
1290         return KoPointedAt();
1291     }
1292     QPointF p = m_textShape->convertScreenPos(point);
1293     KoTextLayoutRootArea *rootArea = m_textShapeData->rootArea();
1294     return rootArea ? rootArea->hitTest(p, Qt::FuzzyHit) : KoPointedAt();
1295 }
1296 
mouseDoubleClickEvent(KoPointerEvent * event)1297 void TextTool::mouseDoubleClickEvent(KoPointerEvent *event)
1298 {
1299     if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) {
1300         event->ignore(); // allow the event to be used by another
1301         return;
1302     }
1303 
1304     if (event->modifiers() & Qt::ShiftModifier) {
1305         // When whift is pressed we behave as a single press
1306         return mousePressEvent(event);
1307     }
1308 
1309     m_textEditor.data()->select(QTextCursor::WordUnderCursor);
1310 
1311     m_clickWithinSelection = false;
1312 
1313     repaintSelection();
1314     updateSelectionHandler();
1315 }
1316 
mouseTripleClickEvent(KoPointerEvent * event)1317 void TextTool::mouseTripleClickEvent(KoPointerEvent *event)
1318 {
1319     if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) {
1320         event->ignore(); // allow the event to be used by another
1321         return;
1322     }
1323 
1324     if (event->modifiers() & Qt::ShiftModifier) {
1325         // When whift is pressed we behave as a single press
1326         return mousePressEvent(event);
1327     }
1328 
1329     m_textEditor.data()->clearSelection();
1330     m_textEditor.data()->movePosition(QTextCursor::StartOfBlock);
1331     m_textEditor.data()->movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1332 
1333     m_clickWithinSelection = false;
1334 
1335     repaintSelection();
1336     updateSelectionHandler();
1337 }
1338 
mouseMoveEvent(KoPointerEvent * event)1339 void TextTool::mouseMoveEvent(KoPointerEvent *event)
1340 {
1341     m_editTipPos = event->globalPos();
1342 
1343     if (event->buttons()) {
1344         updateSelectedShape(event->point, true);
1345     }
1346 
1347     m_editTipTimer.stop();
1348 
1349     if (QToolTip::isVisible())
1350         QToolTip::hideText();
1351 
1352     KoPointedAt pointedAt = hitTest(event->point);
1353 
1354     if (event->buttons() == Qt::NoButton) {
1355         if (m_tablePenMode) {
1356             if (pointedAt.tableHit == KoPointedAt::ColumnDivider || pointedAt.tableHit == KoPointedAt::RowDivider) {
1357                 useTableBorderCursor();
1358             } else {
1359                 useCursor(Qt::IBeamCursor);
1360             }
1361             // do nothing else
1362             return;
1363         }
1364 
1365         if (!m_textShapeData || pointedAt.position < 0) {
1366             if (pointedAt.tableHit == KoPointedAt::ColumnDivider) {
1367                 useCursor(Qt::SplitHCursor);
1368                 m_draggingOrigin = event->point;
1369             } else if (pointedAt.tableHit == KoPointedAt::RowDivider) {
1370                 if (pointedAt.tableRowDivider > 0) {
1371                     useCursor(Qt::SplitVCursor);
1372                     m_draggingOrigin = event->point;
1373                 } else
1374                     useCursor(Qt::IBeamCursor);
1375             } else {
1376                 useCursor(Qt::IBeamCursor);
1377             }
1378             return;
1379         }
1380 
1381         QTextCursor mouseOver(m_textShapeData->document());
1382         mouseOver.setPosition(pointedAt.position);
1383 
1384         if (m_changeTracker && m_changeTracker->containsInlineChanges(mouseOver.charFormat())) {
1385             m_editTipPointedAt = pointedAt;
1386             if (QToolTip::isVisible()) {
1387                 QTimer::singleShot(0, this, SLOT(showEditTip()));
1388             }else {
1389                 m_editTipTimer.start();
1390             }
1391         }
1392 
1393         if ((pointedAt.bookmark || !pointedAt.externalHRef.isEmpty()) || pointedAt.note || (pointedAt.noteReference>0)) {
1394             if (event->modifiers() & Qt::ControlModifier) {
1395                 useCursor(Qt::PointingHandCursor);
1396             }
1397             m_editTipPointedAt = pointedAt;
1398             if (QToolTip::isVisible()) {
1399                 QTimer::singleShot(0, this, SLOT(showEditTip()));
1400             }else {
1401                 m_editTipTimer.start();
1402             }
1403             return;
1404         }
1405 
1406         // check if mouse pointer is over shape with hyperlink
1407         KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point);
1408         if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) {
1409             useCursor(Qt::PointingHandCursor);
1410             return;
1411         }
1412 
1413         useCursor(Qt::IBeamCursor);
1414 
1415         // Set Arrow Cursor when mouse is on top of annotation shape.
1416         if (selectedShape) {
1417             if (selectedShape->shapeId() == "AnnotationTextShapeID") {
1418                 QPointF point(event->point);
1419                 if (point.y() <= (selectedShape->position().y() + 25))
1420                     useCursor(Qt::ArrowCursor);
1421             }
1422         }
1423 
1424         return;
1425     } else {
1426         if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) {
1427             m_tableDragWithShift = event->modifiers() & Qt::ShiftModifier;
1428             if(m_tableDraggedOnce) {
1429                 canvas()->shapeController()->resourceManager()->undoStack()->undo();
1430             }
1431             KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Column Width"));
1432             m_dx = m_draggingOrigin.x() - event->point.x();
1433             if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()
1434                     && m_tableDragInfo.tableTrailSize + m_dx < 0) {
1435                 m_dx = -m_tableDragInfo.tableTrailSize;
1436             }
1437             if (m_tableDragInfo.tableColumnDivider > 0) {
1438                 if (m_tableDragInfo.tableLeadSize - m_dx < 0) {
1439                     m_dx = m_tableDragInfo.tableLeadSize;
1440                 }
1441                 m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table,
1442                     m_tableDragInfo.tableColumnDivider - 1,
1443                     m_tableDragInfo.tableLeadSize - m_dx, topCmd);
1444             } else {
1445                 m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, -m_dx, 0.0);
1446             }
1447             if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) {
1448                 if (!m_tableDragWithShift) {
1449                     m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table,
1450                         m_tableDragInfo.tableColumnDivider,
1451                         m_tableDragInfo.tableTrailSize + m_dx, topCmd);
1452                 }
1453             } else {
1454                 m_tableDragWithShift = true; // act like shift pressed
1455             }
1456             if (m_tableDragWithShift) {
1457                 m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, 0.0, m_dx);
1458             }
1459             m_textEditor.data()->endEditBlock();
1460             m_tableDragInfo.tableDividerPos.setY(m_textShape->convertScreenPos(event->point).y());
1461             if (m_tableDraggedOnce) {
1462                 //we need to redraw like this so we update outside the textshape too
1463                 if (canvas()->canvasWidget())
1464                     canvas()->canvasWidget()->update();
1465                 if (canvas()->canvasItem())
1466                     canvas()->canvasItem()->update();
1467             }
1468             m_tableDraggedOnce = true;
1469         } else if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) {
1470             if(m_tableDraggedOnce) {
1471                 canvas()->shapeController()->resourceManager()->undoStack()->undo();
1472             }
1473             if (m_tableDragInfo.tableRowDivider > 0) {
1474                 KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Row Height"));
1475                 m_dy = m_draggingOrigin.y() - event->point.y();
1476 
1477                 if (m_tableDragInfo.tableLeadSize - m_dy < 0) {
1478                     m_dy = m_tableDragInfo.tableLeadSize;
1479                 }
1480 
1481                 m_textEditor.data()->adjustTableRowHeight(m_tableDragInfo.table,
1482                     m_tableDragInfo.tableRowDivider - 1,
1483                     m_tableDragInfo.tableLeadSize - m_dy, topCmd);
1484 
1485                 m_textEditor.data()->endEditBlock();
1486 
1487                 m_tableDragInfo.tableDividerPos.setX(m_textShape->convertScreenPos(event->point).x());
1488                 if (m_tableDraggedOnce) {
1489                     //we need to redraw like this so we update outside the textshape too
1490                     if (canvas()->canvasWidget())
1491                         canvas()->canvasWidget()->update();
1492                     if (canvas()->canvasItem())
1493                         canvas()->canvasItem()->update();
1494                 }
1495                 m_tableDraggedOnce = true;
1496             }
1497 
1498         } else if (m_tablePenMode) {
1499             // do nothing
1500         } else if (m_clickWithinSelection) {
1501             if (!m_drag && (event->pos() - m_draggingOrigin).manhattanLength()
1502           >= QApplication::startDragDistance()) {
1503                 QMimeData *mimeData = generateMimeData();
1504                 if (mimeData) {
1505                     m_drag = new QDrag(canvas()->canvasWidget());
1506                     m_drag->setMimeData(mimeData);
1507 
1508                     m_drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction);
1509 
1510                     m_drag = 0;
1511                 }
1512             }
1513         } else {
1514             useCursor(Qt::IBeamCursor);
1515             if (pointedAt.position == m_textEditor.data()->position()) return;
1516             if (pointedAt.position >= 0) {
1517                 if (m_textEditor.data()->hasSelection())
1518                     repaintSelection(); // will erase selection
1519                 else
1520                     repaintCaret();
1521 
1522                 m_textEditor.data()->setPosition(pointedAt.position, QTextCursor::KeepAnchor);
1523 
1524                 if (m_textEditor.data()->hasSelection())
1525                     repaintSelection();
1526                 else
1527                     repaintCaret();
1528             }
1529         }
1530 
1531         updateSelectionHandler();
1532     }
1533 }
1534 
mouseReleaseEvent(KoPointerEvent * event)1535 void TextTool::mouseReleaseEvent(KoPointerEvent *event)
1536 {
1537     event->ignore();
1538     editingPluginEvents();
1539 
1540     m_tableDragInfo.tableHit = KoPointedAt::None;
1541     if (m_tableDraggedOnce) {
1542         m_tableDraggedOnce = false;
1543         //we need to redraw like this so we update outside the textshape too
1544         if (canvas()->canvasWidget())
1545             canvas()->canvasWidget()->update();
1546         if (canvas()->canvasItem())
1547             canvas()->canvasItem()->update();
1548     }
1549 
1550     if (!m_textShapeData)
1551         return;
1552 
1553     // check if mouse pointer is not over some shape with hyperlink
1554     KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point);
1555     if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) {
1556         QString url = selectedShape->hyperLink();
1557         runUrl(event, url);
1558         return;
1559     }
1560 
1561     KoPointedAt pointedAt = hitTest(event->point);
1562 
1563     if (m_clickWithinSelection && !m_drag) {
1564         if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw)
1565             m_caretTimer.stop();
1566             m_caretTimer.setInterval(50);
1567             m_caretTimer.start();
1568             m_caretTimerState = true; // turn caret instantly on on click
1569         }
1570         repaintCaret(); // will erase caret
1571         repaintSelection(); // will erase selection
1572         m_textEditor.data()->setPosition(pointedAt.position);
1573         repaintCaret(); // will paint caret in new spot
1574     }
1575 
1576     // Is there an anchor here ?
1577     if ((event->modifiers() & Qt::ControlModifier) && !m_textEditor.data()->hasSelection()) {
1578         if (pointedAt.bookmark) {
1579             m_textEditor.data()->setPosition(pointedAt.bookmark->rangeStart());
1580             ensureCursorVisible();
1581             event->accept();
1582             return;
1583         }
1584         if (pointedAt.note) {
1585             m_textEditor.data()->setPosition(pointedAt.note->textFrame()->firstPosition());
1586             ensureCursorVisible();
1587             event->accept();
1588             return;
1589         }
1590         if (pointedAt.noteReference>0) {
1591             m_textEditor.data()->setPosition(pointedAt.noteReference);
1592             ensureCursorVisible();
1593             event->accept();
1594             return;
1595         }
1596         if (!pointedAt.externalHRef.isEmpty()) {
1597             runUrl(event, pointedAt.externalHRef);
1598         }
1599     }
1600 }
1601 
shortcutOverrideEvent(QKeyEvent * event)1602 void TextTool::shortcutOverrideEvent(QKeyEvent *event)
1603 {
1604     QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers()));
1605     if (hit(item, KStandardShortcut::Begin) ||
1606         hit(item, KStandardShortcut::End)) {
1607         event->accept();
1608     }
1609 }
1610 
keyPressEvent(QKeyEvent * event)1611 void TextTool::keyPressEvent(QKeyEvent *event)
1612 {
1613     int destinationPosition = -1; // for those cases where the moveOperation is not relevant;
1614     QTextCursor::MoveOperation moveOperation = QTextCursor::NoMove;
1615     KoTextEditor *textEditor = m_textEditor.data();
1616     m_tablePenMode = false; // keypress always stops the table (border) pen mode
1617     Q_ASSERT(textEditor);
1618     if (event->key() == Qt::Key_Backspace) {
1619         if (!textEditor->hasSelection() && textEditor->block().textList()
1620             && (textEditor->position() == textEditor->block().position())
1621             && !(m_changeTracker && m_changeTracker->recordChanges())) {
1622             if (!textEditor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) {
1623                 // backspace at beginning of numbered list item, makes it unnumbered
1624                 textEditor->toggleListNumbering(false);
1625             } else {
1626                 KoListLevelProperties llp;
1627                 llp.setLabelType(KoListStyle::None);
1628                 llp.setLevel(0);
1629                 // backspace on numbered, empty parag, removes numbering.
1630                 textEditor->setListProperties(llp);
1631             }
1632         } else if (textEditor->position() > 0 || textEditor->hasSelection()) {
1633             if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) { // delete prev word.
1634                 textEditor->movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);
1635             }
1636             textEditor->deletePreviousChar();
1637 
1638             editingPluginEvents();
1639         }
1640     } else if ((event->key() == Qt::Key_Tab)
1641         && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList()) {
1642         ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel;
1643         ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1);
1644         textEditor->addCommand(cll);
1645         editingPluginEvents();
1646     } else if ((event->key() == Qt::Key_Backtab)
1647         && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList() && !(m_changeTracker && m_changeTracker->recordChanges())) {
1648         ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel;
1649         ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1);
1650         textEditor->addCommand(cll);
1651         editingPluginEvents();
1652     } else if (event->key() == Qt::Key_Delete) {
1653         if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) {// delete next word.
1654             textEditor->movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor);
1655         }
1656         // the event only gets through when the Del is not used in the app
1657         // if the app forwards Del then deleteSelection is used
1658         textEditor->deleteChar();
1659         editingPluginEvents();
1660     } else if ((event->key() == Qt::Key_Left) && (event->modifiers() & Qt::ControlModifier) == 0) {
1661         moveOperation = QTextCursor::Left;
1662     } else if ((event->key() == Qt::Key_Right) && (event->modifiers() & Qt::ControlModifier) == 0) {
1663         moveOperation = QTextCursor::Right;
1664     } else if ((event->key() == Qt::Key_Up) && (event->modifiers() & Qt::ControlModifier) == 0) {
1665         moveOperation = QTextCursor::Up;
1666     } else if ((event->key() == Qt::Key_Down) && (event->modifiers() & Qt::ControlModifier) == 0) {
1667         moveOperation = QTextCursor::Down;
1668     } else {
1669         // check for shortcuts.
1670         QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers()));
1671         if (hit(item, KStandardShortcut::Begin)) {
1672             // Goto beginning of the document. Default: Ctrl-Home
1673             destinationPosition = 0;
1674         } else if (hit(item, KStandardShortcut::End)) {
1675             // Goto end of the document. Default: Ctrl-End
1676             if (m_textShapeData) {
1677                 QTextBlock last = m_textShapeData->document()->lastBlock();
1678                 destinationPosition = last.position() + last.length() - 1;
1679             }
1680         } else if (hit(item, KStandardShortcut::Prior)) { // page up
1681             // Scroll up one page. Default: Prior
1682             event->ignore(); // let app level actions handle it
1683             return;
1684         }
1685         else if (hit(item, KStandardShortcut::Next)) {
1686             // Scroll down one page. Default: Next
1687             event->ignore(); // let app level actions handle it
1688             return;
1689         }
1690         else if (hit(item, KStandardShortcut::BeginningOfLine))
1691             // Goto beginning of current line. Default: Home
1692             moveOperation = QTextCursor::StartOfLine;
1693         else if (hit(item, KStandardShortcut::EndOfLine))
1694             // Goto end of current line. Default: End
1695             moveOperation = QTextCursor::EndOfLine;
1696         else if (hit(item, KStandardShortcut::BackwardWord))
1697             moveOperation = QTextCursor::WordLeft;
1698         else if (hit(item, KStandardShortcut::ForwardWord))
1699             moveOperation = QTextCursor::WordRight;
1700 #ifdef Q_WS_MAC
1701         // Don't reject "alt" key, it may be used for typing text on Mac OS
1702         else if ((event->modifiers() & Qt::ControlModifier)
1703 #else
1704         else if ((event->modifiers() & (Qt::ControlModifier | Qt::AltModifier))
1705 #endif
1706             || event->text().length() == 0 || event->key() == Qt::Key_Escape) {
1707             event->ignore();
1708             return;
1709         } else if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) {
1710             m_prevCursorPosition = textEditor->position();
1711             textEditor->newLine();
1712             updateActions();
1713             editingPluginEvents();
1714         } else if ((event->key() == Qt::Key_Tab || !(event->text().length() == 1 && !event->text().at(0).isPrint()))) { // insert the text
1715             m_prevCursorPosition = textEditor->position();
1716             startingSimpleEdit(); //signal editing plugins that this is a simple edit
1717             textEditor->insertText(event->text());
1718             editingPluginEvents();
1719         }
1720     }
1721     if (moveOperation != QTextCursor::NoMove || destinationPosition != -1) {
1722         useCursor(Qt::BlankCursor);
1723         bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
1724         if (textEditor->hasSelection())
1725             repaintSelection(); // will erase selection
1726         else
1727             repaintCaret();
1728         QTextBlockFormat format = textEditor->blockFormat();
1729 
1730         KoText::Direction dir = static_cast<KoText::Direction>(format.intProperty(KoParagraphStyle::TextProgressionDirection));
1731         bool isRtl;
1732         if (dir == KoText::AutoDirection)
1733             isRtl = textEditor->block().text().isRightToLeft();
1734         else
1735             isRtl =  dir == KoText::RightLeftTopBottom;
1736 
1737         if (isRtl) { // if RTL toggle direction of cursor movement.
1738             switch (moveOperation) {
1739             case QTextCursor::Left: moveOperation = QTextCursor::Right; break;
1740             case QTextCursor::Right: moveOperation = QTextCursor::Left; break;
1741             case QTextCursor::WordRight: moveOperation = QTextCursor::WordLeft; break;
1742             case QTextCursor::WordLeft: moveOperation = QTextCursor::WordRight; break;
1743             default: break;
1744             }
1745         }
1746         int prevPosition = textEditor->position();
1747         if (moveOperation != QTextCursor::NoMove)
1748             textEditor->movePosition(moveOperation,
1749                 shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
1750         else
1751             textEditor->setPosition(destinationPosition,
1752                 shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
1753         if (moveOperation == QTextCursor::Down && prevPosition == textEditor->position()) {
1754             // change behavior a little big from Qt; at the bottom of the doc we go to the end of the doc
1755             textEditor->movePosition(QTextCursor::End,
1756                 shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
1757         }
1758         if (shiftPressed) // altered selection.
1759             repaintSelection();
1760         else
1761             repaintCaret();
1762         updateActions();
1763         editingPluginEvents();
1764     }
1765     if (m_caretTimer.isActive()) { // make the caret not blink but decide on the action if its visible or not.
1766         m_caretTimer.stop();
1767         m_caretTimer.setInterval(50);
1768         m_caretTimer.start();
1769         m_caretTimerState = true; // turn caret on while typing
1770     }
1771     if (moveOperation != QTextCursor::NoMove)
1772         // this difference in handling is need to prevent leaving a trail of old cursors onscreen
1773         ensureCursorVisible();
1774     else
1775         m_delayedEnsureVisible = true;
1776     updateActions();
1777     updateSelectionHandler();
1778 }
1779 
inputMethodQuery(Qt::InputMethodQuery query,const KoViewConverter & converter) const1780 QVariant TextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const
1781 {
1782     KoTextEditor *textEditor = m_textEditor.data();
1783     if (!textEditor || !m_textShapeData)
1784         return QVariant();
1785 
1786     switch (query) {
1787     case Qt::ImMicroFocus: {
1788         // The rectangle covering the area of the input cursor in widget coordinates.
1789         QRectF rect = caretRect(textEditor->cursor());
1790         rect.moveTop(rect.top() - m_textShapeData->documentOffset());
1791         QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter);
1792         qreal zoomX, zoomY;
1793         converter.zoom(&zoomX, &zoomY);
1794         shapeMatrix.scale(zoomX, zoomY);
1795         rect = shapeMatrix.mapRect(rect);
1796         return rect.toRect();
1797     }
1798     case Qt::ImFont:
1799         // The currently used font for text input.
1800         return textEditor->charFormat().font();
1801     case Qt::ImCursorPosition:
1802         // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText).
1803         return textEditor->position() - textEditor->block().position();
1804     case Qt::ImSurroundingText:
1805         // The plain text around the input area, for example the current paragraph.
1806         return textEditor->block().text();
1807     case Qt::ImCurrentSelection:
1808         // The currently selected text.
1809         return textEditor->selectedText();
1810     default:
1811         ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition
1812     }
1813     return QVariant();
1814 }
1815 
inputMethodEvent(QInputMethodEvent * event)1816 void TextTool::inputMethodEvent(QInputMethodEvent *event)
1817 {
1818     KoTextEditor *textEditor = m_textEditor.data();
1819     if (textEditor == 0)
1820         return;
1821     if (event->replacementLength() > 0) {
1822         textEditor->setPosition(textEditor->position() + event->replacementStart());
1823         for (int i = event->replacementLength(); i > 0; --i) {
1824             textEditor->deleteChar();
1825         }
1826     }
1827     if (!event->commitString().isEmpty()) {
1828         QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString());
1829         keyPressEvent(&ke);
1830         // The cursor may reside in a different block before vs. after keyPressEvent.
1831         QTextBlock block = textEditor->block();
1832         QTextLayout *layout = block.layout();
1833         Q_ASSERT(layout);
1834         layout->setPreeditArea(-1, QString());
1835     } else {
1836         QTextBlock block = textEditor->block();
1837         QTextLayout *layout = block.layout();
1838         Q_ASSERT(layout);
1839         layout->setPreeditArea(textEditor->position() - block.position(), event->preeditString());
1840         const_cast<QTextDocument*>(textEditor->document())->markContentsDirty(textEditor->position(), event->preeditString().length());
1841     }
1842     event->accept();
1843 }
1844 
ensureCursorVisible(bool moveView)1845 void TextTool::ensureCursorVisible(bool moveView)
1846 {
1847     KoTextEditor *textEditor = m_textEditor.data();
1848     if (!textEditor || !m_textShapeData)
1849         return;
1850 
1851     bool upToDate;
1852     QRectF cRect = caretRect(textEditor->cursor(), &upToDate);
1853 
1854     KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(m_textShapeData->document()->documentLayout());
1855     Q_ASSERT(lay);
1856     KoTextLayoutRootArea *rootArea = lay->rootAreaForPoint(cRect.center());
1857     if (rootArea && rootArea->associatedShape() && m_textShapeData->rootArea() != rootArea) {
1858         // If we have changed root area we need to update m_textShape and m_textShapeData
1859         m_textShape = static_cast<TextShape*>(rootArea->associatedShape());
1860         Q_ASSERT(m_textShape);
1861         disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
1862         m_textShapeData = static_cast<KoTextShapeData*>(m_textShape->userData());
1863         Q_ASSERT(m_textShapeData);
1864         connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
1865     }
1866 
1867     if (!moveView) {
1868         return;
1869     }
1870 
1871     if (! upToDate) { // paragraph is not yet layouted.
1872         // The number one usecase for this is when the user pressed enter.
1873         // try to do it on next caret blink
1874         m_delayedEnsureVisible = true;
1875         return; // we shouldn't move to an obsolete position
1876     }
1877     cRect.moveTop(cRect.top() - m_textShapeData->documentOffset());
1878     canvas()->ensureVisible(m_textShape->absoluteTransformation(0).mapRect(cRect));
1879 }
1880 
keyReleaseEvent(QKeyEvent * event)1881 void TextTool::keyReleaseEvent(QKeyEvent *event)
1882 {
1883     event->accept();
1884 }
1885 
updateActions()1886 void TextTool::updateActions()
1887 {
1888     bool notInAnnotation = !dynamic_cast<AnnotationTextShape *>(m_textShape);
1889     KoTextEditor *textEditor = m_textEditor.data();
1890     if (textEditor == 0) {
1891         return;
1892     }
1893     m_allowActions = false;
1894 
1895     //Update the characterStyle related GUI elements
1896     QTextCharFormat cf = textEditor->charFormat();
1897     m_actionFormatBold->setChecked(cf.fontWeight() > QFont::Normal);
1898     m_actionFormatItalic->setChecked(cf.fontItalic());
1899     m_actionFormatUnderline->setChecked(cf.intProperty(KoCharacterStyle::UnderlineType) != KoCharacterStyle::NoLineType);
1900     m_actionFormatStrikeOut->setChecked(cf.intProperty(KoCharacterStyle::StrikeOutType) != KoCharacterStyle::NoLineType);
1901     bool super = false, sub = false;
1902     switch (cf.verticalAlignment()) {
1903     case QTextCharFormat::AlignSuperScript:
1904         super = true;
1905         break;
1906     case QTextCharFormat::AlignSubScript:
1907         sub = true;
1908         break;
1909     default:;
1910     }
1911     m_actionFormatSuper->setChecked(super);
1912     m_actionFormatSub->setChecked(sub);
1913     m_actionFormatFontSize->setFontSize(cf.font().pointSizeF());
1914     m_actionFormatFontFamily->setFont(cf.font().family());
1915 
1916     KoTextShapeData::ResizeMethod resizemethod = KoTextShapeData::AutoResize;
1917     if(m_textShapeData) {
1918         resizemethod = m_textShapeData->resizeMethod();
1919     }
1920     m_shrinkToFitAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation);
1921     m_shrinkToFitAction->setChecked(resizemethod == KoTextShapeData::ShrinkToFitResize);
1922 
1923     m_growWidthAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation);
1924     m_growWidthAction->setChecked(resizemethod == KoTextShapeData::AutoGrowWidth || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight);
1925 
1926     m_growHeightAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation);
1927     m_growHeightAction->setChecked(resizemethod == KoTextShapeData::AutoGrowHeight || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight);
1928 
1929     //update paragraphStyle GUI element
1930     QTextBlockFormat bf = textEditor->blockFormat();
1931 
1932     if (bf.hasProperty(KoParagraphStyle::TextProgressionDirection)) {
1933         switch(bf.intProperty(KoParagraphStyle::TextProgressionDirection))
1934         {
1935         case KoText::RightLeftTopBottom:
1936             m_actionChangeDirection->setChecked(true);
1937             break;
1938         case KoText::LeftRightTopBottom:
1939         default:
1940             m_actionChangeDirection->setChecked(false);
1941             break;
1942         }
1943     } else {
1944         m_actionChangeDirection->setChecked(textEditor->block().text().isRightToLeft());
1945     }
1946     if (bf.alignment() == Qt::AlignLeading || bf.alignment() == Qt::AlignTrailing) {
1947         bool revert = (textEditor->block().layout()->textOption().textDirection() == Qt::RightToLeft);
1948         if ((bf.alignment() == Qt::AlignLeading) ^ revert)
1949             m_actionAlignLeft->setChecked(true);
1950         else
1951             m_actionAlignRight->setChecked(true);
1952     } else if (bf.alignment() == Qt::AlignHCenter)
1953         m_actionAlignCenter->setChecked(true);
1954     if (bf.alignment() == Qt::AlignJustify)
1955         m_actionAlignBlock->setChecked(true);
1956     else if (bf.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute))
1957         m_actionAlignLeft->setChecked(true);
1958     else if (bf.alignment() == (Qt::AlignRight | Qt::AlignAbsolute))
1959         m_actionAlignRight->setChecked(true);
1960 
1961     if (textEditor->block().textList()) {
1962         QTextListFormat listFormat = textEditor->block().textList()->format();
1963         if(listFormat.intProperty(KoListStyle::Level) > 1) {
1964             m_actionFormatDecreaseIndent->setEnabled(true);
1965         } else {
1966             m_actionFormatDecreaseIndent->setEnabled(false);
1967         }
1968 
1969         if (listFormat.intProperty(KoListStyle::Level) < 10) {
1970             m_actionFormatIncreaseIndent->setEnabled(true);
1971         } else {
1972             m_actionFormatIncreaseIndent->setEnabled(false);
1973         }
1974     } else {
1975         m_actionFormatDecreaseIndent->setEnabled(textEditor->blockFormat().leftMargin() > 0.);
1976     }
1977 
1978     m_allowActions = true;
1979 
1980     bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality)
1981                             & KoCanvasResourceManager::NoAdvancedText);
1982     if (useAdvancedText) {
1983         action("insert_table")->setEnabled(notInAnnotation);
1984 
1985         bool hasTable = textEditor->currentTable();
1986         action("insert_tablerow_above")->setEnabled(hasTable && notInAnnotation);
1987         action("insert_tablerow_below")->setEnabled(hasTable && notInAnnotation);
1988         action("insert_tablecolumn_left")->setEnabled(hasTable && notInAnnotation);
1989         action("insert_tablecolumn_right")->setEnabled(hasTable && notInAnnotation);
1990         action("delete_tablerow")->setEnabled(hasTable && notInAnnotation);
1991         action("delete_tablecolumn")->setEnabled(hasTable && notInAnnotation);
1992         action("merge_tablecells")->setEnabled(hasTable && notInAnnotation);
1993         action("split_tablecells")->setEnabled(hasTable && notInAnnotation);
1994         action("activate_borderpainter")->setEnabled(hasTable && notInAnnotation);
1995     }
1996     action("insert_annotation")->setEnabled(notInAnnotation);
1997 
1998     ///TODO if selection contains several different format
1999     emit blockChanged(textEditor->block());
2000     emit charFormatChanged(cf, textEditor->blockCharFormat());
2001     emit blockFormatChanged(bf);
2002 }
2003 
updateStyleManager()2004 void TextTool::updateStyleManager()
2005 {
2006     if (!m_textShapeData)
2007         return;
2008     KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager();
2009     emit styleManagerChanged(styleManager);
2010 
2011     //TODO move this to its own method
2012     m_changeTracker = KoTextDocument(m_textShapeData->document()).changeTracker();
2013 }
2014 
activate(ToolActivation toolActivation,const QSet<KoShape * > & shapes)2015 void TextTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
2016 {
2017     Q_UNUSED(toolActivation);
2018     m_caretTimer.start();
2019     m_caretTimerState = true;
2020     foreach (KoShape *shape, shapes) {
2021         TextShape *textShape = dynamic_cast<TextShape*>(shape);
2022         if (textShape) {
2023             if (!m_textEditor) {
2024                 m_textShape = textShape;
2025                 break;
2026             }
2027             // Since there is a text editor, we must select the shape that is edited,
2028             // or else we get out of sync
2029             KoTextShapeData *data = static_cast<KoTextShapeData*>(textShape->userData());
2030             if (data && data->document() == m_textEditor->constCursor().document()) {
2031                 m_textShape = textShape;
2032                 break;
2033             }
2034             if (!m_textShape) {
2035                 m_textShape = textShape;
2036             }
2037         }
2038     }
2039     if (!m_textShape) { // none found
2040         emit done();
2041         // This is how we inform the rulers of the active range
2042         // No shape means no active range
2043         canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF()));
2044         return;
2045     }
2046 
2047     // This is how we inform the rulers of the active range
2048     // For now we will not consider table cells, but just give the shape dimensions
2049     QVariant v;
2050     QRectF rect(QPoint(), m_textShape->size());
2051     rect = m_textShape->absoluteTransformation(0).mapRect(rect);
2052     v.setValue(rect);
2053     canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v);
2054     if ((!m_oldTextEditor.isNull()) && m_oldTextEditor.data()->document() != static_cast<KoTextShapeData*>(m_textShape->userData())->document()) {
2055         m_oldTextEditor.data()->setPosition(m_oldTextEditor.data()->position());
2056         //we need to redraw like this so we update the old textshape whereever it may be
2057         if (canvas()->canvasWidget())
2058             canvas()->canvasWidget()->update();
2059     }
2060     setShapeData(static_cast<KoTextShapeData*>(m_textShape->userData()));
2061     useCursor(Qt::IBeamCursor);
2062 
2063     updateStyleManager();
2064     repaintSelection();
2065     updateSelectionHandler();
2066     updateActions();
2067     if (m_specialCharacterDocker)
2068         m_specialCharacterDocker->setEnabled(true);
2069 }
2070 
deactivate()2071 void TextTool::deactivate()
2072 {
2073     m_caretTimer.stop();
2074     m_caretTimerState = false;
2075     repaintCaret();
2076     m_textShape = 0;
2077 
2078     // This is how we inform the rulers of the active range
2079     // No shape means no active range
2080     canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF()));
2081 
2082     m_oldTextEditor = m_textEditor;
2083     setShapeData(0);
2084 
2085     updateSelectionHandler();
2086     if (m_specialCharacterDocker) {
2087         m_specialCharacterDocker->setEnabled(false);
2088         m_specialCharacterDocker->setVisible(false);
2089     }
2090 }
2091 
repaintDecorations()2092 void TextTool::repaintDecorations()
2093 {
2094     if (m_textShapeData)
2095         repaintSelection();
2096 }
2097 
repaintCaret()2098 void TextTool::repaintCaret()
2099 {
2100     KoTextEditor *textEditor = m_textEditor.data();
2101     if (!textEditor || !m_textShapeData)
2102         return;
2103 
2104     KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(m_textShapeData->document()->documentLayout());
2105     Q_ASSERT(lay); Q_UNUSED(lay);
2106 
2107     // If we have changed root area we need to update m_textShape and m_textShapeData
2108     if (m_delayedEnsureVisible) {
2109         m_delayedEnsureVisible = false;
2110         ensureCursorVisible();
2111         return;
2112     }
2113 
2114     ensureCursorVisible(false); // ensures the various vars are updated
2115 
2116     bool upToDate;
2117     QRectF repaintRect = caretRect(textEditor->cursor(), &upToDate);
2118     repaintRect.moveTop(repaintRect.top() - m_textShapeData->documentOffset());
2119     if (repaintRect.isValid()) {
2120         repaintRect = m_textShape->absoluteTransformation(0).mapRect(repaintRect);
2121 
2122         // Make sure there is enough space to show an icon
2123         QRectF iconSize = canvas()->viewConverter()->viewToDocument(QRect(0, 0, 18, 18));
2124         repaintRect.setX(repaintRect.x() - iconSize.width() / 2);
2125         repaintRect.setRight(repaintRect.right() + iconSize.width() / 2);
2126         repaintRect.setTop(repaintRect.y() - iconSize.height() / 2);
2127         repaintRect.setBottom(repaintRect.bottom() + iconSize.height() / 2);
2128         canvas()->updateCanvas(repaintRect);
2129     }
2130 }
2131 
repaintSelection()2132 void TextTool::repaintSelection()
2133 {
2134     KoTextEditor *textEditor = m_textEditor.data();
2135     if (textEditor == 0)
2136         return;
2137     QTextCursor cursor = *textEditor->cursor();
2138 
2139     QList<TextShape *> shapes;
2140     KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(textEditor->document()->documentLayout());
2141     Q_ASSERT(lay);
2142     foreach (KoShape* shape, lay->shapes()) {
2143         TextShape *textShape = dynamic_cast<TextShape*>(shape);
2144         if (textShape == 0) // when the shape is being deleted its no longer a TextShape but a KoShape
2145             continue;
2146 
2147         //Q_ASSERT(!shapes.contains(textShape));
2148         if (!shapes.contains(textShape)) {
2149             shapes.append(textShape);
2150         }
2151     }
2152 
2153     // loop over all shapes that contain the text and update per shape.
2154     QRectF repaintRect = textRect(cursor);
2155     foreach (TextShape *ts, shapes) {
2156         QRectF rect = repaintRect;
2157         rect.moveTop(rect.y() - ts->textShapeData()->documentOffset());
2158         rect = ts->absoluteTransformation(0).mapRect(rect);
2159         QRectF r = ts->boundingRect().intersected(rect);
2160         canvas()->updateCanvas(r);
2161     }
2162 }
2163 
caretRect(QTextCursor * cursor,bool * upToDate) const2164 QRectF TextTool::caretRect(QTextCursor *cursor, bool *upToDate) const
2165 {
2166     QTextCursor tmpCursor(*cursor);
2167     tmpCursor.setPosition(cursor->position()); // looses the anchor
2168 
2169     QRectF rect = textRect(tmpCursor);
2170     if (rect.size() == QSizeF(0,0)) {
2171         if (upToDate) {
2172             *upToDate = false;
2173         }
2174         rect = m_lastImMicroFocus; // prevent block changed but layout not done
2175     } else {
2176         if (upToDate) {
2177             *upToDate = true;
2178         }
2179         m_lastImMicroFocus = rect;
2180     }
2181     return rect;
2182 }
2183 
textRect(QTextCursor & cursor) const2184 QRectF TextTool::textRect(QTextCursor &cursor) const
2185 {
2186     if (!m_textShapeData)
2187         return QRectF();
2188     KoTextEditor *textEditor = m_textEditor.data();
2189     KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(textEditor->document()->documentLayout());
2190     return lay->selectionBoundingBox(cursor);
2191 }
2192 
selection()2193 KoToolSelection* TextTool::selection()
2194 {
2195     return m_toolSelection;
2196 }
2197 
createOptionWidgets()2198 QList<QPointer<QWidget> > TextTool::createOptionWidgets()
2199 {
2200     QList<QPointer<QWidget> > widgets;
2201     SimpleCharacterWidget *scw = new SimpleCharacterWidget(this, 0);
2202     SimpleParagraphWidget *spw = new SimpleParagraphWidget(this, 0);
2203     if (m_textEditor.data()) {
2204 //        connect(m_textEditor.data(), SIGNAL(paragraphStyleApplied(KoParagraphStyle*)), spw, SLOT(slotParagraphStyleApplied(KoParagraphStyle*)));
2205 //        connect(m_textEditor.data(), SIGNAL(characterStyleApplied(KoCharacterStyle*)), scw, SLOT(slotCharacterStyleApplied(KoCharacterStyle*)));
2206         //initialise the char- and par- widgets with the current block and formats.
2207         scw->setCurrentBlockFormat(m_textEditor.data()->blockFormat());
2208         scw->setCurrentFormat(m_textEditor.data()->charFormat(), m_textEditor.data()-> blockCharFormat());
2209         spw->setCurrentBlock(m_textEditor.data()->block());
2210         spw->setCurrentFormat(m_textEditor.data()->blockFormat());
2211     }
2212     SimpleTableWidget *stw = new SimpleTableWidget(this, 0);
2213     SimpleInsertWidget *siw = new SimpleInsertWidget(this, 0);
2214 
2215 /* We do not use these for now. Let's see if they become useful at a certain point in time. If not, we can remove the whole chain (SimpleCharWidget, SimpleParWidget, DockerStyleComboModel)
2216     if (m_textShapeData && KoTextDocument(m_textShapeData->document()).styleManager()) {
2217         scw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedCharacterStyles());
2218         spw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedParagraphStyles());
2219     }
2220 */
2221     // Connect to/with simple character widget (docker)
2222     connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), scw, SLOT(setStyleManager(KoStyleManager*)));
2223     connect(this, SIGNAL(charFormatChanged(QTextCharFormat,QTextCharFormat)), scw, SLOT(setCurrentFormat(QTextCharFormat,QTextCharFormat)));
2224     connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), scw, SLOT(setCurrentBlockFormat(QTextBlockFormat)));
2225     connect(scw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas()));
2226     connect(scw, SIGNAL(characterStyleSelected(KoCharacterStyle*)), this, SLOT(setStyle(KoCharacterStyle*)));
2227     connect(scw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentCharFormat(QString)));
2228     connect(scw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int)));
2229 
2230 
2231     // Connect to/with simple paragraph widget (docker)
2232     connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), spw, SLOT(setStyleManager(KoStyleManager*)));
2233     connect(this, SIGNAL(blockChanged(QTextBlock)), spw, SLOT(setCurrentBlock(QTextBlock)));
2234     connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), spw, SLOT(setCurrentFormat(QTextBlockFormat)));
2235     connect(spw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas()));
2236     connect(spw, SIGNAL(paragraphStyleSelected(KoParagraphStyle*)), this, SLOT(setStyle(KoParagraphStyle*)));
2237     connect(spw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentBlockFormat(QString)));
2238     connect(spw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int)));
2239 
2240     // Connect to/with simple table widget (docker)
2241     connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), stw, SLOT(setStyleManager(KoStyleManager*)));
2242     connect(stw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas()));
2243     connect(stw, SIGNAL(tableBorderDataUpdated(KoBorder::BorderData)), this, SLOT(setTableBorderData(KoBorder::BorderData)));
2244 
2245     // Connect to/with simple insert widget (docker)
2246     connect(siw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas()));
2247     connect(siw, SIGNAL(insertTableQuick(int,int)), this, SLOT(insertTableQuick(int,int)));
2248 
2249     updateStyleManager();
2250     if (m_textShape) {
2251         updateActions();
2252     }
2253     scw->setWindowTitle(i18n("Character"));
2254     widgets.append(scw);
2255     spw->setWindowTitle(i18n("Paragraph"));
2256     widgets.append(spw);
2257 
2258     bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality)
2259                              & KoCanvasResourceManager::NoAdvancedText);
2260     if (useAdvancedText) {
2261         stw->setWindowTitle(i18n("Table"));
2262         widgets.append(stw);
2263         siw->setWindowTitle(i18n("Insert"));
2264         widgets.append(siw);
2265     }
2266     return widgets;
2267 }
2268 
returnFocusToCanvas()2269 void TextTool::returnFocusToCanvas()
2270 {
2271     canvas()->canvasWidget()->setFocus();
2272 }
2273 
startEditing(KUndo2Command * command)2274 void TextTool::startEditing(KUndo2Command* command)
2275 {
2276     m_currentCommand = command;
2277     m_currentCommandHasChildren = true;
2278 }
2279 
stopEditing()2280 void TextTool::stopEditing()
2281 {
2282     m_currentCommand = 0;
2283     m_currentCommandHasChildren = false;
2284 }
2285 
insertNewSection()2286 void TextTool::insertNewSection()
2287 {
2288     KoTextEditor *textEditor = m_textEditor.data();
2289     if (!textEditor) return;
2290 
2291     textEditor->newSection();
2292 }
2293 
configureSection()2294 void TextTool::configureSection()
2295 {
2296     KoTextEditor *textEditor = m_textEditor.data();
2297     if (!textEditor) return;
2298 
2299     SectionFormatDialog *dia = new SectionFormatDialog(0, m_textEditor.data());
2300     dia->exec();
2301     delete dia;
2302 
2303     returnFocusToCanvas();
2304     updateActions();
2305 }
2306 
splitSections()2307 void TextTool::splitSections()
2308 {
2309     KoTextEditor *textEditor = m_textEditor.data();
2310     if (!textEditor) return;
2311 
2312     SectionsSplitDialog *dia = new SectionsSplitDialog(0, m_textEditor.data());
2313     dia->exec();
2314     delete dia;
2315 
2316     returnFocusToCanvas();
2317     updateActions();
2318 }
2319 
pasteAsText()2320 void TextTool::pasteAsText()
2321 {
2322     KoTextEditor *textEditor = m_textEditor.data();
2323     if (!textEditor) return;
2324 
2325     const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard);
2326     // on windows we do not have data if we try to paste this selection
2327     if (!data) return;
2328 
2329     if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))
2330         ||  data->hasText()) {
2331         m_prevCursorPosition = m_textEditor.data()->position();
2332         m_textEditor.data()->paste(canvas(), data, true);
2333         editingPluginEvents();
2334     }
2335 }
2336 
bold(bool bold)2337 void TextTool::bold(bool bold)
2338 {
2339     m_textEditor.data()->bold(bold);
2340 }
2341 
italic(bool italic)2342 void TextTool::italic(bool italic)
2343 {
2344     m_textEditor.data()->italic(italic);
2345 }
2346 
underline(bool underline)2347 void TextTool::underline(bool underline)
2348 {
2349     m_textEditor.data()->underline(underline);
2350 }
2351 
strikeOut(bool strikeOut)2352 void TextTool::strikeOut(bool strikeOut)
2353 {
2354     m_textEditor.data()->strikeOut(strikeOut);
2355 }
2356 
nonbreakingSpace()2357 void TextTool::nonbreakingSpace()
2358 {
2359     if (!m_allowActions || !m_textEditor.data()) return;
2360     m_textEditor.data()->insertText(QString(QChar(Qt::Key_nobreakspace)));
2361 }
2362 
nonbreakingHyphen()2363 void TextTool::nonbreakingHyphen()
2364 {
2365     if (!m_allowActions || !m_textEditor.data()) return;
2366     m_textEditor.data()->insertText(QString(QChar(0x2013)));
2367 }
2368 
softHyphen()2369 void TextTool::softHyphen()
2370 {
2371     if (!m_allowActions || !m_textEditor.data()) return;
2372     m_textEditor.data()->insertText(QString(QChar(Qt::Key_hyphen)));
2373 }
2374 
lineBreak()2375 void TextTool::lineBreak()
2376 {
2377     if (!m_allowActions || !m_textEditor.data()) return;
2378     m_textEditor.data()->insertText(QString(QChar(0x2028)));
2379 }
2380 
alignLeft()2381 void TextTool::alignLeft()
2382 {
2383     if (!m_allowActions || !m_textEditor.data()) return;
2384     m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignLeft | Qt::AlignAbsolute);
2385 }
2386 
alignRight()2387 void TextTool::alignRight()
2388 {
2389     if (!m_allowActions || !m_textEditor.data()) return;
2390     m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignRight | Qt::AlignAbsolute);
2391 }
2392 
alignCenter()2393 void TextTool::alignCenter()
2394 {
2395     if (!m_allowActions || !m_textEditor.data()) return;
2396     m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignHCenter);
2397 }
2398 
alignBlock()2399 void TextTool::alignBlock()
2400 {
2401     if (!m_allowActions || !m_textEditor.data()) return;
2402     m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignJustify);
2403 }
2404 
superScript(bool on)2405 void TextTool::superScript(bool on)
2406 {
2407     if (!m_allowActions || !m_textEditor.data()) return;
2408     if (on)
2409         m_actionFormatSub->setChecked(false);
2410     m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignTop : Qt::AlignVCenter);
2411 }
2412 
subScript(bool on)2413 void TextTool::subScript(bool on)
2414 {
2415     if (!m_allowActions || !m_textEditor.data()) return;
2416     if (on)
2417         m_actionFormatSuper->setChecked(false);
2418     m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignBottom : Qt::AlignVCenter);
2419 }
2420 
increaseIndent()2421 void TextTool::increaseIndent()
2422 {
2423     if (!m_allowActions || !m_textEditor.data()) return;
2424     if (m_textEditor.data()->block().textList()) {
2425         ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel;
2426         ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1);
2427         m_textEditor.data()->addCommand(cll);
2428         editingPluginEvents();
2429     } else {
2430         m_textEditor.data()->increaseIndent();
2431     }
2432     updateActions();
2433 }
2434 
decreaseIndent()2435 void TextTool::decreaseIndent()
2436 {
2437     if (!m_allowActions || !m_textEditor.data()) return;
2438     if (m_textEditor.data()->block().textList()) {
2439         ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel;
2440         ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1);
2441         m_textEditor.data()->addCommand(cll);
2442         editingPluginEvents();
2443     } else {
2444         m_textEditor.data()->decreaseIndent();
2445     }
2446     updateActions();
2447 }
2448 
decreaseFontSize()2449 void TextTool::decreaseFontSize()
2450 {
2451     if (!m_allowActions || !m_textEditor.data()) return;
2452     m_textEditor.data()->decreaseFontSize();
2453 }
2454 
increaseFontSize()2455 void TextTool::increaseFontSize()
2456 {
2457     if (!m_allowActions || !m_textEditor.data()) return;
2458     m_textEditor.data()->increaseFontSize();
2459 }
2460 
setFontFamily(const QString & font)2461 void TextTool::setFontFamily(const QString &font)
2462 {
2463     if (!m_allowActions || !m_textEditor.data()) return;
2464     m_textEditor.data()->setFontFamily(font);
2465 }
2466 
setFontSize(qreal size)2467 void TextTool::setFontSize (qreal size)
2468 {
2469     if (!m_allowActions || !m_textEditor.data()) return;
2470     m_textEditor.data()->setFontSize(size);
2471 }
2472 
insertIndexMarker()2473 void TextTool::insertIndexMarker()
2474 {
2475     // TODO handle result when we figure out how to report errors from a tool.
2476     m_textEditor.data()->insertIndexMarker();
2477 }
2478 
insertFrameBreak()2479 void TextTool::insertFrameBreak()
2480 {
2481     m_textEditor.data()->insertFrameBreak();
2482 
2483     ensureCursorVisible();
2484     m_delayedEnsureVisible = true;
2485 }
2486 
setStyle(KoCharacterStyle * style)2487 void TextTool::setStyle(KoCharacterStyle *style)
2488 {
2489     KoCharacterStyle *charStyle = style;
2490     //if the given KoCharacterStyle is null, set the KoParagraphStyle character properties
2491     if (!charStyle){
2492         charStyle = static_cast<KoCharacterStyle*>(KoTextDocument(m_textShapeData->document()).styleManager()->paragraphStyle(m_textEditor.data()->blockFormat().intProperty(KoParagraphStyle::StyleId)));
2493     }
2494     if (charStyle) {
2495         m_textEditor.data()->setStyle(charStyle);
2496         updateActions();
2497     }
2498 }
2499 
setStyle(KoParagraphStyle * style)2500 void TextTool::setStyle(KoParagraphStyle *style)
2501 {
2502     m_textEditor.data()->setStyle(style);
2503     updateActions();
2504 }
2505 
insertTable()2506 void TextTool::insertTable()
2507 {
2508     TableDialog *dia = new TableDialog(0);
2509     if (dia->exec() == TableDialog::Accepted)
2510         m_textEditor.data()->insertTable(dia->rows(), dia->columns());
2511     delete dia;
2512 
2513     updateActions();
2514 }
2515 
insertTableQuick(int rows,int columns)2516 void TextTool::insertTableQuick(int rows, int columns)
2517 {
2518     m_textEditor.data()->insertTable(rows, columns);
2519     updateActions();
2520 }
2521 
insertTableRowAbove()2522 void TextTool::insertTableRowAbove()
2523 {
2524     m_textEditor.data()->insertTableRowAbove();
2525 }
2526 
insertTableRowBelow()2527 void TextTool::insertTableRowBelow()
2528 {
2529     m_textEditor.data()->insertTableRowBelow();
2530 }
2531 
insertTableColumnLeft()2532 void TextTool::insertTableColumnLeft()
2533 {
2534     m_textEditor.data()->insertTableColumnLeft();
2535 }
2536 
insertTableColumnRight()2537 void TextTool::insertTableColumnRight()
2538 {
2539     m_textEditor.data()->insertTableColumnRight();
2540 }
2541 
deleteTableColumn()2542 void TextTool::deleteTableColumn()
2543 {
2544     m_textEditor.data()->deleteTableColumn();
2545 }
2546 
deleteTableRow()2547 void TextTool::deleteTableRow()
2548 {
2549     m_textEditor.data()->deleteTableRow();
2550 }
2551 
mergeTableCells()2552 void TextTool::mergeTableCells()
2553 {
2554     m_textEditor.data()->mergeTableCells();
2555 }
2556 
splitTableCells()2557 void TextTool::splitTableCells()
2558 {
2559     m_textEditor.data()->splitTableCells();
2560 }
2561 
useTableBorderCursor()2562 void TextTool::useTableBorderCursor()
2563 {
2564     static const unsigned char data[] = {
2565         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x68, 0x00,
2566         0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfd, 0x00,
2567         0x00, 0x80, 0x7e, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x00, 0xa0, 0x1f, 0x00,
2568         0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x03, 0x00,
2569         0x00, 0xe4, 0x01, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00,
2570         0x40, 0x32, 0x00, 0x00, 0xa0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00,
2571         0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00,
2572         0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
2573     };
2574 
2575     QBitmap result(32, 32);
2576     result.fill(Qt::color0);
2577     QPainter painter(&result);
2578     painter.drawPixmap(0, 0, QBitmap::fromData(QSize(25, 23), data));
2579     QBitmap brushMask = result.createHeuristicMask(false);
2580 
2581     useCursor(QCursor(result, brushMask, 1, 21));
2582 }
2583 
setTableBorderData(const KoBorder::BorderData & data)2584 void TextTool::setTableBorderData(const KoBorder::BorderData &data)
2585 {
2586     m_tablePenMode = true;
2587     m_tablePenBorderData = data;
2588 }
2589 
formatParagraph()2590 void TextTool::formatParagraph()
2591 {
2592     ParagraphSettingsDialog *dia = new ParagraphSettingsDialog(this, m_textEditor.data());
2593     dia->setUnit(canvas()->unit());
2594     dia->setImageCollection(m_textShape->imageCollection());
2595     dia->exec();
2596     delete dia;
2597     returnFocusToCanvas();
2598 }
2599 
testSlot(bool on)2600 void TextTool::testSlot(bool on)
2601 {
2602     debugTextShape << "signal received. bool:" << on;
2603 }
2604 
selectAll()2605 void TextTool::selectAll()
2606 {
2607     KoTextEditor *textEditor = m_textEditor.data();
2608     if (!textEditor || !m_textShapeData)
2609         return;
2610     const int selectionLength = qAbs(textEditor->position() - textEditor->anchor());
2611     textEditor->movePosition(QTextCursor::End);
2612     textEditor->setPosition(0, QTextCursor::KeepAnchor);
2613     repaintSelection();
2614     if (selectionLength != qAbs(textEditor->position() - textEditor->anchor())) // it actually changed
2615         emit selectionChanged(true);
2616 }
2617 
startMacro(const QString & title)2618 void TextTool::startMacro(const QString &title)
2619 {
2620     if (title != i18n("Key Press") && title !=i18n("Autocorrection")) //dirty hack while waiting for refactor of text editing
2621         m_textTyping = false;
2622     else
2623         m_textTyping = true;
2624 
2625     if (title != i18n("Delete") && title != i18n("Autocorrection")) //same dirty hack as above
2626         m_textDeleting = false;
2627     else
2628         m_textDeleting = true;
2629 
2630     if (m_currentCommand) return;
2631 
2632     class MacroCommand : public KUndo2Command
2633     {
2634     public:
2635         MacroCommand(const KUndo2MagicString &title) : KUndo2Command(title), m_first(true) {}
2636         void redo() override {
2637             if (! m_first)
2638                 KUndo2Command::redo();
2639             m_first = false;
2640         }
2641         bool mergeWith(const KUndo2Command *) override {
2642             return false;
2643         }
2644         bool m_first;
2645     };
2646 
2647     /**
2648      * FIXME: The messages genearted by the Text Tool might not be
2649      *        properly translated, since we don't control it in
2650      *        type-safe way.
2651      *
2652      *        The title is already translated string, we just don't
2653      *        have any type control over it.
2654      */
2655     KUndo2MagicString title_workaround = kundo2_noi18n(title);
2656     m_currentCommand = new MacroCommand(title_workaround);
2657     m_currentCommandHasChildren = false;
2658 }
2659 
stopMacro()2660 void TextTool::stopMacro()
2661 {
2662     if (!m_currentCommand)
2663         return;
2664     if (! m_currentCommandHasChildren)
2665         delete m_currentCommand;
2666     m_currentCommand = 0;
2667 }
2668 
showStyleManager(int styleId)2669 void TextTool::showStyleManager(int styleId)
2670 {
2671     if (!m_textShapeData)
2672         return;
2673     KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager();
2674     Q_ASSERT(styleManager);
2675     if (!styleManager)
2676         return;  //don't crash
2677     StyleManagerDialog *dia = new StyleManagerDialog(canvas()->canvasWidget());
2678     dia->setStyleManager(styleManager);
2679     dia->setUnit(canvas()->unit());
2680 
2681     KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(styleId);
2682     if (paragraphStyle) {
2683         dia->setParagraphStyle(paragraphStyle);
2684     }
2685     KoCharacterStyle *characterStyle = styleManager->characterStyle(styleId);
2686     if (characterStyle) {
2687         dia->setCharacterStyle(characterStyle);
2688     }
2689     dia->show();
2690 }
2691 
startTextEditingPlugin(const QString & pluginId)2692 void TextTool::startTextEditingPlugin(const QString &pluginId)
2693 {
2694     KoTextEditingPlugin *plugin = textEditingPluginContainer()->plugin(pluginId);
2695     if (plugin) {
2696         if (m_textEditor.data()->hasSelection()) {
2697             plugin->checkSection(m_textShapeData->document(), m_textEditor.data()->selectionStart(), m_textEditor.data()->selectionEnd());
2698         } else
2699             plugin->finishedWord(m_textShapeData->document(), m_textEditor.data()->position());
2700     }
2701 }
2702 
canvasResourceChanged(int key,const QVariant & var)2703 void TextTool::canvasResourceChanged(int key, const QVariant &var)
2704 {
2705     if (m_textEditor.isNull())
2706         return;
2707     if (!m_textShapeData)
2708         return;
2709     if (m_allowResourceManagerUpdates == false)
2710         return;
2711     if (key == KoText::CurrentTextPosition) {
2712         repaintSelection();
2713         m_textEditor.data()->setPosition(var.toInt());
2714         ensureCursorVisible();
2715     } else if (key == KoText::CurrentTextAnchor) {
2716         repaintSelection();
2717         int pos = m_textEditor.data()->position();
2718         m_textEditor.data()->setPosition(var.toInt());
2719         m_textEditor.data()->setPosition(pos, QTextCursor::KeepAnchor);
2720     } else if (key == KoCanvasResourceManager::Unit) {
2721         m_unit = var.value<KoUnit>();
2722     } else return;
2723 
2724     repaintSelection();
2725 }
2726 
insertSpecialCharacter()2727 void TextTool::insertSpecialCharacter()
2728 {
2729     if (m_specialCharacterDocker == 0) {
2730         m_specialCharacterDocker = new InsertCharacter(canvas()->canvasWidget());
2731         connect(m_specialCharacterDocker, SIGNAL(insertCharacter(QString)),
2732                 this, SLOT(insertString(QString)));
2733     }
2734 
2735     m_specialCharacterDocker->show();
2736 }
2737 
insertString(const QString & string)2738 void TextTool::insertString(const QString& string)
2739 {
2740     m_textEditor.data()->insertText(string);
2741     returnFocusToCanvas();
2742 }
2743 
selectFont()2744 void TextTool::selectFont()
2745 {
2746     FontDia *fontDlg = new FontDia(m_textEditor.data());
2747     fontDlg->exec();
2748     delete fontDlg;
2749     returnFocusToCanvas();
2750 }
2751 
shapeAddedToCanvas()2752 void TextTool::shapeAddedToCanvas()
2753 {
2754     debugTextShape;
2755     if (m_textShape) {
2756         KoSelection *selection = canvas()->shapeManager()->selection();
2757         KoShape *shape = selection->firstSelectedShape();
2758         if (shape != m_textShape && canvas()->shapeManager()->shapes().contains(m_textShape)) {
2759             // this situation applies when someone, not us, changed the selection by selecting another
2760             // text shape. Possibly by adding one.
2761             // Deselect the new shape again, so we can keep editing what we were already editing
2762             selection->select(m_textShape);
2763             selection->deselect(shape);
2764         }
2765     }
2766 }
2767 
shapeDataRemoved()2768 void TextTool::shapeDataRemoved()
2769 {
2770     m_textShapeData = 0;
2771     m_textShape = 0;
2772     if (!m_textEditor.isNull() && !m_textEditor.data()->cursor()->isNull()) {
2773         const QTextDocument *doc = m_textEditor.data()->document();
2774         Q_ASSERT(doc);
2775         KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout*>(doc->documentLayout());
2776         if (!lay || lay->shapes().isEmpty()) {
2777             emit done();
2778             return;
2779         }
2780         m_textShape = static_cast<TextShape*>(lay->shapes().first());
2781         m_textShapeData = static_cast<KoTextShapeData*>(m_textShape->userData());
2782         connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved()));
2783     }
2784 }
2785 
createStyleFromCurrentBlockFormat(const QString & name)2786 void TextTool::createStyleFromCurrentBlockFormat(const QString &name)
2787 {
2788     KoTextDocument document(m_textShapeData->document());
2789     KoStyleManager *styleManager = document.styleManager();
2790     KoParagraphStyle *paragraphStyle = new KoParagraphStyle(m_textEditor.data()->blockFormat(), m_textEditor.data()->charFormat());
2791     paragraphStyle->setName(name);
2792     styleManager->add(paragraphStyle);
2793     m_textEditor.data()->setStyle(paragraphStyle);
2794     emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat());
2795     emit blockFormatChanged(m_textEditor.data()->blockFormat());
2796 }
2797 
createStyleFromCurrentCharFormat(const QString & name)2798 void TextTool::createStyleFromCurrentCharFormat(const QString &name)
2799 {
2800     KoTextDocument document(m_textShapeData->document());
2801     KoStyleManager *styleManager = document.styleManager();
2802     KoCharacterStyle *originalCharStyle = styleManager->characterStyle(m_textEditor.data()->charFormat().intProperty(KoCharacterStyle::StyleId));
2803     KoCharacterStyle *autoStyle;
2804     if (!originalCharStyle) {
2805         KoCharacterStyle blankStyle;
2806         originalCharStyle = &blankStyle;
2807         autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat());
2808         autoStyle->setParentStyle(0);
2809     } else {
2810         autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat());
2811     }
2812     autoStyle->setName(name);
2813     styleManager->add(autoStyle);
2814     m_textEditor.data()->setStyle(autoStyle);
2815     emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat());
2816 }
2817 
2818 // ---------- editing plugins methods.
editingPluginEvents()2819 void TextTool::editingPluginEvents()
2820 {
2821     if (m_prevCursorPosition == -1 || m_prevCursorPosition == m_textEditor.data()->position()) {
2822         debugTextShape<<"m_prevCursorPosition="<<m_prevCursorPosition<<"m_textEditor.data()->position()="<<m_textEditor.data()->position();
2823         return;
2824     }
2825 
2826     QTextBlock block = m_textEditor.data()->block();
2827     if (! block.contains(m_prevCursorPosition)) {
2828         debugTextShape<<"m_prevCursorPosition="<<m_prevCursorPosition;
2829         finishedWord();
2830         finishedParagraph();
2831         m_prevCursorPosition = -1;
2832     } else {
2833         int from = m_prevCursorPosition;
2834         int to = m_textEditor.data()->position();
2835         if (from > to)
2836             qSwap(from, to);
2837         QString section = block.text().mid(from - block.position(), to - from);
2838         debugTextShape<<"from="<<from<<"to="<<to;
2839         if (section.contains(' ')) {
2840             finishedWord();
2841             m_prevCursorPosition = -1;
2842         }
2843     }
2844 }
2845 
finishedWord()2846 void TextTool::finishedWord()
2847 {
2848     if (m_textShapeData && textEditingPluginContainer()) {
2849         foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) {
2850             plugin->finishedWord(m_textShapeData->document(), m_prevCursorPosition);
2851         }
2852     }
2853 }
2854 
finishedParagraph()2855 void TextTool::finishedParagraph()
2856 {
2857     if (m_textShapeData && textEditingPluginContainer()) {
2858         foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) {
2859             plugin->finishedParagraph(m_textShapeData->document(), m_prevCursorPosition);
2860         }
2861     }
2862 }
2863 
startingSimpleEdit()2864 void TextTool::startingSimpleEdit()
2865 {
2866     if (m_textShapeData && textEditingPluginContainer()) {
2867         foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) {
2868             plugin->startingSimpleEdit(m_textShapeData->document(), m_prevCursorPosition);
2869         }
2870     }
2871 
2872 }
2873 
setTextColor(const KoColor & color)2874 void TextTool::setTextColor(const KoColor &color)
2875 {
2876     m_textEditor.data()->setTextColor(color.toQColor());
2877 }
2878 
setBackgroundColor(const KoColor & color)2879 void TextTool::setBackgroundColor(const KoColor &color)
2880 {
2881     m_textEditor.data()->setTextBackgroundColor(color.toQColor());
2882 }
2883 
setAutoResize(bool enabled)2884 void TextTool::setAutoResize(bool enabled)
2885 {
2886     m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoResize, enabled));
2887     updateActions();
2888 }
2889 
setGrowWidthToFit(bool enabled)2890 void TextTool::setGrowWidthToFit(bool enabled)
2891 {
2892     m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowWidth, enabled));
2893     updateActions();
2894 }
2895 
setGrowHeightToFit(bool enabled)2896 void TextTool::setGrowHeightToFit(bool enabled)
2897 {
2898     m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowHeight, enabled));
2899     updateActions();
2900 }
2901 
setShrinkToFit(bool enabled)2902 void TextTool::setShrinkToFit(bool enabled)
2903 {
2904     m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::ShrinkToFitResize, enabled));
2905     updateActions();
2906 }
2907 
runUrl(KoPointerEvent * event,QString & url)2908 void TextTool::runUrl(KoPointerEvent *event, QString &url)
2909 {
2910     QUrl _url = QUrl::fromLocalFile(url);
2911     if (_url.isLocalFile()) {
2912         QMimeDatabase db;
2913         QString type = db.mimeTypeForUrl(_url).name();
2914 
2915         if (KRun::isExecutableFile(_url, type)) {
2916             QString question = i18n("This link points to the program or script '%1'.\n"
2917                                     "Malicious programs can harm your computer. "
2918                                     "Are you sure that you want to run this program?", url);
2919             // this will also start local programs, so adding a "don't warn again"
2920             // checkbox will probably be too dangerous
2921             int choice = KMessageBox::warningYesNo(0, question, i18n("Open Link?"));
2922             if (choice != KMessageBox::Yes)
2923                 return;
2924         }
2925     }
2926 
2927     event->accept();
2928     new KRun(_url, 0);
2929 }
2930 
debugTextDocument()2931 void TextTool::debugTextDocument()
2932 {
2933 #ifndef NDEBUG
2934     if (!m_textShapeData)
2935         return;
2936     const int CHARSPERLINE = 80; // TODO Make configurable using ENV var?
2937     const int CHARPOSITION = 278301935;
2938     KoTextDocument document(m_textShapeData->document());
2939     KoStyleManager *styleManager = document.styleManager();
2940     KoInlineTextObjectManager *inlineManager = document.inlineTextObjectManager();
2941 
2942     QTextBlock block = m_textShapeData->document()->begin();
2943     for (;block.isValid(); block = block.next()) {
2944         QVariant var = block.blockFormat().property(KoParagraphStyle::StyleId);
2945         if (!var.isNull()) {
2946             KoParagraphStyle *ps = styleManager->paragraphStyle(var.toInt());
2947             debugTextShape << "--- Paragraph Style:" << (ps ? ps->name() : QString()) << var.toInt();
2948         }
2949         var = block.charFormat().property(KoCharacterStyle::StyleId);
2950         if (!var.isNull()) {
2951             KoCharacterStyle *cs = styleManager->characterStyle(var.toInt());
2952             debugTextShape << "--- Character Style:" << (cs ? cs->name() : QString()) << var.toInt();
2953         }
2954         int lastPrintedChar = -1;
2955         QTextBlock::iterator it;
2956         QString fragmentText;
2957         QList<QTextCharFormat> inlineCharacters;
2958         for (it = block.begin(); !it.atEnd(); ++it) {
2959             QTextFragment fragment = it.fragment();
2960             if (!fragment.isValid())
2961                 continue;
2962             QTextCharFormat fmt = fragment.charFormat();
2963             debugTextShape << "changeId: " << fmt.property(KoCharacterStyle::ChangeTrackerId);
2964             const int fragmentStart = fragment.position() - block.position();
2965             for (int i = fragmentStart; i < fragmentStart + fragment.length(); i += CHARSPERLINE) {
2966                 if (lastPrintedChar == fragmentStart-1)
2967                     fragmentText += '|';
2968                 if (lastPrintedChar < fragmentStart || i > fragmentStart) {
2969                     QString debug = block.text().mid(lastPrintedChar, CHARSPERLINE);
2970                     lastPrintedChar += CHARSPERLINE;
2971                     if (lastPrintedChar > block.length())
2972                         debug += "\\n";
2973                     debugTextShape << debug;
2974                 }
2975                 var = fmt.property(KoCharacterStyle::StyleId);
2976                 QString charStyleLong, charStyleShort;
2977                 if (! var.isNull()) { // named style
2978                     charStyleShort = QString::number(var.toInt());
2979                     KoCharacterStyle *cs = styleManager->characterStyle(var.toInt());
2980                     if (cs)
2981                         charStyleLong = cs->name();
2982                 }
2983                 if (inlineManager && fmt.hasProperty(KoCharacterStyle::InlineInstanceId)) {
2984                     QTextCharFormat inlineFmt = fmt;
2985                     inlineFmt.setProperty(CHARPOSITION, fragmentStart);
2986                     inlineCharacters << inlineFmt;
2987                 }
2988 
2989                 if (fragment.length() > charStyleLong.length())
2990                     fragmentText += charStyleLong;
2991                 else if (fragment.length() > charStyleShort.length())
2992                     fragmentText += charStyleShort;
2993                 else if (fragment.length() >= 2)
2994                     fragmentText += QChar(8230); // ellipsis
2995 
2996 
2997 
2998                 int rest =  fragmentStart - (lastPrintedChar-CHARSPERLINE) + fragment.length() - fragmentText.length();
2999                 rest = qMin(rest, CHARSPERLINE - fragmentText.length());
3000                 if (rest >= 2)
3001                     fragmentText = QString("%1%2").arg(fragmentText).arg(' ', rest);
3002                 if (rest >= 0)
3003                     fragmentText += '|';
3004                 if (fragmentText.length() >= CHARSPERLINE) {
3005                     debugTextShape << fragmentText;
3006                     fragmentText.clear();
3007                 }
3008             }
3009         }
3010         if (!fragmentText.isEmpty()) {
3011             debugTextShape << fragmentText;
3012         }
3013         else if (block.length() == 1) { // no actual tet
3014             debugTextShape << "\\n";
3015         }
3016         foreach (const QTextCharFormat &cf, inlineCharacters) {
3017             KoInlineObject *object= inlineManager->inlineTextObject(cf);
3018             debugTextShape << "At pos:" << cf.intProperty(CHARPOSITION) << object;
3019             // debugTextShape << "-> id:" << cf.intProperty(577297549);
3020         }
3021         QTextList *list = block.textList();
3022         if (list) {
3023             if (list->format().hasProperty(KoListStyle::StyleId)) {
3024                 KoListStyle *ls = styleManager->listStyle(list->format().intProperty(KoListStyle::StyleId));
3025                 debugTextShape << "   List style applied:" << ls->styleId() << ls->name();
3026             }
3027             else
3028                 debugTextShape << " +- is a list..." << list;
3029         }
3030     }
3031 #endif
3032 }
3033 
debugTextStyles()3034 void TextTool::debugTextStyles()
3035 {
3036 #ifndef NDEBUG
3037     if (!m_textShapeData)
3038         return;
3039     KoTextDocument document(m_textShapeData->document());
3040     KoStyleManager *styleManager = document.styleManager();
3041 
3042     QSet<int> seenStyles;
3043 
3044     foreach (KoParagraphStyle *style, styleManager->paragraphStyles()) {
3045         debugTextShape << style->styleId() << style->name() << (styleManager->defaultParagraphStyle() == style ? "[Default]" : "");
3046         KoListStyle *ls = style->listStyle();
3047         if (ls) { // optional ;)
3048             debugTextShape << "  +- ListStyle: " << ls->styleId() << ls->name()
3049                 << (ls == styleManager->defaultListStyle() ? "[Default]":"");
3050             foreach (int level, ls->listLevels()) {
3051                 KoListLevelProperties llp = ls->levelProperties(level);
3052                 debugTextShape << "  |  level" << llp.level() << " style (enum):" << llp.labelType();
3053                 if (llp.bulletCharacter().unicode() != 0) {
3054                     debugTextShape << "  |  bullet" << llp.bulletCharacter();
3055                 }
3056             }
3057             seenStyles << ls->styleId();
3058         }
3059     }
3060 
3061     bool first = true;
3062     foreach (KoCharacterStyle *style, styleManager->characterStyles()) {
3063         if (seenStyles.contains(style->styleId()))
3064             continue;
3065         if (first) {
3066             debugTextShape << "--- Character styles ---";
3067             first = false;
3068         }
3069         debugTextShape << style->styleId() << style->name();
3070         debugTextShape << style->font();
3071     }
3072 
3073     first = true;
3074     foreach (KoListStyle *style, styleManager->listStyles()) {
3075         if (seenStyles.contains(style->styleId()))
3076             continue;
3077         if (first) {
3078             debugTextShape << "--- List styles ---";
3079             first = false;
3080         }
3081         debugTextShape << style->styleId() << style->name()
3082                 << (style == styleManager->defaultListStyle() ? "[Default]":"");
3083     }
3084 #endif
3085 }
3086 
textDirectionChanged()3087 void TextTool::textDirectionChanged()
3088 {
3089     if (!m_allowActions || !m_textEditor.data()) return;
3090 
3091     QTextBlockFormat blockFormat;
3092     if (m_actionChangeDirection->isChecked()) {
3093         blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::RightLeftTopBottom);
3094     }
3095     else {
3096         blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::LeftRightTopBottom);
3097      }
3098     m_textEditor.data()->mergeBlockFormat(blockFormat);
3099 }
3100 
setListLevel(int level)3101 void TextTool::setListLevel(int level)
3102 {
3103     if (level < 1 || level > 10) {
3104         return;
3105     }
3106 
3107     KoTextEditor *textEditor = m_textEditor.data();
3108     if (textEditor->block().textList()) {
3109         ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::SetLevel;
3110         ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, level);
3111         textEditor->addCommand(cll);
3112         editingPluginEvents();
3113     }
3114 }
3115 
insertAnnotation()3116 void TextTool::insertAnnotation()
3117 {
3118     // HACK to avoid crash when we try to add a comment to an annotation shape.
3119     // We should not get here when the shape is an annotation shape,
3120     // but just disabling the insert_annotation action somehow does not work.
3121     if (m_textShape->shapeId() == AnnotationShape_SHAPEID) {
3122         return;
3123     }
3124     AnnotationTextShape *shape = (AnnotationTextShape*)KoShapeRegistry::instance()->value(AnnotationShape_SHAPEID)->createDefaultShape(canvas()->shapeController()->resourceManager());
3125     textEditor()->addAnnotation(shape);
3126 
3127     // Set annotation creator.
3128     KConfig cfg("calligrarc");
3129     cfg.reparseConfiguration();
3130     KConfigGroup authorGroup(&cfg, "Author");
3131     QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
3132      KSharedConfig::openConfig()->reparseConfiguration();
3133     KConfigGroup appAuthorGroup( KSharedConfig::openConfig(), "Author");
3134     QString profile = appAuthorGroup.readEntry("active-profile", "");
3135     KConfigGroup cgs(&authorGroup, "Author-" + profile);
3136 
3137     if (profiles.contains(profile)) {
3138         KConfigGroup cgs(&authorGroup, "Author-" + profile);
3139         shape->setCreator(cgs.readEntry("creator"));
3140     } else {
3141         if (profile == "anonymous") {
3142             shape->setCreator("Anonymous");
3143         } else {
3144             KUser user(KUser::UseRealUserID);
3145             shape->setCreator(user.property(KUser::FullName).toString());
3146         }
3147     }
3148     // Set Annotation creation date.
3149     shape->setDate(QDate::currentDate().toString(Qt::ISODate));
3150 }
3151