File indexing completed on 2024-05-12 09:56:57
0001 /* 0002 SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com> 0003 SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "FilterChain.h" 0009 #include "Filter.h" 0010 0011 #include "terminalDisplay/TerminalColor.h" 0012 #include "terminalDisplay/TerminalDisplay.h" 0013 #include "terminalDisplay/TerminalFonts.h" 0014 0015 #include <QDebug> 0016 #include <QEvent> 0017 #include <QKeyEvent> 0018 #include <QPainter> 0019 #include <QRect> 0020 0021 #include <algorithm> 0022 0023 using namespace Konsole; 0024 FilterChain::FilterChain(TerminalDisplay *terminalDisplay) 0025 : _terminalDisplay(terminalDisplay) 0026 , _showUrlHint(false) 0027 , _reverseUrlHints(false) 0028 , _urlHintsModifiers(Qt::NoModifier) 0029 { 0030 } 0031 FilterChain::~FilterChain() 0032 { 0033 qDeleteAll(_filters); 0034 } 0035 0036 void FilterChain::addFilter(Filter *filter) 0037 { 0038 _filters.append(filter); 0039 } 0040 0041 void FilterChain::removeFilter(Filter *filter) 0042 { 0043 _filters.removeAll(filter); 0044 } 0045 0046 void FilterChain::reset() 0047 { 0048 for (auto *filter : _filters) { 0049 filter->reset(); 0050 } 0051 } 0052 0053 void FilterChain::setBuffer(const QString *buffer, const QList<int> *linePositions) 0054 { 0055 for (auto *filter : _filters) { 0056 filter->setBuffer(buffer, linePositions); 0057 } 0058 } 0059 0060 void FilterChain::process() 0061 { 0062 for (auto *filter : _filters) { 0063 filter->process(); 0064 } 0065 } 0066 0067 void FilterChain::clear() 0068 { 0069 _filters.clear(); 0070 } 0071 0072 QSharedPointer<HotSpot> FilterChain::hotSpotAt(int line, int column) const 0073 { 0074 for (auto *filter : _filters) { 0075 QSharedPointer<HotSpot> spot = filter->hotSpotAt(line, column); 0076 if (spot != nullptr) { 0077 return spot; 0078 } 0079 } 0080 return nullptr; 0081 } 0082 0083 QList<QSharedPointer<HotSpot>> FilterChain::hotSpots() const 0084 { 0085 QList<QSharedPointer<HotSpot>> list; 0086 for (auto *filter : _filters) { 0087 list.append(filter->hotSpots()); 0088 } 0089 return list; 0090 } 0091 0092 QRegion FilterChain::hotSpotRegion() const 0093 { 0094 QRegion region; 0095 for (const auto &hotSpot : hotSpots()) { 0096 QRect r; 0097 r.setLeft(hotSpot->startColumn()); 0098 r.setTop(hotSpot->startLine()); 0099 if (hotSpot->startLine() == hotSpot->endLine()) { 0100 r.setRight(hotSpot->endColumn()); 0101 r.setBottom(hotSpot->endLine()); 0102 region |= _terminalDisplay->imageToWidget(r); 0103 } else { 0104 r.setRight(_terminalDisplay->columns()); 0105 r.setBottom(hotSpot->startLine()); 0106 region |= _terminalDisplay->imageToWidget(r); 0107 0108 r.setLeft(0); 0109 0110 for (int line = hotSpot->startLine() + 1; line < hotSpot->endLine(); line++) { 0111 r.moveTop(line); 0112 region |= _terminalDisplay->imageToWidget(r); 0113 } 0114 0115 r.moveTop(hotSpot->endLine()); 0116 r.setRight(hotSpot->endColumn()); 0117 region |= _terminalDisplay->imageToWidget(r); 0118 } 0119 } 0120 return region; 0121 } 0122 0123 int FilterChain::count(HotSpot::Type type) const 0124 { 0125 const auto hSpots = hotSpots(); 0126 return std::count_if(std::begin(hSpots), std::end(hSpots), [type](const QSharedPointer<HotSpot> &s) { 0127 return s->type() == type; 0128 }); 0129 } 0130 0131 QList<QSharedPointer<HotSpot>> FilterChain::filterBy(HotSpot::Type type) const 0132 { 0133 QList<QSharedPointer<HotSpot>> hotspots; 0134 for (const auto &spot : hotSpots()) { 0135 if (spot->type() == type) { 0136 hotspots.append(spot); 0137 } 0138 } 0139 return hotspots; 0140 } 0141 0142 void FilterChain::leaveEvent(TerminalDisplay *td, QEvent *ev) 0143 { 0144 Q_UNUSED(td) 0145 Q_UNUSED(ev) 0146 _showUrlHint = false; 0147 } 0148 0149 void FilterChain::keyReleaseEvent(TerminalDisplay *td, QKeyEvent *ev, int charLine, int charColumn) 0150 { 0151 if (_showUrlHint) { 0152 _showUrlHint = false; 0153 td->update(); 0154 } 0155 0156 auto spot = hotSpotAt(charLine, charColumn); 0157 if (spot != nullptr) { 0158 spot->keyReleaseEvent(td, ev); 0159 } 0160 } 0161 0162 bool FilterChain::keyPressEvent(TerminalDisplay *td, QKeyEvent *ev, int charLine, int charColumn) 0163 { 0164 if ((_urlHintsModifiers != 0u) && ev->modifiers() == _urlHintsModifiers) { 0165 QList<QSharedPointer<HotSpot>> hotspots = filterBy(HotSpot::Link); 0166 int nHotSpots = hotspots.count(); 0167 int hintSelected = ev->key() - '1'; 0168 0169 // Triggered a Hotspot via shortcut. 0170 if (hintSelected >= 0 && hintSelected <= 9 && hintSelected < nHotSpots) { 0171 if (_reverseUrlHints) { 0172 hintSelected = nHotSpots - hintSelected - 1; 0173 } 0174 hotspots.at(hintSelected)->activate(); 0175 _showUrlHint = false; 0176 td->update(); 0177 return true; 0178 } 0179 0180 if (!_showUrlHint) { 0181 td->processFilters(); 0182 _showUrlHint = true; 0183 td->update(); 0184 } 0185 } 0186 0187 auto spot = hotSpotAt(charLine, charColumn); 0188 if (spot != nullptr) { 0189 spot->keyPressEvent(td, ev); 0190 } 0191 return false; 0192 } 0193 0194 void FilterChain::mouseMoveEvent(TerminalDisplay *td, QMouseEvent *ev, int charLine, int charColumn) 0195 { 0196 auto spot = hotSpotAt(charLine, charColumn); 0197 if (_hotSpotUnderMouse != spot) { 0198 if (_hotSpotUnderMouse != nullptr) { 0199 _hotSpotUnderMouse->mouseLeaveEvent(td, ev); 0200 } 0201 _hotSpotUnderMouse = spot; 0202 if (_hotSpotUnderMouse != nullptr) { 0203 _hotSpotUnderMouse->mouseEnterEvent(td, ev); 0204 } 0205 } 0206 0207 if (spot != nullptr) { 0208 spot->mouseMoveEvent(td, ev); 0209 } 0210 } 0211 0212 void FilterChain::mouseReleaseEvent(TerminalDisplay *td, QMouseEvent *ev, int charLine, int charColumn) 0213 { 0214 auto spot = hotSpotAt(charLine, charColumn); 0215 if (!spot) { 0216 return; 0217 } 0218 spot->mouseReleaseEvent(td, ev); 0219 } 0220 0221 void FilterChain::paint(TerminalDisplay *td, QPainter &painter) 0222 { 0223 // get color of character under mouse and use it to draw 0224 // lines for filters 0225 QPoint cursorPos = td->mapFromGlobal(QCursor::pos()); 0226 0227 auto [cursorLine, cursorColumn] = td->getCharacterPosition(cursorPos, false); 0228 0229 Character cursorCharacter = td->getCursorCharacter(std::min(cursorColumn, td->columns() - 1), cursorLine); 0230 painter.setPen(QPen(cursorCharacter.foregroundColor.color(td->terminalColor()->colorTable()))); 0231 0232 // iterate over hotspots identified by the display's currently active filters 0233 // and draw appropriate visuals to indicate the presence of the hotspot 0234 0235 const auto spots = hotSpots(); 0236 int urlNumber; 0237 int urlNumInc; 0238 0239 // TODO: Remove _reverseUrllHints from TerminalDisplay. 0240 // TODO: Access reverseUrlHints from the profile, here. 0241 if (_reverseUrlHints) { 0242 // The URL hint numbering should be 'physically' increasing on the 0243 // keyboard, so they start at one and end up on 10 (on 0, or at least 0244 // they used to, but it seems to have changed, I'll fix that later). 0245 urlNumber = count(HotSpot::Link); 0246 urlNumInc = -1; 0247 } else { 0248 urlNumber = 1; 0249 urlNumInc = 1; 0250 } 0251 0252 for (const auto &spot : spots) { 0253 QRegion region; 0254 if (spot->type() == HotSpot::Link || spot->type() == HotSpot::EMailAddress || spot->type() == HotSpot::EscapedUrl || spot->type() == HotSpot::File) { 0255 QPair<QRegion, QRect> spotRegion = 0256 spot->region(td->terminalFont()->fontWidth(), td->terminalFont()->fontHeight(), td->columns(), td->contentRect()); 0257 region = spotRegion.first; 0258 QRect r = spotRegion.second; 0259 0260 // TODO: Move this paint code to HotSpot->drawHint(); 0261 // TODO: Fix the Url Hints access from the Profile. 0262 if (_showUrlHint && (spot->type() == HotSpot::Link || spot->type() == HotSpot::File)) { 0263 if (urlNumber >= 0 && urlNumber < 10) { 0264 // Position at the beginning of the URL 0265 QRect hintRect(*region.begin()); 0266 hintRect.setWidth(r.height()); 0267 painter.fillRect(hintRect, QColor(0, 0, 0, 128)); 0268 painter.setPen(Qt::white); 0269 painter.drawRect(hintRect.adjusted(0, 0, -1, -1)); 0270 painter.drawText(hintRect, Qt::AlignCenter, QString::number(urlNumber)); 0271 } 0272 urlNumber += urlNumInc; 0273 } 0274 } 0275 0276 if (spot->startLine() < 0 || spot->endLine() < 0) { 0277 qDebug() << "ERROR, invalid hotspot:"; 0278 spot->debug(); 0279 } 0280 0281 for (int line = spot->startLine(); line <= spot->endLine(); line++) { 0282 int startColumn = 0; 0283 int endColumn = td->columns() - 1; // TODO use number of _columns which are actually 0284 // occupied on this line rather than the width of the 0285 // display in _columns 0286 0287 // FIXME: the left side condition is always false due to the 0288 // endColumn assignment above 0289 // Check image size so _image[] is valid (see makeImage) 0290 if (endColumn >= td->columns() || line >= td->lines()) { 0291 break; 0292 } 0293 0294 // ignore whitespace at the end of the lines 0295 while (td->getCursorCharacter(endColumn, line).isSpace() && endColumn > 0) { 0296 endColumn--; 0297 } 0298 0299 // increment here because the column which we want to set 'endColumn' to 0300 // is the first whitespace character at the end of the line 0301 endColumn++; 0302 0303 if (line == spot->startLine()) { 0304 startColumn = spot->startColumn(); 0305 } 0306 if (line == spot->endLine()) { 0307 endColumn = spot->endColumn(); 0308 } 0309 0310 // TODO: resolve this comment with the new margin/center code 0311 // subtract one pixel from 0312 // the right and bottom so that 0313 // we do not overdraw adjacent 0314 // hotspots 0315 // 0316 // subtracting one pixel from all sides also prevents an edge case where 0317 // moving the mouse outside a link could still leave it underlined 0318 // because the check below for the position of the cursor 0319 // finds it on the border of the target area 0320 QRect r; 0321 r.setCoords(startColumn * td->terminalFont()->fontWidth() + td->contentRect().left(), 0322 line * td->terminalFont()->fontHeight() + td->contentRect().top(), 0323 endColumn * td->terminalFont()->fontWidth() + td->contentRect().left() - 1, 0324 (line + 1) * td->terminalFont()->fontHeight() + td->contentRect().top() - 1); 0325 0326 // Underline link hotspots 0327 // TODO: Fix accessing the urlHint here. 0328 // TODO: Move this code to UrlFilterHotSpot. 0329 const bool hasMouse = region.contains(td->mapFromGlobal(QCursor::pos())); 0330 if (((spot->type() == HotSpot::Link || spot->type() == HotSpot::File) && _showUrlHint) || hasMouse) { 0331 QFontMetrics metrics(td->font()); 0332 0333 // find the baseline (which is the invisible line that the characters in the font sit on, 0334 // with some having tails dangling below) 0335 const int baseline = r.bottom() - metrics.descent(); 0336 // find the position of the underline below that 0337 const qreal underlinePos = baseline + metrics.underlinePos(); 0338 painter.drawLine(QLineF(r.left(), underlinePos, r.right(), underlinePos)); 0339 0340 // Marker hotspots simply have a transparent rectangular shape 0341 // drawn on top of them 0342 } else if (spot->type() == HotSpot::Marker) { 0343 // TODO - Do not use a hardcoded color for this 0344 const bool isCurrentResultLine = (td->screenWindow()->currentResultLine() == (spot->startLine() + td->screenWindow()->currentLine())); 0345 QColor color = isCurrentResultLine ? QColor(255, 255, 0, 120) : QColor(255, 0, 0, 120); 0346 painter.fillRect(r, color); 0347 } 0348 } 0349 } 0350 } 0351 0352 void FilterChain::setReverseUrlHints(bool value) 0353 { 0354 _reverseUrlHints = value; 0355 } 0356 0357 void FilterChain::setUrlHintsModifiers(Qt::KeyboardModifiers value) 0358 { 0359 _urlHintsModifiers = value; 0360 }