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;
189 CandidateWord::CandidateWord(
Text text)
190 : d_ptr(std::make_unique<CandidateWordPrivate>(std::move(text))) {}
192 CandidateWord::~CandidateWord() {}
194 const Text &CandidateWord::text()
const {
199 void CandidateWord::setText(
Text text) {
201 d->text_ = std::move(text);
209 void CandidateWord::setComment(
Text comment) {
211 d->comment_ = std::move(comment);
216 auto text = d->text_;
217 if (!d->comment_.empty()) {
218 text.append(std::move(separator));
219 text.append(d->comment_);
226 return d->isPlaceHolder_;
229 bool CandidateWord::hasCustomLabel()
const {
231 return d->hasCustomLabel_;
234 const Text &CandidateWord::customLabel()
const {
236 return d->customLabel_;
239 void CandidateWord::setPlaceHolder(
bool placeHolder) {
241 d->isPlaceHolder_ = placeHolder;
244 void CandidateWord::resetCustomLabel() {
246 d->customLabel_ =
Text();
247 d->hasCustomLabel_ =
false;
250 void CandidateWord::setCustomLabel(
Text text) {
252 d->customLabel_ = std::move(text);
253 d->hasCustomLabel_ =
true;
259 int cursorIndex_ = -1;
260 CandidateLayoutHint layoutHint_ = CandidateLayoutHint::Vertical;
261 std::vector<std::shared_ptr<CandidateWord>> candidateWords_;
263 void checkIndex(
int idx)
const {
264 if (idx < 0 || static_cast<size_t>(idx) >= candidateWords_.size()) {
265 throw std::invalid_argument(
266 "DisplayOnlyCandidateList: invalid index");
271 DisplayOnlyCandidateList::DisplayOnlyCandidateList()
272 : d_ptr(std::make_unique<DisplayOnlyCandidateListPrivate>()) {}
274 DisplayOnlyCandidateList::~DisplayOnlyCandidateList() =
default;
276 void DisplayOnlyCandidateList::setContent(
277 const std::vector<std::string> &content) {
278 std::vector<Text> text_content;
279 for (
const auto &str : content) {
280 text_content.emplace_back();
281 text_content.back().append(str);
283 setContent(std::move(text_content));
286 void DisplayOnlyCandidateList::setContent(std::vector<Text> content) {
288 for (
auto &text : content) {
289 d->candidateWords_.emplace_back(
290 std::make_shared<DisplayOnlyCandidateWord>(std::move(text)));
294 void DisplayOnlyCandidateList::setLayoutHint(CandidateLayoutHint hint) {
296 d->layoutHint_ = hint;
299 void DisplayOnlyCandidateList::setCursorIndex(
int index) {
302 d->cursorIndex_ = -1;
304 d->checkIndex(index);
305 d->cursorIndex_ = index;
309 const Text &DisplayOnlyCandidateList::label(
int idx)
const {
312 return d->emptyText_;
315 const CandidateWord &DisplayOnlyCandidateList::candidate(
int idx)
const {
318 return *d->candidateWords_[idx];
321 int DisplayOnlyCandidateList::cursorIndex()
const {
323 return d->cursorIndex_;
326 int DisplayOnlyCandidateList::size()
const {
328 return d->candidateWords_.size();
331 CandidateLayoutHint DisplayOnlyCandidateList::layoutHint()
const {
333 return d->layoutHint_;
339 : bulkCursor_(q), cursorModifiable_(q) {}
341 BulkCursorAdaptorForCommonCandidateList bulkCursor_;
342 CursorModifiableAdaptorForCommonCandidateList cursorModifiable_;
343 bool usedNextBefore_ =
false;
344 int cursorIndex_ = -1;
345 int currentPage_ = 0;
347 std::vector<Text> labels_;
349 std::vector<std::unique_ptr<CandidateWord>> candidateWord_;
350 CandidateLayoutHint layoutHint_ = CandidateLayoutHint::NotSet;
351 bool cursorIncludeUnselected_ =
false;
352 bool cursorKeepInSamePage_ =
false;
353 CursorPositionAfterPaging cursorPositionAfterPaging_ =
354 CursorPositionAfterPaging::DonotChange;
355 std::unique_ptr<ActionableCandidateList> actionable_;
358 auto start = currentPage_ * pageSize_;
359 auto remain =
static_cast<int>(candidateWord_.size()) - start;
360 if (remain > pageSize_) {
366 int toGlobalIndex(
int idx)
const {
367 return idx + (currentPage_ * pageSize_);
370 void checkIndex(
int idx)
const {
371 if (idx < 0 || idx >= size()) {
372 throw std::invalid_argument(
"CommonCandidateList: invalid index");
376 void checkGlobalIndex(
int idx)
const {
377 if (idx < 0 || static_cast<size_t>(idx) >= candidateWord_.size()) {
378 throw std::invalid_argument(
379 "CommonCandidateList: invalid global index");
383 void fixCursorAfterPaging(
int oldIndex) {
388 switch (cursorPositionAfterPaging_) {
389 case CursorPositionAfterPaging::DonotChange:
391 case CursorPositionAfterPaging::ResetToFirst:
392 cursorIndex_ = currentPage_ * pageSize_;
394 case CursorPositionAfterPaging::SameAsLast: {
395 auto currentPageSize = size();
396 if (oldIndex >= currentPageSize) {
397 cursorIndex_ = currentPage_ * pageSize_ + size() - 1;
399 cursorIndex_ = currentPage_ * pageSize_ + oldIndex;
407 CommonCandidateList::CommonCandidateList()
408 : d_ptr(std::make_unique<CommonCandidateListPrivate>(
this)) {
413 setCursorMovable(
this);
414 setBulkCursor(&d->bulkCursor_);
415 setCursorModifiable(&d->cursorModifiable_);
420 CommonCandidateList::~CommonCandidateList() {}
422 std::string keyToLabel(
const Key &key) {
424 if (key.sym() == FcitxKey_None) {
428 #define _APPEND_MODIFIER_STRING(STR, VALUE) \ 429 if (key.states() & KeyState::VALUE) { \ 433 _APPEND_MODIFIER_STRING(
"⌃", Ctrl)
434 _APPEND_MODIFIER_STRING(
"⌥", Alt)
435 _APPEND_MODIFIER_STRING(
"⇧", Shift)
436 _APPEND_MODIFIER_STRING(
"⌘", Super)
438 _APPEND_MODIFIER_STRING(
"C-", Ctrl)
439 _APPEND_MODIFIER_STRING(
"A-", Alt)
440 _APPEND_MODIFIER_STRING(
"S-", Shift)
441 _APPEND_MODIFIER_STRING(
"M-", Super)
444 #undef _APPEND_MODIFIER_STRING 462 fillLabels(d->labels_, labels, [](
const std::string &str) { return str; });
467 fillLabels(d->labels_, keyList,
468 [](
const Key &str) -> std::string { return keyToLabel(str); });
471 void CommonCandidateList::clear() {
473 d->candidateWord_.clear();
476 int CommonCandidateList::currentPage()
const {
478 return d->currentPage_;
481 int CommonCandidateList::cursorIndex()
const {
483 int cursorPage = d->cursorIndex_ / d->pageSize_;
484 if (d->cursorIndex_ >= 0 && cursorPage == d->currentPage_) {
485 return d->cursorIndex_ % d->pageSize_;
490 bool CommonCandidateList::hasNext()
const {
495 return d->currentPage_ + 1 < totalPages();
498 bool CommonCandidateList::hasPrev()
const {
500 return d->currentPage_ > 0;
503 void CommonCandidateList::prev() {
508 setPage(d->currentPage_ - 1);
511 void CommonCandidateList::next() {
516 setPage(d->currentPage_ + 1);
517 d->usedNextBefore_ =
true;
520 bool CommonCandidateList::usedNextBefore()
const {
522 return d->usedNextBefore_;
525 void CommonCandidateList::setPageSize(
int size) {
528 throw std::invalid_argument(
"CommonCandidateList: invalid page size");
534 int CommonCandidateList::pageSize()
const {
539 int CommonCandidateList::size()
const {
546 return d->candidateWord_.size();
549 const CandidateWord &CommonCandidateList::candidate(
int idx)
const {
552 auto globalIndex = d->toGlobalIndex(idx);
553 return *d->candidateWord_[globalIndex];
556 const Text &CommonCandidateList::label(
int idx)
const {
559 if (idx < 0 || idx >= size() ||
560 static_cast<size_t>(idx) >= d->labels_.size()) {
561 throw std::invalid_argument(
"CommonCandidateList: invalid label idx");
564 return d->labels_[idx];
567 void CommonCandidateList::insert(
int idx, std::unique_ptr<CandidateWord> word) {
570 if (idx != static_cast<int>(d->candidateWord_.size())) {
571 d->checkGlobalIndex(idx);
573 d->candidateWord_.insert(d->candidateWord_.begin() + idx, std::move(word));
576 void CommonCandidateList::remove(
int idx) {
578 d->checkGlobalIndex(idx);
579 d->candidateWord_.erase(d->candidateWord_.begin() + idx);
583 int CommonCandidateList::totalPages()
const {
585 return (totalSize() + d->pageSize_ - 1) / d->pageSize_;
588 void CommonCandidateList::setLayoutHint(CandidateLayoutHint hint) {
590 d->layoutHint_ = hint;
593 void CommonCandidateList::setGlobalCursorIndex(
int index) {
596 d->cursorIndex_ = -1;
598 d->checkGlobalIndex(index);
599 d->cursorIndex_ = index;
605 d->checkIndex(index);
606 auto globalIndex = d->toGlobalIndex(index);
607 setGlobalCursorIndex(globalIndex);
612 return d->cursorIndex_;
615 CandidateLayoutHint CommonCandidateList::layoutHint()
const {
617 return d->layoutHint_;
622 d->checkGlobalIndex(idx);
623 return *d->candidateWord_[idx];
626 void CommonCandidateList::move(
int from,
int to) {
628 d->checkGlobalIndex(from);
629 d->checkGlobalIndex(to);
634 std::rotate(d->candidateWord_.begin() + from,
635 d->candidateWord_.begin() + from + 1,
636 d->candidateWord_.begin() + to + 1);
637 }
else if (from > to) {
641 std::rotate(d->candidateWord_.begin() + to,
642 d->candidateWord_.begin() + from,
643 d->candidateWord_.begin() + from + 1);
647 void CommonCandidateList::moveCursor(
bool prev) {
649 if (totalSize() <= 0 || size() <= 0) {
653 int startCursor = d->cursorIndex_;
654 int startPage = d->currentPage_;
655 std::unordered_set<int> deadloopDetect;
657 deadloopDetect.insert(d->cursorIndex_);
658 auto pageBegin = d->pageSize_ * d->currentPage_;
659 if (cursorIndex() < 0) {
660 setGlobalCursorIndex(pageBegin + (prev ? size() - 1 : 0));
664 if (d->cursorKeepInSamePage_) {
665 rotationBase = pageBegin;
666 rotationSize = size();
669 rotationSize = totalSize();
671 auto newGlobalIndex = d->cursorIndex_ + (prev ? -1 : 1);
672 if (newGlobalIndex < rotationBase ||
673 newGlobalIndex >= rotationBase + rotationSize) {
674 if (d->cursorIncludeUnselected_) {
675 d->cursorIndex_ = -1;
678 prev ? (rotationBase + rotationSize - 1) : rotationBase;
681 d->cursorIndex_ = newGlobalIndex;
683 if (!d->cursorKeepInSamePage_ && d->cursorIndex_ >= 0) {
684 setPage(d->cursorIndex_ / d->pageSize_);
687 }
while (!deadloopDetect.contains(d->cursorIndex_) &&
688 d->cursorIndex_ >= 0 &&
689 candidateFromAll(d->cursorIndex_).isPlaceHolder());
690 if (deadloopDetect.contains(d->cursorIndex_)) {
691 d->cursorIndex_ = startCursor;
692 d->currentPage_ = startPage;
696 void CommonCandidateList::prevCandidate() { moveCursor(
true); }
698 void CommonCandidateList::nextCandidate() { moveCursor(
false); }
700 void CommonCandidateList::setCursorIncludeUnselected(
bool v) {
702 d->cursorIncludeUnselected_ = v;
705 void CommonCandidateList::setCursorKeepInSamePage(
bool v) {
707 d->cursorKeepInSamePage_ = v;
710 void CommonCandidateList::setCursorPositionAfterPaging(
711 CursorPositionAfterPaging afterPaging) {
713 d->cursorPositionAfterPaging_ = afterPaging;
716 void CommonCandidateList::setPage(
int page) {
718 auto totalPage = totalPages();
719 if (page >= 0 && page < totalPage) {
720 if (d->currentPage_ != page) {
721 auto oldIndex = cursorIndex();
722 d->currentPage_ = page;
723 d->fixCursorAfterPaging(oldIndex);
726 throw std::invalid_argument(
"invalid page");
730 void CommonCandidateList::replace(
int idx,
731 std::unique_ptr<CandidateWord> word) {
733 d->candidateWord_[idx] = std::move(word);
736 void CommonCandidateList::fixAfterUpdate() {
738 if (d->currentPage_ >= totalPages() && d->currentPage_ > 0) {
739 d->currentPage_ = totalPages() - 1;
741 if (d->cursorIndex_ >= 0) {
742 if (d->cursorIndex_ >= totalSize()) {
749 std::unique_ptr<ActionableCandidateList> actionable) {
751 d->actionable_ = std::move(actionable);
752 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.
void setActionableImpl(std::unique_ptr< ActionableCandidateList > actionable)
Set an optional implemenation 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.