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") + "&nbsp;</b></td><td>" + toStr(cat) + "</td></tr>";
153     details +=
154         "<tr><td align=\"right\"><b>" + i18n("UCS-4") + "&nbsp;</b></td><td>" + "U+" + QStringLiteral("%1").arg(itsItem.ucs4, 4, 16) + "&nbsp;</td></tr>";
155 
156     QString str(QString::fromUcs4(&(itsItem.ucs4), 1));
157     details += "<tr><td align=\"right\"><b>" + i18n("UTF-16") + "&nbsp;</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") + "&nbsp;</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") + "&nbsp;</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