#include "kristalltextbrowser.hpp" #include "kristall.hpp" #include #include #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif #include #include #include const Qt::CursorShape KristallTextBrowser::NORMAL_CURSOR = Qt::IBeamCursor; KristallTextBrowser::KristallTextBrowser(QWidget *parent) : QTextBrowser(parent) { connect(this, &QTextBrowser::anchorClicked, this, &KristallTextBrowser::on_anchorClicked); // Enable touch scrolling on touchscreen devices #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) for (int i = 0; i < QTouchDevice::devices().length(); ++i) { if (QTouchDevice::devices()[i]->type() == QTouchDevice::TouchScreen) { this->viewport()->setAttribute(Qt::WA_AcceptTouchEvents); QScroller::grabGesture(this, QScroller::LeftMouseButtonGesture); break; } } #else for (auto &device: QInputDevice::devices()) { if (device->type() == QInputDevice::DeviceType::TouchScreen) { this->viewport()->setAttribute(Qt::WA_AcceptTouchEvents); QScroller::grabGesture(this, QScroller::LeftMouseButtonGesture); break; } } #endif } void KristallTextBrowser::mouseReleaseEvent(QMouseEvent *event) { if(event->button() == Qt::MiddleButton) { // Deselect any selection. Otherwise the click won't register. setTextCursor(cursorForPosition(event->pos())); // Fake a middle-click event here QMouseEvent fake_event { event->type(), event->localPos(), Qt::LeftButton, event->buttons(), event->modifiers() }; this->signal_new_tab = true; QTextBrowser::mouseReleaseEvent(&fake_event); } else { this->signal_new_tab = event->modifiers().testFlag(Qt::ControlModifier); QTextBrowser::mouseReleaseEvent(event); } } void KristallTextBrowser::on_anchorClicked(const QUrl &url) { emit this->anchorClicked(url, this->signal_new_tab); } void KristallTextBrowser::mouseMoveEvent(QMouseEvent *event) { QTextBrowser::mouseMoveEvent(event); this->updateCursor(); } void KristallTextBrowser::setDefaultCursor(const QCursor &cur) { this->wanted_cursor = cur; this->updateCursor(); } void KristallTextBrowser::focusInEvent(QFocusEvent *event) { QTextBrowser::focusInEvent(event); this->updateCursor(); } void KristallTextBrowser::updateCursor() { // This slight hack allows us to set the "default" cursor, // (i.e when not hovering over links) We need to do this // because QTextBrowser for some reason resets viewport cursor // to ArrowCursor after we hover over links const QCursor& cur = this->viewport()->cursor(); if (cur.shape() != this->wanted_cursor.shape() && cur.shape() != Qt::PointingHandCursor) { this->viewport()->setCursor(wanted_cursor); } } void KristallTextBrowser::keyPressEvent(QKeyEvent *event) { if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_C) { this->betterCopy(); } else { QTextBrowser::keyPressEvent(event); } } void KristallTextBrowser::keyReleaseEvent(QKeyEvent *event) { if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_C) { // Eat the event } else { QTextBrowser::keyReleaseEvent(event); } } void KristallTextBrowser::betterCopy() { // Our own implementation of a copy. All we need to do is get the // selected text, adjust it if needed, and copy it. // // The main adjustment we make here is stripping "fancy" quotes from the text, // as these quotes are usually an annoyance when copying text. // // There is a little trick here though: if a user selects a piece of text // which consists only of whitespace and fancy quotes // the fancy quotes will be included in the copy. // // In all other cases the quotes will be replaced. This can be said to be // a usability feature for the few people that may want to actually copy // the quotes themselves. QTextCursor cursor = QTextBrowser::textCursor(); QString text = cursor.selectedText(); if (text.isEmpty()) return; // Check if text only consists of fancy quotes: static const QRegularExpression REGEX_ONLY_QUOTES(R"(^[\s“”‘’]+$)", QRegularExpression::CaseInsensitiveOption); if (text.contains(REGEX_ONLY_QUOTES)) { // Copy the original text. qApp->clipboard()->setText(text); return; } // Replace fancy quotes static const QRegularExpression REGEX_QUOTES_D("(“|”)"), REGEX_QUOTES_S("(‘|’)"); text.replace(REGEX_QUOTES_D, "\"").replace(REGEX_QUOTES_S, "'"); // From docs: // "If the selection obtained from an editor spans a line break, the text will // contain a Unicode U+2029 paragraph separator character instead of a newline \n character" // Hence, we replace with \r\n on Win$hit, and \n on all other platforms #ifdef Q_OS_WIN32 text.replace(QChar(0x2029), "\r\n"); #else text.replace(QChar(0x2029), "\n"); #endif qApp->clipboard()->setText(text); }