Fcitx
option.h
1 /*
2  * SPDX-FileCopyrightText: 2015-2015 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 #ifndef _FCITX_CONFIG_OPTION_H_
8 #define _FCITX_CONFIG_OPTION_H_
9 
10 #include <algorithm>
11 #include <limits>
12 #include <memory>
13 #include <optional>
14 #include <stdexcept>
15 #include <string>
16 #include <type_traits>
17 #include <utility>
18 #include <vector>
19 #include <fcitx-config/fcitxconfig_export.h>
20 #include <fcitx-config/marshallfunction.h>
21 #include <fcitx-config/option_details.h> // IWYU pragma: export
22 #include <fcitx-config/optiontypename.h>
23 #include <fcitx-config/rawconfig.h>
24 #include <fcitx-utils/flags.h>
25 #include <fcitx-utils/key.h>
26 
27 namespace fcitx {
28 
29 /// An option that launches external tool.
30 class FCITXCONFIG_EXPORT ExternalOption : public OptionBase {
31 public:
32  ExternalOption(Configuration *parent, std::string path,
33  std::string description, std::string uri);
34 
35  std::string typeString() const override;
36  void reset() override;
37  bool isDefault() const override;
38 
39  void marshall(RawConfig &config) const override;
40  bool unmarshall(const RawConfig &config, bool partial) override;
41  std::unique_ptr<Configuration> subConfigSkeleton() const override;
42 
43  bool equalTo(const OptionBase &other) const override;
44  void copyFrom(const OptionBase &other) override;
45 
46  bool skipDescription() const override;
47  bool skipSave() const override;
48  void dumpDescription(RawConfig &config) const override;
49 
50 private:
51  std::string externalUri_;
52 };
53 
54 /// An option that launches external tool.
55 class FCITXCONFIG_EXPORT SubConfigOption : public ExternalOption {
56 public:
57  using ExternalOption::ExternalOption;
58  void dumpDescription(RawConfig &config) const override;
59 };
60 
61 /// Default Constrain with no actual constrain.
62 template <typename T>
63 struct NoConstrain {
64  using Type = T;
65  bool check(const T & /*unused*/) const { return true; }
66  void dumpDescription(RawConfig & /*unused*/) const {}
67 };
68 
69 /// Default Annotation with no options.
70 struct NoAnnotation {
71  bool skipDescription() { return false; }
72  bool skipSave() { return false; }
73  void dumpDescription(RawConfig & /*unused*/) const {}
74 };
75 
76 /// Annotation to display a tooltip in configtool.
78  ToolTipAnnotation(std::string tooltip) : tooltip_(std::move(tooltip)) {}
79 
80  bool skipDescription() { return false; }
81  bool skipSave() { return false; }
82  void dumpDescription(RawConfig &config) const {
83  config.setValueByPath("Tooltip", tooltip_);
84  }
85 
86 private:
87  std::string tooltip_;
88 };
89 
90 /// For a list of sub config, the field that should be used for display.
92  ListDisplayOptionAnnotation(std::string option)
93  : option_(std::move(option)) {}
94 
95  bool skipDescription() { return false; }
96  bool skipSave() { return false; }
97  void dumpDescription(RawConfig &config) const {
98  config.setValueByPath("ListDisplayOption", option_);
99  }
100 
101 private:
102  std::string option_;
103 };
104 
105 /**
106  * Annotation to be used against String type to indicate this is a Font.
107  */
109  bool skipDescription() { return false; }
110  bool skipSave() { return false; }
111  void dumpDescription(RawConfig &config) const {
112  config.setValueByPath("Font", "True");
113  }
114 };
115 
116 /**
117  * Annotation to be used against String type, for those type of string
118  * that should shown as a combobox, but the value is run time based.
119  * User of this annotation should take a sub class of it and set
120  * Enum/n, and EnumI18n/n correspondingly.
121  */
123  bool skipDescription() { return false; }
124  bool skipSave() { return false; }
125  void dumpDescription(RawConfig &config) const {
126  config.setValueByPath("IsEnum", "True");
127  }
128 };
129 
130 /**
131  * Option that will not shown in UI.
132  *
133  * You may want to use HiddenOption instead.
134  *
135  * @see HiddenOption
136  */
138  bool skipDescription() { return true; }
139  bool skipSave() { return false; }
140  void dumpDescription(RawConfig & /*unused*/) const {}
141 };
142 
143 template <typename Annotation>
144 struct HideInDescriptionAnnotation : public Annotation {
145  using Annotation::Annotation;
146  bool skipDescription() { return true; }
147  using Annotation::dumpDescription;
148  using Annotation::skipSave;
149 };
150 
151 /**
152  * List Constrain that applies the constrain to all element.
153  */
154 template <typename SubConstrain>
156  ListConstrain(SubConstrain sub = SubConstrain()) : sub_(std::move(sub)) {}
157 
158  using ElementType = typename SubConstrain::Type;
159  using Type = std::vector<ElementType>;
160  bool check(const Type &value) {
161  return std::ranges::all_of(
162  value, [this](const ElementType &ele) { return sub_.check(ele); });
163  }
164 
165  void dumpDescription(RawConfig &config) const {
166  sub_.dumpDescription(*config.get("ListConstrain", true));
167  }
168 
169 private:
170  SubConstrain sub_;
171 };
172 
173 /**
174  * Optional Constrain that applies the constrain only when the value is not
175  * std::nullopt.
176  */
177 template <typename SubConstrain>
179  OptionalConstrain(SubConstrain sub = SubConstrain())
180  : sub_(std::move(sub)) {}
181 
182  using ElementType = typename SubConstrain::Type;
183  using Type = std::optional<ElementType>;
184  bool check(const Type &value) {
185  if (!value) {
186  return true;
187  }
188  return sub_.check(*value);
189  }
190  void dumpDescription(RawConfig &config) const {
191  sub_.dumpDescription(*config.get("OptionalConstrain", true));
192  }
193 
194 private:
195  SubConstrain sub_;
196 };
197 
198 /// Integer type constrain with a lower and a upper bound.
200 public:
201  using Type = int;
202  IntConstrain(int min = std::numeric_limits<int>::min(),
203  int max = std::numeric_limits<int>::max())
204  : min_(min), max_(max) {}
205  bool check(int value) const { return value >= min_ && value <= max_; }
206  void dumpDescription(RawConfig &config) const {
207  if (min_ != std::numeric_limits<int>::min()) {
208  marshallOption(config["IntMin"], min_);
209  }
210  if (max_ != std::numeric_limits<int>::max()) {
211  marshallOption(config["IntMax"], max_);
212  }
213  }
214 
215 private:
216  int min_;
217  int max_;
218 };
219 
220 /**
221  * String constrain that requires the value to be a valid regular expression.
222  * Use it directly on a String option, or wrapped in ListConstrain for a list
223  * of regular expressions. Frontends that understand the IsRegex marker can
224  * validate the value before saving, while other frontends simply treat it as a
225  * plain string and rely on this constrain to reject invalid values on save.
226  */
227 class FCITXCONFIG_EXPORT RegexConstrain {
228 public:
229  using Type = std::string;
230  bool check(const std::string &value) const;
231  void dumpDescription(RawConfig &config) const;
232 };
233 
234 /// Key option constrain flag.
235 enum class KeyConstrainFlag {
236  /// The key can be modifier only, like Control_L.
237  AllowModifierOnly = (1 << 0),
238  /// The key can be modifier less (Key that usually produce character).
239  AllowModifierLess = (1 << 1),
240 };
241 
243 
244 /// Key option constrain.
246 public:
247  using Type = Key;
248  KeyConstrain(KeyConstrainFlags flags) : flags_(flags) {}
249 
250  bool check(const Key &key) const {
251  if (!flags_.test(KeyConstrainFlag::AllowModifierLess) &&
252  key.states() == 0) {
253  return false;
254  }
255 
256  if (!flags_.test(KeyConstrainFlag::AllowModifierOnly) &&
257  key.isModifier()) {
258  return false;
259  }
260 
261  return true;
262  }
263 
264  void dumpDescription(RawConfig &config) const {
265  if (flags_.test(KeyConstrainFlag::AllowModifierLess)) {
266  config["AllowModifierLess"] = "True";
267  }
268  if (flags_.test(KeyConstrainFlag::AllowModifierOnly)) {
269  config["AllowModifierOnly"] = "True";
270  }
271  }
272 
273 private:
274  KeyConstrainFlags flags_;
275 };
276 
277 /// Default marshaller that write the config RawConfig.
278 template <typename T>
280  DefaultMarshaller() = default;
281 
282  void marshall(RawConfig &config, const T &value) const {
283  return marshallOption(config, value);
284  }
285  bool unmarshall(T &value, const RawConfig &config, bool partial) const {
286  return unmarshallOption(value, config, partial);
287  }
288 };
289 
290 /// A helper class provide writing ability to option value.
291 template <typename OptionType>
293 public:
294  using value_type = typename OptionType::value_type;
295 
296  MutableOption(OptionType *option = nullptr)
297  : option_(option), value_(option ? option->value() : value_type()) {}
298 
299  ~MutableOption() {
300  if (option_) {
301  option_->setValue(std::move(value_));
302  }
303  }
304 
305  MutableOption(MutableOption &&other) noexcept
306  : option_(other.option_), value_(std::move(other.value_)) {
307  other.option_ = nullptr;
308  }
309 
310  MutableOption &operator=(MutableOption &&other) noexcept {
311  option_ = other.option_;
312  value_ = std::move(other.value_);
313  other.option_ = nullptr;
314  }
315 
316  value_type &operator*() { return value_; }
317  value_type *operator->() { return &value_; }
318 
319 private:
320  OptionType *option_;
321  value_type value_;
322 };
323 
324 template <typename Constrain, typename Marshaller, typename Annotation,
325  typename T>
327  Configuration *parent;
328  std::string path;
329  std::string description;
330  T defaultValue;
331  Constrain constrain{};
332  Marshaller marshaller{};
333  Annotation annotation{};
334 };
335 
336 /**
337  * Represent a Configuration option.
338  *
339  */
340 template <typename T, typename Constrain = NoConstrain<T>,
341  typename Marshaller = DefaultMarshaller<T>,
342  typename Annotation = NoAnnotation>
343 class Option : public OptionBaseV3 {
344 public:
345  using value_type = T;
346  using constrain_type = Constrain;
347  using OptionParametersType =
349 
350  Option(Configuration *parent, std::string path, std::string description,
351  const T &defaultValue = T(), Constrain constrain = Constrain(),
352  Marshaller marshaller = Marshaller(),
353  Annotation annotation = Annotation())
354  : OptionBaseV3(parent, std::move(path), std::move(description)),
355  defaultValue_(defaultValue), value_(defaultValue),
356  marshaller_(std::move(marshaller)), constrain_(std::move(constrain)),
357  annotation_(std::move(annotation)) {
358  if (!constrain_.check(defaultValue_)) {
359  throw std::invalid_argument(
360  "defaultValue doesn't satisfy constrain");
361  }
362  }
363 
365  : Option(params.parent, std::move(params.path),
366  std::move(params.description), std::move(params.defaultValue),
367  std::move(params.constrain), std::move(params.marshaller),
368  std::move(params.annotation)) {}
369 
370  std::string typeString() const override { return OptionTypeName<T>::get(); }
371 
372  void dumpDescription(RawConfig &config) const override {
373  OptionBase::dumpDescription(config);
374  if constexpr (not std::is_base_of_v<Configuration, T>) {
375  marshaller_.marshall(config["DefaultValue"], defaultValue_);
376  }
377  constrain_.dumpDescription(config);
378  annotation_.dumpDescription(config);
379  using ::fcitx::dumpDescriptionHelper;
380  dumpDescriptionHelper(
381  config, static_cast<typename RemoveVector<T>::type *>(nullptr));
382  }
383 
384  std::unique_ptr<Configuration> subConfigSkeleton() const override {
385  if constexpr (std::is_base_of_v<Configuration, T>) {
386  auto skeleton = std::make_unique<T>(defaultValue_);
387  skeleton->syncDefaultValueToCurrent();
388  return skeleton;
389  }
390  if constexpr (std::is_base_of_v<Configuration,
391  typename RemoveVector<T>::type>) {
392  return std::make_unique<typename RemoveVector<T>::type>();
393  }
394 
395  return nullptr;
396  }
397 
398  bool isDefault() const override { return defaultValue_ == value_; }
399  void reset() override { value_ = defaultValue_; }
400 
401  const T &value() const { return value_; }
402 
403  const T &defaultValue() const { return defaultValue_; }
404 
405  const T &operator*() const { return value(); }
406  const T *operator->() const { return &value_; }
407 
408  template <typename U>
409  bool setValue(U &&value) {
410  if (!constrain_.check(value)) {
411  return false;
412  }
413  value_ = std::forward<U>(value);
414  return true;
415  }
416 
417  template <typename Dummy = int,
418  std::enable_if_t<!std::is_same<Constrain, NoConstrain<T>>::value,
419  Dummy> = 0>
420  MutableOption<Option> mutableValue() {
421  return {this};
422  }
423 
424  template <typename Dummy = int,
425  std::enable_if_t<std::is_same<Constrain, NoConstrain<T>>::value,
426  Dummy> = 0>
427  T *mutableValue() {
428  return &value_;
429  }
430 
431  void marshall(RawConfig &config) const override {
432  return marshaller_.marshall(config, value_);
433  }
434  bool unmarshall(const RawConfig &config, bool partial) override {
435  T tempValue{};
436  if (partial) {
437  tempValue = value_;
438  }
439  if (!marshaller_.unmarshall(tempValue, config, partial)) {
440  return false;
441  }
442  return setValue(tempValue);
443  }
444 
445  bool equalTo(const OptionBase &other) const override {
446  auto otherP = static_cast<const Option *>(&other);
447  return value_ == otherP->value_;
448  }
449 
450  void copyFrom(const OptionBase &other) override {
451  auto otherP = static_cast<const Option *>(&other);
452  value_ = otherP->value_;
453  }
454 
455  bool skipDescription() const override {
456  return annotation_.skipDescription();
457  }
458 
459  bool skipSave() const override { return annotation_.skipSave(); }
460 
461  void syncDefaultValueToCurrent() override {
462  defaultValue_ = value_;
463  if constexpr (std::is_base_of_v<Configuration, T>) {
464  value_.syncDefaultValueToCurrent();
465  defaultValue_.syncDefaultValueToCurrent();
466  }
467  }
468 
469  auto &annotation() const { return annotation_; }
470 
471 private:
472  T defaultValue_;
473  T value_;
474  Marshaller marshaller_;
475  Constrain constrain_;
476  mutable Annotation annotation_;
477 };
478 
479 /// Shorthand if you want a option type with only custom annotation.
480 template <typename T, typename Annotation>
481 using OptionWithAnnotation =
483 
484 /// Shorthand if you want a option type with only custom annotation.
485 template <typename Annotation>
488  Annotation>;
489 
490 /// Shorthand for KeyList option with constrain.
492 
493 /// Shorthand for create a key list constrain.
494 static inline ListConstrain<KeyConstrain>
495 KeyListConstrain(KeyConstrainFlags flags = KeyConstrainFlags()) {
496  return {KeyConstrain(flags)};
497 }
498 
499 /// Shorthand for option that will not show in UI.
500 template <typename T, typename Constrain = NoConstrain<T>,
501  typename Marshaller = DefaultMarshaller<T>,
502  typename Annotation = NoAnnotation>
503 using HiddenOption =
505 
506 template <bool hidden, typename T>
508 
509 template <typename T, typename Constrain, typename Marshaller,
510  typename Annotation>
512  Option<T, Constrain, Marshaller, Annotation>> {
514 };
515 
516 template <typename T, typename Constrain, typename Marshaller,
517  typename Annotation>
519  Option<T, Constrain, Marshaller, Annotation>> {
521 };
522 
523 template <>
525  using OptionType = SubConfigOption;
526 };
527 
528 template <>
530  class HiddenSubConfigOption : public SubConfigOption {
531  public:
532  using SubConfigOption::SubConfigOption;
533  bool skipDescription() const override { return true; }
534  };
535  using OptionType = HiddenSubConfigOption;
536 };
537 
538 template <bool hidden, typename T>
539 using ConditionalHidden =
541 
542 } // namespace fcitx
543 
544 #endif // _FCITX_CONFIG_OPTION_H_
Optional Constrain that applies the constrain only when the value is not std::nullopt.
Definition: option.h:178
A helper class provide writing ability to option value.
Definition: option.h:292
Describe a Key in fcitx.
Definition: key.h:42
Option that will not shown in UI.
Definition: option.h:137
Default Constrain with no actual constrain.
Definition: option.h:63
Annotation to be used against String type to indicate this is a Font.
Definition: option.h:108
List Constrain that applies the constrain to all element.
Definition: option.h:155
void setValueByPath(const std::string &path, std::string value)
Set a value by path, creating intermediate nodes if needed.
Definition: rawconfig.h:155
A raw configuration tree that stores key-value pairs in a hierarchical structure. ...
Definition: rawconfig.h:37
Definition: action.cpp:17
For a list of sub config, the field that should be used for display.
Definition: option.h:91
std::shared_ptr< RawConfig > get(const std::string &path, bool create=false)
Get a sub-item by path.
Definition: rawconfig.cpp:153
String constrain that requires the value to be a valid regular expression.
Definition: option.h:227
Key option constrain.
Definition: option.h:245
bool isModifier() const
Check if the key is a modifier press.
Definition: key.cpp:460
An option that launches external tool.
Definition: option.h:55
An option that launches external tool.
Definition: option.h:30
Annotation to be used against String type, for those type of string that should shown as a combobox...
Definition: option.h:122
Helper template class to make easier to use type safe enum flags.
Default marshaller that write the config RawConfig.
Definition: option.h:279
Class to represent a key.
Annotation to display a tooltip in configtool.
Definition: option.h:77
Represent a Configuration option.
Definition: option.h:343
Default Annotation with no options.
Definition: option.h:70
Integer type constrain with a lower and a upper bound.
Definition: option.h:199