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