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  return remain;
417  }
418 
419  int toGlobalIndex(int idx) const {
420  return idx + (currentPage_ * pageSize_);
421  }
422 
423  void checkIndex(int idx) const {
424  if (idx < 0 || idx >= size()) {
425  throw std::invalid_argument("CommonCandidateList: invalid index");
426  }
427  }
428 
429  void checkGlobalIndex(int idx) const {
430  if (idx < 0 || static_cast<size_t>(idx) >= totalSize()) {
431  throw std::invalid_argument(
432  "CommonCandidateList: invalid global index");
433  }
434  }
435 
436  void fixCursorAfterPaging(int oldIndex) {
437  if (oldIndex < 0) {
438  return;
439  }
440 
441  switch (cursorPositionAfterPaging_) {
442  case CursorPositionAfterPaging::DonotChange:
443  break;
444  case CursorPositionAfterPaging::ResetToFirst:
445  cursorIndex_ = currentPage_ * pageSize_;
446  break;
447  case CursorPositionAfterPaging::SameAsLast: {
448  auto currentPageSize = size();
449  if (oldIndex >= currentPageSize) {
450  cursorIndex_ = currentPage_ * pageSize_ + size() - 1;
451  } else {
452  cursorIndex_ = currentPage_ * pageSize_ + oldIndex;
453  }
454  break;
455  }
456  }
457  }
458 };
459 
460 CommonCandidateList::CommonCandidateList()
461  : d_ptr(std::make_unique<CommonCandidateListPrivate>(this)) {
462  FCITX_D();
463  setPageable(this);
464  setModifiable(this);
465  setBulk(this);
466  setCursorMovable(this);
467  setBulkCursor(&d->bulkCursor_);
468  setCursorModifiable(&d->cursorModifiable_);
469 
470  setLabels();
471 }
472 
473 CommonCandidateList::~CommonCandidateList() {}
474 
475 std::string keyToLabel(const Key &key) {
476  std::string result;
477  if (key.sym() == FcitxKey_None) {
478  return result;
479  }
480 
481 #define _APPEND_MODIFIER_STRING(STR, VALUE) \
482  if (key.states() & KeyState::VALUE) { \
483  result += (STR); \
484  }
485  if (isApple()) {
486  _APPEND_MODIFIER_STRING("⌃", Ctrl)
487  _APPEND_MODIFIER_STRING("⌥", Alt)
488  _APPEND_MODIFIER_STRING("⇧", Shift)
489  _APPEND_MODIFIER_STRING("⌘", Super)
490  } else {
491  _APPEND_MODIFIER_STRING("C-", Ctrl)
492  _APPEND_MODIFIER_STRING("A-", Alt)
493  _APPEND_MODIFIER_STRING("S-", Shift)
494  _APPEND_MODIFIER_STRING("M-", Super)
495  }
496 
497 #undef _APPEND_MODIFIER_STRING
498 
499  auto chr = Key::keySymToUnicode(key.sym());
500  if (chr) {
501  result += utf8::UCS4ToUTF8(chr);
502  } else {
503  result = Key::keySymToString(key.sym(), KeyStringFormat::Localized);
504  }
505  if (!isApple()) {
506  // add a dot as separator
507  result += ". ";
508  }
509 
510  return result;
511 }
512 
513 void CommonCandidateList::setLabels(const std::vector<std::string> &labels) {
514  FCITX_D();
515  fillLabels(d->labels_, labels, [](const std::string &str) { return str; });
516 }
517 
518 void CommonCandidateList::setSelectionKey(const KeyList &keyList) {
519  FCITX_D();
520  fillLabels(d->labels_, keyList,
521  [](const Key &str) -> std::string { return keyToLabel(str); });
522 }
523 
524 void CommonCandidateList::clear() {
525  FCITX_D();
526  clearFilter();
527  d->candidateWord_.clear();
528 }
529 
530 int CommonCandidateList::currentPage() const {
531  FCITX_D();
532  return d->currentPage_;
533 }
534 
535 int CommonCandidateList::cursorIndex() const {
536  FCITX_D();
537  int cursorPage = d->cursorIndex_ / d->pageSize_;
538  if (d->cursorIndex_ >= 0 && cursorPage == d->currentPage_) {
539  return d->cursorIndex_ % d->pageSize_;
540  }
541  return -1;
542 }
543 
544 bool CommonCandidateList::hasNext() const {
545  // page size = 5
546  // total size = 5 -> 1 page
547  // total size = 6 -> 2 page
548  FCITX_D();
549  return d->currentPage_ + 1 < totalPages();
550 }
551 
552 bool CommonCandidateList::hasPrev() const {
553  FCITX_D();
554  return d->currentPage_ > 0;
555 }
556 
557 void CommonCandidateList::prev() {
558  FCITX_D();
559  if (!hasPrev()) {
560  return;
561  }
562  setPage(d->currentPage_ - 1);
563 }
564 
565 void CommonCandidateList::next() {
566  FCITX_D();
567  if (!hasNext()) {
568  return;
569  }
570  setPage(d->currentPage_ + 1);
571  d->usedNextBefore_ = true;
572 }
573 
574 bool CommonCandidateList::usedNextBefore() const {
575  FCITX_D();
576  return d->usedNextBefore_;
577 }
578 
579 void CommonCandidateList::setPageSize(int size) {
580  FCITX_D();
581  if (size < 1) {
582  throw std::invalid_argument("CommonCandidateList: invalid page size");
583  }
584  d->pageSize_ = size;
585  d->currentPage_ = 0;
586 }
587 
588 int CommonCandidateList::pageSize() const {
589  FCITX_D();
590  return d->pageSize_;
591 }
592 
593 int CommonCandidateList::size() const {
594  FCITX_D();
595  return d->size();
596 }
597 
599  FCITX_D();
600  return d->totalSize();
601 }
602 
603 const CandidateWord &CommonCandidateList::candidate(int idx) const {
604  FCITX_D();
605  d->checkIndex(idx);
606  auto globalIndex = d->toGlobalIndex(idx);
607  return d->candidateAt(globalIndex);
608 }
609 
610 const Text &CommonCandidateList::label(int idx) const {
611  FCITX_D();
612  d->checkIndex(idx);
613  if (idx < 0 || idx >= size() ||
614  static_cast<size_t>(idx) >= d->labels_.size()) {
615  throw std::invalid_argument("CommonCandidateList: invalid label idx");
616  }
617 
618  return d->labels_[idx];
619 }
620 
621 void CommonCandidateList::insert(int idx, std::unique_ptr<CandidateWord> word) {
622  FCITX_D();
623  clearFilter();
624  // it's ok to insert at tail
625  if (idx != static_cast<int>(d->candidateWord_.size())) {
626  d->checkGlobalIndex(idx);
627  }
628  d->candidateWord_.insert(d->candidateWord_.begin() + idx, std::move(word));
629 }
630 
631 void CommonCandidateList::remove(int idx) {
632  FCITX_D();
633  clearFilter();
634  d->checkGlobalIndex(idx);
635  d->candidateWord_.erase(d->candidateWord_.begin() + idx);
636  fixAfterUpdate();
637 }
638 
639 int CommonCandidateList::totalPages() const {
640  FCITX_D();
641  return (totalSize() + d->pageSize_ - 1) / d->pageSize_;
642 }
643 
644 void CommonCandidateList::setLayoutHint(CandidateLayoutHint hint) {
645  FCITX_D();
646  d->layoutHint_ = hint;
647 }
648 
649 void CommonCandidateList::setGlobalCursorIndex(int index) {
650  FCITX_D();
651  if (index < 0) {
652  d->cursorIndex_ = -1;
653  } else {
654  d->checkGlobalIndex(index);
655  d->cursorIndex_ = index;
656  }
657 }
658 
660  FCITX_D();
661  d->checkIndex(index);
662  auto globalIndex = d->toGlobalIndex(index);
663  setGlobalCursorIndex(globalIndex);
664 }
665 
667  FCITX_D();
668  return d->cursorIndex_;
669 }
670 
671 CandidateLayoutHint CommonCandidateList::layoutHint() const {
672  FCITX_D();
673  return d->layoutHint_;
674 }
675 
677  FCITX_D();
678  d->checkGlobalIndex(idx);
679  return d->candidateAt(idx);
680 }
681 
682 void CommonCandidateList::move(int from, int to) {
683  FCITX_D();
684  clearFilter();
685  d->checkGlobalIndex(from);
686  d->checkGlobalIndex(to);
687  if (from < to) {
688  // 1 2 3 4 5
689  // from 2 to 5
690  // 1 3 4 5 2
691  std::rotate(d->candidateWord_.begin() + from,
692  d->candidateWord_.begin() + from + 1,
693  d->candidateWord_.begin() + to + 1);
694  } else if (from > to) {
695  // 1 2 3 4 5
696  // from 5 to 2
697  // 1 5 2 3 4
698  std::rotate(d->candidateWord_.begin() + to,
699  d->candidateWord_.begin() + from,
700  d->candidateWord_.begin() + from + 1);
701  }
702 }
703 
704 void CommonCandidateList::moveCursor(bool prev) {
705  FCITX_D();
706  if (totalSize() <= 0 || size() <= 0) {
707  return;
708  }
709 
710  int startCursor = d->cursorIndex_;
711  int startPage = d->currentPage_;
712  std::unordered_set<int> deadloopDetect;
713  do {
714  deadloopDetect.insert(d->cursorIndex_);
715  auto pageBegin = d->pageSize_ * d->currentPage_;
716  if (cursorIndex() < 0) {
717  setGlobalCursorIndex(pageBegin + (prev ? size() - 1 : 0));
718  } else {
719  int rotationBase;
720  int rotationSize;
721  if (d->cursorKeepInSamePage_) {
722  rotationBase = pageBegin;
723  rotationSize = size();
724  } else {
725  rotationBase = 0;
726  rotationSize = totalSize();
727  }
728  auto newGlobalIndex = d->cursorIndex_ + (prev ? -1 : 1);
729  if (newGlobalIndex < rotationBase ||
730  newGlobalIndex >= rotationBase + rotationSize) {
731  if (d->cursorIncludeUnselected_) {
732  d->cursorIndex_ = -1;
733  } else {
734  d->cursorIndex_ =
735  prev ? (rotationBase + rotationSize - 1) : rotationBase;
736  }
737  } else {
738  d->cursorIndex_ = newGlobalIndex;
739  }
740  if (!d->cursorKeepInSamePage_ && d->cursorIndex_ >= 0) {
741  setPage(d->cursorIndex_ / d->pageSize_);
742  }
743  }
744  } while (!deadloopDetect.contains(d->cursorIndex_) &&
745  d->cursorIndex_ >= 0 &&
746  candidateFromAll(d->cursorIndex_).isPlaceHolder());
747  if (deadloopDetect.contains(d->cursorIndex_)) {
748  d->cursorIndex_ = startCursor;
749  d->currentPage_ = startPage;
750  }
751 }
752 
753 void CommonCandidateList::prevCandidate() { moveCursor(true); }
754 
755 void CommonCandidateList::nextCandidate() { moveCursor(false); }
756 
757 void CommonCandidateList::setCursorIncludeUnselected(bool v) {
758  FCITX_D();
759  d->cursorIncludeUnselected_ = v;
760 }
761 
762 void CommonCandidateList::setCursorKeepInSamePage(bool v) {
763  FCITX_D();
764  d->cursorKeepInSamePage_ = v;
765 }
766 
767 void CommonCandidateList::setCursorPositionAfterPaging(
768  CursorPositionAfterPaging afterPaging) {
769  FCITX_D();
770  d->cursorPositionAfterPaging_ = afterPaging;
771 }
772 
773 void CommonCandidateList::setPage(int page) {
774  FCITX_D();
775  auto totalPage = totalPages();
776  if (page >= 0 && page < totalPage) {
777  if (d->currentPage_ != page) {
778  auto oldIndex = cursorIndex();
779  d->currentPage_ = page;
780  d->fixCursorAfterPaging(oldIndex);
781  }
782  } else {
783  throw std::invalid_argument("invalid page");
784  }
785 }
786 
787 void CommonCandidateList::replace(int idx,
788  std::unique_ptr<CandidateWord> word) {
789  FCITX_D();
790  clearFilter();
791  d->checkGlobalIndex(idx);
792  d->candidateWord_[idx] = std::move(word);
793 }
794 
795 void CommonCandidateList::fixAfterUpdate() {
796  FCITX_D();
797  if (d->currentPage_ >= totalPages() && d->currentPage_ > 0) {
798  d->currentPage_ = totalPages() - 1;
799  }
800  if (d->cursorIndex_ >= 0) {
801  if (d->cursorIndex_ >= totalSize()) {
802  d->cursorIndex_ = 0;
803  }
804  }
805 }
806 
808  std::unique_ptr<ActionableCandidateList> actionable) {
809  FCITX_D();
810  d->actionable_ = std::move(actionable);
811  setActionable(d->actionable_.get());
812 }
813 
815  std::unique_ptr<TabbedCandidateList> tabbed) {
816  FCITX_D();
817  d->tabbed_ = std::move(tabbed);
818  setTabbed(d->tabbed_.get());
819 }
820 
822  const std::function<bool(const CandidateWord &)> &filterFunc) {
823  FCITX_D();
824  setModifiable(nullptr);
825 
826  d->filteredCandidateWord_.emplace();
827  std::ranges::copy(
828  d->candidateWord_ |
829  std::views::filter(
830  [&filterFunc](const std::unique_ptr<CandidateWord> &word) {
831  return filterFunc(*word);
832  }) |
833  std::views::transform(
834  [](const std::unique_ptr<CandidateWord> &word) {
835  return word.get();
836  }),
837  std::back_inserter(*d->filteredCandidateWord_));
838  fixAfterUpdate();
839 }
840 
842  FCITX_D();
843  if (!d->filteredCandidateWord_) {
844  return;
845  }
846  d->filteredCandidateWord_.reset();
847  setModifiable(this);
848  fixAfterUpdate();
849 }
850 
852  FCITX_D();
853  return *d->candidateWord_[idx];
854 }
855 
857  FCITX_D();
858  return d->candidateWord_.size();
859 }
860 
861 } // 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.