Fcitx
candidatelist.cpp
1 /*
2  * SPDX-FileCopyrightText: 2017-2017 CSSlayer <wengxt@gmail.com>
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  */
7 
8 #include "candidatelist.h"
9 #include <algorithm>
10 #include <cstddef>
11 #include <functional>
12 #include <iterator>
13 #include <memory>
14 #include <optional>
15 #include <stdexcept>
16 #include <string>
17 #include <unordered_set>
18 #include <utility>
19 #include <vector>
20 #include <fcitx-utils/macros.h>
21 #include <fcitx-utils/utf8.h>
22 #include <ranges>
23 #include "fcitx-utils/key.h"
24 #include "fcitx-utils/keysym.h"
25 #include "fcitx-utils/misc.h"
26 #include "text.h"
27 
28 namespace fcitx {
29 
30 namespace {
31 
32 constexpr size_t regularLabelSize = 10;
33 
34 template <typename Container, typename Transformer>
35 void fillLabels(std::vector<Text> &labels, const Container &container,
36  const Transformer &trans) {
37  labels.clear();
38  labels.reserve(std::max(std::size(container), regularLabelSize));
39  for (const auto &item : container) {
40  labels.emplace_back(trans(item));
41  }
42  while (labels.size() < regularLabelSize) {
43  labels.emplace_back();
44  }
45 }
46 
47 template <typename CandidateListType, typename InterfaceType>
48 class CandidateListInterfaceAdapter : public QPtrHolder<CandidateListType>,
49  public InterfaceType {
50 public:
51  CandidateListInterfaceAdapter(CandidateListType *q)
52  : QPtrHolder<CandidateListType>(q) {}
53 };
54 
55 #define FCITX_COMMA_IF_2 ,
56 #define FCITX_COMMA_IF_1
57 #define FCITX_COMMA_IF(X) FCITX_EXPAND(FCITX_CONCATENATE(FCITX_COMMA_IF_, X))
58 
59 #define FCITX_FORWARD_METHOD_ARG(N, ARG) ARG arg##N FCITX_COMMA_IF(N)
60 
61 #define FCITX_FORWARD_METHOD_ARG_NAME(N, ARG) arg##N FCITX_COMMA_IF(N)
62 
63 #define FCITX_FORWARD_METHOD(RETURN_TYPE, METHOD_NAME, ARGS, ...) \
64  RETURN_TYPE METHOD_NAME(FCITX_FOR_EACH_IDX(FCITX_FORWARD_METHOD_ARG, \
65  FCITX_REMOVE_PARENS(ARGS))) \
66  __VA_ARGS__ override { \
67  FCITX_Q(); \
68  return q->METHOD_NAME(FCITX_FOR_EACH_IDX( \
69  FCITX_FORWARD_METHOD_ARG_NAME, FCITX_REMOVE_PARENS(ARGS))); \
70  }
71 
72 class BulkCursorAdaptorForCommonCandidateList
73  : public CandidateListInterfaceAdapter<CommonCandidateList,
74  BulkCursorCandidateList> {
75 public:
76  using CandidateListInterfaceAdapter::CandidateListInterfaceAdapter;
77 
78  FCITX_FORWARD_METHOD(void, setGlobalCursorIndex, (int));
79  FCITX_FORWARD_METHOD(int, globalCursorIndex, (), const);
80 };
81 
82 class CursorModifiableAdaptorForCommonCandidateList
83  : public CandidateListInterfaceAdapter<CommonCandidateList,
84  CursorModifiableCandidateList> {
85 public:
86  using CandidateListInterfaceAdapter::CandidateListInterfaceAdapter;
87 
88  FCITX_FORWARD_METHOD(void, setCursorIndex, (int));
89 };
90 
91 } // namespace
92 
93 ActionableCandidateList::~ActionableCandidateList() = default;
94 
95 TabbedCandidateList::~TabbedCandidateList() = default;
96 
98 public:
99  BulkCandidateList *bulk_ = nullptr;
100  ModifiableCandidateList *modifiable_ = nullptr;
101  PageableCandidateList *pageable_ = nullptr;
102  CursorMovableCandidateList *cursorMovable_ = nullptr;
103  BulkCursorCandidateList *bulkCursor_ = nullptr;
104  CursorModifiableCandidateList *cursorModifiable_ = nullptr;
105  ActionableCandidateList *actionable_ = nullptr;
106  TabbedCandidateList *tabbed_ = nullptr;
107 };
108 
109 CandidateList::CandidateList()
110  : d_ptr(std::make_unique<CandidateListPrivate>()) {}
111 
112 CandidateList::~CandidateList() {}
113 
114 bool CandidateList::empty() const { return size() == 0; }
115 
116 BulkCandidateList *CandidateList::toBulk() const {
117  FCITX_D();
118  return d->bulk_;
119 }
120 
121 ModifiableCandidateList *CandidateList::toModifiable() const {
122  FCITX_D();
123  return d->modifiable_;
124 }
125 
126 PageableCandidateList *CandidateList::toPageable() const {
127  FCITX_D();
128  return d->pageable_;
129 }
130 
131 CursorMovableCandidateList *CandidateList::toCursorMovable() const {
132  FCITX_D();
133  return d->cursorMovable_;
134 }
135 
136 CursorModifiableCandidateList *CandidateList::toCursorModifiable() const {
137  FCITX_D();
138  return d->cursorModifiable_;
139 }
140 
141 BulkCursorCandidateList *CandidateList::toBulkCursor() const {
142  FCITX_D();
143  return d->bulkCursor_;
144 }
145 
146 ActionableCandidateList *CandidateList::toActionable() const {
147  FCITX_D();
148  return d->actionable_;
149 }
150 
152  FCITX_D();
153  return d->tabbed_;
154 }
155 
156 void CandidateList::setBulk(BulkCandidateList *list) {
157  FCITX_D();
158  d->bulk_ = list;
159 }
160 
161 void CandidateList::setModifiable(ModifiableCandidateList *list) {
162  FCITX_D();
163  d->modifiable_ = list;
164 }
165 
166 void CandidateList::setPageable(PageableCandidateList *list) {
167  FCITX_D();
168  d->pageable_ = list;
169 }
170 
171 void CandidateList::setCursorMovable(CursorMovableCandidateList *list) {
172  FCITX_D();
173  d->cursorMovable_ = list;
174 }
175 
176 void CandidateList::setCursorModifiable(CursorModifiableCandidateList *list) {
177  FCITX_D();
178  d->cursorModifiable_ = list;
179 }
180 
181 void CandidateList::setBulkCursor(BulkCursorCandidateList *list) {
182  FCITX_D();
183  d->bulkCursor_ = list;
184 }
185 
186 void CandidateList::setActionable(ActionableCandidateList *list) {
187  FCITX_D();
188  d->actionable_ = list;
189 }
190 
192  FCITX_D();
193  d->tabbed_ = list;
194 }
195 
197 public:
198  CandidateWordPrivate(Text &&text) : text_(std::move(text)) {}
199  Text text_;
200  bool isPlaceHolder_ = false;
201  Text customLabel_;
202  bool hasCustomLabel_ = false;
203  Text comment_;
204  bool spaceBetweenComment_ = true;
205 };
206 
207 CandidateWord::CandidateWord(Text text)
208  : d_ptr(std::make_unique<CandidateWordPrivate>(std::move(text))) {}
209 
210 CandidateWord::~CandidateWord() {}
211 
212 const Text &CandidateWord::text() const {
213  FCITX_D();
214  return d->text_;
215 }
216 
217 void CandidateWord::setText(Text text) {
218  FCITX_D();
219  d->text_ = std::move(text);
220 }
221 
222 const Text &CandidateWord::comment() const {
223  FCITX_D();
224  return d->comment_;
225 }
226 
227 void CandidateWord::setComment(Text comment) {
228  FCITX_D();
229  d->comment_ = std::move(comment);
230 }
231 
232 Text CandidateWord::textWithComment(std::string separator) const {
233  FCITX_D();
234  auto text = d->text_;
235  if (!d->comment_.empty()) {
236  text.append(std::move(separator));
237  text.append(d->comment_);
238  }
239  return text;
240 }
241 
243  FCITX_D();
244  return d->isPlaceHolder_;
245 }
246 
247 bool CandidateWord::hasCustomLabel() const {
248  FCITX_D();
249  return d->hasCustomLabel_;
250 }
251 
252 const Text &CandidateWord::customLabel() const {
253  FCITX_D();
254  return d->customLabel_;
255 }
256 
257 void CandidateWord::setPlaceHolder(bool placeHolder) {
258  FCITX_D();
259  d->isPlaceHolder_ = placeHolder;
260 }
261 
262 void CandidateWord::resetCustomLabel() {
263  FCITX_D();
264  d->customLabel_ = Text();
265  d->hasCustomLabel_ = false;
266 }
267 
268 void CandidateWord::setCustomLabel(Text text) {
269  FCITX_D();
270  d->customLabel_ = std::move(text);
271  d->hasCustomLabel_ = true;
272 }
273 
275  FCITX_D();
276  return d->spaceBetweenComment_;
277 }
278 
279 void CandidateWord::setSpaceBetweenComment(bool space) {
280  FCITX_D();
281  d->spaceBetweenComment_ = space;
282 }
283 
285 public:
286  Text emptyText_;
287  int cursorIndex_ = -1;
288  CandidateLayoutHint layoutHint_ = CandidateLayoutHint::Vertical;
289  std::vector<std::shared_ptr<CandidateWord>> candidateWords_;
290 
291  void checkIndex(int idx) const {
292  if (idx < 0 || static_cast<size_t>(idx) >= candidateWords_.size()) {
293  throw std::invalid_argument(
294  "DisplayOnlyCandidateList: invalid index");
295  }
296  }
297 };
298 
299 DisplayOnlyCandidateList::DisplayOnlyCandidateList()
300  : d_ptr(std::make_unique<DisplayOnlyCandidateListPrivate>()) {}
301 
302 DisplayOnlyCandidateList::~DisplayOnlyCandidateList() = default;
303 
304 void DisplayOnlyCandidateList::setContent(
305  const std::vector<std::string> &content) {
306  std::vector<Text> text_content;
307  for (const auto &str : content) {
308  text_content.emplace_back();
309  text_content.back().append(str);
310  }
311  setContent(std::move(text_content));
312 }
313 
314 void DisplayOnlyCandidateList::setContent(std::vector<Text> content) {
315  FCITX_D();
316  for (auto &text : content) {
317  d->candidateWords_.emplace_back(
318  std::make_shared<DisplayOnlyCandidateWord>(std::move(text)));
319  }
320 }
321 
322 void DisplayOnlyCandidateList::setLayoutHint(CandidateLayoutHint hint) {
323  FCITX_D();
324  d->layoutHint_ = hint;
325 }
326 
327 void DisplayOnlyCandidateList::setCursorIndex(int index) {
328  FCITX_D();
329  if (index < 0) {
330  d->cursorIndex_ = -1;
331  } else {
332  d->checkIndex(index);
333  d->cursorIndex_ = index;
334  }
335 }
336 
337 const Text &DisplayOnlyCandidateList::label(int idx) const {
338  FCITX_D();
339  d->checkIndex(idx);
340  return d->emptyText_;
341 }
342 
343 const CandidateWord &DisplayOnlyCandidateList::candidate(int idx) const {
344  FCITX_D();
345  d->checkIndex(idx);
346  return *d->candidateWords_[idx];
347 }
348 
349 int DisplayOnlyCandidateList::cursorIndex() const {
350  FCITX_D();
351  return d->cursorIndex_;
352 }
353 
354 int DisplayOnlyCandidateList::size() const {
355  FCITX_D();
356  return d->candidateWords_.size();
357 }
358 
359 CandidateLayoutHint DisplayOnlyCandidateList::layoutHint() const {
360  FCITX_D();
361  return d->layoutHint_;
362 }
363 
365 public:
367  : bulkCursor_(q), cursorModifiable_(q) {}
368 
369  BulkCursorAdaptorForCommonCandidateList bulkCursor_;
370  CursorModifiableAdaptorForCommonCandidateList cursorModifiable_;
371  bool usedNextBefore_ = false;
372  int cursorIndex_ = -1;
373  int currentPage_ = 0;
374  int pageSize_ = 5;
375  std::vector<Text> labels_;
376  // use shared_ptr for type erasure
377  std::vector<std::unique_ptr<CandidateWord>> candidateWord_;
378  std::optional<std::vector<CandidateWord *>> filteredCandidateWord_;
379 
380  CandidateWord &candidateAt(int idx) {
381  if (filteredCandidateWord_) {
382  return *(*filteredCandidateWord_)[idx];
383  }
384  return *candidateWord_[idx];
385  }
386 
387  const CandidateWord &candidateAt(int idx) const {
388  if (filteredCandidateWord_) {
389  return *(*filteredCandidateWord_)[idx];
390  }
391  return *candidateWord_[idx];
392  }
393 
394  CandidateLayoutHint layoutHint_ = CandidateLayoutHint::NotSet;
395  bool cursorIncludeUnselected_ = false;
396  bool cursorKeepInSamePage_ = false;
397  CursorPositionAfterPaging cursorPositionAfterPaging_ =
398  CursorPositionAfterPaging::DonotChange;
399  std::unique_ptr<ActionableCandidateList> actionable_;
400 
401  std::unique_ptr<TabbedCandidateList> tabbed_;
402 
403  size_t totalSize() const {
404  if (filteredCandidateWord_) {
405  return filteredCandidateWord_->size();
406  }
407  return candidateWord_.size();
408  }
409 
410  int size() const {
411  auto start = currentPage_ * pageSize_;
412  auto remain = static_cast<int>(totalSize()) - start;
413  if (remain > pageSize_) {
414  return pageSize_;
415  }
416  // This means a bug, but try to be defensive and return 0 instead of
417  // negative number.
418  if (remain < 0) {
419  return 0;
420  }
421  return remain;
422  }
423 
424  int toGlobalIndex(int idx) const {
425  return idx + (currentPage_ * pageSize_);
426  }
427 
428  void checkIndex(int idx) const {
429  if (idx < 0 || idx >= size()) {
430  throw std::invalid_argument("CommonCandidateList: invalid index");
431  }
432  }
433 
434  void checkGlobalIndex(int idx) const {
435  if (idx < 0 || static_cast<size_t>(idx) >= totalSize()) {
436  throw std::invalid_argument(
437  "CommonCandidateList: invalid global index");
438  }
439  }
440 
441  void fixCursorAfterPaging(int oldIndex) {
442  if (oldIndex < 0) {
443  return;
444  }
445 
446  switch (cursorPositionAfterPaging_) {
447  case CursorPositionAfterPaging::DonotChange:
448  break;
449  case CursorPositionAfterPaging::ResetToFirst:
450  cursorIndex_ = currentPage_ * pageSize_;
451  break;
452  case CursorPositionAfterPaging::SameAsLast: {
453  auto currentPageSize = size();
454  if (oldIndex >= currentPageSize) {
455  cursorIndex_ = currentPage_ * pageSize_ + size() - 1;
456  } else {
457  cursorIndex_ = currentPage_ * pageSize_ + oldIndex;
458  }
459  break;
460  }
461  }
462  }
463 };
464 
465 CommonCandidateList::CommonCandidateList()
466  : d_ptr(std::make_unique<CommonCandidateListPrivate>(this)) {
467  FCITX_D();
468  setPageable(this);
469  setModifiable(this);
470  setBulk(this);
471  setCursorMovable(this);
472  setBulkCursor(&d->bulkCursor_);
473  setCursorModifiable(&d->cursorModifiable_);
474 
475  setLabels();
476 }
477 
478 CommonCandidateList::~CommonCandidateList() {}
479 
480 std::string keyToLabel(const Key &key) {
481  std::string result;
482  if (key.sym() == FcitxKey_None) {
483  return result;
484  }
485 
486 #define _APPEND_MODIFIER_STRING(STR, VALUE) \
487  if (key.states() & KeyState::VALUE) { \
488  result += (STR); \
489  }
490  if (isApple()) {
491  _APPEND_MODIFIER_STRING("⌃", Ctrl)
492  _APPEND_MODIFIER_STRING("⌥", Alt)
493  _APPEND_MODIFIER_STRING("⇧", Shift)
494  _APPEND_MODIFIER_STRING("⌘", Super)
495  } else {
496  _APPEND_MODIFIER_STRING("C-", Ctrl)
497  _APPEND_MODIFIER_STRING("A-", Alt)
498  _APPEND_MODIFIER_STRING("S-", Shift)
499  _APPEND_MODIFIER_STRING("M-", Super)
500  }
501 
502 #undef _APPEND_MODIFIER_STRING
503 
504  auto chr = Key::keySymToUnicode(key.sym());
505  if (chr) {
506  result += utf8::UCS4ToUTF8(chr);
507  } else {
508  result = Key::keySymToString(key.sym(), KeyStringFormat::Localized);
509  }
510  if (!isApple()) {
511  // add a dot as separator
512  result += ". ";
513  }
514 
515  return result;
516 }
517 
518 void CommonCandidateList::setLabels(const std::vector<std::string> &labels) {
519  FCITX_D();
520  fillLabels(d->labels_, labels, [](const std::string &str) { return str; });
521 }
522 
523 void CommonCandidateList::setSelectionKey(const KeyList &keyList) {
524  FCITX_D();
525  fillLabels(d->labels_, keyList,
526  [](const Key &str) -> std::string { return keyToLabel(str); });
527 }
528 
529 void CommonCandidateList::clear() {
530  FCITX_D();
531  clearFilter();
532  d->candidateWord_.clear();
533 }
534 
535 int CommonCandidateList::currentPage() const {
536  FCITX_D();
537  return d->currentPage_;
538 }
539 
540 int CommonCandidateList::cursorIndex() const {
541  FCITX_D();
542  int cursorPage = d->cursorIndex_ / d->pageSize_;
543  if (d->cursorIndex_ >= 0 && cursorPage == d->currentPage_) {
544  return d->cursorIndex_ % d->pageSize_;
545  }
546  return -1;
547 }
548 
549 bool CommonCandidateList::hasNext() const {
550  // page size = 5
551  // total size = 5 -> 1 page
552  // total size = 6 -> 2 page
553  FCITX_D();
554  return d->currentPage_ + 1 < totalPages();
555 }
556 
557 bool CommonCandidateList::hasPrev() const {
558  FCITX_D();
559  return d->currentPage_ > 0;
560 }
561 
562 void CommonCandidateList::prev() {
563  FCITX_D();
564  if (!hasPrev()) {
565  return;
566  }
567  setPage(d->currentPage_ - 1);
568 }
569 
570 void CommonCandidateList::next() {
571  FCITX_D();
572  if (!hasNext()) {
573  return;
574  }
575  setPage(d->currentPage_ + 1);
576  d->usedNextBefore_ = true;
577 }
578 
579 bool CommonCandidateList::usedNextBefore() const {
580  FCITX_D();
581  return d->usedNextBefore_;
582 }
583 
584 void CommonCandidateList::setPageSize(int size) {
585  FCITX_D();
586  if (size < 1) {
587  throw std::invalid_argument("CommonCandidateList: invalid page size");
588  }
589  d->pageSize_ = size;
590  d->currentPage_ = 0;
591 }
592 
593 int CommonCandidateList::pageSize() const {
594  FCITX_D();
595  return d->pageSize_;
596 }
597 
598 int CommonCandidateList::size() const {
599  FCITX_D();
600  return d->size();
601 }
602 
604  FCITX_D();
605  return d->totalSize();
606 }
607 
608 const CandidateWord &CommonCandidateList::candidate(int idx) const {
609  FCITX_D();
610  d->checkIndex(idx);
611  auto globalIndex = d->toGlobalIndex(idx);
612  return d->candidateAt(globalIndex);
613 }
614 
615 const Text &CommonCandidateList::label(int idx) const {
616  FCITX_D();
617  d->checkIndex(idx);
618  if (idx < 0 || idx >= size() ||
619  static_cast<size_t>(idx) >= d->labels_.size()) {
620  throw std::invalid_argument("CommonCandidateList: invalid label idx");
621  }
622 
623  return d->labels_[idx];
624 }
625 
626 void CommonCandidateList::insert(int idx, std::unique_ptr<CandidateWord> word) {
627  FCITX_D();
628  clearFilter();
629  // it's ok to insert at tail
630  if (idx != static_cast<int>(d->candidateWord_.size())) {
631  d->checkGlobalIndex(idx);
632  }
633  d->candidateWord_.insert(d->candidateWord_.begin() + idx, std::move(word));
634 }
635 
636 void CommonCandidateList::remove(int idx) {
637  FCITX_D();
638  clearFilter();
639  d->checkGlobalIndex(idx);
640  d->candidateWord_.erase(d->candidateWord_.begin() + idx);
641  fixAfterUpdate();
642 }
643 
644 int CommonCandidateList::totalPages() const {
645  FCITX_D();
646  return (totalSize() + d->pageSize_ - 1) / d->pageSize_;
647 }
648 
649 void CommonCandidateList::setLayoutHint(CandidateLayoutHint hint) {
650  FCITX_D();
651  d->layoutHint_ = hint;
652 }
653 
654 void CommonCandidateList::setGlobalCursorIndex(int index) {
655  FCITX_D();
656  if (index < 0) {
657  d->cursorIndex_ = -1;
658  } else {
659  d->checkGlobalIndex(index);
660  d->cursorIndex_ = index;
661  }
662 }
663 
665  FCITX_D();
666  d->checkIndex(index);
667  auto globalIndex = d->toGlobalIndex(index);
668  setGlobalCursorIndex(globalIndex);
669 }
670 
672  FCITX_D();
673  return d->cursorIndex_;
674 }
675 
676 CandidateLayoutHint CommonCandidateList::layoutHint() const {
677  FCITX_D();
678  return d->layoutHint_;
679 }
680 
682  FCITX_D();
683  d->checkGlobalIndex(idx);
684  return d->candidateAt(idx);
685 }
686 
687 void CommonCandidateList::move(int from, int to) {
688  FCITX_D();
689  clearFilter();
690  d->checkGlobalIndex(from);
691  d->checkGlobalIndex(to);
692  if (from < to) {
693  // 1 2 3 4 5
694  // from 2 to 5
695  // 1 3 4 5 2
696  std::rotate(d->candidateWord_.begin() + from,
697  d->candidateWord_.begin() + from + 1,
698  d->candidateWord_.begin() + to + 1);
699  } else if (from > to) {
700  // 1 2 3 4 5
701  // from 5 to 2
702  // 1 5 2 3 4
703  std::rotate(d->candidateWord_.begin() + to,
704  d->candidateWord_.begin() + from,
705  d->candidateWord_.begin() + from + 1);
706  }
707 }
708 
709 void CommonCandidateList::moveCursor(bool prev) {
710  FCITX_D();
711  if (totalSize() <= 0 || size() <= 0) {
712  return;
713  }
714 
715  int startCursor = d->cursorIndex_;
716  int startPage = d->currentPage_;
717  std::unordered_set<int> deadloopDetect;
718  do {
719  deadloopDetect.insert(d->cursorIndex_);
720  auto pageBegin = d->pageSize_ * d->currentPage_;
721  if (cursorIndex() < 0) {
722  setGlobalCursorIndex(pageBegin + (prev ? size() - 1 : 0));
723  } else {
724  int rotationBase;
725  int rotationSize;
726  if (d->cursorKeepInSamePage_) {
727  rotationBase = pageBegin;
728  rotationSize = size();
729  } else {
730  rotationBase = 0;
731  rotationSize = totalSize();
732  }
733  auto newGlobalIndex = d->cursorIndex_ + (prev ? -1 : 1);
734  if (newGlobalIndex < rotationBase ||
735  newGlobalIndex >= rotationBase + rotationSize) {
736  if (d->cursorIncludeUnselected_) {
737  d->cursorIndex_ = -1;
738  } else {
739  d->cursorIndex_ =
740  prev ? (rotationBase + rotationSize - 1) : rotationBase;
741  }
742  } else {
743  d->cursorIndex_ = newGlobalIndex;
744  }
745  if (!d->cursorKeepInSamePage_ && d->cursorIndex_ >= 0) {
746  setPage(d->cursorIndex_ / d->pageSize_);
747  }
748  }
749  } while (!deadloopDetect.contains(d->cursorIndex_) &&
750  d->cursorIndex_ >= 0 &&
751  candidateFromAll(d->cursorIndex_).isPlaceHolder());
752  if (deadloopDetect.contains(d->cursorIndex_)) {
753  d->cursorIndex_ = startCursor;
754  d->currentPage_ = startPage;
755  }
756 }
757 
758 void CommonCandidateList::prevCandidate() { moveCursor(true); }
759 
760 void CommonCandidateList::nextCandidate() { moveCursor(false); }
761 
762 void CommonCandidateList::setCursorIncludeUnselected(bool v) {
763  FCITX_D();
764  d->cursorIncludeUnselected_ = v;
765 }
766 
767 void CommonCandidateList::setCursorKeepInSamePage(bool v) {
768  FCITX_D();
769  d->cursorKeepInSamePage_ = v;
770 }
771 
772 void CommonCandidateList::setCursorPositionAfterPaging(
773  CursorPositionAfterPaging afterPaging) {
774  FCITX_D();
775  d->cursorPositionAfterPaging_ = afterPaging;
776 }
777 
778 void CommonCandidateList::setPage(int page) {
779  FCITX_D();
780  auto totalPage = totalPages();
781  if (page >= 0 && page < totalPage) {
782  if (d->currentPage_ != page) {
783  auto oldIndex = cursorIndex();
784  d->currentPage_ = page;
785  d->fixCursorAfterPaging(oldIndex);
786  }
787  } else {
788  throw std::invalid_argument("invalid page");
789  }
790 }
791 
792 void CommonCandidateList::replace(int idx,
793  std::unique_ptr<CandidateWord> word) {
794  FCITX_D();
795  clearFilter();
796  d->checkGlobalIndex(idx);
797  d->candidateWord_[idx] = std::move(word);
798 }
799 
800 void CommonCandidateList::fixAfterUpdate() {
801  FCITX_D();
802  auto totalPage = totalPages();
803  if (d->currentPage_ >= totalPage && d->currentPage_ > 0) {
804  if (totalPage > 0) {
805  d->currentPage_ = totalPage - 1;
806  } else {
807  d->currentPage_ = 0;
808  }
809  }
810  if (d->cursorIndex_ >= 0) {
811  if (d->cursorIndex_ >= totalSize()) {
812  d->cursorIndex_ = 0;
813  }
814  }
815 }
816 
818  std::unique_ptr<ActionableCandidateList> actionable) {
819  FCITX_D();
820  d->actionable_ = std::move(actionable);
821  setActionable(d->actionable_.get());
822 }
823 
825  std::unique_ptr<TabbedCandidateList> tabbed) {
826  FCITX_D();
827  d->tabbed_ = std::move(tabbed);
828  setTabbed(d->tabbed_.get());
829 }
830 
832  const std::function<bool(const CandidateWord &)> &filterFunc) {
833  FCITX_D();
834  setModifiable(nullptr);
835 
836  d->filteredCandidateWord_.emplace();
837  std::ranges::copy(
838  d->candidateWord_ |
839  std::views::filter(
840  [&filterFunc](const std::unique_ptr<CandidateWord> &word) {
841  return filterFunc(*word);
842  }) |
843  std::views::transform(
844  [](const std::unique_ptr<CandidateWord> &word) {
845  return word.get();
846  }),
847  std::back_inserter(*d->filteredCandidateWord_));
848  fixAfterUpdate();
849 }
850 
852  FCITX_D();
853  if (!d->filteredCandidateWord_) {
854  return;
855  }
856  d->filteredCandidateWord_.reset();
857  setModifiable(this);
858  fixAfterUpdate();
859 }
860 
862  FCITX_D();
863  return *d->candidateWord_[idx];
864 }
865 
867  FCITX_D();
868  return d->candidateWord_.size();
869 }
870 
871 } // namespace fcitx
Describe a Key in fcitx.
Definition: key.h:41
bool isPlaceHolder() const
Whether the candidate is only a place holder.
std::string UCS4ToUTF8(uint32_t code)
Convert UCS4 to UTF8 string.
Definition: utf8.cpp:21
Formatted string commonly used in user interface.
static uint32_t keySymToUnicode(KeySym sym)
Convert keysym to a unicode.
Definition: key.cpp:738
TabbedCandidateList * toTabbed() const
Cast to TabbedCandidateList if available.
const CandidateWord & candidateFromAll(int idx) const override
If idx is out of range, it may raise exception.
Definition: action.cpp:17
int globalCursorIndex() const
Return Global cursor index.
C++ Utility functions for handling utf8 strings.
A class represents a formatted string.
Definition: text.h:27
bool spaceBetweenComment() const
Whether there should be no space between text and comment.
void setActionableImpl(std::unique_ptr< ActionableCandidateList > actionable)
Set an optional implementation of actionable candidate list.
void setLabels(const std::vector< std::string > &labels={})
Set the label of candidate list.
void setCursorIndex(int index)
Set cursor index on current page.
void setTabbedImpl(std::unique_ptr< TabbedCandidateList > tabbed)
Set an optional implementation of tabbed candidate list.
void setSelectionKey(const KeyList &keyList)
Set the label of candidate list by key.
void clearFilter()
Clear the filter function for the candidate list.
size_t originSize() const
Return the total number of candidates, ignore filter.
Text textWithComment(std::string separator=" ") const
Return text with comment.
void setFilter(const std::function< bool(const CandidateWord &)> &filterFunc)
Set a filter function for the candidate list.
Interface for tab-related actions on candidate list.
Interface for trigger actions on candidates.
const Text & comment() const
Return comment corresponding to the candidate.
const CandidateWord & originCandidate(size_t idx) const
Return the candidate at the specified index, ignore filter.
Return the human readable string in localized format.
static std::string keySymToString(KeySym sym, KeyStringFormat format=KeyStringFormat::Portable)
Convert keysym to a string.
Definition: key.cpp:660
Base class of candidate word.
Definition: candidatelist.h:42
Key sym related types.
A common simple candidate list that serves most of the purpose.
int totalSize() const override
It&#39;s possible for this function to return -1 if the implement has no clear number how many candidates...
Class to represent a key.
void setTabbed(TabbedCandidateList *list)
Set the TabbedCandidateList implementation.