8 #include "candidatelist.h" 17 #include <unordered_set> 20 #include <fcitx-utils/macros.h> 25 #include "fcitx-utils/misc.h" 32 constexpr
size_t regularLabelSize = 10;
34 template <
typename Container,
typename Transformer>
35 void fillLabels(std::vector<Text> &labels,
const Container &container,
36 const Transformer &trans) {
38 labels.reserve(std::max(std::size(container), regularLabelSize));
39 for (
const auto &item : container) {
40 labels.emplace_back(trans(item));
42 while (labels.size() < regularLabelSize) {
43 labels.emplace_back();
47 template <
typename Cand
idateListType,
typename InterfaceType>
48 class CandidateListInterfaceAdapter :
public QPtrHolder<CandidateListType>,
49 public InterfaceType {
51 CandidateListInterfaceAdapter(CandidateListType *q)
52 : QPtrHolder<CandidateListType>(q) {}
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)) 59 #define FCITX_FORWARD_METHOD_ARG(N, ARG) ARG arg##N FCITX_COMMA_IF(N) 61 #define FCITX_FORWARD_METHOD_ARG_NAME(N, ARG) arg##N FCITX_COMMA_IF(N) 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 { \ 68 return q->METHOD_NAME(FCITX_FOR_EACH_IDX( \ 69 FCITX_FORWARD_METHOD_ARG_NAME, FCITX_REMOVE_PARENS(ARGS))); \ 72 class BulkCursorAdaptorForCommonCandidateList
73 :
public CandidateListInterfaceAdapter<CommonCandidateList,
74 BulkCursorCandidateList> {
76 using CandidateListInterfaceAdapter::CandidateListInterfaceAdapter;
78 FCITX_FORWARD_METHOD(
void, setGlobalCursorIndex, (
int));
79 FCITX_FORWARD_METHOD(
int, globalCursorIndex, (),
const);
82 class CursorModifiableAdaptorForCommonCandidateList
83 :
public CandidateListInterfaceAdapter<CommonCandidateList,
84 CursorModifiableCandidateList> {
86 using CandidateListInterfaceAdapter::CandidateListInterfaceAdapter;
88 FCITX_FORWARD_METHOD(
void, setCursorIndex, (
int));
93 ActionableCandidateList::~ActionableCandidateList() =
default;
95 TabbedCandidateList::~TabbedCandidateList() =
default;
109 CandidateList::CandidateList()
110 : d_ptr(std::make_unique<CandidateListPrivate>()) {}
112 CandidateList::~CandidateList() {}
114 bool CandidateList::empty()
const {
return size() == 0; }
123 return d->modifiable_;
133 return d->cursorMovable_;
138 return d->cursorModifiable_;
143 return d->bulkCursor_;
148 return d->actionable_;
163 d->modifiable_ = list;
173 d->cursorMovable_ = list;
178 d->cursorModifiable_ = list;
183 d->bulkCursor_ = list;
188 d->actionable_ = list;
200 bool isPlaceHolder_ =
false;
202 bool hasCustomLabel_ =
false;
204 bool spaceBetweenComment_ =
true;
207 CandidateWord::CandidateWord(
Text text)
208 : d_ptr(std::make_unique<CandidateWordPrivate>(std::move(text))) {}
210 CandidateWord::~CandidateWord() {}
212 const Text &CandidateWord::text()
const {
217 void CandidateWord::setText(
Text text) {
219 d->text_ = std::move(text);
227 void CandidateWord::setComment(
Text comment) {
229 d->comment_ = std::move(comment);
234 auto text = d->text_;
235 if (!d->comment_.empty()) {
236 text.append(std::move(separator));
237 text.append(d->comment_);
244 return d->isPlaceHolder_;
247 bool CandidateWord::hasCustomLabel()
const {
249 return d->hasCustomLabel_;
252 const Text &CandidateWord::customLabel()
const {
254 return d->customLabel_;
257 void CandidateWord::setPlaceHolder(
bool placeHolder) {
259 d->isPlaceHolder_ = placeHolder;
262 void CandidateWord::resetCustomLabel() {
264 d->customLabel_ =
Text();
265 d->hasCustomLabel_ =
false;
268 void CandidateWord::setCustomLabel(
Text text) {
270 d->customLabel_ = std::move(text);
271 d->hasCustomLabel_ =
true;
276 return d->spaceBetweenComment_;
279 void CandidateWord::setSpaceBetweenComment(
bool space) {
281 d->spaceBetweenComment_ = space;
287 int cursorIndex_ = -1;
288 CandidateLayoutHint layoutHint_ = CandidateLayoutHint::Vertical;
289 std::vector<std::shared_ptr<CandidateWord>> candidateWords_;
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");
299 DisplayOnlyCandidateList::DisplayOnlyCandidateList()
300 : d_ptr(std::make_unique<DisplayOnlyCandidateListPrivate>()) {}
302 DisplayOnlyCandidateList::~DisplayOnlyCandidateList() =
default;
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);
311 setContent(std::move(text_content));
314 void DisplayOnlyCandidateList::setContent(std::vector<Text> content) {
316 for (
auto &text : content) {
317 d->candidateWords_.emplace_back(
318 std::make_shared<DisplayOnlyCandidateWord>(std::move(text)));
322 void DisplayOnlyCandidateList::setLayoutHint(CandidateLayoutHint hint) {
324 d->layoutHint_ = hint;
327 void DisplayOnlyCandidateList::setCursorIndex(
int index) {
330 d->cursorIndex_ = -1;
332 d->checkIndex(index);
333 d->cursorIndex_ = index;
337 const Text &DisplayOnlyCandidateList::label(
int idx)
const {
340 return d->emptyText_;
343 const CandidateWord &DisplayOnlyCandidateList::candidate(
int idx)
const {
346 return *d->candidateWords_[idx];
349 int DisplayOnlyCandidateList::cursorIndex()
const {
351 return d->cursorIndex_;
354 int DisplayOnlyCandidateList::size()
const {
356 return d->candidateWords_.size();
359 CandidateLayoutHint DisplayOnlyCandidateList::layoutHint()
const {
361 return d->layoutHint_;
367 : bulkCursor_(q), cursorModifiable_(q) {}
369 BulkCursorAdaptorForCommonCandidateList bulkCursor_;
370 CursorModifiableAdaptorForCommonCandidateList cursorModifiable_;
371 bool usedNextBefore_ =
false;
372 int cursorIndex_ = -1;
373 int currentPage_ = 0;
375 std::vector<Text> labels_;
377 std::vector<std::unique_ptr<CandidateWord>> candidateWord_;
378 std::optional<std::vector<CandidateWord *>> filteredCandidateWord_;
381 if (filteredCandidateWord_) {
382 return *(*filteredCandidateWord_)[idx];
384 return *candidateWord_[idx];
388 if (filteredCandidateWord_) {
389 return *(*filteredCandidateWord_)[idx];
391 return *candidateWord_[idx];
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_;
401 std::unique_ptr<TabbedCandidateList> tabbed_;
403 size_t totalSize()
const {
404 if (filteredCandidateWord_) {
405 return filteredCandidateWord_->size();
407 return candidateWord_.size();
411 auto start = currentPage_ * pageSize_;
412 auto remain =
static_cast<int>(totalSize()) - start;
413 if (remain > pageSize_) {
419 int toGlobalIndex(
int idx)
const {
420 return idx + (currentPage_ * pageSize_);
423 void checkIndex(
int idx)
const {
424 if (idx < 0 || idx >= size()) {
425 throw std::invalid_argument(
"CommonCandidateList: invalid index");
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");
436 void fixCursorAfterPaging(
int oldIndex) {
441 switch (cursorPositionAfterPaging_) {
442 case CursorPositionAfterPaging::DonotChange:
444 case CursorPositionAfterPaging::ResetToFirst:
445 cursorIndex_ = currentPage_ * pageSize_;
447 case CursorPositionAfterPaging::SameAsLast: {
448 auto currentPageSize = size();
449 if (oldIndex >= currentPageSize) {
450 cursorIndex_ = currentPage_ * pageSize_ + size() - 1;
452 cursorIndex_ = currentPage_ * pageSize_ + oldIndex;
460 CommonCandidateList::CommonCandidateList()
461 : d_ptr(std::make_unique<CommonCandidateListPrivate>(
this)) {
466 setCursorMovable(
this);
467 setBulkCursor(&d->bulkCursor_);
468 setCursorModifiable(&d->cursorModifiable_);
473 CommonCandidateList::~CommonCandidateList() {}
475 std::string keyToLabel(
const Key &key) {
477 if (key.sym() == FcitxKey_None) {
481 #define _APPEND_MODIFIER_STRING(STR, VALUE) \ 482 if (key.states() & KeyState::VALUE) { \ 486 _APPEND_MODIFIER_STRING(
"⌃", Ctrl)
487 _APPEND_MODIFIER_STRING(
"⌥", Alt)
488 _APPEND_MODIFIER_STRING(
"⇧", Shift)
489 _APPEND_MODIFIER_STRING(
"⌘", Super)
491 _APPEND_MODIFIER_STRING(
"C-", Ctrl)
492 _APPEND_MODIFIER_STRING(
"A-", Alt)
493 _APPEND_MODIFIER_STRING(
"S-", Shift)
494 _APPEND_MODIFIER_STRING(
"M-", Super)
497 #undef _APPEND_MODIFIER_STRING 515 fillLabels(d->labels_, labels, [](
const std::string &str) { return str; });
520 fillLabels(d->labels_, keyList,
521 [](
const Key &str) -> std::string { return keyToLabel(str); });
524 void CommonCandidateList::clear() {
527 d->candidateWord_.clear();
530 int CommonCandidateList::currentPage()
const {
532 return d->currentPage_;
535 int CommonCandidateList::cursorIndex()
const {
537 int cursorPage = d->cursorIndex_ / d->pageSize_;
538 if (d->cursorIndex_ >= 0 && cursorPage == d->currentPage_) {
539 return d->cursorIndex_ % d->pageSize_;
544 bool CommonCandidateList::hasNext()
const {
549 return d->currentPage_ + 1 < totalPages();
552 bool CommonCandidateList::hasPrev()
const {
554 return d->currentPage_ > 0;
557 void CommonCandidateList::prev() {
562 setPage(d->currentPage_ - 1);
565 void CommonCandidateList::next() {
570 setPage(d->currentPage_ + 1);
571 d->usedNextBefore_ =
true;
574 bool CommonCandidateList::usedNextBefore()
const {
576 return d->usedNextBefore_;
579 void CommonCandidateList::setPageSize(
int size) {
582 throw std::invalid_argument(
"CommonCandidateList: invalid page size");
588 int CommonCandidateList::pageSize()
const {
593 int CommonCandidateList::size()
const {
600 return d->totalSize();
603 const CandidateWord &CommonCandidateList::candidate(
int idx)
const {
606 auto globalIndex = d->toGlobalIndex(idx);
607 return d->candidateAt(globalIndex);
610 const Text &CommonCandidateList::label(
int idx)
const {
613 if (idx < 0 || idx >= size() ||
614 static_cast<size_t>(idx) >= d->labels_.size()) {
615 throw std::invalid_argument(
"CommonCandidateList: invalid label idx");
618 return d->labels_[idx];
621 void CommonCandidateList::insert(
int idx, std::unique_ptr<CandidateWord> word) {
625 if (idx != static_cast<int>(d->candidateWord_.size())) {
626 d->checkGlobalIndex(idx);
628 d->candidateWord_.insert(d->candidateWord_.begin() + idx, std::move(word));
631 void CommonCandidateList::remove(
int idx) {
634 d->checkGlobalIndex(idx);
635 d->candidateWord_.erase(d->candidateWord_.begin() + idx);
639 int CommonCandidateList::totalPages()
const {
641 return (totalSize() + d->pageSize_ - 1) / d->pageSize_;
644 void CommonCandidateList::setLayoutHint(CandidateLayoutHint hint) {
646 d->layoutHint_ = hint;
649 void CommonCandidateList::setGlobalCursorIndex(
int index) {
652 d->cursorIndex_ = -1;
654 d->checkGlobalIndex(index);
655 d->cursorIndex_ = index;
661 d->checkIndex(index);
662 auto globalIndex = d->toGlobalIndex(index);
663 setGlobalCursorIndex(globalIndex);
668 return d->cursorIndex_;
671 CandidateLayoutHint CommonCandidateList::layoutHint()
const {
673 return d->layoutHint_;
678 d->checkGlobalIndex(idx);
679 return d->candidateAt(idx);
682 void CommonCandidateList::move(
int from,
int to) {
685 d->checkGlobalIndex(from);
686 d->checkGlobalIndex(to);
691 std::rotate(d->candidateWord_.begin() + from,
692 d->candidateWord_.begin() + from + 1,
693 d->candidateWord_.begin() + to + 1);
694 }
else if (from > to) {
698 std::rotate(d->candidateWord_.begin() + to,
699 d->candidateWord_.begin() + from,
700 d->candidateWord_.begin() + from + 1);
704 void CommonCandidateList::moveCursor(
bool prev) {
706 if (totalSize() <= 0 || size() <= 0) {
710 int startCursor = d->cursorIndex_;
711 int startPage = d->currentPage_;
712 std::unordered_set<int> deadloopDetect;
714 deadloopDetect.insert(d->cursorIndex_);
715 auto pageBegin = d->pageSize_ * d->currentPage_;
716 if (cursorIndex() < 0) {
717 setGlobalCursorIndex(pageBegin + (prev ? size() - 1 : 0));
721 if (d->cursorKeepInSamePage_) {
722 rotationBase = pageBegin;
723 rotationSize = size();
726 rotationSize = totalSize();
728 auto newGlobalIndex = d->cursorIndex_ + (prev ? -1 : 1);
729 if (newGlobalIndex < rotationBase ||
730 newGlobalIndex >= rotationBase + rotationSize) {
731 if (d->cursorIncludeUnselected_) {
732 d->cursorIndex_ = -1;
735 prev ? (rotationBase + rotationSize - 1) : rotationBase;
738 d->cursorIndex_ = newGlobalIndex;
740 if (!d->cursorKeepInSamePage_ && d->cursorIndex_ >= 0) {
741 setPage(d->cursorIndex_ / d->pageSize_);
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;
753 void CommonCandidateList::prevCandidate() { moveCursor(
true); }
755 void CommonCandidateList::nextCandidate() { moveCursor(
false); }
757 void CommonCandidateList::setCursorIncludeUnselected(
bool v) {
759 d->cursorIncludeUnselected_ = v;
762 void CommonCandidateList::setCursorKeepInSamePage(
bool v) {
764 d->cursorKeepInSamePage_ = v;
767 void CommonCandidateList::setCursorPositionAfterPaging(
768 CursorPositionAfterPaging afterPaging) {
770 d->cursorPositionAfterPaging_ = afterPaging;
773 void CommonCandidateList::setPage(
int page) {
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);
783 throw std::invalid_argument(
"invalid page");
787 void CommonCandidateList::replace(
int idx,
788 std::unique_ptr<CandidateWord> word) {
791 d->checkGlobalIndex(idx);
792 d->candidateWord_[idx] = std::move(word);
795 void CommonCandidateList::fixAfterUpdate() {
797 if (d->currentPage_ >= totalPages() && d->currentPage_ > 0) {
798 d->currentPage_ = totalPages() - 1;
800 if (d->cursorIndex_ >= 0) {
801 if (d->cursorIndex_ >= totalSize()) {
808 std::unique_ptr<ActionableCandidateList> actionable) {
810 d->actionable_ = std::move(actionable);
811 setActionable(d->actionable_.get());
815 std::unique_ptr<TabbedCandidateList> tabbed) {
817 d->tabbed_ = std::move(tabbed);
818 setTabbed(d->tabbed_.get());
822 const std::function<
bool(
const CandidateWord &)> &filterFunc) {
824 setModifiable(
nullptr);
826 d->filteredCandidateWord_.emplace();
830 [&filterFunc](
const std::unique_ptr<CandidateWord> &word) {
831 return filterFunc(*word);
833 std::views::transform(
834 [](
const std::unique_ptr<CandidateWord> &word) {
837 std::back_inserter(*d->filteredCandidateWord_));
843 if (!d->filteredCandidateWord_) {
846 d->filteredCandidateWord_.reset();
853 return *d->candidateWord_[idx];
858 return d->candidateWord_.size();
bool isPlaceHolder() const
Whether the candidate is only a place holder.
std::string UCS4ToUTF8(uint32_t code)
Convert UCS4 to UTF8 string.
Formatted string commonly used in user interface.
static uint32_t keySymToUnicode(KeySym sym)
Convert keysym to a unicode.
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.
int globalCursorIndex() const
Return Global cursor index.
C++ Utility functions for handling utf8 strings.
A class represents a formatted string.
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.
Base class of candidate word.
A common simple candidate list that serves most of the purpose.
int totalSize() const override
It'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.