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;
102 CandidateList::CandidateList()
103 : d_ptr(std::make_unique<CandidateListPrivate>()) {}
105 CandidateList::~CandidateList() {}
107 bool CandidateList::empty()
const {
return size() == 0; }
116 return d->modifiable_;
126 return d->cursorMovable_;
131 return d->cursorModifiable_;
136 return d->bulkCursor_;
141 return d->actionable_;
151 d->modifiable_ = list;
161 d->cursorMovable_ = list;
166 d->cursorModifiable_ = list;
171 d->bulkCursor_ = list;
176 d->actionable_ = list;
183 bool isPlaceHolder_ =
false;
185 bool hasCustomLabel_ =
false;
187 bool spaceBetweenComment_ =
true;
190 CandidateWord::CandidateWord(
Text text)
191 : d_ptr(std::make_unique<CandidateWordPrivate>(std::move(text))) {}
193 CandidateWord::~CandidateWord() {}
195 const Text &CandidateWord::text()
const {
200 void CandidateWord::setText(
Text text) {
202 d->text_ = std::move(text);
210 void CandidateWord::setComment(
Text comment) {
212 d->comment_ = std::move(comment);
217 auto text = d->text_;
218 if (!d->comment_.empty()) {
219 text.append(std::move(separator));
220 text.append(d->comment_);
227 return d->isPlaceHolder_;
230 bool CandidateWord::hasCustomLabel()
const {
232 return d->hasCustomLabel_;
235 const Text &CandidateWord::customLabel()
const {
237 return d->customLabel_;
240 void CandidateWord::setPlaceHolder(
bool placeHolder) {
242 d->isPlaceHolder_ = placeHolder;
245 void CandidateWord::resetCustomLabel() {
247 d->customLabel_ =
Text();
248 d->hasCustomLabel_ =
false;
251 void CandidateWord::setCustomLabel(
Text text) {
253 d->customLabel_ = std::move(text);
254 d->hasCustomLabel_ =
true;
259 return d->spaceBetweenComment_;
262 void CandidateWord::setSpaceBetweenComment(
bool space) {
264 d->spaceBetweenComment_ = space;
270 int cursorIndex_ = -1;
271 CandidateLayoutHint layoutHint_ = CandidateLayoutHint::Vertical;
272 std::vector<std::shared_ptr<CandidateWord>> candidateWords_;
274 void checkIndex(
int idx)
const {
275 if (idx < 0 || static_cast<size_t>(idx) >= candidateWords_.size()) {
276 throw std::invalid_argument(
277 "DisplayOnlyCandidateList: invalid index");
282 DisplayOnlyCandidateList::DisplayOnlyCandidateList()
283 : d_ptr(std::make_unique<DisplayOnlyCandidateListPrivate>()) {}
285 DisplayOnlyCandidateList::~DisplayOnlyCandidateList() =
default;
287 void DisplayOnlyCandidateList::setContent(
288 const std::vector<std::string> &content) {
289 std::vector<Text> text_content;
290 for (
const auto &str : content) {
291 text_content.emplace_back();
292 text_content.back().append(str);
294 setContent(std::move(text_content));
297 void DisplayOnlyCandidateList::setContent(std::vector<Text> content) {
299 for (
auto &text : content) {
300 d->candidateWords_.emplace_back(
301 std::make_shared<DisplayOnlyCandidateWord>(std::move(text)));
305 void DisplayOnlyCandidateList::setLayoutHint(CandidateLayoutHint hint) {
307 d->layoutHint_ = hint;
310 void DisplayOnlyCandidateList::setCursorIndex(
int index) {
313 d->cursorIndex_ = -1;
315 d->checkIndex(index);
316 d->cursorIndex_ = index;
320 const Text &DisplayOnlyCandidateList::label(
int idx)
const {
323 return d->emptyText_;
326 const CandidateWord &DisplayOnlyCandidateList::candidate(
int idx)
const {
329 return *d->candidateWords_[idx];
332 int DisplayOnlyCandidateList::cursorIndex()
const {
334 return d->cursorIndex_;
337 int DisplayOnlyCandidateList::size()
const {
339 return d->candidateWords_.size();
342 CandidateLayoutHint DisplayOnlyCandidateList::layoutHint()
const {
344 return d->layoutHint_;
350 : bulkCursor_(q), cursorModifiable_(q) {}
352 BulkCursorAdaptorForCommonCandidateList bulkCursor_;
353 CursorModifiableAdaptorForCommonCandidateList cursorModifiable_;
354 bool usedNextBefore_ =
false;
355 int cursorIndex_ = -1;
356 int currentPage_ = 0;
358 std::vector<Text> labels_;
360 std::vector<std::unique_ptr<CandidateWord>> candidateWord_;
361 CandidateLayoutHint layoutHint_ = CandidateLayoutHint::NotSet;
362 bool cursorIncludeUnselected_ =
false;
363 bool cursorKeepInSamePage_ =
false;
364 CursorPositionAfterPaging cursorPositionAfterPaging_ =
365 CursorPositionAfterPaging::DonotChange;
366 std::unique_ptr<ActionableCandidateList> actionable_;
369 auto start = currentPage_ * pageSize_;
370 auto remain =
static_cast<int>(candidateWord_.size()) - start;
371 if (remain > pageSize_) {
377 int toGlobalIndex(
int idx)
const {
378 return idx + (currentPage_ * pageSize_);
381 void checkIndex(
int idx)
const {
382 if (idx < 0 || idx >= size()) {
383 throw std::invalid_argument(
"CommonCandidateList: invalid index");
387 void checkGlobalIndex(
int idx)
const {
388 if (idx < 0 || static_cast<size_t>(idx) >= candidateWord_.size()) {
389 throw std::invalid_argument(
390 "CommonCandidateList: invalid global index");
394 void fixCursorAfterPaging(
int oldIndex) {
399 switch (cursorPositionAfterPaging_) {
400 case CursorPositionAfterPaging::DonotChange:
402 case CursorPositionAfterPaging::ResetToFirst:
403 cursorIndex_ = currentPage_ * pageSize_;
405 case CursorPositionAfterPaging::SameAsLast: {
406 auto currentPageSize = size();
407 if (oldIndex >= currentPageSize) {
408 cursorIndex_ = currentPage_ * pageSize_ + size() - 1;
410 cursorIndex_ = currentPage_ * pageSize_ + oldIndex;
418 CommonCandidateList::CommonCandidateList()
419 : d_ptr(std::make_unique<CommonCandidateListPrivate>(
this)) {
424 setCursorMovable(
this);
425 setBulkCursor(&d->bulkCursor_);
426 setCursorModifiable(&d->cursorModifiable_);
431 CommonCandidateList::~CommonCandidateList() {}
433 std::string keyToLabel(
const Key &key) {
435 if (key.sym() == FcitxKey_None) {
439 #define _APPEND_MODIFIER_STRING(STR, VALUE) \ 440 if (key.states() & KeyState::VALUE) { \ 444 _APPEND_MODIFIER_STRING(
"⌃", Ctrl)
445 _APPEND_MODIFIER_STRING(
"⌥", Alt)
446 _APPEND_MODIFIER_STRING(
"⇧", Shift)
447 _APPEND_MODIFIER_STRING(
"⌘", Super)
449 _APPEND_MODIFIER_STRING(
"C-", Ctrl)
450 _APPEND_MODIFIER_STRING(
"A-", Alt)
451 _APPEND_MODIFIER_STRING(
"S-", Shift)
452 _APPEND_MODIFIER_STRING(
"M-", Super)
455 #undef _APPEND_MODIFIER_STRING 473 fillLabels(d->labels_, labels, [](
const std::string &str) { return str; });
478 fillLabels(d->labels_, keyList,
479 [](
const Key &str) -> std::string { return keyToLabel(str); });
482 void CommonCandidateList::clear() {
484 d->candidateWord_.clear();
487 int CommonCandidateList::currentPage()
const {
489 return d->currentPage_;
492 int CommonCandidateList::cursorIndex()
const {
494 int cursorPage = d->cursorIndex_ / d->pageSize_;
495 if (d->cursorIndex_ >= 0 && cursorPage == d->currentPage_) {
496 return d->cursorIndex_ % d->pageSize_;
501 bool CommonCandidateList::hasNext()
const {
506 return d->currentPage_ + 1 < totalPages();
509 bool CommonCandidateList::hasPrev()
const {
511 return d->currentPage_ > 0;
514 void CommonCandidateList::prev() {
519 setPage(d->currentPage_ - 1);
522 void CommonCandidateList::next() {
527 setPage(d->currentPage_ + 1);
528 d->usedNextBefore_ =
true;
531 bool CommonCandidateList::usedNextBefore()
const {
533 return d->usedNextBefore_;
536 void CommonCandidateList::setPageSize(
int size) {
539 throw std::invalid_argument(
"CommonCandidateList: invalid page size");
545 int CommonCandidateList::pageSize()
const {
550 int CommonCandidateList::size()
const {
557 return d->candidateWord_.size();
560 const CandidateWord &CommonCandidateList::candidate(
int idx)
const {
563 auto globalIndex = d->toGlobalIndex(idx);
564 return *d->candidateWord_[globalIndex];
567 const Text &CommonCandidateList::label(
int idx)
const {
570 if (idx < 0 || idx >= size() ||
571 static_cast<size_t>(idx) >= d->labels_.size()) {
572 throw std::invalid_argument(
"CommonCandidateList: invalid label idx");
575 return d->labels_[idx];
578 void CommonCandidateList::insert(
int idx, std::unique_ptr<CandidateWord> word) {
581 if (idx != static_cast<int>(d->candidateWord_.size())) {
582 d->checkGlobalIndex(idx);
584 d->candidateWord_.insert(d->candidateWord_.begin() + idx, std::move(word));
587 void CommonCandidateList::remove(
int idx) {
589 d->checkGlobalIndex(idx);
590 d->candidateWord_.erase(d->candidateWord_.begin() + idx);
594 int CommonCandidateList::totalPages()
const {
596 return (totalSize() + d->pageSize_ - 1) / d->pageSize_;
599 void CommonCandidateList::setLayoutHint(CandidateLayoutHint hint) {
601 d->layoutHint_ = hint;
604 void CommonCandidateList::setGlobalCursorIndex(
int index) {
607 d->cursorIndex_ = -1;
609 d->checkGlobalIndex(index);
610 d->cursorIndex_ = index;
616 d->checkIndex(index);
617 auto globalIndex = d->toGlobalIndex(index);
618 setGlobalCursorIndex(globalIndex);
623 return d->cursorIndex_;
626 CandidateLayoutHint CommonCandidateList::layoutHint()
const {
628 return d->layoutHint_;
633 d->checkGlobalIndex(idx);
634 return *d->candidateWord_[idx];
637 void CommonCandidateList::move(
int from,
int to) {
639 d->checkGlobalIndex(from);
640 d->checkGlobalIndex(to);
645 std::rotate(d->candidateWord_.begin() + from,
646 d->candidateWord_.begin() + from + 1,
647 d->candidateWord_.begin() + to + 1);
648 }
else if (from > to) {
652 std::rotate(d->candidateWord_.begin() + to,
653 d->candidateWord_.begin() + from,
654 d->candidateWord_.begin() + from + 1);
658 void CommonCandidateList::moveCursor(
bool prev) {
660 if (totalSize() <= 0 || size() <= 0) {
664 int startCursor = d->cursorIndex_;
665 int startPage = d->currentPage_;
666 std::unordered_set<int> deadloopDetect;
668 deadloopDetect.insert(d->cursorIndex_);
669 auto pageBegin = d->pageSize_ * d->currentPage_;
670 if (cursorIndex() < 0) {
671 setGlobalCursorIndex(pageBegin + (prev ? size() - 1 : 0));
675 if (d->cursorKeepInSamePage_) {
676 rotationBase = pageBegin;
677 rotationSize = size();
680 rotationSize = totalSize();
682 auto newGlobalIndex = d->cursorIndex_ + (prev ? -1 : 1);
683 if (newGlobalIndex < rotationBase ||
684 newGlobalIndex >= rotationBase + rotationSize) {
685 if (d->cursorIncludeUnselected_) {
686 d->cursorIndex_ = -1;
689 prev ? (rotationBase + rotationSize - 1) : rotationBase;
692 d->cursorIndex_ = newGlobalIndex;
694 if (!d->cursorKeepInSamePage_ && d->cursorIndex_ >= 0) {
695 setPage(d->cursorIndex_ / d->pageSize_);
698 }
while (!deadloopDetect.contains(d->cursorIndex_) &&
699 d->cursorIndex_ >= 0 &&
700 candidateFromAll(d->cursorIndex_).isPlaceHolder());
701 if (deadloopDetect.contains(d->cursorIndex_)) {
702 d->cursorIndex_ = startCursor;
703 d->currentPage_ = startPage;
707 void CommonCandidateList::prevCandidate() { moveCursor(
true); }
709 void CommonCandidateList::nextCandidate() { moveCursor(
false); }
711 void CommonCandidateList::setCursorIncludeUnselected(
bool v) {
713 d->cursorIncludeUnselected_ = v;
716 void CommonCandidateList::setCursorKeepInSamePage(
bool v) {
718 d->cursorKeepInSamePage_ = v;
721 void CommonCandidateList::setCursorPositionAfterPaging(
722 CursorPositionAfterPaging afterPaging) {
724 d->cursorPositionAfterPaging_ = afterPaging;
727 void CommonCandidateList::setPage(
int page) {
729 auto totalPage = totalPages();
730 if (page >= 0 && page < totalPage) {
731 if (d->currentPage_ != page) {
732 auto oldIndex = cursorIndex();
733 d->currentPage_ = page;
734 d->fixCursorAfterPaging(oldIndex);
737 throw std::invalid_argument(
"invalid page");
741 void CommonCandidateList::replace(
int idx,
742 std::unique_ptr<CandidateWord> word) {
744 d->candidateWord_[idx] = std::move(word);
747 void CommonCandidateList::fixAfterUpdate() {
749 if (d->currentPage_ >= totalPages() && d->currentPage_ > 0) {
750 d->currentPage_ = totalPages() - 1;
752 if (d->cursorIndex_ >= 0) {
753 if (d->cursorIndex_ >= totalSize()) {
760 std::unique_ptr<ActionableCandidateList> actionable) {
762 d->actionable_ = std::move(actionable);
763 setActionable(d->actionable_.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.
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 setSelectionKey(const KeyList &keyList)
Set the label of candidate list by key.
Text textWithComment(std::string separator=" ") const
Return text with comment.
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.