1 /*
2 Inspired by konq_filetip.cc
3
4 SPDX-FileCopyrightText: 2003-2007 Craig Drummond <craig@kde.org>
5 SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
6 SPDX-FileCopyrightText: 2000, 2001, 2002 David Faure <faure@kde.org>
7 SPDX-FileCopyrightText: 2004 Martin Koller <m.koller@surfeu.at>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12 #include "CharTip.h"
13 #include "FontPreview.h"
14 #include "UnicodeCategories.h"
15 #include <QApplication>
16 #include <QBoxLayout>
17 #include <QEvent>
18 #include <QLabel>
19 #include <QPixmap>
20 #include <QResizeEvent>
21 #include <QScreen>
22 #include <QTimer>
23 #include <QToolTip>
24
25 namespace KFI
26 {
getCategory(quint32 ucs2)27 EUnicodeCategory getCategory(quint32 ucs2)
28 {
29 for (int i = 0; UNICODE_INVALID != constUnicodeCategoryList[i].category; ++i) {
30 if (constUnicodeCategoryList[i].start <= ucs2 && constUnicodeCategoryList[i].end >= ucs2) {
31 return constUnicodeCategoryList[i].category;
32 }
33 }
34
35 return UNICODE_UNASSIGNED;
36 }
37
toStr(EUnicodeCategory cat)38 static QString toStr(EUnicodeCategory cat)
39 {
40 switch (cat) {
41 case UNICODE_CONTROL:
42 return i18n("Other, Control");
43 case UNICODE_FORMAT:
44 return i18n("Other, Format");
45 case UNICODE_UNASSIGNED:
46 return i18n("Other, Not Assigned");
47 case UNICODE_PRIVATE_USE:
48 return i18n("Other, Private Use");
49 case UNICODE_SURROGATE:
50 return i18n("Other, Surrogate");
51 case UNICODE_LOWERCASE_LETTER:
52 return i18n("Letter, Lowercase");
53 case UNICODE_MODIFIER_LETTER:
54 return i18n("Letter, Modifier");
55 case UNICODE_OTHER_LETTER:
56 return i18n("Letter, Other");
57 case UNICODE_TITLECASE_LETTER:
58 return i18n("Letter, Titlecase");
59 case UNICODE_UPPERCASE_LETTER:
60 return i18n("Letter, Uppercase");
61 case UNICODE_COMBINING_MARK:
62 return i18n("Mark, Spacing Combining");
63 case UNICODE_ENCLOSING_MARK:
64 return i18n("Mark, Enclosing");
65 case UNICODE_NON_SPACING_MARK:
66 return i18n("Mark, Non-Spacing");
67 case UNICODE_DECIMAL_NUMBER:
68 return i18n("Number, Decimal Digit");
69 case UNICODE_LETTER_NUMBER:
70 return i18n("Number, Letter");
71 case UNICODE_OTHER_NUMBER:
72 return i18n("Number, Other");
73 case UNICODE_CONNECT_PUNCTUATION:
74 return i18n("Punctuation, Connector");
75 case UNICODE_DASH_PUNCTUATION:
76 return i18n("Punctuation, Dash");
77 case UNICODE_CLOSE_PUNCTUATION:
78 return i18n("Punctuation, Close");
79 case UNICODE_FINAL_PUNCTUATION:
80 return i18n("Punctuation, Final Quote");
81 case UNICODE_INITIAL_PUNCTUATION:
82 return i18n("Punctuation, Initial Quote");
83 case UNICODE_OTHER_PUNCTUATION:
84 return i18n("Punctuation, Other");
85 case UNICODE_OPEN_PUNCTUATION:
86 return i18n("Punctuation, Open");
87 case UNICODE_CURRENCY_SYMBOL:
88 return i18n("Symbol, Currency");
89 case UNICODE_MODIFIER_SYMBOL:
90 return i18n("Symbol, Modifier");
91 case UNICODE_MATH_SYMBOL:
92 return i18n("Symbol, Math");
93 case UNICODE_OTHER_SYMBOL:
94 return i18n("Symbol, Other");
95 case UNICODE_LINE_SEPARATOR:
96 return i18n("Separator, Line");
97 case UNICODE_PARAGRAPH_SEPARATOR:
98 return i18n("Separator, Paragraph");
99 case UNICODE_SPACE_SEPARATOR:
100 return i18n("Separator, Space");
101 default:
102 return "";
103 }
104 }
105
CCharTip(CFontPreview * parent)106 CCharTip::CCharTip(CFontPreview *parent)
107 : QFrame(nullptr, Qt::ToolTip | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint)
108 , itsParent(parent)
109 {
110 itsPixmapLabel = new QLabel(this);
111 itsLabel = new QLabel(this);
112 itsTimer = new QTimer(this);
113
114 QBoxLayout *layout = new QBoxLayout(QBoxLayout::LeftToRight, this);
115 layout->setContentsMargins(8, 8, 8, 8);
116 layout->setSpacing(0);
117 layout->addWidget(itsPixmapLabel);
118 layout->addWidget(itsLabel);
119
120 setPalette(QToolTip::palette());
121 setFrameShape(QFrame::Box);
122 setFrameShadow(QFrame::Plain);
123 hide();
124 }
125
~CCharTip()126 CCharTip::~CCharTip()
127 {
128 }
129
setItem(const CFcEngine::TChar & ch)130 void CCharTip::setItem(const CFcEngine::TChar &ch)
131 {
132 hideTip();
133
134 itsItem = ch;
135 itsTimer->disconnect(this);
136 connect(itsTimer, &QTimer::timeout, this, &CCharTip::showTip);
137 itsTimer->setSingleShot(true);
138 itsTimer->start(300);
139 }
140
showTip()141 void CCharTip::showTip()
142 {
143 if (!itsParent->underMouse()) {
144 return;
145 }
146
147 static const int constPixSize = 96;
148
149 EUnicodeCategory cat(getCategory(itsItem.ucs4));
150 QString details("<table>");
151
152 details += "<tr><td align=\"right\"><b>" + i18n("Category") + " </b></td><td>" + toStr(cat) + "</td></tr>";
153 details +=
154 "<tr><td align=\"right\"><b>" + i18n("UCS-4") + " </b></td><td>" + "U+" + QStringLiteral("%1").arg(itsItem.ucs4, 4, 16) + " </td></tr>";
155
156 QString str(QString::fromUcs4(&(itsItem.ucs4), 1));
157 details += "<tr><td align=\"right\"><b>" + i18n("UTF-16") + " </b></td><td>";
158
159 const ushort *utf16(str.utf16());
160
161 for (int i = 0; utf16[i]; ++i) {
162 if (i) {
163 details += ' ';
164 }
165 details += QStringLiteral("0x%1").arg(utf16[i], 4, 16);
166 }
167 details += "</td></tr>";
168 details += "<tr><td align=\"right\"><b>" + i18n("UTF-8") + " </b></td><td>";
169
170 QByteArray utf8(str.toUtf8());
171
172 for (int i = 0; i < utf8.size(); ++i) {
173 if (i) {
174 details += ' ';
175 }
176 details += QStringLiteral("0x%1").arg((unsigned char)(utf8.constData()[i]), 2, 16);
177 }
178 details += "</td></tr>";
179
180 // Note: the "<b></b> below is just to stop Qt converting the xml entry into
181 // a character!
182 if ((0x0001 <= itsItem.ucs4 && itsItem.ucs4 <= 0xD7FF) || (0xE000 <= itsItem.ucs4 && itsItem.ucs4 <= 0xFFFD)
183 || (0x10000 <= itsItem.ucs4 && itsItem.ucs4 <= 0x10FFFF)) {
184 details +=
185 "<tr><td align=\"right\"><b>" + i18n("XML Decimal Entity") + " </b></td><td>" + "&#<b></b>" + QString::number(itsItem.ucs4) + ";</td></tr>";
186 }
187
188 details += "</table>";
189 itsLabel->setText(details);
190
191 QList<CFcEngine::TRange> range;
192 range.append(CFcEngine::TRange(itsItem.ucs4, 0));
193
194 QColor bgnd(Qt::white);
195 bgnd.setAlpha(0);
196
197 QImage img = itsParent->engine()->draw(itsParent->itsFontName,
198 itsParent->itsStyleInfo,
199 itsParent->itsCurrentFace - 1,
200 palette().text().color(),
201 bgnd,
202 constPixSize,
203 constPixSize,
204 false,
205 range,
206 nullptr);
207
208 if (!img.isNull()) {
209 itsPixmapLabel->setPixmap(QPixmap::fromImage(img));
210 } else {
211 itsPixmapLabel->setPixmap(QPixmap());
212 }
213
214 itsTimer->disconnect(this);
215 connect(itsTimer, &QTimer::timeout, this, &CCharTip::hideTip);
216 itsTimer->setSingleShot(true);
217 itsTimer->start(15000);
218
219 qApp->installEventFilter(this);
220 reposition();
221 show();
222 }
223
hideTip()224 void CCharTip::hideTip()
225 {
226 itsTimer->stop();
227 qApp->removeEventFilter(this);
228 hide();
229 }
230
reposition()231 void CCharTip::reposition()
232 {
233 QRect rect(itsItem);
234
235 rect.moveTopRight(itsParent->mapToGlobal(rect.topRight()));
236
237 QPoint pos(rect.center());
238 QRect desk(QApplication::screenAt(rect.center())->geometry());
239
240 if ((rect.center().x() + width()) > desk.right()) {
241 if (pos.x() - width() < 0) {
242 pos.setX(0);
243 } else {
244 pos.setX(pos.x() - width());
245 }
246 }
247 // should the tooltip be shown above or below the ivi ?
248 if (rect.bottom() + height() > desk.bottom()) {
249 pos.setY(rect.top() - height());
250 } else {
251 pos.setY(rect.bottom() + 1);
252 }
253
254 move(pos);
255 update();
256 }
257
resizeEvent(QResizeEvent * event)258 void CCharTip::resizeEvent(QResizeEvent *event)
259 {
260 QFrame::resizeEvent(event);
261 reposition();
262 }
263
eventFilter(QObject *,QEvent * e)264 bool CCharTip::eventFilter(QObject *, QEvent *e)
265 {
266 switch (e->type()) {
267 case QEvent::Leave:
268 case QEvent::MouseButtonPress:
269 case QEvent::MouseButtonRelease:
270 case QEvent::KeyPress:
271 case QEvent::KeyRelease:
272 case QEvent::FocusIn:
273 case QEvent::FocusOut:
274 case QEvent::Wheel:
275 hideTip();
276 default:
277 break;
278 }
279
280 return false;
281 }
282
283 }
284