1 
2 // Own
3 #include "kcmcss.h"
4 
5 // Qt
6 #include <QCheckBox>
7 #include <QDialogButtonBox>
8 #include <QUrl>
9 #include <QStandardPaths>
10 
11 // KDE
12 #include <kcolorbutton.h>
13 #include <kconfig.h>
14 #include <kconfiggroup.h>
15 #include <kurlrequester.h>
16 #include <kmimetypetrader.h>
17 #include <kparts/part.h>
18 #include <kparts/openurlarguments.h>
19 
20 // Local
21 #include "template.h"
22 
23 #include "ui_cssconfig.h"
24 
25 class CSSConfigWidget: public QWidget, public Ui::CSSConfigWidget
26 {
27 public:
CSSConfigWidget(QWidget * parent)28     CSSConfigWidget(QWidget *parent) : QWidget(parent)
29     {
30         setupUi(this);
31     }
32 };
33 
CSSConfig(QWidget * parent,const QVariantList &)34 CSSConfig::CSSConfig(QWidget *parent, const QVariantList &)
35     : QWidget(parent)
36     , configWidget(new CSSConfigWidget(this))
37     , customDialogBase(new QDialog(this))
38     , customDialog(new CSSCustomDialog(customDialogBase))
39 {
40     customDialogBase->setObjectName(QStringLiteral("customCSSDialog"));
41     customDialogBase->setModal(true);
42     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, customDialogBase);
43     buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
44     connect(buttonBox, &QDialogButtonBox::rejected, customDialogBase, &QDialog::reject);
45 
46     QVBoxLayout *vLayout = new QVBoxLayout(customDialogBase);
47     vLayout->addWidget(customDialog);
48     vLayout->addStretch(1);
49     vLayout->addWidget(buttonBox);
50 
51     setToolTip(i18n("<h1>Konqueror Stylesheets</h1> This module allows you to apply your own color"
52                       " and font settings to Konqueror by using"
53                       " stylesheets (CSS). You can either specify"
54                       " options or apply your own self-written"
55                       " stylesheet by pointing to its location.<br />"
56                       " Note that these settings will always have"
57                       " precedence before all other settings made"
58                       " by the site author. This can be useful to"
59                       " visually impaired people or for web pages"
60                       " that are unreadable due to bad design."));
61 
62     connect(configWidget->useDefault,    &QAbstractButton::clicked,    this, &CSSConfig::changed);
63     connect(configWidget->useAccess,     &QAbstractButton::clicked,    this, &CSSConfig::changed);
64     connect(configWidget->useUser,       &QAbstractButton::clicked,    this, &CSSConfig::changed);
65     connect(configWidget->urlRequester,  &KUrlRequester::textChanged,  this, &CSSConfig::changed);
66     connect(configWidget->customize,     &QAbstractButton::clicked,    this, &CSSConfig::slotCustomize);
67     connect(customDialog,                &CSSCustomDialog::changed,    this, &CSSConfig::changed);
68 
69     QVBoxLayout *vbox = new QVBoxLayout(this);
70     vbox->setContentsMargins(0, 0, 0, 0);
71     vbox->addWidget(configWidget);
72 }
73 
load()74 void CSSConfig::load()
75 {
76     QSignalBlocker block(customDialog);
77 
78     KConfig *c = new KConfig(QStringLiteral("kcmcssrc"), KConfig::NoGlobals);
79     KConfigGroup group = c->group("Stylesheet");
80     QString u = group.readEntry("Use", "default");
81     configWidget->useDefault->setChecked(u == QLatin1String("default"));
82     configWidget->useUser->setChecked(u == QLatin1String("user"));
83     configWidget->useAccess->setChecked(u == QLatin1String("access"));
84     configWidget->urlRequester->setUrl(QUrl::fromUserInput(group.readEntry("SheetName")));
85 
86     group = c->group("Font");
87     customDialog->basefontsize->setEditText(QString::number(group.readEntry("BaseSize", 12)));
88     customDialog->dontScale->setChecked(group.readEntry("DontScale", false));
89 
90     const QString fname(group.readEntry("Family", "Arial"));
91     for (int i = 0; i < customDialog->fontFamily->count(); ++i) {
92         if (customDialog->fontFamily->itemText(i) == fname) {
93             customDialog->fontFamily->setCurrentIndex(i);
94             break;
95         }
96     }
97 
98     customDialog->sameFamily->setChecked(group.readEntry("SameFamily", false));
99 
100     group = c->group("Colors");
101     QString m = group.readEntry("Mode", "black-on-white");
102     customDialog->blackOnWhite->setChecked(m == QLatin1String("black-on-white"));
103     customDialog->whiteOnBlack->setChecked(m == QLatin1String("white-on-black"));
104     customDialog->customColor->setChecked(m == QLatin1String("custom"));
105 
106     QColor white(Qt::white);
107     QColor black(Qt::black);
108     customDialog->backgroundColorButton->setColor(group.readEntry("BackColor", white));
109     customDialog->foregroundColorButton->setColor(group.readEntry("ForeColor", black));
110     customDialog->sameColor->setChecked(group.readEntry("SameColor", false));
111 
112     // Images
113     group = c->group("Images");
114     customDialog->hideImages->setChecked(group.readEntry("Hide", false));
115     customDialog->hideBackground->setChecked(group.readEntry("HideBackground", true));
116 
117     delete c;
118 }
119 
save()120 void CSSConfig::save()
121 {
122     // write to config file
123     KConfig *c = new KConfig(QStringLiteral("kcmcssrc"), KConfig::NoGlobals);
124     KConfigGroup group = c->group("Stylesheet");
125     if (configWidget->useDefault->isChecked()) {
126         group.writeEntry("Use", "default");
127     }
128     if (configWidget->useUser->isChecked()) {
129         group.writeEntry("Use", "user");
130     }
131     if (configWidget->useAccess->isChecked()) {
132         group.writeEntry("Use", "access");
133     }
134     group.writeEntry("SheetName", configWidget->urlRequester->url().url());
135 
136     group = c->group("Font");
137     group.writeEntry("BaseSize", customDialog->basefontsize->currentText());
138     group.writeEntry("DontScale", customDialog->dontScale->isChecked());
139     group.writeEntry("SameFamily", customDialog->sameFamily->isChecked());
140     group.writeEntry("Family", customDialog->fontFamily->currentText());
141 
142     group = c->group("Colors");
143     if (customDialog->blackOnWhite->isChecked()) {
144         group.writeEntry("Mode", "black-on-white");
145     }
146     if (customDialog->whiteOnBlack->isChecked()) {
147         group.writeEntry("Mode", "white-on-black");
148     }
149     if (customDialog->customColor->isChecked()) {
150         group.writeEntry("Mode", "custom");
151     }
152     group.writeEntry("BackColor", customDialog->backgroundColorButton->color());
153     group.writeEntry("ForeColor", customDialog->foregroundColorButton->color());
154     group.writeEntry("SameColor", customDialog->sameColor->isChecked());
155 
156     group = c->group("Images");
157     group.writeEntry("Hide", customDialog->hideImages->isChecked());
158     group.writeEntry("HideBackground", customDialog->hideBackground->isChecked());
159 
160     c->sync();
161     delete c;
162 
163     // generate CSS template
164     QString dest;
165     const QString templ(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kcmcss/template.css")));
166     if (!templ.isEmpty()) {
167         CSSTemplate css(templ);
168         dest = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kcmcss/";
169         QDir().mkpath(dest);
170         dest += QLatin1String("override.css");
171         css.expandToFile(dest, customDialog->cssDict());
172     }
173 
174     // make konqueror use the right stylesheet
175     c = new KConfig(QStringLiteral("konquerorrc"), KConfig::NoGlobals);
176     group = c->group("HTML Settings");
177     group.writeEntry("UserStyleSheetEnabled", !configWidget->useDefault->isChecked());
178 
179     if (configWidget->useUser->isChecked()) {
180         group.writeEntry("UserStyleSheet", configWidget->urlRequester->url().url());
181     }
182     if (configWidget->useAccess->isChecked()) {
183         group.writeEntry("UserStyleSheet", dest);
184     }
185 
186     c->sync();
187     delete c;
188 }
189 
defaults()190 void CSSConfig::defaults()
191 {
192     configWidget->useDefault->setChecked(true);
193     configWidget->useUser->setChecked(false);
194     configWidget->useAccess->setChecked(false);
195     configWidget->urlRequester->setUrl(QUrl());
196 
197     customDialog->basefontsize->setEditText(QString::number(12));
198     customDialog->dontScale->setChecked(false);
199 
200     const QString fname(QStringLiteral("Arial"));
201     for (int i = 0; i < customDialog->fontFamily->count(); ++i) {
202         if (customDialog->fontFamily->itemText(i) == fname) {
203             customDialog->fontFamily->setCurrentIndex(i);
204             break;
205         }
206     }
207 
208     customDialog->sameFamily->setChecked(false);
209     customDialog->blackOnWhite->setChecked(true);
210     customDialog->whiteOnBlack->setChecked(false);
211     customDialog->customColor->setChecked(false);
212     customDialog->backgroundColorButton->setColor(Qt::white);
213     customDialog->foregroundColorButton->setColor(Qt::black);
214     customDialog->sameColor->setChecked(false);
215 
216     customDialog->hideImages->setChecked(false);
217     customDialog->hideBackground->setChecked(true);
218 }
219 
px(int i,double scale)220 static QString px(int i, double scale)
221 {
222     QString px;
223     px.setNum(static_cast<int>(i * scale));
224     px += QLatin1String("px");
225     return px;
226 }
227 
cssDict()228 QMap<QString, QString> CSSCustomDialog::cssDict()
229 {
230     QMap<QString, QString> dict;
231 
232     // Fontsizes ------------------------------------------------------
233 
234     int bfs = basefontsize->currentText().toInt();
235     dict.insert(QStringLiteral("fontsize-base"), px(bfs, 1.0));
236 
237     if (dontScale->isChecked()) {
238         dict.insert(QStringLiteral("fontsize-small-1"), px(bfs, 1.0));
239         dict.insert(QStringLiteral("fontsize-large-1"), px(bfs, 1.0));
240         dict.insert(QStringLiteral("fontsize-large-2"), px(bfs, 1.0));
241         dict.insert(QStringLiteral("fontsize-large-3"), px(bfs, 1.0));
242         dict.insert(QStringLiteral("fontsize-large-4"), px(bfs, 1.0));
243         dict.insert(QStringLiteral("fontsize-large-5"), px(bfs, 1.0));
244     } else {
245         // TODO: use something harmonic here
246         dict.insert(QStringLiteral("fontsize-small-1"), px(bfs, 0.8));
247         dict.insert(QStringLiteral("fontsize-large-1"), px(bfs, 1.2));
248         dict.insert(QStringLiteral("fontsize-large-2"), px(bfs, 1.4));
249         dict.insert(QStringLiteral("fontsize-large-3"), px(bfs, 1.5));
250         dict.insert(QStringLiteral("fontsize-large-4"), px(bfs, 1.6));
251         dict.insert(QStringLiteral("fontsize-large-5"), px(bfs, 1.8));
252     }
253 
254     // Colors --------------------------------------------------------
255 
256     if (customColor->isChecked()) {
257         dict.insert(QStringLiteral("background-color"), backgroundColorButton->color().name());
258         dict.insert(QStringLiteral("foreground-color"), foregroundColorButton->color().name());
259     } else {
260         const char *blackOnWhiteFG[2] = {"White", "Black"};
261         bool bw = blackOnWhite->isChecked();
262         dict.insert(QStringLiteral("foreground-color"), QLatin1String(blackOnWhiteFG[bw]));
263         dict.insert(QStringLiteral("background-color"), QLatin1String(blackOnWhiteFG[!bw]));
264     }
265 
266     const char *notImportant[2] = {"", "! important"};
267     dict.insert(QStringLiteral("force-color"), QLatin1String(notImportant[sameColor->isChecked()]));
268 
269     // Fonts -------------------------------------------------------------
270     dict.insert(QStringLiteral("font-family"), fontFamily->currentText());
271     dict.insert(QStringLiteral("force-font"), QLatin1String(notImportant[sameFamily->isChecked()]));
272 
273     // Images
274 
275     const char *bgNoneImportant[2] = {"", "background-image : none ! important"};
276     dict.insert(QStringLiteral("display-images"), QLatin1String(bgNoneImportant[hideImages->isChecked()]));
277     dict.insert(QStringLiteral("display-background"), QLatin1String(bgNoneImportant[hideBackground->isChecked()]));
278 
279     return dict;
280 }
281 
slotCustomize()282 void CSSConfig::slotCustomize()
283 {
284     customDialog->slotPreview();
285     customDialogBase->exec();
286 }
287 
CSSCustomDialog(QWidget * parent)288 CSSCustomDialog::CSSCustomDialog(QWidget *parent)
289     : QWidget(parent)
290 {
291     setupUi(this);
292     connect(this,                   &CSSCustomDialog::changed,                  this, &CSSCustomDialog::slotPreview);
293 
294     connect(basefontsize,           QOverload<int>::of(&QComboBox::activated),  this, &CSSCustomDialog::changed);
295     connect(basefontsize,           &QComboBox::editTextChanged,                this, &CSSCustomDialog::changed);
296     connect(dontScale,              &QAbstractButton::clicked,                  this, &CSSCustomDialog::changed);
297     connect(blackOnWhite,           &QAbstractButton::clicked,                  this, &CSSCustomDialog::changed);
298     connect(whiteOnBlack,           &QAbstractButton::clicked,                  this, &CSSCustomDialog::changed);
299     connect(customColor,            &QAbstractButton::clicked,                  this, &CSSCustomDialog::changed);
300     connect(foregroundColorButton,  &KColorButton::changed,                     this, &CSSCustomDialog::changed);
301     connect(backgroundColorButton,  &KColorButton::changed,                     this, &CSSCustomDialog::changed);
302     connect(fontFamily,             QOverload<int>::of(&QComboBox::activated),  this, &CSSCustomDialog::changed);
303     connect(fontFamily,             &QComboBox::editTextChanged,                this, &CSSCustomDialog::changed);
304     connect(sameFamily,             &QAbstractButton::clicked,                  this, &CSSCustomDialog::changed);
305     connect(sameColor,              &QAbstractButton::clicked,                  this, &CSSCustomDialog::changed);
306     connect(hideImages,             &QAbstractButton::clicked,                  this, &CSSCustomDialog::changed);
307     connect(hideBackground,         &QAbstractButton::clicked,                  this, &CSSCustomDialog::changed);
308 
309     //QStringList fonts;
310     //KFontChooser::getFontList(fonts, 0);
311     //fontFamily->addItems(fonts);
312     part = KMimeTypeTrader::createPartInstanceFromQuery<KParts::ReadOnlyPart>(QStringLiteral("text/html"), parent, this);
313     QVBoxLayout *l = new QVBoxLayout(previewBox);
314     l->addWidget(part->widget());
315 }
316 
toDataUri(const QString & content,const QByteArray & contentType)317 static QUrl toDataUri(const QString &content, const QByteArray &contentType)
318 {
319     QByteArray data("data:");
320     data += contentType;
321     data += ";charset=utf-8;base64,";
322     data += content.toUtf8().toBase64();
323     return QUrl::fromEncoded(data);
324 }
325 
slotPreview()326 void CSSCustomDialog::slotPreview()
327 {
328     const QString templ(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kcmcss/template.css")));
329 
330     if (templ.isEmpty()) {
331         return;
332     }
333 
334     CSSTemplate css(templ);
335 
336     QString data(i18n("<html>\n<head>\n<style>\n<!--\n"
337                       "%1"
338                       "\n-->\n</style>\n</head>\n"
339                       "<body>\n"
340                       "<h1>Heading 1</h1>\n"
341                       "<h2>Heading 2</h2>\n"
342                       "<h3>Heading 3</h3>\n"
343                       "\n"
344                       "<p>User-defined stylesheets allow increased\n"
345                       "accessibility for visually handicapped\n"
346                       "people.</p>\n"
347                       "\n"
348                       "</body>\n"
349                       "</html>\n", css.expandToString(cssDict())));
350 
351     KParts::OpenUrlArguments args(part->arguments());
352     args.setReload(true); // Make sure the content is always freshly reloaded.
353     part->setArguments(args);
354     part->openUrl(toDataUri(data, "text/html"));
355 }
356 
357