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-2012 Pierre Stirnweiss <pstirnweiss@googlemail.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #include "SimpleCharacterWidget.h"
23 #include "TextTool.h"
24 #include "../commands/ChangeListCommand.h"
25 #include "StylesModel.h"
26 #include "DockerStylesComboModel.h"
27 #include "StylesDelegate.h"
28 #include <KoStyleThumbnailer.h>
29 
30 #include <QAction>
31 #include <kselectaction.h>
32 #include <KoTextBlockData.h>
33 #include <KoCharacterStyle.h>
34 #include <KoParagraphStyle.h>
35 #include <KoInlineTextObjectManager.h>
36 #include <KoTextDocumentLayout.h>
37 #include <KoZoomHandler.h>
38 #include <KoStyleManager.h>
39 
40 #include <QDebug>
41 
42 #include <QTextLayout>
43 #include <QComboBox>
44 
SimpleCharacterWidget(TextTool * tool,QWidget * parent)45 SimpleCharacterWidget::SimpleCharacterWidget(TextTool *tool, QWidget *parent)
46     : QWidget(parent),
47     m_styleManager(0),
48     m_blockSignals(false),
49     m_comboboxHasBidiItems(false),
50     m_tool(tool),
51     m_thumbnailer(new KoStyleThumbnailer()),
52     m_stylesModel(new StylesModel(0, StylesModel::CharacterStyle)),
53     m_sortedStylesModel(new DockerStylesComboModel()),
54     m_stylesDelegate(0)
55 {
56     widget.setupUi(this);
57     widget.bold->setDefaultAction(tool->action("format_bold"));
58     widget.italic->setDefaultAction(tool->action("format_italic"));
59     widget.strikeOut->setDefaultAction(tool->action("format_strike"));
60     widget.underline->setDefaultAction(tool->action("format_underline"));
61     widget.textColor->setDefaultAction(tool->action("format_textcolor"));
62     widget.backgroundColor->setDefaultAction(tool->action("format_backgroundcolor"));
63     widget.superscript->setDefaultAction(tool->action("format_super"));
64     widget.subscript->setDefaultAction(tool->action("format_sub"));
65     widget.moreOptions->setText("...");
66     widget.moreOptions->setToolTip(i18n("Change font format"));
67     connect(widget.moreOptions, SIGNAL(clicked(bool)), tool->action("format_font"), SLOT(trigger()));
68 
69     connect(widget.bold, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
70     connect(widget.italic, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
71     connect(widget.strikeOut, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
72     connect(widget.underline, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
73     connect(widget.textColor, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
74     connect(widget.backgroundColor, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
75     connect(widget.superscript, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
76     connect(widget.subscript, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus()));
77 
78     QWidgetAction *fontFamilyAction = qobject_cast<QWidgetAction *>(tool->action("format_fontfamily"));
79     QComboBox *family = fontFamilyAction ? qobject_cast<QComboBox*> (fontFamilyAction->requestWidget(this)) : 0;
80     if (family) { // kdelibs 4.1 didn't return anything here.
81         widget.fontsFrame->addWidget(family,0,0);
82         connect(family, SIGNAL(activated(int)), this, SIGNAL(doneWithFocus()));
83         connect(family, SIGNAL(activated(int)), this, SLOT(fontFamilyActivated(int)));
84     }
85     QWidgetAction *fontSizeAction = qobject_cast<QWidgetAction *>(tool->action("format_fontsize"));
86     QComboBox *size = fontSizeAction ? qobject_cast<QComboBox*> (fontSizeAction->requestWidget(this)) : 0;
87     if (size) { // kdelibs 4.1 didn't return anything here.
88         widget.fontsFrame->addWidget(size,0,1);
89         connect(size, SIGNAL(activated(int)), this, SIGNAL(doneWithFocus()));
90         connect(size, SIGNAL(activated(int)), this, SLOT(fontSizeActivated(int)));
91         QDoubleValidator* validator = new QDoubleValidator(2, 999, 1, size);
92         size->setValidator(validator);
93     }
94 
95     widget.fontsFrame->setColumnStretch(0,1);
96 
97     m_stylesModel->setStyleThumbnailer(m_thumbnailer);
98     widget.characterStyleCombo->setStylesModel(m_sortedStylesModel);
99     connect(widget.characterStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
100     connect(widget.characterStyleCombo, SIGNAL(newStyleRequested(QString)), this, SIGNAL(newStyleRequested(QString)));
101     connect(widget.characterStyleCombo, SIGNAL(newStyleRequested(QString)), this, SIGNAL(doneWithFocus()));
102     connect(widget.characterStyleCombo, SIGNAL(showStyleManager(int)), this, SLOT(slotShowStyleManager(int)));
103 
104     m_sortedStylesModel->setStylesModel(m_stylesModel);
105 }
106 
~SimpleCharacterWidget()107 SimpleCharacterWidget::~SimpleCharacterWidget()
108 {
109     //the model is set on the comboBox which takes ownership
110     delete m_thumbnailer;
111 }
112 
setStyleManager(KoStyleManager * sm)113 void SimpleCharacterWidget::setStyleManager(KoStyleManager *sm)
114 {
115     Q_ASSERT(sm);
116     if (!sm || m_styleManager == sm) {
117         return;
118     }
119     if (m_styleManager) {
120         disconnect(m_styleManager, SIGNAL(styleApplied(const KoCharacterStyle*)), this, SLOT(slotParagraphStyleApplied(const KoCharacterStyle*)));
121     }
122     m_styleManager = sm;
123     //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.
124     disconnect(widget.characterStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
125     m_stylesModel->setStyleManager(sm);
126     m_sortedStylesModel->setStyleManager(sm);
127     connect(widget.characterStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
128     connect(m_styleManager, SIGNAL(styleApplied(const KoCharacterStyle*)), this, SLOT(slotCharacterStyleApplied(const KoCharacterStyle*)));
129 }
130 
setInitialUsedStyles(QVector<int> list)131 void SimpleCharacterWidget::setInitialUsedStyles(QVector<int> list)
132 {
133     m_sortedStylesModel->setInitialUsedStyles(list);
134 }
135 
setCurrentFormat(const QTextCharFormat & format,const QTextCharFormat & refBlockCharFormat)136 void SimpleCharacterWidget::setCurrentFormat(const QTextCharFormat& format, const QTextCharFormat& refBlockCharFormat)
137 {
138     if (!m_styleManager || format == m_currentCharFormat) {
139         return;
140     }
141     m_currentCharFormat = format;
142 
143     KoCharacterStyle *style(m_styleManager->characterStyle(m_currentCharFormat.intProperty(KoCharacterStyle::StyleId)));
144     bool useParagraphStyle = false;
145     if (!style) {
146         style = static_cast<KoCharacterStyle*>(m_styleManager->paragraphStyle(m_currentCharFormat.intProperty(KoParagraphStyle::StyleId)));
147         useParagraphStyle = true;
148     }
149     if (style) {
150         bool unchanged = true;
151         QTextCharFormat comparisonFormat = refBlockCharFormat;
152         style->applyStyle(comparisonFormat);
153         //Here we are making quite a few assumptions:
154         //i. we can set the "ensured" properties on a blank charFormat. These corresponds to Qt default. We are not creating false positive (ie. different styles showing as identical).
155         //ii. a property whose toBool returns as false is identical to an unset property (this is done through the clearUnsetProperties method)
156         style->ensureMinimalProperties(comparisonFormat);
157         style->ensureMinimalProperties(m_currentCharFormat);
158         clearUnsetProperties(comparisonFormat);
159         clearUnsetProperties(m_currentCharFormat);
160         if (m_currentCharFormat.properties().count() != comparisonFormat.properties().count()) {
161             unchanged = false;
162         }
163         else {
164             foreach(int property, m_currentCharFormat.properties().keys()) {
165                 if (m_currentCharFormat.property(property) != comparisonFormat.property(property)) {
166                     unchanged = false;
167                 }
168             }
169         }
170         disconnect(widget.characterStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
171          //TODO, this is very brittle index 1 is because index 0 is the title. The proper solution to that would be for the "None" style to have a styleId which does not get applied on the text, but can be used in the ui
172         widget.characterStyleCombo->setCurrentIndex((useParagraphStyle) ? 1 : m_sortedStylesModel->indexOf(style).row());
173         widget.characterStyleCombo->setStyleIsOriginal(unchanged);
174         widget.characterStyleCombo->slotUpdatePreview();
175         connect(widget.characterStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
176     }
177 }
178 
clearUnsetProperties(QTextFormat & format)179 void SimpleCharacterWidget::clearUnsetProperties(QTextFormat &format)
180 {
181     foreach(int property, format.properties().keys()) {
182         if (!format.property(property).toBool()) {
183             format.clearProperty(property);
184         }
185     }
186 }
187 
fontFamilyActivated(int index)188 void SimpleCharacterWidget::fontFamilyActivated(int index) {
189     /**
190      * Hack:
191      *
192      * Selecting a font that is already selected in the combobox
193      * will not trigger the action, so we help it on the way by
194      * manually triggering it here if that happens.
195      */
196     if (index == m_lastFontFamilyIndex) {
197         KSelectAction *action = qobject_cast<KSelectAction*>(m_tool->action("format_fontfamily"));
198         if(action->currentAction())
199             action->currentAction()->trigger();
200     }
201     m_lastFontFamilyIndex = index;
202 }
203 
fontSizeActivated(int index)204 void SimpleCharacterWidget::fontSizeActivated(int index) {
205     /**
206      * Hack:
207      *
208      * Selecting a font size that is already selected in the
209      * combobox will not trigger the action, so we help it on
210      * the way by manually triggering it here if that happens.
211      */
212     if (index == m_lastFontSizeIndex) {
213         KSelectAction *action = qobject_cast<KSelectAction*>(m_tool->action("format_fontsize"));
214         action->currentAction()->trigger();
215     }
216     m_lastFontSizeIndex = index;
217 }
218 
setCurrentBlockFormat(const QTextBlockFormat & format)219 void SimpleCharacterWidget::setCurrentBlockFormat(const QTextBlockFormat &format)
220 {
221     if (format == m_currentBlockFormat)
222         return;
223     m_currentBlockFormat = format;
224 
225     m_stylesModel->setCurrentParagraphStyle(format.intProperty(KoParagraphStyle::StyleId));
226     disconnect(widget.characterStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
227     widget.characterStyleCombo->slotUpdatePreview();
228     connect(widget.characterStyleCombo, SIGNAL(selected(QModelIndex)), this, SLOT(styleSelected(QModelIndex)));
229 }
230 
styleSelected(int index)231 void SimpleCharacterWidget::styleSelected(int index)
232 {
233     KoCharacterStyle *charStyle = m_styleManager->characterStyle(m_sortedStylesModel->index(index, 0, QModelIndex()).internalId());
234 
235     //if the selected item correspond to a null characterStyle, send the null pointer. the tool should set the characterStyle as per paragraph
236     emit characterStyleSelected(charStyle);
237     emit doneWithFocus();
238 }
239 
styleSelected(const QModelIndex & index)240 void SimpleCharacterWidget::styleSelected(const QModelIndex &index)
241 {
242     if (!index.isValid()) {
243         emit doneWithFocus();
244         return;
245     }
246     KoCharacterStyle *charStyle = m_styleManager->characterStyle(index.internalId());
247 
248     //if the selected item correspond to a null characterStyle, send the null pointer. the tool should set the characterStyle as per paragraph
249     emit characterStyleSelected(charStyle);
250     emit doneWithFocus();
251 }
252 
slotShowStyleManager(int index)253 void SimpleCharacterWidget::slotShowStyleManager(int index)
254 {
255     int styleId = m_sortedStylesModel->index(index, 0, QModelIndex()).internalId();
256     emit showStyleManager(styleId);
257     emit doneWithFocus();
258 }
259 
slotCharacterStyleApplied(const KoCharacterStyle * style)260 void SimpleCharacterWidget::slotCharacterStyleApplied(const KoCharacterStyle *style)
261 {
262     m_sortedStylesModel->styleApplied(style);
263 }
264