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