Perceptual Color

multispinbox.h
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4#ifndef MULTISPINBOX_H
5#define MULTISPINBOX_H
6
7#include "constpropagatinguniquepointer.h"
8#include "importexport.h"
9#include <qabstractspinbox.h>
10#include <qglobal.h>
11#include <qlineedit.h>
12#include <qlist.h>
13#include <qsize.h>
14class QAction;
15class QEvent;
16class QFocusEvent;
17class QWidget;
18
19#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
20// Including multispinboxsection.h is necessary on Qt6,
21// otherwise moc will fail. (IWYU does not detect this dependency.)
22#include "multispinboxsection.h" // IWYU pragma: keep
23#include <qtmetamacros.h>
24#else
25#include <qobjectdefs.h>
26#include <qstring.h>
27namespace PerceptualColor
28{
29class MultiSpinBoxSection;
30}
31class QObject;
32#endif
33
34namespace PerceptualColor
35{
36class MultiSpinBoxPrivate;
37
38/** @brief A spin box that can hold multiple sections (each with its own
39 * value) at the same time.
40 *
41 * This widget is similar to <tt>QDateTimeEdit</tt> which also provides
42 * multiple sections (day, month, year…) within a single spin box.
43 * However, <em>this</em> widget is flexible. You can define on your own
44 * the behaviour of each section.
45 *
46 * @image html MultiSpinBox.png "MultiSpinBox" width=200
47 *
48 * This widget works with floating point precision. You can set
49 * the number of decimal places for each section individually,
50 * via @ref MultiSpinBoxSection::decimals. (This
51 * value can also be <tt>0</tt> to get integer-like behaviour.)
52 *
53 * Example code to create a @ref MultiSpinBox for a HSV color value
54 * (Hue 0°–360°, Saturation 0–255, Value 0–255) comes here:
55 * @snippet testmultispinbox.cpp MultiSpinBox Basic example
56 *
57 * You can also have additional buttons within the spin box via the
58 * @ref addActionButton() function.
59 *
60 * @note This class inherits from <tt>QAbstractSpinBox</tt>, but some
61 * parts of the parent class’s API are not supported in <em>this</em>
62 * class. Do not use them:
63 * - <tt>selectAll()</tt> does not work as expected.
64 * - <tt>wrapping()</tt> is ignored. Instead, you can configures
65 * the <em>wrapping</em> individually for each section via
66 * @ref MultiSpinBoxSection::isWrapping.
67 * - <tt>specialValue()</tt> is not supported.
68 * <!-- Just as in QDateTimeEdit! -->
69 * - <tt>hasAcceptableInput()</tt> is not guaranteed to obey to a particular
70 * and stable semantic.
71 * - <tt>fixup()</tt>, <tt>interpretText()</tt>, <tt>validate()</tt> are
72 * not used nor do they anything.
73 * - <tt>keyboardTracking()</tt> is ignored. See the signal
74 * @ref sectionValuesChanged for details.
75 * - <tt>correctionMode()</tt> is ignored.
76 * - <tt>isGroupSeparatorShown</tt> is ignored.
77 *
78 * @internal
79 *
80 * Further remarks on inherited API of <tt>QAbstractSpinBox</tt>:
81 * - <tt>selectAll()</tt>:
82 * This slot has a default behaviour that relies on internal
83 * <tt>QAbstractSpinBox</tt> private implementations, which we cannot use
84 * because they are not part of the public API and can therefore change
85 * at any moment. As it isn’t virtual, we cannot reimplement it either.
86 * - <tt>fixup(), interpretText(), validate()</tt>:
87 * As long as we do not interact with the private API of
88 * <tt>QAbstractSpinBox</tt> (which we cannot do because
89 * there is no stability guaranteed), those functions are never
90 * called by <tt>QAbstractSpinBox</tt> nor does their default
91 * implementation do anything. (They seem rather like an implementation
92 * detail of Qt that was leaked to the public API.) We don’t use them
93 * either.
94 * - <tt>isGroupSeparatorShown</tt>:
95 * Implementing this seems complicate. In the base class, the setter
96 * is not virtual, and this property does not have a notify signal
97 * either. But we would have to react on a changes in this property:
98 * The content of the <tt>QLineEdit</tt> has to be updated. And the
99 * @ref minimumSizeHint and the @ref sizeHint will change, therefore
100 * <tt>updateGeometry</tt> has to be called. It seems better not to
101 * implement this. Alternatively, it could be implemented with a
102 * per-section approach via @ref MultiSpinBoxSection.
103 *
104 * @note The interface of this class could theoretically
105 * be similar to other Qt classes that offer similar concepts of various
106 * data within a list: QComboBox, QHeaderView, QDateTimeEdit, QList – of
107 * course with consistent naming. But usually you will not modify a single
108 * section configuration, but the hole set of configurations. Therefore we do
109 * the configuration by @ref MultiSpinBoxSection objects, similar
110 * to <tt>QNetworkConfiguration</tt> objects. Allowing changes to individual
111 * sections would require a lot of additional code to make sure that after
112 * such a change, the text cursor is set the the appropriate position and
113 * the text selection is also appropriate. This might be problematic,
114 * and gives also little benefit.
115 * However, a full-featured interface could look like that:
116 * @snippet testmultispinbox.cpp MultiSpinBox Full-featured interface
117 *
118 * @todo i18n bug: Use a MultiSpinBox with a locale that uses “,” as decimal
119 * separator, and with a value with some decimals. Try to type “0,1”. It will
120 * not be accepted. However, “0.1” will be accepted (and, when moving on,
121 * corrected to “0,1”). This is not the expected behaviour.
122 *
123 * @todo i18n bug: Enter HLC values like “<tt>80.</tt>” or “<tt>80,</tt>”
124 * or “<tt>80e</tt>”. Depending on the locale, it is possible to
125 * actually enter these characters, but apparently on validation it
126 * is not accepted and the value is replaced by <tt>0</tt>.
127 * MultiSpinBox should never become 0 because the validator
128 * allows something that the converter cannot convert!
129 *
130 * @todo In @ref ColorDialog go to the HLC @ref MultiSpinBox and place
131 * the text cursor behind the degree sign, than press the ⌫ (backspace) key.
132 * Actual behaviour: An error message is printed on the console: “The function
133 * updateCurrentValueFromText in file […]multispinbox.cpp near […] was called
134 * with the invalid “lineEditText“ argument […]. The call is ignored.
135 * This is a bug.” Expected behaviour: No error message is printed.
136 *
137 * @todo <tt>Ctrl-A</tt> support for this class. (Does this shortcut
138 * trigger <tt>selectAll()</tt>?) <tt>Ctrl-U</tt> support for this class?
139 * If so, do it via @ref clear(). And: If the user tries to delete
140 * everything, delete instead only the current value!? (By the way:
141 * How does QDateTimeEdit handle this?)
142 *
143 * @todo Bug: In @ref ColorDialog, choose a tab with one of the diagrams.
144 * Then, switch back the the “numeric“ tab. Expected behaviour: When
145 * a @ref MultiSpinBox gets back the focus, always the first section should
146 * be <em>highlighted/selected</em>, independent from what was selected or
147 * the cursor position before the @ref MultiSpinBox lost the focus.
148 * (While <tt>QSpinBox</tt> and <tt>QDoubleSpinBox</tt> don’t do that
149 * either, <tt>QDateTimeEdit</tt> indeed <em>does</em>, and that seems
150 * appropriate also for @ref MultiSpinBox.
151 *
152 * @todo Now, @ref setSectionValues does not select automatically the first
153 * section anymore. Is this in conformance with <tt>QDateTimeEdit</tt>?
154 * Test: Change the value in the middle. Push “Apply” button. Now, the
155 * curser is at the end of the spin box, but the active section is still
156 * the one in the middle (you can try this by using your mouse wheel on
157 * the widget).
158 *
159 * @todo Currently, if the widget has <em>not</em> the focus but the
160 * mouse moves over it and the scroll wheel is used, it’s the first
161 * section that will be changed, and not the one where the mouse is,
162 * as the user might expect. Even QDateTimeEdit does the same thing
163 * (thus they do not change the first section, but the last one that
164 * was editing before). But it would be great if we could do better here.
165 * But: Is this realistic and will the required code work on all
166 * platforms?
167 *
168 * @todo When adding Bengali digits (for example by copy and paste) to a
169 * @ref MultiSpinBox that was localized to en_US, than sometimes this is
170 * accepted (thought later “corrected” to 0), and sometimes not. This
171 * behaviour is inconsistent and wrong.
172 *
173 * @todo Apparently, the validator doesn’t restrict the input actually to the
174 * given range. For QDoubleSpinBox however, the line edit <em>is</em>
175 * restricted! Example: even if 100 is maximum, it is possible to write 444.
176 * Maybe our @ref ExtendedDoubleValidator should not rely on Qt’s validator,
177 * but on if QLocale is able to convert (result: valid) or not (result:
178 * invalid)?!.
179 *
180 * @todo If exposing this class as public API of this library, would
181 * it make sense to implement the complete public API of QAbstractSpinBox
182 * from which we inherit? Currently, some parts of the QAbstractSpinBox API
183 * are nor (properly) implemented by this class… */
185{
186 Q_OBJECT
187
188 /** @brief A list containing the values of all sections.
189 *
190 * @note It is not this property, but @ref sectionConfigurations
191 * which determines the actually available count of sections in this
192 * widget. If you want to change the number of available sections,
193 * call <em>first</em> @ref setSectionConfigurations and only
194 * <em>after</em> that adapt this property.
195 *
196 * @invariant This property contains always as many elements as
197 * @ref sectionConfigurations contains.
198 *
199 * @sa READ @ref sectionValues() const
200 * @sa WRITE @ref setSectionValues()
201 * @sa NOTIFY @ref sectionValuesChanged() */
202 Q_PROPERTY(QList<double> sectionValues READ sectionValues WRITE setSectionValues NOTIFY sectionValuesChanged USER true)
203
204public:
205 Q_INVOKABLE explicit MultiSpinBox(QWidget *parent = nullptr);
206 /** @brief Default destructor */
207 virtual ~MultiSpinBox() noexcept override;
208 void addActionButton(QAction *action, QLineEdit::ActionPosition position);
209 virtual void clear() override;
210 [[nodiscard]] virtual QSize minimumSizeHint() const override;
211 [[nodiscard]] Q_INVOKABLE QList<PerceptualColor::MultiSpinBoxSection> sectionConfigurations() const;
212 /** @brief Getter for property @ref sectionValues
213 * @returns the property @ref sectionValues */
214 [[nodiscard]] QList<double> sectionValues() const;
215 Q_INVOKABLE void setSectionConfigurations(const QList<PerceptualColor::MultiSpinBoxSection> &newSectionConfigurations);
216 [[nodiscard]] virtual QSize sizeHint() const override;
217 virtual void stepBy(int steps) override;
218
219public Q_SLOTS:
220 void setSectionValues(const QList<double> &newSectionValues);
221
222Q_SIGNALS:
223 /** @brief Notify signal for property @ref sectionValues.
224 *
225 * This signal is emitted whenever the value in one or more sections
226 * changes.
227 *
228 * @param newSectionValues the new @ref sectionValues
229 *
230 * Depending on your use case (for
231 * example if you want to use for <em>queued</em> signal-slot connections),
232 * you might consider calling <tt>qRegisterMetaType()</tt> for
233 * this type, once you have a QApplication object.
234 *
235 * @note The property <tt>keyboardTracking()</tt> of the base class
236 * is currently ignored. Keyboard tracking is <em>always</em> enabled:
237 * The spinbox emits this signal while the new value is being entered
238 * from the keyboard – one signal for each key stroke. */
239 void sectionValuesChanged(const QList<double> &newSectionValues);
240
241protected:
242 virtual void changeEvent(QEvent *event) override;
243 virtual bool event(QEvent *event) override;
244 virtual void focusInEvent(QFocusEvent *event) override;
245 virtual bool focusNextPrevChild(bool next) override;
246 virtual void focusOutEvent(QFocusEvent *event) override;
247 [[nodiscard]] virtual QAbstractSpinBox::StepEnabled stepEnabled() const override;
248
249private:
250 Q_DISABLE_COPY(MultiSpinBox)
251
252 /** @internal
253 *
254 * @brief Declare the private implementation as friend class.
255 *
256 * This allows the private class to access the protected members and
257 * functions of instances of <em>this</em> class. */
258 friend class MultiSpinBoxPrivate;
259 /** @brief Pointer to implementation (pimpl) */
260 ConstPropagatingUniquePointer<MultiSpinBoxPrivate> d_pointer;
261
262 /** @internal @brief Only for unit tests. */
263 friend class TestMultiSpinBox;
264};
265
266} // namespace PerceptualColor
267
268#endif // MULTISPINBOX_H
The configuration of a single section within a MultiSpinBox.
A spin box that can hold multiple sections (each with its own value) at the same time.
This file provides support for C++ symbol import and export.
#define PERCEPTUALCOLOR_IMPORTEXPORT
A macro that either exports dynamic library symbols or imports dynamic library symbols or does nothin...
The namespace of this library.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:18:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.