KWidgetsAddons

kcharselect.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <[email protected]>
4  SPDX-FileCopyrightText: 2017 Harald Sitter <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "kcharselect.h"
10 #include "kcharselect_p.h"
11 
12 #include "loggingcategory.h"
13 
14 #include <QAction>
15 #include <QActionEvent>
16 #include <QApplication>
17 #include <QBoxLayout>
18 #include <QComboBox>
19 #include <QDebug>
20 #include <QDoubleSpinBox>
21 #include <QFontComboBox>
22 #include <QHeaderView>
23 #include <QLineEdit>
24 #include <QRegularExpression>
25 #include <QSplitter>
26 #include <QTextBrowser>
27 #include <QTimer>
28 #include <QToolButton>
29 
30 Q_GLOBAL_STATIC(KCharSelectData, s_data)
31 
32 class KCharSelectTablePrivate
33 {
34 public:
35  KCharSelectTablePrivate(KCharSelectTable *q)
36  : q(q)
37  {
38  }
39 
40  KCharSelectTable *const q;
41 
42  QFont font;
43  KCharSelectItemModel *model = nullptr;
44  QVector<uint> chars;
45  uint chr;
46 
47  void resizeCells();
48  void doubleClicked(const QModelIndex &index);
49  void slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
50 };
51 
52 class KCharSelectPrivate
53 {
54  Q_DECLARE_TR_FUNCTIONS(KCharSelect)
55 
56 public:
57  struct HistoryItem {
58  uint c;
59  bool fromSearch;
60  QString searchString;
61  };
62 
63  enum { MaxHistoryItems = 100 };
64 
65  KCharSelectPrivate(KCharSelect *q)
66  : q(q)
67  {
68  }
69 
70  KCharSelect *const q;
71 
72  QToolButton *backButton = nullptr;
73  QToolButton *forwardButton = nullptr;
74  QLineEdit *searchLine = nullptr;
75  QFontComboBox *fontCombo = nullptr;
76  QSpinBox *fontSizeSpinBox = nullptr;
77  QComboBox *sectionCombo = nullptr;
78  QComboBox *blockCombo = nullptr;
79  KCharSelectTable *charTable = nullptr;
80  QTextBrowser *detailBrowser = nullptr;
81 
82  bool searchMode = false; // a search is active
83  bool historyEnabled = false;
84  bool allPlanesEnabled = false;
85  int inHistory = 0; // index of current char in history
86  QList<HistoryItem> history;
87  QObject *actionParent = nullptr;
88 
89  QString createLinks(QString s);
90  void historyAdd(uint c, bool fromSearch, const QString &searchString);
91  void showFromHistory(int index);
92  void updateBackForwardButtons();
93  void activateSearchLine();
94  void back();
95  void forward();
96  void fontSelected();
97  void charSelected(uint c);
98  void updateCurrentChar(uint c);
99  void slotUpdateUnicode(uint c);
100  void sectionSelected(int index);
101  void blockSelected(int index);
102  void searchEditChanged();
103  void search();
104  void linkClicked(QUrl url);
105 };
106 
107 /******************************************************************/
108 /* Class: KCharSelectTable */
109 /******************************************************************/
110 
111 KCharSelectTable::KCharSelectTable(QWidget *parent, const QFont &_font)
112  : QTableView(parent)
113  , d(new KCharSelectTablePrivate(this))
114 {
115  d->font = _font;
116 
117  setTabKeyNavigation(false);
118  setSelectionBehavior(QAbstractItemView::SelectItems);
119  setSelectionMode(QAbstractItemView::SingleSelection);
120 
121  QPalette _palette;
122  _palette.setColor(backgroundRole(), palette().color(QPalette::Base));
123  setPalette(_palette);
124  verticalHeader()->setVisible(false);
125  verticalHeader()->setSectionResizeMode(QHeaderView::Custom);
126  horizontalHeader()->setVisible(false);
127  horizontalHeader()->setSectionResizeMode(QHeaderView::Custom);
128 
130  setDragEnabled(true);
131  setAcceptDrops(true);
132  setDropIndicatorShown(false);
133  setDragDropMode(QAbstractItemView::DragDrop);
134  setTextElideMode(Qt::ElideNone);
135 
136  connect(this, &KCharSelectTable::doubleClicked, this, [this](const QModelIndex &index) {
137  d->doubleClicked(index);
138  });
139 
140  d->resizeCells();
141 }
142 
143 KCharSelectTable::~KCharSelectTable() = default;
144 
145 void KCharSelectTable::setFont(const QFont &_font)
146 {
147  QTableView::setFont(_font);
148  d->font = _font;
149  if (d->model) {
150  d->model->setFont(_font);
151  }
152  d->resizeCells();
153 }
154 
155 uint KCharSelectTable::chr()
156 {
157  return d->chr;
158 }
159 
160 QFont KCharSelectTable::font() const
161 {
162  return d->font;
163 }
164 
165 QVector<uint> KCharSelectTable::displayedChars() const
166 {
167  return d->chars;
168 }
169 
170 void KCharSelectTable::setChar(uint c)
171 {
172  int pos = d->chars.indexOf(c);
173  if (pos != -1) {
174  setCurrentIndex(model()->index(pos / model()->columnCount(), pos % model()->columnCount()));
175  }
176 }
177 
178 void KCharSelectTable::setContents(const QVector<uint> &chars)
179 {
180  d->chars = chars;
181 
182  auto oldModel = d->model;
183  d->model = new KCharSelectItemModel(chars, d->font, this);
184  setModel(d->model);
185  d->resizeCells();
186 
187  // Setting a model changes the selectionModel. Make sure to always reconnect.
188  connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &selected, const QItemSelection &deselected) {
189  d->slotSelectionChanged(selected, deselected);
190  });
191 
192  connect(d->model, &KCharSelectItemModel::showCharRequested, this, &KCharSelectTable::showCharRequested);
193 
194  delete oldModel; // The selection model is thrown away when the model gets destroyed().
195 }
196 
197 void KCharSelectTable::scrollTo(const QModelIndex &index, ScrollHint hint)
198 {
199  // this prevents horizontal scrolling when selecting a character in the last column
200  if (index.isValid() && index.column() != 0) {
201  QTableView::scrollTo(d->model->index(index.row(), 0), hint);
202  } else {
203  QTableView::scrollTo(index, hint);
204  }
205 }
206 
207 void KCharSelectTablePrivate::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
208 {
209  Q_UNUSED(deselected);
210  if (!model || selected.indexes().isEmpty()) {
211  return;
212  }
213  QVariant temp = model->data(selected.indexes().at(0), KCharSelectItemModel::CharacterRole);
214  if (temp.type() != QVariant::UInt) {
215  return;
216  }
217  uint c = temp.toUInt();
218  chr = c;
219  Q_EMIT q->focusItemChanged(c);
220 }
221 
222 void KCharSelectTable::resizeEvent(QResizeEvent *e)
223 {
225  if (e->size().width() != e->oldSize().width()) {
226  // Resize our cells. But do so asynchronously through the event loop.
227  // Otherwise we can end up with an infinite loop as resizing the cells in turn results in
228  // a layout change which results in a resize event. More importantly doing this blockingly
229  // crashes QAccessible as the resize we potentially cause will discard objects which are
230  // still being used in the call chain leading to this event.
231  // https://bugs.kde.org/show_bug.cgi?id=374933
232  // https://bugreports.qt.io/browse/QTBUG-58153
233  // This can be removed once a fixed Qt version is the lowest requirement for Frameworks.
234  auto timer = new QTimer(this);
235  timer->setSingleShot(true);
236  connect(timer, &QTimer::timeout, [&, timer]() {
237  d->resizeCells();
238  timer->deleteLater();
239  });
240  timer->start(0);
241  }
242 }
243 
244 void KCharSelectTablePrivate::resizeCells()
245 {
246  KCharSelectItemModel *model = static_cast<KCharSelectItemModel *>(q->model());
247  if (!model)
248  return;
249 
250  const int viewportWidth = q->viewport()->size().width();
251 
253 
254  // Determine the max width of the displayed characters
255  // fontMetrics.maxWidth() doesn't help because of font fallbacks
256  // (testcase: Malayalam characters)
257  int maxCharWidth = 0;
258  const QVector<uint> chars = model->chars();
259  for (int i = 0; i < chars.size(); ++i) {
260  uint thisChar = chars.at(i);
261  if (s_data()->isPrint(thisChar)) {
262  maxCharWidth = qMax(maxCharWidth, fontMetrics.boundingRect(QString::fromUcs4(&thisChar, 1)).width());
263  }
264  }
265  // Avoid too narrow cells
266  maxCharWidth = qMax(maxCharWidth, 2 * fontMetrics.xHeight());
267  maxCharWidth = qMax(maxCharWidth, fontMetrics.height());
268  // Add the necessary padding, trying to match the delegate
269  const int textMargin = q->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, q) + 1;
270  maxCharWidth += 2 * textMargin;
271 
272  const int columns = qMax(1, viewportWidth / maxCharWidth);
273  model->setColumnCount(columns);
274 
275  const uint oldChar = q->chr();
276 
277  const int new_w = viewportWidth / columns;
278  const int rows = model->rowCount();
279  q->setUpdatesEnabled(false);
280  QHeaderView *hHeader = q->horizontalHeader();
281  hHeader->setMinimumSectionSize(new_w);
282  const int spaceLeft = viewportWidth - new_w * columns;
283  for (int i = 0; i <= columns; ++i) {
284  if (i < spaceLeft) {
285  hHeader->resizeSection(i, new_w + 1);
286  } else {
287  hHeader->resizeSection(i, new_w);
288  }
289  }
290 
291  QHeaderView *vHeader = q->verticalHeader();
292 #ifdef Q_OS_WIN
293  int new_h = fontMetrics.lineSpacing() + 1;
294 #else
295  int new_h = fontMetrics.xHeight() * 3;
296 #endif
297  const int fontHeight = fontMetrics.height();
298  if (new_h < 5 || new_h < 4 + fontHeight) {
299  new_h = qMax(5, 4 + fontHeight);
300  }
301  vHeader->setMinimumSectionSize(new_h);
302  for (int i = 0; i < rows; ++i) {
303  vHeader->resizeSection(i, new_h);
304  }
305 
306  q->setUpdatesEnabled(true);
307  q->setChar(oldChar);
308 }
309 
310 void KCharSelectTablePrivate::doubleClicked(const QModelIndex &index)
311 {
312  uint c = model->data(index, KCharSelectItemModel::CharacterRole).toUInt();
313  if (s_data()->isPrint(c)) {
314  Q_EMIT q->activated(c);
315  }
316 }
317 
318 void KCharSelectTable::keyPressEvent(QKeyEvent *e)
319 {
320  if (d->model) {
321  switch (e->key()) {
322  case Qt::Key_Space:
323  Q_EMIT activated(QChar::Space);
324  return;
325  case Qt::Key_Enter:
326  case Qt::Key_Return: {
327  if (!currentIndex().isValid()) {
328  return;
329  }
330  uint c = d->model->data(currentIndex(), KCharSelectItemModel::CharacterRole).toUInt();
331  if (s_data()->isPrint(c)) {
332  Q_EMIT activated(c);
333  }
334  return;
335  }
336  default:
337  break;
338  }
339  }
341 }
342 
343 /******************************************************************/
344 /* Class: KCharSelect */
345 /******************************************************************/
346 
347 KCharSelect::KCharSelect(QWidget *parent, const Controls controls)
348  : QWidget(parent)
349  , d(new KCharSelectPrivate(this))
350 {
351  initWidget(controls, nullptr);
352 }
353 
354 KCharSelect::KCharSelect(QWidget *parent, QObject *actionParent, const Controls controls)
355  : QWidget(parent)
356  , d(new KCharSelectPrivate(this))
357 {
358  initWidget(controls, actionParent);
359 }
360 
361 void attachToActionParent(QAction *action, QObject *actionParent, const QList<QKeySequence> &shortcuts)
362 {
363  if (!action || !actionParent) {
364  return;
365  }
366 
367  action->setParent(actionParent);
368 
369  if (actionParent->inherits("KActionCollection")) {
370  QMetaObject::invokeMethod(actionParent, "addAction", Q_ARG(QString, action->objectName()), Q_ARG(QAction *, action));
371  QMetaObject::invokeMethod(actionParent, "setDefaultShortcuts", Q_ARG(QAction *, action), Q_ARG(QList<QKeySequence>, shortcuts));
372  } else {
373  action->setShortcuts(shortcuts);
374  }
375 }
376 
377 void KCharSelect::initWidget(const Controls controls, QObject *actionParent)
378 {
379  d->actionParent = actionParent;
380 
381  QVBoxLayout *mainLayout = new QVBoxLayout(this);
382  mainLayout->setContentsMargins(0, 0, 0, 0);
383  if (SearchLine & controls) {
384  QHBoxLayout *searchLayout = new QHBoxLayout();
385  mainLayout->addLayout(searchLayout);
386  d->searchLine = new QLineEdit(this);
387  searchLayout->addWidget(d->searchLine);
388  d->searchLine->setPlaceholderText(tr("Enter a search term or character...", "@info:placeholder"));
389  d->searchLine->setClearButtonEnabled(true);
390  d->searchLine->setToolTip(tr("Enter a search term or character here", "@info:tooltip"));
391 
392  QAction *findAction = new QAction(this);
393  connect(findAction, &QAction::triggered, this, [this]() {
394  d->activateSearchLine();
395  });
396  findAction->setObjectName(QStringLiteral("edit_find"));
397  findAction->setText(tr("&Find...", "@action"));
398  findAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-find")));
399  attachToActionParent(findAction, actionParent, QKeySequence::keyBindings(QKeySequence::Find));
400 
401  connect(d->searchLine, &QLineEdit::textChanged, this, [this]() {
402  d->searchEditChanged();
403  });
404  connect(d->searchLine, &QLineEdit::returnPressed, this, [this]() {
405  d->search();
406  });
407  }
408 
409  if ((SearchLine & controls) && ((FontCombo & controls) || (FontSize & controls) || (BlockCombos & controls))) {
410  QFrame *line = new QFrame(this);
413  mainLayout->addWidget(line);
414  }
415 
416  QHBoxLayout *comboLayout = new QHBoxLayout();
417 
418  d->backButton = new QToolButton(this);
419  comboLayout->addWidget(d->backButton);
420  d->backButton->setEnabled(false);
421  d->backButton->setText(tr("Previous in History", "@action:button Goes to previous character"));
422  d->backButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
423  d->backButton->setToolTip(tr("Go to previous character in history", "@info:tooltip"));
424 
425  d->forwardButton = new QToolButton(this);
426  comboLayout->addWidget(d->forwardButton);
427  d->forwardButton->setEnabled(false);
428  d->forwardButton->setText(tr("Next in History", "@action:button Goes to next character"));
429  d->forwardButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
430  d->forwardButton->setToolTip(tr("Go to next character in history", "info:tooltip"));
431 
432  QAction *backAction = new QAction(this);
433  connect(backAction, &QAction::triggered, d->backButton, &QAbstractButton::animateClick);
434  backAction->setObjectName(QStringLiteral("go_back"));
435  backAction->setText(tr("&Back", "@action go back"));
436  backAction->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
437  attachToActionParent(backAction, actionParent, QKeySequence::keyBindings(QKeySequence::Back));
438 
439  QAction *forwardAction = new QAction(this);
440  connect(forwardAction, &QAction::triggered, d->forwardButton, &QAbstractButton::animateClick);
441  forwardAction->setObjectName(QStringLiteral("go_forward"));
442  forwardAction->setText(tr("&Forward", "@action go forward"));
443  forwardAction->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
444  attachToActionParent(forwardAction, actionParent, QKeySequence::keyBindings(QKeySequence::Forward));
445 
446  if (QApplication::isRightToLeft()) { // swap the back/forward icons
447  QIcon tmp = backAction->icon();
448  backAction->setIcon(forwardAction->icon());
449  forwardAction->setIcon(tmp);
450  }
451 
452  connect(d->backButton, &QToolButton::clicked, this, [this]() {
453  d->back();
454  });
455  connect(d->forwardButton, &QToolButton::clicked, this, [this]() {
456  d->forward();
457  });
458 
459  d->sectionCombo = new QComboBox(this);
460  d->sectionCombo->setObjectName(QStringLiteral("sectionCombo"));
461  d->sectionCombo->setToolTip(tr("Select a category", "@info:tooltip"));
462  comboLayout->addWidget(d->sectionCombo);
463  d->blockCombo = new QComboBox(this);
464  d->blockCombo->setObjectName(QStringLiteral("blockCombo"));
465  d->blockCombo->setToolTip(tr("Select a block to be displayed", "@info:tooltip"));
466  d->blockCombo->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
467  comboLayout->addWidget(d->blockCombo, 1);
468  d->sectionCombo->addItems(s_data()->sectionList());
469  d->blockCombo->setMinimumWidth(QFontMetrics(QWidget::font()).averageCharWidth() * 25);
470 
471  connect(d->sectionCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
472  d->sectionSelected(index);
473  });
474 
475  connect(d->blockCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) {
476  d->blockSelected(index);
477  });
478 
479  d->fontCombo = new QFontComboBox(this);
480  comboLayout->addWidget(d->fontCombo);
481  d->fontCombo->setEditable(true);
482  d->fontCombo->resize(d->fontCombo->sizeHint());
483  d->fontCombo->setToolTip(tr("Set font", "@info:tooltip"));
484 
485  d->fontSizeSpinBox = new QSpinBox(this);
486  comboLayout->addWidget(d->fontSizeSpinBox);
487  d->fontSizeSpinBox->setValue(QWidget::font().pointSize());
488  d->fontSizeSpinBox->setRange(1, 400);
489  d->fontSizeSpinBox->setSingleStep(1);
490  d->fontSizeSpinBox->setToolTip(tr("Set font size", "@info:tooltip"));
491 
492  connect(d->fontCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this]() {
493  d->fontSelected();
494  });
495  connect(d->fontSizeSpinBox, &QSpinBox::valueChanged, this, [this]() {
496  d->fontSelected();
497  });
498 
499  if ((HistoryButtons & controls) || (FontCombo & controls) || (FontSize & controls) || (BlockCombos & controls)) {
500  mainLayout->addLayout(comboLayout);
501  }
502  if (!(HistoryButtons & controls)) {
503  d->backButton->hide();
504  d->forwardButton->hide();
505  }
506  if (!(FontCombo & controls)) {
507  d->fontCombo->hide();
508  }
509  if (!(FontSize & controls)) {
510  d->fontSizeSpinBox->hide();
511  }
512  if (!(BlockCombos & controls)) {
513  d->sectionCombo->hide();
514  d->blockCombo->hide();
515  }
516 
517  QSplitter *splitter = new QSplitter(this);
518  if ((CharacterTable & controls) || (DetailBrowser & controls)) {
519  mainLayout->addWidget(splitter);
520  } else {
521  splitter->hide();
522  }
523  d->charTable = new KCharSelectTable(this, QFont());
524  if (CharacterTable & controls) {
525  splitter->addWidget(d->charTable);
526  } else {
527  d->charTable->hide();
528  }
529 
530  const QSize sz(200, 200);
531  d->charTable->resize(sz);
532  d->charTable->setMinimumSize(sz);
533 
534  d->charTable->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
535 
537 
538  connect(d->charTable, &KCharSelectTable::focusItemChanged, this, [this](uint c) {
539  d->updateCurrentChar(c);
540  });
541  connect(d->charTable, &KCharSelectTable::activated, this, [this](uint c) {
542  d->charSelected(c);
543  });
544  connect(d->charTable, &KCharSelectTable::showCharRequested, this, &KCharSelect::setCurrentCodePoint);
545 
546  d->detailBrowser = new QTextBrowser(this);
547  if (DetailBrowser & controls) {
548  splitter->addWidget(d->detailBrowser);
549  } else {
550  d->detailBrowser->hide();
551  }
552  d->detailBrowser->setOpenLinks(false);
553  connect(d->detailBrowser, &QTextBrowser::anchorClicked, this, [this](const QUrl &url) {
554  d->linkClicked(url);
555  });
556 
558  if (SearchLine & controls) {
559  setFocusProxy(d->searchLine);
560  } else {
561  setFocusProxy(d->charTable);
562  }
563 
564  d->sectionSelected(0); // this will also call blockSelected(0)
566 
567  d->historyEnabled = true;
568 }
569 
570 KCharSelect::~KCharSelect() = default;
571 
573 {
574  return QWidget::sizeHint();
575 }
576 
578 {
579  d->fontCombo->setCurrentFont(_font);
580  d->fontSizeSpinBox->setValue(_font.pointSize());
581  d->fontSelected();
582 }
583 
585 {
586  d->allPlanesEnabled = all;
587 }
588 
590 {
591  return d->allPlanesEnabled;
592 }
593 
595 {
596  if (d->allPlanesEnabled) {
597  qFatal("You must use KCharSelect::currentCodePoint instead of KCharSelect::currentChar");
598  }
599  return QChar(d->charTable->chr());
600 }
601 
603 {
604  return d->charTable->chr();
605 }
606 
608 {
609  return d->charTable->font();
610 }
611 
613 {
614  if (d->allPlanesEnabled) {
615  qFatal("You must use KCharSelect::displayedCodePoints instead of KCharSelect::displayedChars");
616  }
617  QList<QChar> result;
618  const auto displayedChars = d->charTable->displayedChars();
619  result.reserve(displayedChars.size());
620  for (uint c : displayedChars) {
621  result.append(QChar(c));
622  }
623  return result;
624 }
625 
627 {
628  return d->charTable->displayedChars();
629 }
630 
632 {
633  if (d->allPlanesEnabled) {
634  qCritical("You should use KCharSelect::setCurrentCodePoint instead of KCharSelect::setCurrentChar");
635  }
637 }
638 
640 {
641  if (!d->allPlanesEnabled && QChar::requiresSurrogates(c)) {
642  qCritical("You must setAllPlanesEnabled(true) to use non-BMP characters");
644  }
645  if (c > QChar::LastValidCodePoint) {
646  qCWarning(KWidgetsAddonsLog, "Code point outside Unicode range");
648  }
649  bool oldHistoryEnabled = d->historyEnabled;
650  d->historyEnabled = false;
651  int block = s_data()->blockIndex(c);
652  int section = s_data()->sectionIndex(block);
653  d->sectionCombo->setCurrentIndex(section);
654  int index = d->blockCombo->findData(block);
655  if (index != -1) {
656  d->blockCombo->setCurrentIndex(index);
657  }
658  d->historyEnabled = oldHistoryEnabled;
659  d->charTable->setChar(c);
660 }
661 
662 void KCharSelectPrivate::historyAdd(uint c, bool fromSearch, const QString &searchString)
663 {
664  // qCDebug(KWidgetsAddonsLog) << "about to add char" << c << "fromSearch" << fromSearch << "searchString" << searchString;
665 
666  if (!historyEnabled) {
667  return;
668  }
669 
670  if (!history.isEmpty() && c == history.last().c) {
671  // avoid duplicates
672  return;
673  }
674 
675  // behave like a web browser, i.e. if user goes back from B to A then clicks C, B is forgotten
676  while (!history.isEmpty() && inHistory != history.count() - 1) {
677  history.removeLast();
678  }
679 
680  while (history.size() >= MaxHistoryItems) {
681  history.removeFirst();
682  }
683 
684  HistoryItem item;
685  item.c = c;
686  item.fromSearch = fromSearch;
687  item.searchString = searchString;
688  history.append(item);
689 
690  inHistory = history.count() - 1;
691  updateBackForwardButtons();
692 }
693 
694 void KCharSelectPrivate::showFromHistory(int index)
695 {
696  Q_ASSERT(index >= 0 && index < history.count());
697  Q_ASSERT(index != inHistory);
698 
699  inHistory = index;
700  updateBackForwardButtons();
701 
702  const HistoryItem &item = history[index];
703  // qCDebug(KWidgetsAddonsLog) << "index" << index << "char" << item.c << "fromSearch" << item.fromSearch
704  // << "searchString" << item.searchString;
705 
706  // avoid adding an item from history into history again
707  bool oldHistoryEnabled = historyEnabled;
708  historyEnabled = false;
709  if (item.fromSearch) {
710  if (searchLine->text() != item.searchString) {
711  searchLine->setText(item.searchString);
712  search();
713  }
714  charTable->setChar(item.c);
715  } else {
716  searchLine->clear();
717  q->setCurrentCodePoint(item.c);
718  }
719  historyEnabled = oldHistoryEnabled;
720 }
721 
722 void KCharSelectPrivate::updateBackForwardButtons()
723 {
724  backButton->setEnabled(inHistory > 0);
725  forwardButton->setEnabled(inHistory < history.count() - 1);
726 }
727 
728 void KCharSelectPrivate::activateSearchLine()
729 {
730  searchLine->setFocus();
731  searchLine->selectAll();
732 }
733 
734 void KCharSelectPrivate::back()
735 {
736  Q_ASSERT(inHistory > 0);
737  showFromHistory(inHistory - 1);
738 }
739 
740 void KCharSelectPrivate::forward()
741 {
742  Q_ASSERT(inHistory + 1 < history.count());
743  showFromHistory(inHistory + 1);
744 }
745 
746 void KCharSelectPrivate::fontSelected()
747 {
748  QFont font = fontCombo->currentFont();
749  font.setPointSize(fontSizeSpinBox->value());
750  charTable->setFont(font);
751  Q_EMIT q->currentFontChanged(font);
752 }
753 
754 void KCharSelectPrivate::charSelected(uint c)
755 {
756  if (!allPlanesEnabled) {
757  Q_EMIT q->charSelected(QChar(c));
758  }
759  Q_EMIT q->codePointSelected(c);
760 }
761 
762 void KCharSelectPrivate::updateCurrentChar(uint c)
763 {
764  if (!allPlanesEnabled) {
765  Q_EMIT q->currentCharChanged(QChar(c));
766  }
767  Q_EMIT q->currentCodePointChanged(c);
768  if (searchMode) {
769  // we are in search mode. make the two comboboxes show the section & block for this character.
770  //(when we are not in search mode the current character always belongs to the current section & block.)
771  int block = s_data()->blockIndex(c);
772  int section = s_data()->sectionIndex(block);
773  sectionCombo->setCurrentIndex(section);
774  int index = blockCombo->findData(block);
775  if (index != -1) {
776  blockCombo->setCurrentIndex(index);
777  }
778  }
779 
780  if (searchLine) {
781  historyAdd(c, searchMode, searchLine->text());
782  }
783 
784  slotUpdateUnicode(c);
785 }
786 
787 void KCharSelectPrivate::slotUpdateUnicode(uint c)
788 {
789  QString html = QLatin1String("<p>") + tr("Character:") + QLatin1Char(' ') + s_data()->display(c, charTable->font()) + QLatin1Char(' ')
790  + s_data()->formatCode(c) + QLatin1String("<br />");
791 
792  QString name = s_data()->name(c);
793  if (!name.isEmpty()) {
794  // is name ever empty? </p> should always be there...
795  html += tr("Name: ") + name.toHtmlEscaped() + QLatin1String("</p>");
796  }
797  const QStringList aliases = s_data()->aliases(c);
798  const QStringList notes = s_data()->notes(c);
799  const QVector<uint> seeAlso = s_data()->seeAlso(c);
800  const QStringList equivalents = s_data()->equivalents(c);
801  const QStringList approxEquivalents = s_data()->approximateEquivalents(c);
802  const QVector<uint> decomposition = s_data()->decomposition(c);
803  if (!(aliases.isEmpty() && notes.isEmpty() && seeAlso.isEmpty() && equivalents.isEmpty() && approxEquivalents.isEmpty() && decomposition.isEmpty())) {
804  html += QLatin1String("<p><b>") + tr("Annotations and Cross References") + QLatin1String("</b></p>");
805  }
806 
807  if (!aliases.isEmpty()) {
808  html += QLatin1String("<p style=\"margin-bottom: 0px;\">") + tr("Alias names:") + QLatin1String("</p><ul style=\"margin-top: 0px;\">");
809  for (const QString &alias : aliases) {
810  html += QLatin1String("<li>") + alias.toHtmlEscaped() + QLatin1String("</li>");
811  }
812  html += QLatin1String("</ul>");
813  }
814 
815  if (!notes.isEmpty()) {
816  html += QLatin1String("<p style=\"margin-bottom: 0px;\">") + tr("Notes:") + QLatin1String("</p><ul style=\"margin-top: 0px;\">");
817  for (const QString &note : notes) {
818  html += QLatin1String("<li>") + createLinks(note.toHtmlEscaped()) + QLatin1String("</li>");
819  }
820  html += QLatin1String("</ul>");
821  }
822 
823  if (!seeAlso.isEmpty()) {
824  html += QLatin1String("<p style=\"margin-bottom: 0px;\">") + tr("See also:") + QLatin1String("</p><ul style=\"margin-top: 0px;\">");
825  for (uint c2 : seeAlso) {
827  continue;
828  }
829  html += QLatin1String("<li><a href=\"") + QString::number(c2, 16) + QLatin1String("\">");
830  if (s_data()->isPrint(c2)) {
831  html += QLatin1String("&#8206;&#") + QString::number(c2) + QLatin1String("; ");
832  }
833  html += s_data()->formatCode(c2) + QLatin1Char(' ') + s_data()->name(c2).toHtmlEscaped() + QLatin1String("</a></li>");
834  }
835  html += QLatin1String("</ul>");
836  }
837 
838  if (!equivalents.isEmpty()) {
839  html += QLatin1String("<p style=\"margin-bottom: 0px;\">") + tr("Equivalents:") + QLatin1String("</p><ul style=\"margin-top: 0px;\">");
840  for (const QString &equivalent : equivalents) {
841  html += QLatin1String("<li>") + createLinks(equivalent.toHtmlEscaped()) + QLatin1String("</li>");
842  }
843  html += QLatin1String("</ul>");
844  }
845 
846  if (!approxEquivalents.isEmpty()) {
847  html += QLatin1String("<p style=\"margin-bottom: 0px;\">") + tr("Approximate equivalents:") + QLatin1String("</p><ul style=\"margin-top: 0px;\">");
848  for (const QString &approxEquivalent : approxEquivalents) {
849  html += QLatin1String("<li>") + createLinks(approxEquivalent.toHtmlEscaped()) + QLatin1String("</li>");
850  }
851  html += QLatin1String("</ul>");
852  }
853 
854  if (!decomposition.isEmpty()) {
855  html += QLatin1String("<p style=\"margin-bottom: 0px;\">") + tr("Decomposition:") + QLatin1String("</p><ul style=\"margin-top: 0px;\">");
856  for (uint c2 : decomposition) {
858  continue;
859  }
860  html += QLatin1String("<li>") + createLinks(s_data()->formatCode(c2, 4, QString())) + QLatin1String("</li>");
861  }
862  html += QLatin1String("</ul>");
863  }
864 
865  QStringList unihan = s_data()->unihanInfo(c);
866  if (unihan.count() == 7) {
867  html += QLatin1String("<p><b>") + tr("CJK Ideograph Information") + QLatin1String("</b></p><p>");
868  bool newline = true;
869  if (!unihan[0].isEmpty()) {
870  html += tr("Definition in English: ") + unihan[0];
871  newline = false;
872  }
873  if (!unihan[2].isEmpty()) {
874  if (!newline) {
875  html += QLatin1String("<br>");
876  }
877  html += tr("Mandarin Pronunciation: ") + unihan[2];
878  newline = false;
879  }
880  if (!unihan[1].isEmpty()) {
881  if (!newline) {
882  html += QLatin1String("<br>");
883  }
884  html += tr("Cantonese Pronunciation: ") + unihan[1];
885  newline = false;
886  }
887  if (!unihan[6].isEmpty()) {
888  if (!newline) {
889  html += QLatin1String("<br>");
890  }
891  html += tr("Japanese On Pronunciation: ") + unihan[6];
892  newline = false;
893  }
894  if (!unihan[5].isEmpty()) {
895  if (!newline) {
896  html += QLatin1String("<br>");
897  }
898  html += tr("Japanese Kun Pronunciation: ") + unihan[5];
899  newline = false;
900  }
901  if (!unihan[3].isEmpty()) {
902  if (!newline) {
903  html += QLatin1String("<br>");
904  }
905  html += tr("Tang Pronunciation: ") + unihan[3];
906  newline = false;
907  }
908  if (!unihan[4].isEmpty()) {
909  if (!newline) {
910  html += QLatin1String("<br>");
911  }
912  html += tr("Korean Pronunciation: ") + unihan[4];
913  newline = false;
914  }
915  html += QLatin1String("</p>");
916  }
917 
918  html += QLatin1String("<p><b>") + tr("General Character Properties") + QLatin1String("</b><br>");
919  html += tr("Block: ") + s_data()->block(c) + QLatin1String("<br>");
920  html += tr("Unicode category: ") + s_data()->categoryText(s_data()->category(c)) + QLatin1String("</p>");
921 
922  const QByteArray utf8 = QString::fromUcs4(&c, 1).toUtf8();
923 
924  html += QLatin1String("<p><b>") + tr("Various Useful Representations") + QLatin1String("</b><br>");
925  html += tr("UTF-8:");
926  for (unsigned char c : utf8) {
927  html += QLatin1Char(' ') + s_data()->formatCode(c, 2, QStringLiteral("0x"));
928  }
929  html += QLatin1String("<br>") + tr("UTF-16: ");
930  if (QChar::requiresSurrogates(c)) {
931  html += s_data()->formatCode(QChar::highSurrogate(c), 4, QStringLiteral("0x"));
932  html += QLatin1Char(' ') + s_data->formatCode(QChar::lowSurrogate(c), 4, QStringLiteral("0x"));
933  } else {
934  html += s_data()->formatCode(c, 4, QStringLiteral("0x"));
935  }
936  html += QLatin1String("<br>") + tr("C octal escaped UTF-8: ");
937  for (unsigned char c : utf8) {
938  html += s_data()->formatCode(c, 3, QStringLiteral("\\"), 8);
939  }
940  html += QLatin1String("<br>") + tr("XML decimal entity:") + QLatin1String(" &amp;#") + QString::number(c) + QLatin1String(";</p>");
941 
942  detailBrowser->setHtml(html);
943 }
944 
945 QString KCharSelectPrivate::createLinks(QString s)
946 {
947  static const QRegularExpression rx(QStringLiteral("\\b([\\dABCDEF]{4,5})\\b"), QRegularExpression::UseUnicodePropertiesOption);
950  QSet<QString> chars;
951  while (iter.hasNext()) {
952  match = iter.next();
953  chars.insert(match.captured(1));
954  }
955 
956  for (const QString &c : qAsConst(chars)) {
957  int unicode = c.toInt(nullptr, 16);
958  if (!allPlanesEnabled && QChar::requiresSurrogates(unicode)) {
959  continue;
960  }
961  QString link = QLatin1String("<a href=\"") + c + QLatin1String("\">");
962  if (s_data()->isPrint(unicode)) {
963  link += QLatin1String("&#8206;&#") + QString::number(unicode) + QLatin1String(";&nbsp;");
964  }
965  link += QLatin1String("U+") + c + QLatin1Char(' ');
966  link += s_data()->name(unicode).toHtmlEscaped() + QLatin1String("</a>");
967  s.replace(c, link);
968  }
969  return s;
970 }
971 
972 void KCharSelectPrivate::sectionSelected(int index)
973 {
974  blockCombo->clear();
975  const QVector<int> blocks = s_data()->sectionContents(index);
976  for (int block : blocks) {
977  if (!allPlanesEnabled) {
978  const QVector<uint> contents = s_data()->blockContents(block);
979  if (!contents.isEmpty() && QChar::requiresSurrogates(contents.at(0))) {
980  continue;
981  }
982  }
983  blockCombo->addItem(s_data()->blockName(block), QVariant(block));
984  }
985  blockCombo->setCurrentIndex(0);
986 }
987 
988 void KCharSelectPrivate::blockSelected(int index)
989 {
990  if (index == -1) {
991  // the combo box has been cleared and is about to be filled again (because the section has changed)
992  return;
993  }
994  if (searchMode) {
995  // we are in search mode, so don't fill the table with this block.
996  return;
997  }
998 
999  int block = blockCombo->itemData(index).toInt();
1000  const QVector<uint> contents = s_data()->blockContents(block);
1001  charTable->setContents(contents);
1002  Q_EMIT q->displayedCharsChanged();
1003  charTable->setChar(contents[0]);
1004 }
1005 
1006 void KCharSelectPrivate::searchEditChanged()
1007 {
1008  if (searchLine->text().isEmpty()) {
1009  sectionCombo->setEnabled(true);
1010  blockCombo->setEnabled(true);
1011 
1012  // upon leaving search mode, keep the same character selected
1013  searchMode = false;
1014  uint c = charTable->chr();
1015  bool oldHistoryEnabled = historyEnabled;
1016  historyEnabled = false;
1017  blockSelected(blockCombo->currentIndex());
1018  historyEnabled = oldHistoryEnabled;
1019  q->setCurrentCodePoint(c);
1020  } else {
1021  sectionCombo->setEnabled(false);
1022  blockCombo->setEnabled(false);
1023 
1024  int length = searchLine->text().length();
1025  if (length >= 3) {
1026  search();
1027  }
1028  }
1029 }
1030 
1031 void KCharSelectPrivate::search()
1032 {
1033  if (searchLine->text().isEmpty()) {
1034  return;
1035  }
1036  searchMode = true;
1037  QVector<uint> contents = s_data()->find(searchLine->text());
1038  if (!allPlanesEnabled) {
1039  QVector<uint>::iterator it = contents.begin();
1040  while (it != contents.end()) {
1041  if (QChar::requiresSurrogates(*it)) {
1042  it = contents.erase(it);
1043  } else {
1044  ++it;
1045  }
1046  }
1047  }
1048  charTable->setContents(contents);
1049  Q_EMIT q->displayedCharsChanged();
1050  if (!contents.isEmpty()) {
1051  charTable->setChar(contents[0]);
1052  }
1053 }
1054 
1055 void KCharSelectPrivate::linkClicked(QUrl url)
1056 {
1057  QString hex = url.toString();
1058  if (hex.size() > 6) {
1059  return;
1060  }
1061  int unicode = hex.toInt(nullptr, 16);
1062  if (unicode > QChar::LastValidCodePoint) {
1063  return;
1064  }
1065  searchLine->clear();
1066  q->setCurrentCodePoint(unicode);
1067 }
1068 
1069 ////
1070 
1071 QVariant KCharSelectItemModel::data(const QModelIndex &index, int role) const
1072 {
1073  int pos = m_columns * (index.row()) + index.column();
1074  if (!index.isValid() || pos < 0 || pos >= m_chars.size() || index.row() < 0 || index.column() < 0) {
1075  if (role == Qt::BackgroundRole) {
1076  return QVariant(qApp->palette().color(QPalette::Button));
1077  }
1078  return QVariant();
1079  }
1080 
1081  uint c = m_chars[pos];
1082  if (role == Qt::ToolTipRole) {
1083  QString result = s_data()->display(c, m_font) + QLatin1String("<br />") + s_data()->name(c).toHtmlEscaped() + QLatin1String("<br />")
1084  + tr("Unicode code point:") + QLatin1Char(' ') + s_data()->formatCode(c) + QLatin1String("<br />") + tr("In decimal", "Character")
1085  + QLatin1Char(' ') + QString::number(c);
1086  return QVariant(result);
1087  } else if (role == Qt::TextAlignmentRole) {
1089  } else if (role == Qt::DisplayRole) {
1090  if (s_data()->isPrint(c)) {
1091  return QVariant(QString::fromUcs4(&c, 1));
1092  }
1093  return QVariant();
1094  } else if (role == Qt::BackgroundRole) {
1095  QFontMetrics fm = QFontMetrics(m_font);
1096  if (fm.inFontUcs4(c) && s_data()->isPrint(c)) {
1097  return QVariant(qApp->palette().color(QPalette::Base));
1098  } else {
1099  return QVariant(qApp->palette().color(QPalette::Button));
1100  }
1101  } else if (role == Qt::FontRole) {
1102  return QVariant(m_font);
1103  } else if (role == CharacterRole) {
1104  return QVariant(c);
1105  }
1106  return QVariant();
1107 }
1108 
1109 bool KCharSelectItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
1110 {
1111  Q_UNUSED(row)
1112  Q_UNUSED(parent)
1113  if (action == Qt::IgnoreAction) {
1114  return true;
1115  }
1116 
1117  if (!data->hasText()) {
1118  return false;
1119  }
1120 
1121  if (column > 0) {
1122  return false;
1123  }
1124  QString text = data->text();
1125  if (text.isEmpty()) {
1126  return false;
1127  }
1128  Q_EMIT showCharRequested(text.toUcs4().at(0));
1129  return true;
1130 }
1131 
1132 void KCharSelectItemModel::setColumnCount(int columns)
1133 {
1134  if (columns == m_columns) {
1135  return;
1136  }
1137  Q_EMIT layoutAboutToBeChanged();
1138  m_columns = columns;
1139  Q_EMIT layoutChanged();
1140 }
1141 
1142 #include "moc_kcharselect.cpp"
1143 #include "moc_kcharselect_p.cpp"
bool isRightToLeft()
void setText(const QString &text)
QModelIndexList indexes() const const
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
QString captured(int nth) const const
ScrollBarAlwaysOff
void triggered(bool checked)
void setPointSize(int pointSize)
QString & append(QChar ch)
StrongFocus
void setContentsMargins(int left, int top, int right, int bottom)
const QPalette & palette() const const
int width() const const
KCharSelect(QWidget *parent, const Controls controls=AllGuiElements)
Constructor.
ushort lowSurrogate(uint ucs4)
void setColor(QPalette::ColorGroup group, QPalette::ColorRole role, const QColor &color)
QVector< uint > displayedCodePoints() const
Returns a list of Unicode code points of the currently displayed characters.
QVector::iterator begin()
QRegularExpressionMatchIterator globalMatch(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
void setFrameShape(QFrame::Shape)
void setFocusPolicy(Qt::FocusPolicy policy)
void reserve(int alloc)
Shows the font size spin box.
Definition: kcharselect.h:87
void setIcon(const QIcon &icon)
int size() const const
QVector::iterator erase(QVector::iterator begin, QVector::iterator end)
void textChanged(const QString &text)
virtual void resizeEvent(QResizeEvent *event) override
Shows the search widgets.
Definition: kcharselect.h:79
bool hasText() const const
QSet::iterator insert(const T &value)
Shows the detail browser.
Definition: kcharselect.h:99
bool allPlanesEnabled() const
QString toString(QUrl::FormattingOptions options) const const
QString tr(const char *sourceText, const char *disambiguation, int n)
AlignHCenter
void addWidget(QWidget *widget)
Shows the category/block selection combo boxes.
Definition: kcharselect.h:91
void resizeSection(int logicalIndex, int size)
virtual void scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)=0
void clear()
void valueChanged(int i)
void setAllPlanesEnabled(bool all)
Sets the allowed Unicode code planes.
void timeout()
bool isValid() const const
QRect boundingRect(QChar ch) const const
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
QString number(int n, int base)
QRegularExpressionMatch next()
int count(const T &value) const const
void append(const T &value)
void setShortcuts(const QList< QKeySequence > &shortcuts)
QChar currentChar() const
Returns the currently selected character.
uint toUInt(bool *ok) const const
void setMinimumSectionSize(int size)
const QList< QKeySequence > & back()
bool inFontUcs4(uint ucs4) const const
QString text() const const
bool inherits(const char *className) const const
Shows the Back/Forward buttons.
Definition: kcharselect.h:103
void setCurrentCodePoint(uint codePoint)
Highlights the character with the specified codePoint.
const QSize & oldSize() const const
QPalette::ColorRole backgroundRole() const const
int toInt(bool *ok, int base) const const
Shows the actual table.
Definition: kcharselect.h:95
bool isEmpty() const const
BackgroundRole
void setFocusProxy(QWidget *w)
bool isEmpty() const const
void clicked(bool checked)
int row() const const
QList< QChar > displayedChars() const
Returns a list of currently displayed characters.
QPoint pos() const const
QString fromUcs4(const uint *unicode, int size)
void setCurrentChar(const QChar &c)
Highlights the character c.
QSize sizeHint() const override
Reimplemented.
void hide()
Shows the font combo box.
Definition: kcharselect.h:83
const QList< QKeySequence > & forward()
ushort unicode() const const
void setAcceptDrops(bool on)
int key() const const
QString toHtmlEscaped() const const
void setCurrentFont(const QFont &font)
Sets the font which is displayed to font.
const QSize & size() const const
void setParent(QObject *parent)
void anchorClicked(const QUrl &link)
void setFont(const QFont &)
int xHeight() const const
virtual QSize sizeHint() const const
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
void setFrameShadow(QFrame::Shadow)
Character selection widget.
Definition: kcharselect.h:60
void returnPressed()
QString & replace(int position, int n, QChar after)
const T & at(int i) const const
PM_FocusFrameHMargin
int width() const const
QFontMetrics fontMetrics() const const
bool isEmpty() const const
int count() const const
bool isValid(QStringView ifopt)
int height() const const
ElideNone
int column() const const
ushort highSurrogate(uint ucs4)
QIcon fromTheme(const QString &name)
QList< QKeySequence > keyBindings(QKeySequence::StandardKey key)
virtual void keyPressEvent(QKeyEvent *event) override
QVector< uint > toUcs4() const const
bool requiresSurrogates(uint ucs4)
QVariant::Type type() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
DropAction
int size() const const
QVector::iterator end()
QFont currentFont() const
Returns the currently displayed font.
uint currentCodePoint() const
Returns the Unicode code point of the currently selected character.
Key_Space
int lineSpacing() const const
int pointSize() const const
Q_EMITQ_EMIT
void animateClick(int msec)
void currentIndexChanged(int index)
void addLayout(QLayout *layout, int stretch)
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Jul 29 2021 22:44:32 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.