Okular

js_field.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Pino Toscano <pino@kde.org>
3 SPDX-FileCopyrightText: 2008 Harri Porten <porten@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "js_field_p.h"
9
10#include <QDebug>
11#include <QHash>
12#include <QJSEngine>
13#include <QTimer>
14
15#include "../debug_p.h"
16#include "../document_p.h"
17#include "../form.h"
18#include "../page.h"
19#include "../page_p.h"
20#include "js_display_p.h"
21
22using namespace Okular;
23
24#define OKULAR_NAME QStringLiteral("okular_name")
25
27Q_GLOBAL_STATIC(FormCache, g_fieldCache)
29Q_GLOBAL_STATIC(ButtonCache, g_buttonCache)
30
31// Helper for modified fields
32static void updateField(FormField *field)
33{
34 Page *page = g_fieldCache->value(field);
35 if (page) {
36 Document *doc = PagePrivate::get(page)->m_doc->m_parent;
37 const int pageNumber = page->number();
38 QTimer::singleShot(0, doc, [doc, pageNumber] { doc->refreshPixmaps(pageNumber); });
39 Q_EMIT doc->refreshFormWidget(field);
40 } else {
41 qWarning() << "Could not get page of field" << field;
42 }
43}
44
45// Field.doc
46QJSValue JSField::doc() const
47{
48 return qjsEngine(this)->globalObject().property(QStringLiteral("Doc"));
49}
50
51// Field.name
52QString JSField::name() const
53{
54 return m_field->fullyQualifiedName();
55}
56
57// Field.readonly (getter)
58bool JSField::readonly() const
59{
60 return m_field->isReadOnly();
61}
62
63// Field.readonly (setter)
64void JSField::setReadonly(bool readonly)
65{
66 m_field->setReadOnly(readonly);
67
68 updateField(m_field);
69}
70
71static QString fieldGetTypeHelper(const FormField *field)
72{
73 switch (field->type()) {
75 const FormFieldButton *button = static_cast<const FormFieldButton *>(field);
76 switch (button->buttonType()) {
78 return QStringLiteral("button");
80 return QStringLiteral("checkbox");
82 return QStringLiteral("radiobutton");
83 }
84 break;
85 }
87 return QStringLiteral("text");
89 const FormFieldChoice *choice = static_cast<const FormFieldChoice *>(field);
90 switch (choice->choiceType()) {
92 return QStringLiteral("combobox");
94 return QStringLiteral("listbox");
95 }
96 break;
97 }
99 return QStringLiteral("signature");
100 }
101 return QString();
102}
103
104// Field.type
105QString JSField::type() const
106{
107 return fieldGetTypeHelper(m_field);
108}
109
110QJSValue JSField::fieldGetValueCore(bool asString) const
111{
113
114 switch (m_field->type()) {
116 const FormFieldButton *button = static_cast<const FormFieldButton *>(m_field);
117 if (button->state()) {
118 result = QStringLiteral("Yes");
119 } else {
120 result = QStringLiteral("Off");
121 }
122 break;
123 }
124 case FormField::FormText: {
125 const FormFieldText *text = static_cast<const FormFieldText *>(m_field);
126 const QLocale locale(QStringLiteral("en_US"));
127 QString numericalText = text->text();
128 numericalText.replace(QStringLiteral(","), QStringLiteral(".")); // As we do not need to account for thousand separator, commas are definitely decimal separators if they appear
129 bool ok;
130 const double textAsNumber = locale.toDouble(numericalText, &ok);
131 if (ok && !asString) {
132 result = textAsNumber;
133 } else {
134 result = text->text();
135 }
136 break;
137 }
139 const FormFieldChoice *choice = static_cast<const FormFieldChoice *>(m_field);
140 const QList<int> currentChoices = choice->currentChoices();
141 if (choice->choiceType() == FormFieldChoice::ComboBox) {
142 if (currentChoices.count() == 0 && choice->isEditable()) {
143 result = choice->editChoice();
144 }
145 } else if (choice->choiceType() == FormFieldChoice::ListBox) {
146 // value returns array for a listbox with multiple selections
147 if (currentChoices.count() > 1) {
148 result = qjsEngine(this)->newArray(currentChoices.count());
149 int arrayIndex = 0;
150 for (const int &selectionIndex : currentChoices) {
151 result.setProperty(arrayIndex++, choice->exportValueForChoice(choice->choices().at(selectionIndex)));
152 }
153 }
154 }
155 // for both combobox and listbox, return the selection if exactly one item is selected
156 if (currentChoices.count() == 1) {
157 result = choice->exportValueForChoice(choice->choices().at(currentChoices[0]));
158 }
159 break;
160 }
162 break;
163 }
164 }
165
166 qCDebug(OkularCoreDebug) << "fieldGetValueCore:"
167 << " Field: " << m_field->fullyQualifiedName() << " Type: " << fieldGetTypeHelper(m_field) << " Value: " << result.toString() << (result.isString() ? "(as string)" : "");
168 return result;
169}
170// Field.value (getter)
171QJSValue JSField::value() const
172{
173 return fieldGetValueCore(/*asString*/ false);
174}
175
176// Field.value (setter)
177void JSField::setValue(const QJSValue &value)
178{
179 qCDebug(OkularCoreDebug) << "fieldSetValue: Field: " << m_field->fullyQualifiedName() << " Type: " << fieldGetTypeHelper(m_field) << " Value: " << value.toString();
180 switch (m_field->type()) {
182 FormFieldButton *button = static_cast<FormFieldButton *>(m_field);
183 const QString text = value.toString();
184 if (text == QStringLiteral("Yes")) {
185 button->setState(true);
186 updateField(m_field);
187 } else if (text == QStringLiteral("Off")) {
188 button->setState(false);
189 updateField(m_field);
190 }
191 break;
192 }
193 case FormField::FormText: {
194 FormFieldText *textField = static_cast<FormFieldText *>(m_field);
195 Page *page = g_fieldCache->value(m_field);
196 if (page) {
197 Document *doc = PagePrivate::get(page)->m_doc->m_parent;
198 const QString text = value.toString();
199 if (text != textField->text()) {
200 textField->setText(text);
201 }
202 doc->processKVCFActions(textField);
203 } else {
204 qWarning() << "Could not get page of field" << m_field;
205 }
206 break;
207 }
209 FormFieldChoice *choice = static_cast<FormFieldChoice *>(m_field);
210 if (choice->choiceType() == FormFieldChoice::ComboBox) {
211 const QString text = value.toString();
212 Page *page = g_fieldCache->value(m_field);
213 if (page) {
214 Document *doc = PagePrivate::get(page)->m_doc->m_parent;
215 const int idx = choice->choices().indexOf(text);
216 if (idx == -1) {
217 if (choice->isEditable()) {
218 choice->setEditChoice(text);
219 doc->processKVCFActions(choice);
220 } else {
221 qWarning() << "Set not possible, invalid or unknown";
222 }
223 } else {
224 choice->setCurrentChoices({idx});
225 doc->processKVCFActions(choice);
226 }
227 }
228 }
229 break;
230 }
232 break;
233 }
234 }
235}
236
237// Field.valueAsString (getter)
238QJSValue JSField::valueAsString() const
239{
240 return fieldGetValueCore(/*asString*/ true);
241}
242
243// Field.hidden (getter)
244bool JSField::hidden() const
245{
246 return !m_field->isVisible();
247}
248
249// Field.hidden (setter)
250void JSField::setHidden(bool hidden)
251{
252 m_field->setVisible(!hidden);
253
254 updateField(m_field);
255}
256
257// Field.display (getter)
258int JSField::display() const
259{
260 bool visible = m_field->isVisible();
261 if (visible) {
262 return m_field->isPrintable() ? FormDisplay::FormVisible : FormDisplay::FormNoPrint;
263 }
264 return m_field->isPrintable() ? FormDisplay::FormNoView : FormDisplay::FormHidden;
265}
266
267// Field.display (setter)
268void JSField::setDisplay(int display)
269{
270 switch (display) {
271 case FormDisplay::FormVisible:
272 m_field->setVisible(true);
273 m_field->setPrintable(true);
274 break;
275 case FormDisplay::FormHidden:
276 m_field->setVisible(false);
277 m_field->setPrintable(false);
278 break;
279 case FormDisplay::FormNoPrint:
280 m_field->setVisible(true);
281 m_field->setPrintable(false);
282 break;
283 case FormDisplay::FormNoView:
284 m_field->setVisible(false);
285 m_field->setPrintable(true);
286 break;
287 }
288 updateField(m_field);
289}
290
291QJSValue JSField::numItems() const
292{
294 if (m_field->type() == FormField::FormChoice) {
295 const FormFieldChoice *choice = static_cast<const FormFieldChoice *>(m_field);
296 result = static_cast<uint>(choice->choices().size());
297 }
298 return result;
299}
300
301QJSValue JSField::currentValueIndices() const
302{
304 if (m_field->type() == FormField::FormChoice) {
305 const FormFieldChoice *choice = static_cast<const FormFieldChoice *>(m_field);
306 const QList<int> currentChoices = choice->currentChoices();
307 if (choice->choiceType() == FormFieldChoice::ComboBox) {
308 if (currentChoices.count() == 0 && choice->isEditable()) {
309 result = -1;
310 }
311 } else if (choice->choiceType() == FormFieldChoice::ListBox && currentChoices.count() > 1) {
312 result = qjsEngine(this)->newArray(currentChoices.size());
313 int arrayIndex = 0;
314 for (const int &selectionIndex : currentChoices) {
315 result.setProperty(arrayIndex++, selectionIndex);
316 }
317 }
318 if (currentChoices.count() == 1) {
319 result = currentChoices[0];
320 }
321 }
322 return result;
323}
324
325void JSField::setCurrentValueIndices(const QJSValue &value)
326{
327 if (m_field->type() == FormField::FormChoice) {
328 FormFieldChoice *choice = static_cast<FormFieldChoice *>(m_field);
329 QList<int> tempChoiceList;
330 if (value.isArray()) {
331 for (quint32 i = 0; i < value.property(QStringLiteral("length")).toUInt(); i++) {
332 tempChoiceList << value.property(i).toInt();
333 }
334 } else if (value.isNumber()) {
335 tempChoiceList << value.toInt();
336 }
337 const QList<int> choiceList = tempChoiceList;
338 choice->setCurrentChoices(choiceList);
339 updateField(choice);
340 }
341}
342
343// Instead of getting the Icon, we pick the field.
344QJSValue JSField::buttonGetIcon([[maybe_unused]] int nFace) const
345{
346 QJSValue fieldObject = qjsEngine(this)->newObject();
347 fieldObject.setProperty(OKULAR_NAME, m_field->fullyQualifiedName());
348 g_buttonCache->insert(m_field->fullyQualifiedName(), m_field);
349
350 return fieldObject;
351}
352
353/*
354 * Now we send to the button what Icon should be drawn on it
355 */
356void JSField::buttonSetIcon(const QJSValue &oIcon, [[maybe_unused]] int nFace)
357{
358 const QString fieldName = oIcon.property(OKULAR_NAME).toString();
359
360 if (m_field->type() == Okular::FormField::FormButton) {
361 FormFieldButton *button = static_cast<FormFieldButton *>(m_field);
362 const auto formField = g_buttonCache->value(fieldName);
363 if (formField) {
364 button->setIcon(formField);
365 }
366 }
367
368 updateField(m_field);
369}
370
371JSField::JSField(FormField *field, QObject *parent)
372 : QObject(parent)
373 , m_field(field)
374{
375}
376
377JSField::~JSField() = default;
378
379QJSValue JSField::wrapField(QJSEngine *engine, FormField *field, Page *page)
380{
381 // ### cache unique wrapper
382 QJSValue f = engine->newQObject(new JSField(field));
383 f.setProperty(QStringLiteral("page"), page->number());
384 g_fieldCache->insert(field, page);
385 return f;
386}
387
388void JSField::clearCachedFields()
389{
390 if (g_fieldCache.exists()) {
391 g_fieldCache->clear();
392 }
393
394 if (g_buttonCache.exists()) {
395 g_buttonCache->clear();
396 }
397}
398
399QJSValue JSField::getItemAt(int nIdx, bool bExportValue)
400{
402 if (m_field->type() == FormField::FormChoice) {
403 const FormFieldChoice *choice = static_cast<const FormFieldChoice *>(m_field);
404 if (bExportValue) {
405 if (nIdx < 0 || nIdx >= choice->choices().size()) {
406 result = choice->exportValueForChoice(choice->choices().at(choice->choices().size() - 1));
407 } else {
408 result = choice->exportValueForChoice(choice->choices().at(nIdx));
409 }
410 } else {
411 if (nIdx < 0 || nIdx >= choice->choices().size()) {
412 result = choice->choices().at(choice->choices().size() - 1);
413 } else {
414 result = choice->choices().at(nIdx);
415 }
416 }
417 }
418 return result;
419}
Interface of a button form field.
Definition form.h:247
@ Push
A simple push button.
Definition form.h:253
@ CheckBox
A check box.
Definition form.h:254
@ Radio
A radio button.
Definition form.h:255
virtual void setState(bool state)
Sets the state of the button to the new state .
Definition form.cpp:201
virtual void setIcon(Okular::FormField *field)
Sets the icon of the Button to the Icon of the field parameter.
Definition form.cpp:205
virtual ButtonType buttonType() const =0
The particular type of the button field.
virtual bool state() const =0
The state of the button.
Interface of a choice form field.
Definition form.h:421
virtual void setCurrentChoices(const QList< int > &choices)
Sets the selected choices to choices .
Definition form.cpp:346
QString exportValueForChoice(const QString &choice) const
Returns the export value for a given choice.
Definition form.cpp:375
@ ComboBox
A combo box choice field.
Definition form.h:427
@ ListBox
A list box choice field.
Definition form.h:428
virtual void setEditChoice(const QString &text)
Sets the text entered into an editable combo box choice field.
Definition form.cpp:355
virtual QString editChoice() const
The text entered into an editable combo box choice field.
Definition form.cpp:350
virtual QList< int > currentChoices() const =0
The currently selected choices.
virtual bool isEditable() const
Whether this ComboBox is editable, ie the user can type in a custom value.
Definition form.cpp:336
virtual ChoiceType choiceType() const =0
The particular type of the choice field.
virtual QStringList choices() const =0
The possible choices of the choice field.
Interface of a text form field.
Definition form.h:310
virtual void setText(const QString &text)
Sets the new text in the text field.
Definition form.cpp:241
virtual QString text() const =0
The text of text field.
The base interface of a form field.
Definition form.h:40
@ FormText
A field of variable text. See FormFieldText::TextType.
Definition form.h:52
@ FormChoice
A choice field. See FormFieldChoice::ChoiceType.
Definition form.h:53
@ FormButton
A "button". See FormFieldButton::ButtonType.
Definition form.h:51
@ FormSignature
A signature.
Definition form.h:54
FieldType type() const
The type of the field.
Definition form.cpp:47
virtual QVariant value() const
Returns the value associated wit the form field.
Definition form.cpp:88
global.h
Definition action.h:17
QJSValue newQObject(QObject *object)
bool isArray() const const
bool isNumber() const const
QJSValue property(const QString &name) const const
void setProperty(const QString &name, const QJSValue &value)
qint32 toInt() const const
QString toString() const const
const_reference at(qsizetype i) const const
qsizetype count() const const
qsizetype size() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:58:33 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.