File indexing completed on 2024-05-12 05:52:08
0001 /* 0002 SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com> 0003 SPDX-License-Identifier: LGPL-2.0-or-later 0004 */ 0005 #include "tooltip.h" 0006 0007 #include <QApplication> 0008 #include <QEvent> 0009 #include <QFontMetrics> 0010 #include <QMouseEvent> 0011 #include <QPointer> 0012 #include <QScreen> 0013 #include <QScrollBar> 0014 #include <QString> 0015 #include <QTextBrowser> 0016 #include <QTimer> 0017 0018 #include <KSyntaxHighlighting/Definition> 0019 #include <KSyntaxHighlighting/Repository> 0020 #include <KSyntaxHighlighting/SyntaxHighlighter> 0021 #include <KTextEditor/Document> 0022 #include <KTextEditor/Editor> 0023 #include <KTextEditor/View> 0024 #include <KWindowSystem> 0025 0026 // #include <ktexteditor_utils.h> 0027 0028 class TooltipPrivate : public QTextBrowser 0029 { 0030 Q_OBJECT 0031 0032 public: 0033 void setTooltipText(const QString &text, TextHintMarkupKind kind) 0034 { 0035 if (text.isEmpty()) 0036 return; 0037 0038 m_kind = kind; 0039 // we have to do this to handle soft line 0040 if (kind == TextHintMarkupKind::PlainText) { 0041 setPlainText(text); 0042 } else { 0043 QString htext = text; 0044 htext.replace(QLatin1Char('\n'), QStringLiteral(" \n")); 0045 setMarkdown(htext); 0046 } 0047 resizeTip(text); 0048 } 0049 0050 void appendMarkdown(const QString &text) 0051 { 0052 auto md = toMarkdown(); 0053 // hbreak 0054 md += QStringLiteral("\n----\n"); 0055 md += text; 0056 setMarkdown(md); 0057 } 0058 0059 void appendTooltipText(const QString &text, TextHintMarkupKind kind) 0060 { 0061 auto cursor = textCursor(); 0062 cursor.movePosition(QTextCursor::End); 0063 if (m_kind == TextHintMarkupKind::PlainText && kind == TextHintMarkupKind::PlainText) { 0064 cursor.insertText(QStringLiteral("\n")); 0065 cursor.insertText(text); 0066 } else if (m_kind == TextHintMarkupKind::MarkDown && kind == TextHintMarkupKind::MarkDown) { 0067 appendMarkdown(text); 0068 } else if (m_kind == TextHintMarkupKind::PlainText && kind == TextHintMarkupKind::MarkDown) { 0069 appendMarkdown(text); 0070 } else if (m_kind == TextHintMarkupKind::MarkDown && kind == TextHintMarkupKind::PlainText) { 0071 appendMarkdown(text); 0072 } 0073 0074 // resize if too small 0075 if (height() < 300) { 0076 resize(width(), height() + 200); 0077 } 0078 } 0079 0080 void setView(KTextEditor::View *view) 0081 { 0082 // view changed? 0083 // => update definition 0084 // => update font 0085 if (view != m_view) { 0086 if (m_view && m_view->focusProxy()) { 0087 m_view->focusProxy()->removeEventFilter(this); 0088 } 0089 0090 m_view = view; 0091 0092 hl.setDefinition(KTextEditor::Editor::instance()->repository().definitionForFileName(m_view->document()->url().toString())); 0093 0094 if (m_view && m_view->focusProxy()) { 0095 m_view->focusProxy()->installEventFilter(this); 0096 } 0097 } 0098 } 0099 0100 TooltipPrivate(QWidget *parent, bool manual) 0101 : QTextBrowser(parent) 0102 , hl(document()) 0103 , m_manual(manual) 0104 { 0105 setWindowFlags(Qt::FramelessWindowHint | Qt::BypassGraphicsProxyWidget | Qt::ToolTip); 0106 setAttribute(Qt::WA_DeleteOnClose, true); 0107 document()->setDocumentMargin(5); 0108 setFrameStyle(QFrame::Box | QFrame::Raised); 0109 connect(&m_hideTimer, &QTimer::timeout, this, &TooltipPrivate::hideTooltip); 0110 0111 setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0112 setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0113 0114 // doc links 0115 setOpenExternalLinks(true); 0116 0117 auto updateColors = [this](KTextEditor::Editor *e) { 0118 auto theme = e->theme(); 0119 hl.setTheme(theme); 0120 0121 auto pal = palette(); 0122 const QColor bg = theme.editorColor(KSyntaxHighlighting::Theme::BackgroundColor); 0123 pal.setColor(QPalette::Base, bg); 0124 const QColor normal = theme.textColor(KSyntaxHighlighting::Theme::Normal); 0125 pal.setColor(QPalette::Text, normal); 0126 setPalette(pal); 0127 0128 setFont(KTextEditor::Editor::instance()->font()); 0129 }; 0130 updateColors(KTextEditor::Editor::instance()); 0131 connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, updateColors); 0132 } 0133 0134 bool eventFilter(QObject *, QEvent *e) override 0135 { 0136 switch (e->type()) { 0137 // only consider KeyPress 0138 // a key release might get triggered by the trail of a shortcut key activation 0139 case QEvent::KeyPress: 0140 hideTooltip(); 0141 break; 0142 case QEvent::WindowActivate: 0143 case QEvent::WindowDeactivate: 0144 case QEvent::FocusOut: 0145 case QEvent::FocusIn: 0146 if (!inContextMenu && !m_view->hasFocus()) { 0147 hideTooltip(); 0148 } 0149 break; 0150 case QEvent::MouseMove: 0151 if (!m_manual && !hasFocus()) 0152 hideTooltipWithDelay(); 0153 break; 0154 case QEvent::MouseButtonPress: 0155 case QEvent::MouseButtonRelease: 0156 case QEvent::MouseButtonDblClick: 0157 case QEvent::Wheel: 0158 if (!rect().contains(static_cast<QSinglePointEvent *>(e)->position().toPoint())) { 0159 hideTooltip(); 0160 } 0161 break; 0162 default: 0163 break; 0164 } 0165 return false; 0166 } 0167 0168 Q_SLOT void hideTooltip() 0169 { 0170 deleteLater(); 0171 } 0172 0173 Q_SLOT void hideTooltipWithDelay() 0174 { 0175 m_hideTimer.start(100); 0176 } 0177 0178 void resizeTip(const QString &text) 0179 { 0180 QFontMetrics fm(font()); 0181 QSize size = fm.size(0, text); 0182 0183 const int height = fm.lineSpacing() * (document()->lineCount()); 0184 0185 size.setHeight(std::min(height, m_view->height() / 3)); 0186 size.setWidth(std::min<int>(size.width(), m_view->width() / 2.5)); 0187 0188 const int contentsMarginsWidth = this->contentsMargins().left() + this->contentsMargins().right(); 0189 const int contentsMarginsHeight = this->contentsMargins().top() + this->contentsMargins().top(); 0190 const int docMargin = 2 * document()->documentMargin(); 0191 const int wMargins = contentsMarginsWidth + docMargin + verticalScrollBar()->height(); 0192 const int hMargins = contentsMarginsHeight + docMargin + horizontalScrollBar()->height(); 0193 0194 size.setWidth(size.width() + wMargins); 0195 size.setHeight(size.height() + hMargins); 0196 resize(size); 0197 } 0198 0199 void place(QPoint p) 0200 { 0201 const auto offset = QPoint(3, 21); 0202 p += offset; 0203 0204 // wayland automatically keeps popups on screen 0205 if (KWindowSystem::isPlatformWayland()) { 0206 move(p); 0207 return; 0208 } 0209 0210 // try to get right screen, important: QApplication::screenAt(p) might return nullptr 0211 // see crash in bug 439804 0212 const QScreen *screenForTooltip = QApplication::screenAt(p); 0213 if (!screenForTooltip) { 0214 screenForTooltip = screen(); 0215 } 0216 const QRect screen = screenForTooltip->availableGeometry(); 0217 0218 if (p.x() + width() > screen.x() + screen.width()) 0219 p.rx() -= 4 + width(); 0220 if (p.y() + this->height() > screen.y() + screen.height()) 0221 p.ry() -= 24 + this->height(); 0222 if (p.y() < screen.y()) 0223 p.setY(screen.y()); 0224 if (p.x() + this->width() > screen.x() + screen.width()) 0225 p.setX(screen.x() + screen.width() - this->width()); 0226 if (p.x() < screen.x()) 0227 p.setX(screen.x()); 0228 if (p.y() + this->height() > screen.y() + screen.height()) 0229 p.setY(screen.y() + screen.height() - this->height()); 0230 0231 this->move(p); 0232 } 0233 0234 protected: 0235 void enterEvent(QEnterEvent *event) override 0236 { 0237 inContextMenu = false; 0238 m_hideTimer.stop(); 0239 QTextBrowser::enterEvent(event); 0240 } 0241 0242 void leaveEvent(QEvent *event) override 0243 { 0244 if (!m_hideTimer.isActive() && !inContextMenu) { 0245 hideTooltip(); 0246 } 0247 QTextBrowser::leaveEvent(event); 0248 } 0249 0250 void mouseMoveEvent(QMouseEvent *event) override 0251 { 0252 auto pos = event->pos(); 0253 if (rect().contains(pos)) { 0254 return QTextBrowser::mouseMoveEvent(event); 0255 } 0256 } 0257 0258 void contextMenuEvent(QContextMenuEvent *e) override 0259 { 0260 inContextMenu = true; 0261 QTextBrowser::contextMenuEvent(e); 0262 } 0263 0264 private: 0265 bool inContextMenu = false; 0266 QPointer<KTextEditor::View> m_view; 0267 QTimer m_hideTimer; 0268 KSyntaxHighlighting::SyntaxHighlighter hl; 0269 bool m_manual; 0270 TextHintMarkupKind m_kind = TextHintMarkupKind::PlainText; 0271 }; 0272 0273 void KateTooltip::show(const QString &text, TextHintMarkupKind kind, QPoint pos, KTextEditor::View *v, bool manual) 0274 { 0275 if (text.isEmpty()) 0276 return; 0277 0278 if (!v || !v->document()) { 0279 return; 0280 } 0281 0282 static QPointer<TooltipPrivate> tooltip = nullptr; 0283 if (tooltip && tooltip->isVisible()) { 0284 tooltip->appendTooltipText(text, kind); 0285 return; 0286 } 0287 delete tooltip; 0288 0289 tooltip = new TooltipPrivate(v, manual); 0290 tooltip->setView(v); 0291 tooltip->setTooltipText(text, kind); 0292 tooltip->place(pos); 0293 tooltip->show(); 0294 } 0295 0296 #include "tooltip.moc"