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_) {
424 int toGlobalIndex(
int idx)
const {
425 return idx + (currentPage_ * pageSize_);
428 void checkIndex(
int idx)
const {
429 if (idx < 0 || idx >= size()) {
430 throw std::invalid_argument(
"CommonCandidateList: invalid index");
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");
441 void fixCursorAfterPaging(
int oldIndex) {
446 switch (cursorPositionAfterPaging_) {
447 case CursorPositionAfterPaging::DonotChange:
449 case CursorPositionAfterPaging::ResetToFirst:
450 cursorIndex_ = currentPage_ * pageSize_;
452 case CursorPositionAfterPaging::SameAsLast: {
453 auto currentPageSize = size();
454 if (oldIndex >= currentPageSize) {
455 cursorIndex_ = currentPage_ * pageSize_ + size() - 1;
457 cursorIndex_ = currentPage_ * pageSize_ + oldIndex;
465 CommonCandidateList::CommonCandidateList()
466 : d_ptr(std::make_unique<CommonCandidateListPrivate>(
this)) {
471 setCursorMovable(
this);
472 setBulkCursor(&d->bulkCursor_);
473 setCursorModifiable(&d->cursorModifiable_);
478 CommonCandidateList::~CommonCandidateList() {}
480 std::string keyToLabel(
const Key &key) {
482 if (key.sym() == FcitxKey_None) {
486 #define _APPEND_MODIFIER_STRING(STR, VALUE) \ 487 if (key.states() & KeyState::VALUE) { \ 491 _APPEND_MODIFIER_STRING(
"⌃", Ctrl)
492 _APPEND_MODIFIER_STRING(
"⌥", Alt)
493 _APPEND_MODIFIER_STRING(
"⇧", Shift)
494 _APPEND_MODIFIER_STRING(
"⌘", Super)
496 _APPEND_MODIFIER_STRING(
"C-", Ctrl)
497 _APPEND_MODIFIER_STRING(
"A-", Alt)
498 _APPEND_MODIFIER_STRING(
"S-", Shift)
499 _APPEND_MODIFIER_STRING(
"M-", Super)
502 #undef _APPEND_MODIFIER_STRING 520 fillLabels(d->labels_, labels, [](
const std::string &str) { return str; });
525 fillLabels(d->labels_, keyList,
526 [](
const Key &str) -> std::string { return keyToLabel(str); });
529 void CommonCandidateList::clear() {
532 d->candidateWord_.clear();
535 int CommonCandidateList::currentPage()
const {
537 return d->currentPage_;
540 int CommonCandidateList::cursorIndex()
const {
542 int cursorPage = d->cursorIndex_ / d->pageSize_;
543 if (d->cursorIndex_ >= 0 && cursorPage == d->currentPage_) {
544 return d->cursorIndex_ % d->pageSize_;
549 bool CommonCandidateList::hasNext()
const {
554 return d->currentPage_ + 1 < totalPages();
557 bool CommonCandidateList::hasPrev()
const {
559 return d->currentPage_ > 0;
562 void CommonCandidateList::prev() {
567 setPage(d->currentPage_ - 1);
570 void CommonCandidateList::next() {
575 setPage(d->currentPage_ + 1);
576 d->usedNextBefore_ =
true;
579 bool CommonCandidateList::usedNextBefore()
const {
581 return d->usedNextBefore_;
584 void CommonCandidateList::setPageSize(
int size) {
587 throw std::invalid_argument(
"CommonCandidateList: invalid page size");
593 int CommonCandidateList::pageSize()
const {
598 int CommonCandidateList::size()
const {
605 return d->totalSize();
608 const CandidateWord &CommonCandidateList::candidate(
int idx)
const {
611 auto globalIndex = d->toGlobalIndex(idx);
612 return d->candidateAt(globalIndex);
615 const Text &CommonCandidateList::label(
int idx)
const {
618 if (idx < 0 || idx >= size() ||
619 static_cast<size_t>(idx) >= d->labels_.size()) {
620 throw std::invalid_argument(
"CommonCandidateList: invalid label idx");
623 return d->labels_[idx];
626 void CommonCandidateList::insert(
int idx, std::unique_ptr<CandidateWord> word) {
630 if (idx != static_cast<int>(d->candidateWord_.size())) {
631 d->checkGlobalIndex(idx);
633 d->candidateWord_.insert(d->candidateWord_.begin() + idx, std::move(word));
636 void CommonCandidateList::remove(
int idx) {
639 d->checkGlobalIndex(idx);
640 d->candidateWord_.erase(d->candidateWord_.begin() + idx);
644 int CommonCandidateList::totalPages()
const {
646 return (totalSize() + d->pageSize_ - 1) / d->pageSize_;
649 void CommonCandidateList::setLayoutHint(CandidateLayoutHint hint) {
651 d->layoutHint_ = hint;
654 void CommonCandidateList::setGlobalCursorIndex(
int index) {
657 d->cursorIndex_ = -1;
659 d->checkGlobalIndex(index);
660 d->cursorIndex_ = index;
666 d->checkIndex(index);
667 auto globalIndex = d->toGlobalIndex(index);
668 setGlobalCursorIndex(globalIndex);
673 return d->cursorIndex_;
676 CandidateLayoutHint CommonCandidateList::layoutHint()
const {
678 return d->layoutHint_;
683 d->checkGlobalIndex(idx);
684 return d->candidateAt(idx);
687 void CommonCandidateList::move(
int from,
int to) {
690 d->checkGlobalIndex(from);
691 d->checkGlobalIndex(to);
696 std::rotate(d->candidateWord_.begin() + from,
697 d->candidateWord_.begin() + from + 1,
698 d->candidateWord_.begin() + to + 1);
699 }
else if (from > to) {
703 std::rotate(d->candidateWord_.begin() + to,
704 d->candidateWord_.begin() + from,
705 d->candidateWord_.begin() + from + 1);
709 void CommonCandidateList::moveCursor(
bool prev) {
711 if (totalSize() <= 0 || size() <= 0) {
715 int startCursor = d->cursorIndex_;
716 int startPage = d->currentPage_;
717 std::unordered_set<int> deadloopDetect;
719 deadloopDetect.insert(d->cursorIndex_);
720 auto pageBegin = d->pageSize_ * d->currentPage_;
721 if (cursorIndex() < 0) {
722 setGlobalCursorIndex(pageBegin + (prev ? size() - 1 : 0));
726 if (d->cursorKeepInSamePage_) {
727 rotationBase = pageBegin;
728 rotationSize = size();
731 rotationSize = totalSize();
733 auto newGlobalIndex = d->cursorIndex_ + (prev ? -1 : 1);
734 if (newGlobalIndex < rotationBase ||
735 newGlobalIndex >= rotationBase + rotationSize) {
736 if (d->cursorIncludeUnselected_) {
737 d->cursorIndex_ = -1;
740 prev ? (rotationBase + rotationSize - 1) : rotationBase;
743 d->cursorIndex_ = newGlobalIndex;
745 if (!d->cursorKeepInSamePage_ && d->cursorIndex_ >= 0) {
746 setPage(d->cursorIndex_ / d->pageSize_);
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;
758 void CommonCandidateList::prevCandidate() { moveCursor(
true); }
760 void CommonCandidateList::nextCandidate() { moveCursor(
false); }
762 void CommonCandidateList::setCursorIncludeUnselected(
bool v) {
764 d->cursorIncludeUnselected_ = v;
767 void CommonCandidateList::setCursorKeepInSamePage(
bool v) {
769 d->cursorKeepInSamePage_ = v;
772 void CommonCandidateList::setCursorPositionAfterPaging(
773 CursorPositionAfterPaging afterPaging) {
775 d->cursorPositionAfterPaging_ = afterPaging;
778 void CommonCandidateList::setPage(
int page) {
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);
788 throw std::invalid_argument(
"invalid page");
792 void CommonCandidateList::replace(
int idx,
793 std::unique_ptr<CandidateWord> word) {
796 d->checkGlobalIndex(idx);
797 d->candidateWord_[idx] = std::move(word);
800 void CommonCandidateList::fixAfterUpdate() {
802 auto totalPage = totalPages();
803 if (d->currentPage_ >= totalPage && d->currentPage_ > 0) {
805 d->currentPage_ = totalPage - 1;
810 if (d->cursorIndex_ >= 0) {
811 if (d->cursorIndex_ >= totalSize()) {
818 std::unique_ptr<ActionableCandidateList> actionable) {
820 d->actionable_ = std::move(actionable);
821 setActionable(d->actionable_.get());
825 std::unique_ptr<TabbedCandidateList> tabbed) {
827 d->tabbed_ = std::move(tabbed);
828 setTabbed(d->tabbed_.get());
832 const std::function<
bool(
const CandidateWord &)> &filterFunc) {
834 setModifiable(
nullptr);
836 d->filteredCandidateWord_.emplace();
840 [&filterFunc](
const std::unique_ptr<CandidateWord> &word) {
841 return filterFunc(*word);
843 std::views::transform(
844 [](
const std::unique_ptr<CandidateWord> &word) {
847 std::back_inserter(*d->filteredCandidateWord_));
853 if (!d->filteredCandidateWord_) {
856 d->filteredCandidateWord_.reset();
863 return *d->candidateWord_[idx];
868 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.