1 /* This file is part of the KDE project
2  * Copyright (C) 2007, 2008, 2010 Thomas Zander <zander@kde.org>
3  * Copyright (C) 2009-2010 C. Boemann <cbo@boemann.dk>
4  * Copyright (C) 2011 Mojtaba Shahi Senobari <mojtaba.shahi3000@gmail.com>
5  * Copyright (C) 2011-2012 Pierre Stirnweiss <pstirnweiss@googlemail.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 #include "SimpleParagraphWidget.h"
23 #include "TextTool.h"
24 #include <ListItemsHelper.h>
25 #include "FormattingButton.h"
26 #include <KoStyleThumbnailer.h>
27 
28 #include "StylesCombo.h"
29 #include "StylesModel.h"
30 #include "DockerStylesComboModel.h"
31 #include "StylesDelegate.h"
32 #include "ListLevelChooser.h"
33 #include "commands/ChangeListLevelCommand.h"
34 
35 #include <KoTextEditor.h>
36 #include <KoTextBlockData.h>
37 #include <KoParagraphStyle.h>
38 #include <KoInlineTextObjectManager.h>
39 #include <KoTextRangeManager.h>
40 #include <KoTextDocumentLayout.h>
41 #include <KoZoomHandler.h>
42 #include <KoStyleManager.h>
43 #include <KoListLevelProperties.h>
44 #include <KoShapePaintingContext.h>
45 
46 #include <QAction>
47 
48 #include <QTextLayout>
49 #include <QFlags>
50 #include <QMenu>
51 #include <QWidgetAction>
52 #include <KisSignalMapper.h>
53 
54 #include <QDebug>
55 
SimpleParagraphWidget(TextTool * tool,QWidget * parent)56 SimpleParagraphWidget::SimpleParagraphWidget(TextTool *tool, QWidget *parent)
57     : QWidget(parent)
58     , m_styleManager(0)
59     , m_blockSignals(false)
60     , m_tool(tool)
61     , m_directionButtonState(Auto)
62     , m_thumbnailer(new KoStyleThumbnailer())
63     , m_mapper(new KisSignalMapper(this))
64     , m_stylesModel(new StylesModel(0, StylesModel::ParagraphStyle))
65     , m_sortedStylesModel(new DockerStylesComboModel())
66     , m_stylesDelegate(0)
67 {
68     widget.setupUi(this);
69     widget.alignCenter->setDefaultAction(tool->action("format_aligncenter"));
70     widget.alignBlock->setDefaultAction(tool->action("format_alignblock"));
71     // RTL layout will reverse the button order, but the align left/right then get mixed up.
72     // this makes sure that whatever happens the 'align left' is to the left of the 'align right'
73     if (QApplication::isRightToLeft()) {
74         widget.alignLeft->setDefaultAction(tool->action("format_alignright"));
75         widget.alignRight->setDefaultAction(tool->action("format_alignleft"));
76     } else {
77         widget.alignLeft->setDefaultAction(tool->action("format_alignleft"));
78         widget.alignRight->setDefaultAction(tool->action("format_alignright"));
79     }
80 
81     widget.decreaseIndent->setDefaultAction(tool->action("format_decreaseindent"));
82     widget.increaseIndent->setDefaultAction(tool->action("format_increaseindent"));
83     widget.changeTextDirection->setDefaultAction(tool->action("change_text_direction"));
84 
85     widget.moreOptions->setText("...");
86     widget.moreOptions->setToolTip(i18n("Change paragraph format"));
87     connect(widget.moreOptions, SIGNAL(clicked(bool)), tool->action("format_paragraph"), SLOT(trigger()));
88 
89     connect(widget.changeTextDirection, SIGNAL(clicked()), this, SIGNAL(doneWithFocus()));
90     connect(widget.alignCenter, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
91     connect(widget.alignBlock, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
92     connect(widget.alignLeft, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
93     connect(widget.alignRight, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
94     connect(widget.decreaseIndent, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
95     connect(widget.increaseIndent, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
96 
97     widget.bulletListButton->setDefaultAction(tool->action("format_bulletlist"));
98     widget.bulletListButton->setNumColumns(3);
99 
100     fillListButtons();
101     widget.bulletListButton->addSeparator();
102 
103     connect(widget.bulletListButton, SIGNAL(itemTriggered(int)), this, SLOT(listStyleChanged(int)));
104 
105     m_stylesModel->setStyleThumbnailer(m_thumbnailer);
106     widget.paragraphStyleCombo->setStylesModel(m_sortedStylesModel);
107     connect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
108     connect(widget.paragraphStyleCombo, SIGNAL(newStyleRequested(QString)), this, SIGNAL(newStyleRequested(QString)));
109     connect(widget.paragraphStyleCombo, SIGNAL(newStyleRequested(QString)), this, SIGNAL(doneWithFocus()));
110     connect(widget.paragraphStyleCombo, SIGNAL(showStyleManager(int)), this, SLOT(slotShowStyleManager(int)));
111 
112     connect(m_mapper, SIGNAL(mapped(int)), this, SLOT(changeListLevel(int)));
113 
114     m_sortedStylesModel->setStylesModel(m_stylesModel);
115 }
116 
~SimpleParagraphWidget()117 SimpleParagraphWidget::~SimpleParagraphWidget()
118 {
119     //the style model is set on the comboBox who takes over ownership
120     delete m_thumbnailer;
121 }
122 
fillListButtons()123 void SimpleParagraphWidget::fillListButtons()
124 {
125     KoZoomHandler zoomHandler;
126     zoomHandler.setZoom(1.2);
127     zoomHandler.setDpi(72, 72);
128 
129     KoInlineTextObjectManager itom;
130     KoTextRangeManager tlm;
131     TextShape textShape(&itom, &tlm);
132     textShape.setSize(QSizeF(300, 100));
133     QTextCursor cursor(textShape.textShapeData()->document());
134     Q_FOREACH (const Lists::ListStyleItem &item, Lists::genericListStyleItems()) {
135         QPixmap pm(48, 48);
136 
137         pm.fill(Qt::transparent);
138         QPainter p(&pm);
139 
140         p.translate(0, -1.5);
141         p.setRenderHint(QPainter::Antialiasing);
142         if (item.style != KoListStyle::None) {
143             KoListStyle listStyle;
144             KoListLevelProperties llp = listStyle.levelProperties(1);
145             llp.setStyle(item.style);
146             if (KoListStyle::isNumberingStyle(item.style)) {
147                 llp.setStartValue(1);
148                 llp.setListItemSuffix(".");
149             }
150             listStyle.setLevelProperties(llp);
151             cursor.select(QTextCursor::Document);
152             QTextCharFormat textCharFormat = cursor.blockCharFormat();
153             textCharFormat.setFontPointSize(11);
154             textCharFormat.setFontWeight(QFont::Normal);
155             cursor.setCharFormat(textCharFormat);
156 
157             QTextBlock cursorBlock = cursor.block();
158             KoTextBlockData data(cursorBlock);
159             cursor.insertText("----");
160             listStyle.applyStyle(cursor.block(), 1);
161             cursorBlock = cursor.block();
162             KoTextBlockData data1(cursorBlock);
163             cursor.insertText("\n----");
164             cursorBlock = cursor.block();
165             KoTextBlockData data2(cursorBlock);
166             cursor.insertText("\n----");
167             cursorBlock = cursor.block();
168             KoTextBlockData data3(cursorBlock);
169 
170             KoTextDocumentLayout *lay = dynamic_cast<KoTextDocumentLayout *>(textShape.textShapeData()->document()->documentLayout());
171             if (lay) {
172                 lay->layout();
173             }
174 
175             KoShapePaintingContext paintContext; //FIXME
176             textShape.paintComponent(p, paintContext);
177             widget.bulletListButton->addItem(pm, static_cast<int>(item.style));
178         }
179     }
180 
181     widget.bulletListButton->addSeparator();
182 
183     QAction *action = new QAction(i18n("Change List Level"), this);
184     action->setToolTip(i18n("Change the level the list is at"));
185 
186     QMenu *listLevelMenu = new QMenu();
187     const int levelIndent = 13;
188     for (int level = 0; level < 10; ++level) {
189         QWidgetAction *wa = new QWidgetAction(listLevelMenu);
190         ListLevelChooser *chooserWidget = new ListLevelChooser((levelIndent * level) + 5);
191         wa->setDefaultWidget(chooserWidget);
192         listLevelMenu->addAction(wa);
193         m_mapper->setMapping(wa, level + 1);
194         connect(chooserWidget, SIGNAL(clicked()), wa, SLOT(trigger()));
195         connect(wa, SIGNAL(triggered()), m_mapper, SLOT(map()));
196     }
197 
198     action->setMenu(listLevelMenu);
199     widget.bulletListButton->addAction(action);
200 }
201 
setCurrentBlock(const QTextBlock & block)202 void SimpleParagraphWidget::setCurrentBlock(const QTextBlock &block)
203 {
204     if (block == m_currentBlock) {
205         return;
206     }
207 
208     m_currentBlock = block;
209     m_blockSignals = true;
210     struct Finally {
211         Finally(SimpleParagraphWidget *p)
212         {
213             parent = p;
214         }
215         ~Finally()
216         {
217             parent->m_blockSignals = false;
218         }
219         SimpleParagraphWidget *parent;
220     };
221     Finally finally(this);
222 
223     setCurrentFormat(m_currentBlock.blockFormat());
224 }
225 
setCurrentFormat(const QTextBlockFormat & format)226 void SimpleParagraphWidget::setCurrentFormat(const QTextBlockFormat &format)
227 {
228     if (!m_styleManager || format == m_currentBlockFormat) {
229         return;
230     }
231     m_currentBlockFormat = format;
232 
233     int id = m_currentBlockFormat.intProperty(KoParagraphStyle::StyleId);
234     KoParagraphStyle *style(m_styleManager->paragraphStyle(id));
235     if (style) {
236         bool unchanged = true;
237 
238         Q_FOREACH (int property, m_currentBlockFormat.properties().keys()) {
239             switch (property) {
240             case QTextFormat::ObjectIndex:
241             case KoParagraphStyle::ListStyleId:
242             case KoParagraphStyle::OutlineLevel:
243             case KoParagraphStyle::ListStartValue:
244             case KoParagraphStyle::IsListHeader:
245             case KoParagraphStyle::UnnumberedListItem:
246                 continue;
247             // These can be both content and style properties so let's ignore
248             case KoParagraphStyle::BreakBefore:
249             case KoParagraphStyle::MasterPageName:
250                 continue;
251 
252             default:
253                 break;
254             }
255             if (property == QTextBlockFormat::BlockAlignment) { //the default alignment can be retrieved in the defaultTextOption. However, calligra sets the Qt::AlignAbsolute flag, so we need to or this flag with the default alignment before comparing.
256                 if ((m_currentBlockFormat.property(property) != style->value(property))
257                         && !(style->value(property).isNull()
258                              && ((m_currentBlockFormat.intProperty(property)) == int(m_currentBlock.document()->defaultTextOption().alignment() | Qt::AlignAbsolute)))) {
259                     unchanged = false;
260                     break;
261                 } else {
262                     continue;
263                 }
264             }
265             if (property == KoParagraphStyle::TextProgressionDirection) {
266                 if (style->value(property).isNull() && m_currentBlockFormat.intProperty(property) == KoText::LeftRightTopBottom) {
267                     //LTR seems to be Qt default when unset
268                     continue;
269                 }
270             }
271             if ((m_currentBlockFormat.property(property) != style->value(property)) && !(style->value(property).isNull() && !m_currentBlockFormat.property(property).toBool())) {
272                 //the last check seems to work. might be cause of a bug. The problem is when comparing an unset property in the style with a set to {0, false, ...) property in the format (eg. set then unset bold)
273                 unchanged = false;
274                 break;
275             }
276         }
277         //we are updating the combo's selected item to what is the current format. we do not want this to apply the style as it would mess up the undo stack, the change tracking,...
278         disconnect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
279         m_sortedStylesModel->styleApplied(style);
280         widget.paragraphStyleCombo->setCurrentIndex(m_sortedStylesModel->indexOf(style).row());
281         widget.paragraphStyleCombo->setStyleIsOriginal(unchanged);
282         m_stylesModel->setCurrentParagraphStyle(id);
283         widget.paragraphStyleCombo->slotUpdatePreview();
284         connect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
285     }
286 }
287 
setStyleManager(KoStyleManager * sm)288 void SimpleParagraphWidget::setStyleManager(KoStyleManager *sm)
289 {
290     Q_ASSERT(sm);
291     if (!sm || m_styleManager == sm) {
292         return;
293     }
294     if (m_styleManager) {
295         disconnect(m_styleManager, SIGNAL(styleApplied(const KoParagraphStyle*)), this, SLOT(slotParagraphStyleApplied(const KoParagraphStyle*)));
296     }
297     m_styleManager = sm;
298     //we want to disconnect this before setting the stylemanager. Populating the model apparently selects the first inserted item. We don't want this to actually set a new style.
299     disconnect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
300     m_stylesModel->setStyleManager(sm);
301     m_sortedStylesModel->setStyleManager(sm);
302     connect(widget.paragraphStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
303     connect(m_styleManager, SIGNAL(styleApplied(const KoParagraphStyle*)), this, SLOT(slotParagraphStyleApplied(const KoParagraphStyle*)));
304 }
305 
setInitialUsedStyles(QVector<int> list)306 void SimpleParagraphWidget::setInitialUsedStyles(QVector<int> list)
307 {
308     m_sortedStylesModel->setInitialUsedStyles(list);
309 }
310 
listStyleChanged(int id)311 void SimpleParagraphWidget::listStyleChanged(int id)
312 {
313     emit doneWithFocus();
314     if (m_blockSignals) {
315         return;
316     }
317     KoListLevelProperties llp;
318     llp.setStyle(static_cast<KoListStyle::Style>(id));
319     llp.setLevel(1);
320     KoTextEditor::ChangeListFlags flags(KoTextEditor::AutoListStyle);
321     m_tool->textEditor()->setListProperties(llp, flags);
322 }
323 
styleSelected(int index)324 void SimpleParagraphWidget::styleSelected(int index)
325 {
326     KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(m_sortedStylesModel->index(index, 0, QModelIndex()).internalId());
327     if (paragStyle) {
328         emit paragraphStyleSelected(paragStyle);
329     }
330     emit doneWithFocus();
331 }
332 
styleSelected(const QModelIndex & index)333 void SimpleParagraphWidget::styleSelected(const QModelIndex &index)
334 {
335     if (!index.isValid()) {
336         return;
337     }
338     KoParagraphStyle *paragStyle = m_styleManager->paragraphStyle(index.internalId());
339     if (paragStyle) {
340         emit paragraphStyleSelected(paragStyle);
341     }
342     emit doneWithFocus();
343 }
344 
slotShowStyleManager(int index)345 void SimpleParagraphWidget::slotShowStyleManager(int index)
346 {
347     int styleId = m_sortedStylesModel->index(index, 0, QModelIndex()).internalId();
348     emit showStyleManager(styleId);
349     emit doneWithFocus();
350 }
351 
slotParagraphStyleApplied(const KoParagraphStyle * style)352 void SimpleParagraphWidget::slotParagraphStyleApplied(const KoParagraphStyle *style)
353 {
354     m_sortedStylesModel->styleApplied(style);
355 }
356 
changeListLevel(int level)357 void SimpleParagraphWidget::changeListLevel(int level)
358 {
359     emit doneWithFocus();
360     if (m_blockSignals) {
361         return;
362     }
363 
364     m_tool->setListLevel(level);
365 }
366