8 #include "candidatelist.h" 14 #include <unordered_set> 17 #include <fcitx-utils/macros.h> 21 #include "fcitx-utils/misc.h" 28 constexpr
size_t regularLabelSize = 10;
30 template <
typename Container,
typename Transformer>
31 void fillLabels(std::vector<Text> &labels,
const Container &container,
32 const Transformer &trans) {
34 labels.reserve(std::max(std::size(container), regularLabelSize));
35 for (
const auto &item : container) {
36 labels.emplace_back(trans(item));
38 while (labels.size() < regularLabelSize) {
39 labels.emplace_back();
43 template <
typename Cand
idateListType,
typename InterfaceType>
44 class CandidateListInterfaceAdapter :
public QPtrHolder<CandidateListType>,
45 public InterfaceType {
47 CandidateListInterfaceAdapter(CandidateListType *q)
48 : QPtrHolder<CandidateListType>(q) {}
51 #define FCITX_COMMA_IF_2 , 52 #define FCITX_COMMA_IF_1 53 #define FCITX_COMMA_IF(X) FCITX_EXPAND(FCITX_CONCATENATE(FCITX_COMMA_IF_, X)) 55 #define FCITX_FORWARD_METHOD_ARG(N, ARG) ARG arg##N FCITX_COMMA_IF(N) 57 #define FCITX_FORWARD_METHOD_ARG_NAME(N, ARG) arg##N FCITX_COMMA_IF(N) 59 #define FCITX_FORWARD_METHOD(RETURN_TYPE, METHOD_NAME, ARGS, ...) \ 60 RETURN_TYPE METHOD_NAME(FCITX_FOR_EACH_IDX(FCITX_FORWARD_METHOD_ARG, \ 61 FCITX_REMOVE_PARENS(ARGS))) \ 62 __VA_ARGS__ override { \ 64 return q->METHOD_NAME(FCITX_FOR_EACH_IDX( \ 65 FCITX_FORWARD_METHOD_ARG_NAME, FCITX_REMOVE_PARENS(ARGS))); \ 68 class BulkCursorAdaptorForCommonCandidateList
69 :
public CandidateListInterfaceAdapter<CommonCandidateList,
70 BulkCursorCandidateList> {
72 using CandidateListInterfaceAdapter::CandidateListInterfaceAdapter;
74 FCITX_FORWARD_METHOD(
void, setGlobalCursorIndex, (
int));
75 FCITX_FORWARD_METHOD(
int, globalCursorIndex, (),
const);
78 class CursorModifiableAdaptorForCommonCandidateList
79 :
public CandidateListInterfaceAdapter<CommonCandidateList,
80 CursorModifiableCandidateList> {
82 using CandidateListInterfaceAdapter::CandidateListInterfaceAdapter;
84 FCITX_FORWARD_METHOD(
void, setCursorIndex, (
int));
89 ActionableCandidateList::~ActionableCandidateList() =
default;
91 TabbedCandidateList::~TabbedCandidateList() =
default;
105 CandidateList::CandidateList()
106 : d_ptr(std::make_unique<CandidateListPrivate>()) {}
108 CandidateList::~CandidateList() {}
110 bool CandidateList::empty()
const {
return size() == 0; }
119 return d->modifiable_;
129 return d->cursorMovable_;
134 return d->cursorModifiable_;
139 return d->bulkCursor_;
144 return d->actionable_;
159 d->modifiable_ = list;
169 d->cursorMovable_ = list;
174 d->cursorModifiable_ = list;
179 d->bulkCursor_ = list;
184 d->actionable_ = list;
196 bool isPlaceHolder_ =
false;
198 bool hasCustomLabel_ =
false;
200 bool spaceBetweenComment_ =
true;
203 CandidateWord::CandidateWord(
Text text)
204 : d_ptr(std::make_unique<CandidateWordPrivate>(std::move(text))) {}
206 CandidateWord::~CandidateWord() {}
208 const Text &CandidateWord::text()
const {
213 void CandidateWord::setText(
Text text) {
215 d->text_ = std::move(text);
223 void CandidateWord::setComment(
Text comment) {
225 d->comment_ = std::move(comment);
230 auto text = d->text_;
231 if (!d->comment_.empty()) {
232 text.append(std::move(separator));
233 text.append(d->comment_);
240 return d->isPlaceHolder_;
243 bool CandidateWord::hasCustomLabel()
const {
245 return d->hasCustomLabel_;
248 const Text &CandidateWord::customLabel()
const {
250 return d->customLabel_;
253 void CandidateWord::setPlaceHolder(
bool placeHolder) {
255 d->isPlaceHolder_ = placeHolder;
258 void CandidateWord::resetCustomLabel() {
260 d->customLabel_ =
Text();
261 d->hasCustomLabel_ =
false;
264 void CandidateWord::setCustomLabel(
Text text) {
266 d->customLabel_ = std::move(text);
267 d->hasCustomLabel_ =
true;
272 return d->spaceBetweenComment_;
275 void CandidateWord::setSpaceBetweenComment(
bool space) {
277 d->spaceBetweenComment_ = space;
283 int cursorIndex_ = -1;
284 CandidateLayoutHint layoutHint_ = CandidateLayoutHint::Vertical;
285 std::vector<std::shared_ptr<CandidateWord>> candidateWords_;
287 void checkIndex(
int idx)
const {
288 if (idx < 0 || static_cast<size_t>(idx) >= candidateWords_.size()) {
289 throw std::invalid_argument(
290 "DisplayOnlyCandidateList: invalid index");
295 DisplayOnlyCandidateList::DisplayOnlyCandidateList()
296 : d_ptr(std::make_unique<DisplayOnlyCandidateListPrivate>()) {}
298 DisplayOnlyCandidateList::~DisplayOnlyCandidateList() =
default;
300 void DisplayOnlyCandidateList::setContent(
301 const std::vector<std::string> &content) {
302 std::vector<Text> text_content;
303 for (
const auto &str : content) {
304 text_content.emplace_back();
305 text_content.back().append(str);
307 setContent(std::move(text_content));
310 void DisplayOnlyCandidateList::setContent(std::vector<Text> content) {
312 for (
auto &text : content) {
313 d->candidateWords_.emplace_back(
314 std::make_shared<DisplayOnlyCandidateWord>(std::move(text)));
318 void DisplayOnlyCandidateList::setLayoutHint(CandidateLayoutHint hint) {
320 d->layoutHint_ = hint;
323 void DisplayOnlyCandidateList::setCursorIndex(
int index) {
326 d->cursorIndex_ = -1;
328 d->checkIndex(index);
329 d->cursorIndex_ = index;
333 const Text &DisplayOnlyCandidateList::label(
int idx)
const {
336 return d->emptyText_;
339 const CandidateWord &DisplayOnlyCandidateList::candidate(
int idx)
const {
342 return *d->candidateWords_[idx];
345 int DisplayOnlyCandidateList::cursorIndex()
const {
347 return d->cursorIndex_;
350 int DisplayOnlyCandidateList::size()
const {
352 return d->candidateWords_.size();
355 CandidateLayoutHint DisplayOnlyCandidateList::layoutHint()
const {
357 return d->layoutHint_;
363 : bulkCursor_(q), cursorModifiable_(q) {}
365 BulkCursorAdaptorForCommonCandidateList bulkCursor_;
366 CursorModifiableAdaptorForCommonCandidateList cursorModifiable_;
367 bool usedNextBefore_ =
false;
368 int cursorIndex_ = -1;
369 int currentPage_ = 0;
371 std::vector<Text> labels_;
373 std::vector<std::unique_ptr<CandidateWord>> candidateWord_;
374 CandidateLayoutHint layoutHint_ = CandidateLayoutHint::NotSet;
375 bool cursorIncludeUnselected_ =
false;
376 bool cursorKeepInSamePage_ =
false;
377 CursorPositionAfterPaging cursorPositionAfterPaging_ =
378 CursorPositionAfterPaging::DonotChange;
379 std::unique_ptr<ActionableCandidateList> actionable_;
381 std::unique_ptr<TabbedCandidateList> tabbed_;
384 auto start = currentPage_ * pageSize_;
385 auto remain =
static_cast<int>(candidateWord_.size()) - start;
386 if (remain > pageSize_) {
392 int toGlobalIndex(
int idx)
const {
393 return idx + (currentPage_ * pageSize_);
396 void checkIndex(
int idx)
const {
397 if (idx < 0 || idx >= size()) {
398 throw std::invalid_argument(
"CommonCandidateList: invalid index");
402 void checkGlobalIndex(
int idx)
const {
403 if (idx < 0 || static_cast<size_t>(idx) >= candidateWord_.size()) {
404 throw std::invalid_argument(
405 "CommonCandidateList: invalid global index");
409 void fixCursorAfterPaging(
int oldIndex) {
414 switch (cursorPositionAfterPaging_) {
415 case CursorPositionAfterPaging::DonotChange:
417 case CursorPositionAfterPaging::ResetToFirst:
418 cursorIndex_ = currentPage_ * pageSize_;
420 case CursorPositionAfterPaging::SameAsLast: {
421 auto currentPageSize = size();
422 if (oldIndex >= currentPageSize) {
423 cursorIndex_ = currentPage_ * pageSize_ + size() - 1;
425 cursorIndex_ = currentPage_ * pageSize_ + oldIndex;
433 CommonCandidateList::CommonCandidateList()
434 : d_ptr(std::make_unique<CommonCandidateListPrivate>(
this)) {
439 setCursorMovable(
this);
440 setBulkCursor(&d->bulkCursor_);
441 setCursorModifiable(&d->cursorModifiable_);
446 CommonCandidateList::~CommonCandidateList() {}
448 std::string keyToLabel(
const Key &key) {
450 if (key.sym() == FcitxKey_None) {
454 #define _APPEND_MODIFIER_STRING(STR, VALUE) \ 455 if (key.states() & KeyState::VALUE) { \ 459 _APPEND_MODIFIER_STRING(
"⌃", Ctrl)
460 _APPEND_MODIFIER_STRING(
"⌥", Alt)
461 _APPEND_MODIFIER_STRING(
"⇧", Shift)
462 _APPEND_MODIFIER_STRING(
"⌘", Super)
464 _APPEND_MODIFIER_STRING(
"C-", Ctrl)
465 _APPEND_MODIFIER_STRING(
"A-", Alt)
466 _APPEND_MODIFIER_STRING(
"S-", Shift)
467 _APPEND_MODIFIER_STRING(
"M-", Super)
470 #undef _APPEND_MODIFIER_STRING 488 fillLabels(d->labels_, labels, [](
const std::string &str) { return str; });
493 fillLabels(d->labels_, keyList,
494 [](
const Key &str) -> std::string { return keyToLabel(str); });
497 void CommonCandidateList::clear() {
499 d->candidateWord_.clear();
502 int CommonCandidateList::currentPage()
const {
504 return d->currentPage_;
507 int CommonCandidateList::cursorIndex()
const {
509 int cursorPage = d->cursorIndex_ / d->pageSize_;
510 if (d->cursorIndex_ >= 0 && cursorPage == d->currentPage_) {
511 return d->cursorIndex_ % d->pageSize_;
516 bool CommonCandidateList::hasNext()
const {
521 return d->currentPage_ + 1 < totalPages();
524 bool CommonCandidateList::hasPrev()
const {
526 return d->currentPage_ > 0;
529 void CommonCandidateList::prev() {
534 setPage(d->currentPage_ - 1);
537 void CommonCandidateList::next() {
542 setPage(d->currentPage_ + 1);
543 d->usedNextBefore_ =
true;
546 bool CommonCandidateList::usedNextBefore()
const {
548 return d->usedNextBefore_;
551 void CommonCandidateList::setPageSize(
int size) {
554 throw std::invalid_argument(
"CommonCandidateList: invalid page size");
560 int CommonCandidateList::pageSize()
const {
565 int CommonCandidateList::size()
const {
572 return d->candidateWord_.size();
575 const CandidateWord &CommonCandidateList::candidate(
int idx)
const {
578 auto globalIndex = d->toGlobalIndex(idx);
579 return *d->candidateWord_[globalIndex];
582 const Text &CommonCandidateList::label(
int idx)
const {
585 if (idx < 0 || idx >= size() ||
586 static_cast<size_t>(idx) >= d->labels_.size()) {
587 throw std::invalid_argument(
"CommonCandidateList: invalid label idx");
590 return d->labels_[idx];
593 void CommonCandidateList::insert(
int idx, std::unique_ptr<CandidateWord> word) {
596 if (idx != static_cast<int>(d->candidateWord_.size())) {
597 d->checkGlobalIndex(idx);
599 d->candidateWord_.insert(d->candidateWord_.begin() + idx, std::move(word));
602 void CommonCandidateList::remove(
int idx) {
604 d->checkGlobalIndex(idx);
605 d->candidateWord_.erase(d->candidateWord_.begin() + idx);
609 int CommonCandidateList::totalPages()
const {
611 return (totalSize() + d->pageSize_ - 1) / d->pageSize_;
614 void CommonCandidateList::setLayoutHint(CandidateLayoutHint hint) {
616 d->layoutHint_ = hint;
619 void CommonCandidateList::setGlobalCursorIndex(
int index) {
622 d->cursorIndex_ = -1;
624 d->checkGlobalIndex(index);
625 d->cursorIndex_ = index;
631 d->checkIndex(index);
632 auto globalIndex = d->toGlobalIndex(index);
633 setGlobalCursorIndex(globalIndex);
638 return d->cursorIndex_;
641 CandidateLayoutHint CommonCandidateList::layoutHint()
const {
643 return d->layoutHint_;
648 d->checkGlobalIndex(idx);
649 return *d->candidateWord_[idx];
652 void CommonCandidateList::move(
int from,
int to) {
654 d->checkGlobalIndex(from);
655 d->checkGlobalIndex(to);
660 std::rotate(d->candidateWord_.begin() + from,
661 d->candidateWord_.begin() + from + 1,
662 d->candidateWord_.begin() + to + 1);
663 }
else if (from > to) {
667 std::rotate(d->candidateWord_.begin() + to,
668 d->candidateWord_.begin() + from,
669 d->candidateWord_.begin() + from + 1);
673 void CommonCandidateList::moveCursor(
bool prev) {
675 if (totalSize() <= 0 || size() <= 0) {
679 int startCursor = d->cursorIndex_;
680 int startPage = d->currentPage_;
681 std::unordered_set<int> deadloopDetect;
683 deadloopDetect.insert(d->cursorIndex_);
684 auto pageBegin = d->pageSize_ * d->currentPage_;
685 if (cursorIndex() < 0) {
686 setGlobalCursorIndex(pageBegin + (prev ? size() - 1 : 0));
690 if (d->cursorKeepInSamePage_) {
691 rotationBase = pageBegin;
692 rotationSize = size();
695 rotationSize = totalSize();
697 auto newGlobalIndex = d->cursorIndex_ + (prev ? -1 : 1);
698 if (newGlobalIndex < rotationBase ||
699 newGlobalIndex >= rotationBase + rotationSize) {
700 if (d->cursorIncludeUnselected_) {
701 d->cursorIndex_ = -1;
704 prev ? (rotationBase + rotationSize - 1) : rotationBase;
707 d->cursorIndex_ = newGlobalIndex;
709 if (!d->cursorKeepInSamePage_ && d->cursorIndex_ >= 0) {
710 setPage(d->cursorIndex_ / d->pageSize_);
713 }
while (!deadloopDetect.contains(d->cursorIndex_) &&
714 d->cursorIndex_ >= 0 &&
715 candidateFromAll(d->cursorIndex_).isPlaceHolder());
716 if (deadloopDetect.contains(d->cursorIndex_)) {
717 d->cursorIndex_ = startCursor;
718 d->currentPage_ = startPage;
722 void CommonCandidateList::prevCandidate() { moveCursor(
true); }
724 void CommonCandidateList::nextCandidate() { moveCursor(
false); }
726 void CommonCandidateList::setCursorIncludeUnselected(
bool v) {
728 d->cursorIncludeUnselected_ = v;
731 void CommonCandidateList::setCursorKeepInSamePage(
bool v) {
733 d->cursorKeepInSamePage_ = v;
736 void CommonCandidateList::setCursorPositionAfterPaging(
737 CursorPositionAfterPaging afterPaging) {
739 d->cursorPositionAfterPaging_ = afterPaging;
742 void CommonCandidateList::setPage(
int page) {
744 auto totalPage = totalPages();
745 if (page >= 0 && page < totalPage) {
746 if (d->currentPage_ != page) {
747 auto oldIndex = cursorIndex();
748 d->currentPage_ = page;
749 d->fixCursorAfterPaging(oldIndex);
752 throw std::invalid_argument(
"invalid page");
756 void CommonCandidateList::replace(
int idx,
757 std::unique_ptr<CandidateWord> word) {
759 d->candidateWord_[idx] = std::move(word);
762 void CommonCandidateList::fixAfterUpdate() {
764 if (d->currentPage_ >= totalPages() && d->currentPage_ > 0) {
765 d->currentPage_ = totalPages() - 1;
767 if (d->cursorIndex_ >= 0) {
768 if (d->cursorIndex_ >= totalSize()) {
775 std::unique_ptr<ActionableCandidateList> actionable) {
777 d->actionable_ = std::move(actionable);
778 setActionable(d->actionable_.get());
782 std::unique_ptr<TabbedCandidateList> tabbed) {
784 d->tabbed_ = std::move(tabbed);
785 setTabbed(d->tabbed_.get());
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.
Text textWithComment(std::string separator=" ") const
Return text with comment.
Interface for tab-related actions on candidate list.
Interface for trigger actions on candidates.
const Text & comment() const
Return comment corresponding to the candidate.
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.