KTextEditor

kateviewhelpers.cpp
1 /* This file is part of the KDE libraries
2  Copyright (C) 2008, 2009 Matthew Woehlke <[email protected]>
3  Copyright (C) 2007 Mirko Stocker <[email protected]>
4  Copyright (C) 2002 John Firebaugh <[email protected]>
5  Copyright (C) 2001 Anders Lund <[email protected]>
6  Copyright (C) 2001 Christoph Cullmann <[email protected]>
7  Copyright (C) 2011 Svyatoslav Kuzmich <[email protected]>
8  Copyright (C) 2012 Kåre Särs <[email protected]> (Minimap)
9  Copyright 2017-2018 Friedrich W. H. Kossebau <[email protected]>
10 
11  This library is free software; you can redistribute it and/or
12  modify it under the terms of the GNU Library General Public
13  License version 2 as published by the Free Software Foundation.
14 
15  This library is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  Library General Public License for more details.
19 
20  You should have received a copy of the GNU Library General Public License
21  along with this library; see the file COPYING.LIB. If not, write to
22  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  Boston, MA 02110-1301, USA.
24 */
25 
26 #include "kateviewhelpers.h"
27 
28 #include "kateabstractinputmode.h"
29 #include "kateannotationitemdelegate.h"
30 #include "katecmd.h"
31 #include "katecommandrangeexpressionparser.h"
32 #include "kateconfig.h"
33 #include "katedocument.h"
34 #include "kateglobal.h"
35 #include "katelayoutcache.h"
36 #include "katepartdebug.h"
37 #include "katerenderer.h"
38 #include "katetextlayout.h"
39 #include "katetextpreview.h"
40 #include "kateview.h"
41 #include "kateviewinternal.h"
42 #include <katebuffer.h>
43 #include <ktexteditor/annotationinterface.h>
44 #include <ktexteditor/attribute.h>
45 #include <ktexteditor/movingrange.h>
46 
47 #include <KCharsets>
48 #include <KColorUtils>
49 #include <KConfigGroup>
50 #include <KLocalizedString>
51 #include <khelpclient.h>
52 
53 #include <QAction>
54 #include <QActionGroup>
55 #include <QBoxLayout>
56 #include <QCursor>
57 #include <QKeyEvent>
58 #include <QLinearGradient>
59 #include <QMenu>
60 #include <QPainter>
61 #include <QPainterPath>
62 #include <QPalette>
63 #include <QPen>
64 #include <QRegularExpression>
65 #include <QStyle>
66 #include <QStyleOption>
67 #include <QTextCodec>
68 #include <QTimer>
69 #include <QToolButton>
70 #include <QToolTip>
71 #include <QVariant>
72 #include <QWhatsThis>
73 #include <QtAlgorithms>
74 
75 #include <math.h>
76 
77 // BEGIN KateMessageLayout
78 KateMessageLayout::KateMessageLayout(QWidget *parent)
79  : QLayout(parent)
80 {
81 }
82 
83 KateMessageLayout::~KateMessageLayout()
84 {
85  while (QLayoutItem *item = takeAt(0))
86  delete item;
87 }
88 
89 void KateMessageLayout::addItem(QLayoutItem *item)
90 {
91  Q_ASSERT(false);
93 }
94 
95 void KateMessageLayout::addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos)
96 {
97  add(new QWidgetItem(widget), pos);
98 }
99 
100 int KateMessageLayout::count() const
101 {
102  return m_items.size();
103 }
104 
105 QLayoutItem *KateMessageLayout::itemAt(int index) const
106 {
107  if (index < 0 || index >= m_items.size())
108  return nullptr;
109 
110  return m_items[index]->item;
111 }
112 
113 void KateMessageLayout::setGeometry(const QRect &rect)
114 {
115  QLayout::setGeometry(rect);
116  const int s = spacing();
117  const QRect adjustedRect = rect.adjusted(s, s, -s, -s);
118 
119  for (auto wrapper : m_items) {
120  QLayoutItem *item = wrapper->item;
121  auto position = wrapper->position;
122 
123  if (position == KTextEditor::Message::TopInView) {
124  const QRect r(adjustedRect.width() - item->sizeHint().width(), s, item->sizeHint().width(), item->sizeHint().height());
125  item->setGeometry(r);
126  } else if (position == KTextEditor::Message::BottomInView) {
127  const QRect r(adjustedRect.width() - item->sizeHint().width(), adjustedRect.height() - item->sizeHint().height(), item->sizeHint().width(), item->sizeHint().height());
128  item->setGeometry(r);
129  } else if (position == KTextEditor::Message::CenterInView) {
130  QRect r(0, 0, item->sizeHint().width(), item->sizeHint().height());
131  r.moveCenter(adjustedRect.center());
132  item->setGeometry(r);
133  } else {
134  Q_ASSERT_X(false, "setGeometry", "Only TopInView, CenterInView, and BottomInView are supported.");
135  }
136  }
137 }
138 
139 QSize KateMessageLayout::sizeHint() const
140 {
141  return QSize();
142 }
143 
144 QLayoutItem *KateMessageLayout::takeAt(int index)
145 {
146  if (index >= 0 && index < m_items.size()) {
147  ItemWrapper *layoutStruct = m_items.takeAt(index);
148  return layoutStruct->item;
149  }
150  return nullptr;
151 }
152 
153 void KateMessageLayout::add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos)
154 {
155  m_items.push_back(new ItemWrapper(item, pos));
156 }
157 // END KateMessageLayout
158 
159 // BEGIN KateScrollBar
160 static const int s_lineWidth = 100;
161 static const int s_pixelMargin = 8;
162 static const int s_linePixelIncLimit = 6;
163 
164 const unsigned char KateScrollBar::characterOpacity[256] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 15
165  0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, // <- 31
166  0, 125, 41, 221, 138, 195, 218, 21, 142, 142, 137, 137, 97, 87, 87, 140, // <- 47
167  223, 164, 183, 190, 191, 193, 214, 158, 227, 216, 103, 113, 146, 140, 146, 149, // <- 63
168  248, 204, 240, 174, 217, 197, 178, 205, 209, 176, 168, 211, 160, 246, 238, 218, // <- 79
169  195, 229, 227, 196, 167, 212, 188, 238, 197, 169, 189, 158, 21, 151, 115, 90, // <- 95
170  15, 192, 209, 153, 208, 187, 162, 221, 183, 149, 161, 191, 146, 203, 167, 182, // <- 111
171  208, 203, 139, 166, 158, 167, 157, 189, 164, 179, 156, 167, 145, 166, 109, 0, // <- 127
172  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 143
173  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 159
174  0, 125, 184, 187, 146, 201, 127, 203, 89, 194, 156, 141, 117, 87, 202, 88, // <- 175
175  115, 165, 118, 121, 85, 190, 236, 87, 88, 111, 151, 140, 194, 191, 203, 148, // <- 191
176  215, 215, 222, 224, 223, 234, 230, 192, 208, 208, 216, 217, 187, 187, 194, 195, // <- 207
177  228, 255, 228, 228, 235, 239, 237, 150, 255, 222, 222, 229, 232, 180, 197, 225, // <- 223
178  208, 208, 216, 217, 212, 230, 218, 170, 202, 202, 211, 204, 156, 156, 165, 159, // <- 239
179  214, 194, 197, 197, 206, 206, 201, 132, 214, 183, 183, 192, 187, 195, 227, 198};
180 
181 KateScrollBar::KateScrollBar(Qt::Orientation orientation, KateViewInternal *parent)
182  : QScrollBar(orientation, parent->m_view)
183  , m_middleMouseDown(false)
184  , m_leftMouseDown(false)
185  , m_view(parent->m_view)
186  , m_doc(parent->doc())
187  , m_viewInternal(parent)
188  , m_textPreview(nullptr)
189  , m_showMarks(false)
190  , m_showMiniMap(false)
191  , m_miniMapAll(true)
192  , m_needsUpdateOnShow(false)
193  , m_miniMapWidth(40)
194  , m_grooveHeight(height())
195  , m_linesModified(0)
196 {
197  connect(this, SIGNAL(valueChanged(int)), this, SLOT(sliderMaybeMoved(int)));
198  connect(m_doc, SIGNAL(marksChanged(KTextEditor::Document *)), this, SLOT(marksChanged()));
199 
200  m_updateTimer.setInterval(300);
201  m_updateTimer.setSingleShot(true);
202  QTimer::singleShot(10, this, SLOT(updatePixmap()));
203 
204  // track mouse for text preview widget
205  setMouseTracking(orientation == Qt::Vertical);
206 
207  // setup text preview timer
208  m_delayTextPreviewTimer.setSingleShot(true);
209  m_delayTextPreviewTimer.setInterval(250);
210  connect(&m_delayTextPreviewTimer, SIGNAL(timeout()), this, SLOT(showTextPreview()));
211 }
212 
213 void KateScrollBar::showEvent(QShowEvent* event)
214 {
215  QScrollBar::showEvent(event);
216 
217  if (m_needsUpdateOnShow) {
218  m_needsUpdateOnShow = false;
219  updatePixmap();
220  }
221 }
222 
223 KateScrollBar::~KateScrollBar()
224 {
225  delete m_textPreview;
226 }
227 
228 void KateScrollBar::setShowMiniMap(bool b)
229 {
230  if (b && !m_showMiniMap) {
231  connect(m_view, SIGNAL(selectionChanged(KTextEditor::View *)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection);
232  connect(m_doc, SIGNAL(textChanged(KTextEditor::Document *)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection);
233  connect(m_view, SIGNAL(delayedUpdateOfView()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection);
234  connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updatePixmap()), Qt::UniqueConnection);
235  connect(&(m_view->textFolding()), SIGNAL(foldingRangesChanged()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection);
236  } else if (!b) {
237  disconnect(&m_updateTimer);
238  }
239 
240  m_showMiniMap = b;
241 
242  updateGeometry();
243  update();
244 }
245 
246 QSize KateScrollBar::sizeHint() const
247 {
248  if (m_showMiniMap) {
249  return QSize(m_miniMapWidth, QScrollBar::sizeHint().height());
250  }
251  return QScrollBar::sizeHint();
252 }
253 
254 int KateScrollBar::minimapYToStdY(int y)
255 {
256  // Check if the minimap fills the whole scrollbar
257  if (m_stdGroveRect.height() == m_mapGroveRect.height()) {
258  return y;
259  }
260 
261  // check if y is on the step up/down
262  if ((y < m_stdGroveRect.top()) || (y > m_stdGroveRect.bottom())) {
263  return y;
264  }
265 
266  if (y < m_mapGroveRect.top()) {
267  return m_stdGroveRect.top() + 1;
268  }
269 
270  if (y > m_mapGroveRect.bottom()) {
271  return m_stdGroveRect.bottom() - 1;
272  }
273 
274  // check for div/0
275  if (m_mapGroveRect.height() == 0) {
276  return y;
277  }
278 
279  int newY = (y - m_mapGroveRect.top()) * m_stdGroveRect.height() / m_mapGroveRect.height();
280  newY += m_stdGroveRect.top();
281  return newY;
282 }
283 
284 void KateScrollBar::mousePressEvent(QMouseEvent *e)
285 {
286  // delete text preview
287  hideTextPreview();
288 
289  if (e->button() == Qt::MidButton) {
290  m_middleMouseDown = true;
291  } else if (e->button() == Qt::LeftButton) {
292  m_leftMouseDown = true;
293  }
294 
295  if (m_showMiniMap) {
296  if (m_leftMouseDown && e->pos().y() > m_mapGroveRect.top() && e->pos().y() < m_mapGroveRect.bottom()) {
297  // if we show the minimap left-click jumps directly to the selected position
298  int newVal = (e->pos().y() - m_mapGroveRect.top()) / (double)m_mapGroveRect.height() * (double)(maximum() + pageStep()) - pageStep() / 2;
299  newVal = qBound(0, newVal, maximum());
300  setSliderPosition(newVal);
301  }
302  QMouseEvent eMod(QEvent::MouseButtonPress, QPoint(6, minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers());
304  } else {
306  }
307 
308  m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0);
309  const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
310  const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
311  QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
312 
313  redrawMarks();
314 }
315 
316 void KateScrollBar::mouseReleaseEvent(QMouseEvent *e)
317 {
318  if (e->button() == Qt::MidButton) {
319  m_middleMouseDown = false;
320  } else if (e->button() == Qt::LeftButton) {
321  m_leftMouseDown = false;
322  }
323 
324  redrawMarks();
325 
326  if (m_leftMouseDown || m_middleMouseDown) {
328  }
329 
330  if (m_showMiniMap) {
331  QMouseEvent eMod(QEvent::MouseButtonRelease, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers());
333  } else {
335  }
336 }
337 
338 void KateScrollBar::mouseMoveEvent(QMouseEvent *e)
339 {
340  if (m_showMiniMap) {
341  QMouseEvent eMod(QEvent::MouseMove, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers());
343  } else {
345  }
346 
347  if (e->buttons() & (Qt::LeftButton | Qt::MidButton)) {
348  redrawMarks();
349 
350  // current line tool tip
351  m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0);
352  const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
353  const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
354  QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
355  }
356 
357  showTextPreviewDelayed();
358 }
359 
360 void KateScrollBar::leaveEvent(QEvent *event)
361 {
362  hideTextPreview();
363 
365 }
366 
367 bool KateScrollBar::eventFilter(QObject *object, QEvent *event)
368 {
369  Q_UNUSED(object)
370 
371  if (m_textPreview && event->type() == QEvent::WindowDeactivate) {
372  // We need hide the scrollbar TextPreview widget
373  hideTextPreview();
374  }
375 
376  return false;
377 }
378 
379 void KateScrollBar::paintEvent(QPaintEvent *e)
380 {
381  if (m_doc->marks().size() != m_lines.size()) {
382  recomputeMarksPositions();
383  }
384  if (m_showMiniMap) {
385  miniMapPaintEvent(e);
386  } else {
387  normalPaintEvent(e);
388  }
389 }
390 
391 void KateScrollBar::showTextPreviewDelayed()
392 {
393  if (!m_textPreview) {
394  if (!m_delayTextPreviewTimer.isActive()) {
395  m_delayTextPreviewTimer.start();
396  }
397  } else {
398  showTextPreview();
399  }
400 }
401 
402 void KateScrollBar::showTextPreview()
403 {
404  if (orientation() != Qt::Vertical || isSliderDown() || (minimum() == maximum()) || !m_view->config()->scrollBarPreview()) {
405  return;
406  }
407 
408  // only show when main window is active (#392396)
409  if (window() && !window()->isActiveWindow()) {
410  return;
411  }
412 
413  QRect grooveRect;
414  if (m_showMiniMap) {
415  // If mini-map is shown, the height of the map might not be the whole height
416  grooveRect = m_mapGroveRect;
417  } else {
418  QStyleOptionSlider opt;
419  opt.init(this);
420  opt.subControls = QStyle::SC_None;
421  opt.activeSubControls = QStyle::SC_None;
422  opt.orientation = orientation();
423  opt.minimum = minimum();
424  opt.maximum = maximum();
425  opt.sliderPosition = sliderPosition();
426  opt.sliderValue = value();
427  opt.singleStep = singleStep();
428  opt.pageStep = pageStep();
429 
430  grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this);
431  }
432 
433  if (m_view->config()->scrollPastEnd()) {
434  // Adjust the grove size to accommodate the added pageStep at the bottom
435  int adjust = pageStep() * grooveRect.height() / (maximum() + pageStep() - minimum());
436  grooveRect.adjust(0, 0, 0, -adjust);
437  }
438 
439  const QPoint cursorPos = mapFromGlobal(QCursor::pos());
440  if (grooveRect.contains(cursorPos)) {
441  if (!m_textPreview) {
442  m_textPreview = new KateTextPreview(m_view, this);
443  m_textPreview->setAttribute(Qt::WA_ShowWithoutActivating);
444  m_textPreview->setFrameStyle(QFrame::StyledPanel);
445 
446  // event filter to catch application WindowDeactivate event, to hide the preview window
447  qApp->installEventFilter(this);
448  }
449 
450  const qreal posInPercent = static_cast<double>(cursorPos.y() - grooveRect.top()) / grooveRect.height();
451  const qreal startLine = posInPercent * m_view->textFolding().visibleLines();
452 
453  m_textPreview->resize(m_view->width() / 2, m_view->height() / 5);
454  const int xGlobal = mapToGlobal(QPoint(0, 0)).x();
455  const int yGlobal = qMin(mapToGlobal(QPoint(0, height())).y() - m_textPreview->height(), qMax(mapToGlobal(QPoint(0, 0)).y(), mapToGlobal(cursorPos).y() - m_textPreview->height() / 2));
456  m_textPreview->move(xGlobal - m_textPreview->width(), yGlobal);
457  m_textPreview->setLine(startLine);
458  m_textPreview->setCenterView(true);
459  m_textPreview->setScaleFactor(0.75);
460  m_textPreview->raise();
461  m_textPreview->show();
462  } else {
463  hideTextPreview();
464  }
465 }
466 
467 void KateScrollBar::hideTextPreview()
468 {
469  if (m_delayTextPreviewTimer.isActive()) {
470  m_delayTextPreviewTimer.stop();
471  }
472 
473  qApp->removeEventFilter(this);
474  delete m_textPreview;
475 }
476 
477 // This function is optimized for bing called in sequence.
478 const QColor KateScrollBar::charColor(const QVector<Kate::TextLineData::Attribute> &attributes, int &attributeIndex, const QVector<QTextLayout::FormatRange> &decorations, const QColor &defaultColor, int x, QChar ch)
479 {
480  QColor color = defaultColor;
481 
482  bool styleFound = false;
483 
484  // Query the decorations, that is, things like search highlighting, or the
485  // KDevelop DUChain highlighting, for a color to use
486  for (auto &range : decorations) {
487  if (range.start <= x && range.start + range.length > x) {
488  // If there's a different background color set (search markers, ...)
489  // use that, otherwise use the foreground color.
490  if (range.format.hasProperty(QTextFormat::BackgroundBrush)) {
491  color = range.format.background().color();
492  } else {
493  color = range.format.foreground().color();
494  }
495  styleFound = true;
496  break;
497  }
498  }
499 
500  // If there's no decoration set for the current character (this will mostly be the case for
501  // plain Kate), query the styles, that is, the default kate syntax highlighting.
502  if (!styleFound) {
503  // go to the block containing x
504  while ((attributeIndex < attributes.size()) && ((attributes[attributeIndex].offset + attributes[attributeIndex].length) < x)) {
505  ++attributeIndex;
506  }
507  if ((attributeIndex < attributes.size()) && (x < attributes[attributeIndex].offset + attributes[attributeIndex].length)) {
508  color = m_view->renderer()->attribute(attributes[attributeIndex].attributeValue)->foreground().color();
509  }
510  }
511 
512  // Query how much "blackness" the character has.
513  // This causes for example a dot or a dash to appear less intense
514  // than an A or similar.
515  // This gives the pixels created a bit of structure, which makes it look more
516  // like real text.
517  color.setAlpha((ch.unicode() < 256) ? characterOpacity[ch.unicode()] : 222);
518 
519  return color;
520 }
521 
522 void KateScrollBar::updatePixmap()
523 {
524  // QTime time;
525  // time.start();
526 
527  if (!m_showMiniMap) {
528  // make sure no time is wasted if the option is disabled
529  return;
530  }
531 
532  if (!isVisible()) {
533  // don't update now if the document is not visible; do it when
534  // the document is shown again instead
535  m_needsUpdateOnShow = true;
536  return;
537  }
538 
539  // For performance reason, only every n-th line will be drawn if the widget is
540  // sufficiently small compared to the amount of lines in the document.
541  int docLineCount = m_view->textFolding().visibleLines();
542  int pixmapLineCount = docLineCount;
543  if (m_view->config()->scrollPastEnd()) {
544  pixmapLineCount += pageStep();
545  }
546  int pixmapLinesUnscaled = pixmapLineCount;
547  if (m_grooveHeight < 5) {
548  m_grooveHeight = 5;
549  }
550  int charIncrement = 1;
551  int lineIncrement = 1;
552  if ((m_grooveHeight > 10) && (pixmapLineCount >= m_grooveHeight * 2)) {
553  charIncrement = pixmapLineCount / m_grooveHeight;
554  while (charIncrement > s_linePixelIncLimit) {
555  lineIncrement++;
556  pixmapLineCount = pixmapLinesUnscaled / lineIncrement;
557  charIncrement = pixmapLineCount / m_grooveHeight;
558  }
559  pixmapLineCount /= charIncrement;
560  }
561 
562  int pixmapLineWidth = s_pixelMargin + s_lineWidth / charIncrement;
563 
564  // qCDebug(LOG_KTE) << "l" << lineIncrement << "c" << charIncrement << "d";
565  // qCDebug(LOG_KTE) << "pixmap" << pixmapLineCount << pixmapLineWidth << "docLines" << m_view->textFolding().visibleLines() << "height" << m_grooveHeight;
566 
567  const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color();
568  const QColor defaultTextColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color();
569  const QColor selectionBgColor = m_view->renderer()->config()->selectionColor();
570  QColor modifiedLineColor = m_view->renderer()->config()->modifiedLineColor();
571  QColor savedLineColor = m_view->renderer()->config()->savedLineColor();
572  // move the modified line color away from the background color
573  modifiedLineColor.setHsv(modifiedLineColor.hue(), 255, 255 - backgroundColor.value() / 3);
574  savedLineColor.setHsv(savedLineColor.hue(), 100, 255 - backgroundColor.value() / 3);
575 
576  // increase dimensions by ratio
577  m_pixmap = QPixmap(pixmapLineWidth * m_view->devicePixelRatioF(), pixmapLineCount * m_view->devicePixelRatioF());
578  m_pixmap.fill(QColor("transparent"));
579 
580  // The text currently selected in the document, to be drawn later.
581  const KTextEditor::Range &selection = m_view->selectionRange();
582 
583  QPainter painter;
584  if (painter.begin(&m_pixmap)) {
585  // init pen once, afterwards, only change it if color changes to avoid a lot of allocation for setPen
586  painter.setPen(selectionBgColor);
587 
588  // Do not force updates of the highlighting if the document is very large
589  bool simpleMode = m_doc->lines() > 7500;
590 
591  int pixelY = 0;
592  int drawnLines = 0;
593 
594  // Iterate over all visible lines, drawing them.
595  for (int virtualLine = 0; virtualLine < docLineCount; virtualLine += lineIncrement) {
596  int realLineNumber = m_view->textFolding().visibleLineToLine(virtualLine);
597  QString lineText = m_doc->line(realLineNumber);
598 
599  if (!simpleMode) {
600  m_doc->buffer().ensureHighlighted(realLineNumber);
601  }
602  const Kate::TextLine &kateline = m_doc->plainKateTextLine(realLineNumber);
603 
604  const QVector<Kate::TextLineData::Attribute> &attributes = kateline->attributesList();
605  QVector<QTextLayout::FormatRange> decorations = m_view->renderer()->decorationsForLine(kateline, realLineNumber);
606  int attributeIndex = 0;
607 
608  // Draw selection if it is on an empty line
609  if (selection.contains(KTextEditor::Cursor(realLineNumber, 0)) && lineText.size() == 0) {
610  if (selectionBgColor != painter.pen().color()) {
611  painter.setPen(selectionBgColor);
612  }
613  painter.drawLine(s_pixelMargin, pixelY, s_pixelMargin + s_lineWidth - 1, pixelY);
614  }
615 
616  // Iterate over the line to draw the background
617  int selStartX = -1;
618  int selEndX = -1;
619  int pixelX = s_pixelMargin; // use this to control the offset of the text from the left
620  for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) {
621  if (pixelX >= s_lineWidth + s_pixelMargin) {
622  break;
623  }
624  // Query the selection and draw it behind the character
625  if (selection.contains(KTextEditor::Cursor(realLineNumber, x))) {
626  if (selStartX == -1)
627  selStartX = pixelX;
628  selEndX = pixelX;
629  if (lineText.size() - 1 == x) {
630  selEndX = s_lineWidth + s_pixelMargin - 1;
631  }
632  }
633 
634  if (lineText[x] == QLatin1Char('\t')) {
635  pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width...
636  } else {
637  pixelX++;
638  }
639  }
640 
641  if (selStartX != -1) {
642  if (selectionBgColor != painter.pen().color()) {
643  painter.setPen(selectionBgColor);
644  }
645  painter.drawLine(selStartX, pixelY, selEndX, pixelY);
646  }
647 
648  // Iterate over all the characters in the current line
649  pixelX = s_pixelMargin;
650  for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) {
651  if (pixelX >= s_lineWidth + s_pixelMargin) {
652  break;
653  }
654 
655  // draw the pixels
656  if (lineText[x] == QLatin1Char(' ')) {
657  pixelX++;
658  } else if (lineText[x] == QLatin1Char('\t')) {
659  pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width...
660  } else {
661  const QColor newPenColor(charColor(attributes, attributeIndex, decorations, defaultTextColor, x, lineText[x]));
662  if (newPenColor != painter.pen().color()) {
663  painter.setPen(newPenColor);
664  }
665 
666  // Actually draw the pixel with the color queried from the renderer.
667  painter.drawPoint(pixelX, pixelY);
668 
669  pixelX++;
670  }
671  }
672  drawnLines++;
673  if (((drawnLines) % charIncrement) == 0) {
674  pixelY++;
675  }
676  }
677  // qCDebug(LOG_KTE) << drawnLines;
678  // Draw line modification marker map.
679  // Disable this if the document is really huge,
680  // since it requires querying every line.
681  if (m_doc->lines() < 50000) {
682  for (int lineno = 0; lineno < docLineCount; lineno++) {
683  int realLineNo = m_view->textFolding().visibleLineToLine(lineno);
684  const Kate::TextLine &line = m_doc->plainKateTextLine(realLineNo);
685  const QColor &col = line->markedAsModified() ? modifiedLineColor : savedLineColor;
686  if (line->markedAsModified() || line->markedAsSavedOnDisk()) {
687  int pos = (lineno * pixmapLineCount) / pixmapLinesUnscaled;
688  painter.fillRect(2, pos, 3, 1, col);
689  }
690  }
691  }
692 
693  // end painting
694  painter.end();
695  }
696 
697  // set right ratio
698  m_pixmap.setDevicePixelRatio(m_view->devicePixelRatioF());
699 
700  // qCDebug(LOG_KTE) << time.elapsed();
701  // Redraw the scrollbar widget with the updated pixmap.
702  update();
703 }
704 
705 void KateScrollBar::miniMapPaintEvent(QPaintEvent *e)
706 {
708 
709  QPainter painter(this);
710 
711  QStyleOptionSlider opt;
712  opt.init(this);
713  opt.subControls = QStyle::SC_None;
714  opt.activeSubControls = QStyle::SC_None;
715  opt.orientation = orientation();
716  opt.minimum = minimum();
717  opt.maximum = maximum();
718  opt.sliderPosition = sliderPosition();
719  opt.sliderValue = value();
720  opt.singleStep = singleStep();
721  opt.pageStep = pageStep();
722 
723  QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this);
724  m_stdGroveRect = grooveRect;
725  if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this).height() == 0) {
726  int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this);
727  grooveRect.moveTop(alignMargin);
728  grooveRect.setHeight(grooveRect.height() - alignMargin);
729  }
730  if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this).height() == 0) {
731  int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this);
732  grooveRect.setHeight(grooveRect.height() - alignMargin);
733  }
734  m_grooveHeight = grooveRect.height();
735 
736  const int docXMargin = 1;
737  // style()->drawControl(QStyle::CE_ScrollBarAddLine, &opt, &painter, this);
738  // style()->drawControl(QStyle::CE_ScrollBarSubLine, &opt, &painter, this);
739 
740  // calculate the document size and position
741  const int docHeight = qMin(grooveRect.height(), int(m_pixmap.height() / m_pixmap.devicePixelRatio() * 2)) - 2 * docXMargin;
742  const int yoffset = 1; // top-aligned in stead of center-aligned (grooveRect.height() - docHeight) / 2;
743  const QRect docRect(QPoint(grooveRect.left() + docXMargin, yoffset + grooveRect.top()), QSize(grooveRect.width() - docXMargin, docHeight));
744  m_mapGroveRect = docRect;
745 
746  // calculate the visible area
747  int max = qMax(maximum() + 1, 1);
748  int visibleStart = value() * docHeight / (max + pageStep()) + docRect.top() + 0.5;
749  int visibleEnd = (value() + pageStep()) * docHeight / (max + pageStep()) + docRect.top();
750  QRect visibleRect = docRect;
751  visibleRect.moveTop(visibleStart);
752  visibleRect.setHeight(visibleEnd - visibleStart);
753 
754  // calculate colors
755  const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color();
756  const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color();
757  const QColor highlightColor = palette().link().color();
758 
759  const int backgroundLightness = backgroundColor.lightness();
760  const int foregroundLightness = foregroundColor.lightness();
761  const int lighnessDiff = (foregroundLightness - backgroundLightness);
762 
763  // get a color suited for the color theme
764  QColor darkShieldColor = palette().color(QPalette::Mid);
765  int hue, sat, light;
766  darkShieldColor.getHsl(&hue, &sat, &light);
767  // apply suitable lightness
768  darkShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.35);
769  // gradient for nicer results
770  QLinearGradient gradient(0, 0, width(), 0);
771  gradient.setColorAt(0, darkShieldColor);
772  gradient.setColorAt(0.3, darkShieldColor.lighter(115));
773  gradient.setColorAt(1, darkShieldColor);
774 
775  QColor lightShieldColor;
776  lightShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.15);
777 
778  QColor outlineColor;
779  outlineColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.5);
780 
781  // draw the grove background in case the document is small
782  painter.setPen(Qt::NoPen);
783  painter.setBrush(backgroundColor);
784  painter.drawRect(grooveRect);
785 
786  // adjust the rectangles
787  QRect sliderRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this);
788  sliderRect.setX(docXMargin);
789  sliderRect.setWidth(width() - docXMargin * 2);
790 
791  if ((docHeight + 2 * docXMargin >= grooveRect.height()) && (sliderRect.height() > visibleRect.height() + 2)) {
792  visibleRect.adjust(2, 0, -3, 0);
793  } else {
794  visibleRect.adjust(1, 0, -1, 2);
795  sliderRect.setTop(visibleRect.top() - 1);
796  sliderRect.setBottom(visibleRect.bottom() + 1);
797  }
798 
799  // Smooth transform only when squeezing
800  if (grooveRect.height() < m_pixmap.height() / m_pixmap.devicePixelRatio()) {
801  painter.setRenderHint(QPainter::SmoothPixmapTransform);
802  }
803 
804  // draw the modified lines margin
805  QRect pixmapMarginRect(QPoint(0, 0), QSize(s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio()));
806  QRect docPixmapMarginRect(QPoint(0, docRect.top()), QSize(s_pixelMargin, docRect.height()));
807  painter.drawPixmap(docPixmapMarginRect, m_pixmap, pixmapMarginRect);
808 
809  // calculate the stretch and draw the stretched lines (scrollbar marks)
810  QRect pixmapRect(QPoint(s_pixelMargin, 0), QSize(m_pixmap.width() / m_pixmap.devicePixelRatio() - s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio()));
811  QRect docPixmapRect(QPoint(s_pixelMargin, docRect.top()), QSize(docRect.width() - s_pixelMargin, docRect.height()));
812  painter.drawPixmap(docPixmapRect, m_pixmap, pixmapRect);
813 
814  // delimit the end of the document
815  const int y = docPixmapRect.height() + grooveRect.y();
816  if (y + 2 < grooveRect.y() + grooveRect.height()) {
817  QColor fg(foregroundColor);
818  fg.setAlpha(30);
819  painter.setBrush(Qt::NoBrush);
820  painter.setPen(QPen(fg, 1));
821  painter.drawLine(grooveRect.x() + 1, y + 2, width() - 1, y + 2);
822  }
823 
824  // fade the invisible sections
825  const QRect top(grooveRect.x(),
826  grooveRect.y(),
827  grooveRect.width(),
828  visibleRect.y() - grooveRect.y() // Pen width
829  );
830  const QRect bottom(grooveRect.x(),
831  grooveRect.y() + visibleRect.y() + visibleRect.height() - grooveRect.y(), // Pen width
832  grooveRect.width(),
833  grooveRect.height() - (visibleRect.y() - grooveRect.y()) - visibleRect.height());
834 
835  QColor faded(backgroundColor);
836  faded.setAlpha(110);
837  painter.fillRect(top, faded);
838  painter.fillRect(bottom, faded);
839 
840  // add a thin line to limit the scrollbar
841  QColor c(foregroundColor);
842  c.setAlpha(10);
843  painter.setPen(QPen(c, 1));
844  painter.drawLine(0, 0, 0, height());
845 
846  if (m_showMarks) {
847  QHashIterator<int, QColor> it = m_lines;
848  QPen penBg;
849  penBg.setWidth(4);
850  lightShieldColor.setAlpha(180);
851  penBg.setColor(lightShieldColor);
852  painter.setPen(penBg);
853  while (it.hasNext()) {
854  it.next();
855  int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();
856  ;
857  painter.drawLine(6, y, width() - 6, y);
858  }
859 
860  it = m_lines;
861  QPen pen;
862  pen.setWidth(2);
863  while (it.hasNext()) {
864  it.next();
865  pen.setColor(it.value());
866  painter.setPen(pen);
867  int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();
868  painter.drawLine(6, y, width() - 6, y);
869  }
870  }
871 
872  // slider outline
873  QColor sliderColor(highlightColor);
874  sliderColor.setAlpha(50);
875  painter.fillRect(sliderRect, sliderColor);
876  painter.setPen(QPen(highlightColor, 0));
877  // rounded rect looks ugly for some reason, so we draw 4 lines.
878  painter.drawLine(sliderRect.left(), sliderRect.top() + 1, sliderRect.left(), sliderRect.bottom() - 1);
879  painter.drawLine(sliderRect.right(), sliderRect.top() + 1, sliderRect.right(), sliderRect.bottom() - 1);
880  painter.drawLine(sliderRect.left() + 1, sliderRect.top(), sliderRect.right() - 1, sliderRect.top());
881  painter.drawLine(sliderRect.left() + 1, sliderRect.bottom(), sliderRect.right() - 1, sliderRect.bottom());
882 }
883 
884 void KateScrollBar::normalPaintEvent(QPaintEvent *e)
885 {
887 
888  if (!m_showMarks) {
889  return;
890  }
891 
892  QPainter painter(this);
893 
894  QStyleOptionSlider opt;
895  opt.init(this);
896  opt.subControls = QStyle::SC_None;
897  opt.activeSubControls = QStyle::SC_None;
898  opt.orientation = orientation();
899  opt.minimum = minimum();
900  opt.maximum = maximum();
901  opt.sliderPosition = sliderPosition();
902  opt.sliderValue = value();
903  opt.singleStep = singleStep();
904  opt.pageStep = pageStep();
905 
906  QRect rect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this);
907  int sideMargin = width() - rect.width();
908  if (sideMargin < 4) {
909  sideMargin = 4;
910  }
911  sideMargin /= 2;
912 
913  QHashIterator<int, QColor> it = m_lines;
914  while (it.hasNext()) {
915  it.next();
916  painter.setPen(it.value());
917  if (it.key() < rect.top() || it.key() > rect.bottom()) {
918  painter.drawLine(0, it.key(), width(), it.key());
919  } else {
920  painter.drawLine(0, it.key(), sideMargin, it.key());
921  painter.drawLine(width() - sideMargin, it.key(), width(), it.key());
922  }
923  }
924 }
925 
926 void KateScrollBar::resizeEvent(QResizeEvent *e)
927 {
929  m_updateTimer.start();
930  m_lines.clear();
931  update();
932 }
933 
934 void KateScrollBar::sliderChange(SliderChange change)
935 {
936  // call parents implementation
937  QScrollBar::sliderChange(change);
938 
939  if (change == QAbstractSlider::SliderValueChange) {
940  redrawMarks();
941  } else if (change == QAbstractSlider::SliderRangeChange) {
942  marksChanged();
943  }
944 
945  if (m_leftMouseDown || m_middleMouseDown) {
946  const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
947  const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
948  QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
949  }
950 }
951 
952 void KateScrollBar::marksChanged()
953 {
954  m_lines.clear();
955  update();
956 }
957 
958 void KateScrollBar::redrawMarks()
959 {
960  if (!m_showMarks) {
961  return;
962  }
963  update();
964 }
965 
966 void KateScrollBar::recomputeMarksPositions()
967 {
968  // get the style options to compute the scrollbar pixels
969  QStyleOptionSlider opt;
970  initStyleOption(&opt);
971  QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this);
972 
973  // cache top margin and groove height
974  const int top = grooveRect.top();
975  const int h = grooveRect.height() - 1;
976 
977  // make sure we have a sane height
978  if (h <= 0) {
979  return;
980  }
981 
982  // get total visible (=without folded) lines in the document
983  int visibleLines = m_view->textFolding().visibleLines() - 1;
984  if (m_view->config()->scrollPastEnd()) {
985  visibleLines += m_viewInternal->linesDisplayed() - 1;
986  visibleLines -= m_view->config()->autoCenterLines();
987  }
988 
989  // now repopulate the scrollbar lines list
990  m_lines.clear();
991  const QHash<int, KTextEditor::Mark *> &marks = m_doc->marks();
992  for (QHash<int, KTextEditor::Mark *>::const_iterator i = marks.constBegin(); i != marks.constEnd(); ++i) {
993  KTextEditor::Mark *mark = i.value();
994  const int line = m_view->textFolding().lineToVisibleLine(mark->line);
995  const double ratio = static_cast<double>(line) / visibleLines;
996  m_lines.insert(top + (int)(h * ratio), KateRendererConfig::global()->lineMarkerColor((KTextEditor::MarkInterface::MarkTypes)mark->type));
997  }
998 }
999 
1000 void KateScrollBar::sliderMaybeMoved(int value)
1001 {
1002  if (m_middleMouseDown) {
1003  // we only need to emit this signal once, as for the following slider
1004  // movements the signal sliderMoved() is already emitted.
1005  // Thus, set m_middleMouseDown to false right away.
1006  m_middleMouseDown = false;
1007  emit sliderMMBMoved(value);
1008  }
1009 }
1010 // END
1011 
1012 // BEGIN KateCmdLineEditFlagCompletion
1017 class KateCmdLineEditFlagCompletion : public KCompletion
1018 {
1019 public:
1020  KateCmdLineEditFlagCompletion()
1021  {
1022  ;
1023  }
1024 
1025  QString makeCompletion(const QString & /*s*/) override
1026  {
1027  return QString();
1028  }
1029 };
1030 // END KateCmdLineEditFlagCompletion
1031 
1032 // BEGIN KateCmdLineEdit
1033 KateCommandLineBar::KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent)
1034  : KateViewBarWidget(true, parent)
1035 {
1036  QHBoxLayout *topLayout = new QHBoxLayout();
1037  centralWidget()->setLayout(topLayout);
1038  topLayout->setContentsMargins(0, 0, 0, 0);
1039  m_lineEdit = new KateCmdLineEdit(this, view);
1040  connect(m_lineEdit, SIGNAL(hideRequested()), SIGNAL(hideMe()));
1041  topLayout->addWidget(m_lineEdit);
1042 
1043  QToolButton *helpButton = new QToolButton(this);
1044  helpButton->setAutoRaise(true);
1045  helpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
1046  topLayout->addWidget(helpButton);
1047  connect(helpButton, SIGNAL(clicked()), this, SLOT(showHelpPage()));
1048 
1049  setFocusProxy(m_lineEdit);
1050 }
1051 
1052 void KateCommandLineBar::showHelpPage()
1053 {
1054  KHelpClient::invokeHelp(QStringLiteral("advanced-editing-tools-commandline"), QStringLiteral("kate"));
1055 }
1056 
1057 KateCommandLineBar::~KateCommandLineBar()
1058 {
1059 }
1060 
1061 // inserts the given string in the command line edit and (if selected = true) selects it so the user
1062 // can type over it if they want to
1063 void KateCommandLineBar::setText(const QString &text, bool selected)
1064 {
1065  m_lineEdit->setText(text);
1066  if (selected) {
1067  m_lineEdit->selectAll();
1068  }
1069 }
1070 
1071 void KateCommandLineBar::execute(const QString &text)
1072 {
1073  m_lineEdit->slotReturnPressed(text);
1074 }
1075 
1076 KateCmdLineEdit::KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view)
1077  : KLineEdit()
1078  , m_view(view)
1079  , m_bar(bar)
1080  , m_msgMode(false)
1081  , m_histpos(0)
1082  , m_cmdend(0)
1083  , m_command(nullptr)
1084 {
1085  connect(this, SIGNAL(returnPressed(QString)), this, SLOT(slotReturnPressed(QString)));
1086 
1087  setCompletionObject(KateCmd::self()->commandCompletionObject());
1088  setAutoDeleteCompletionObject(false);
1089 
1090  m_hideTimer = new QTimer(this);
1091  m_hideTimer->setSingleShot(true);
1092  connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hideLineEdit()));
1093 
1094  // make sure the timer is stopped when the user switches views. if not, focus will be given to the
1095  // wrong view when KateViewBar::hideCurrentBarWidget() is called after 4 seconds. (the timer is
1096  // used for showing things like "Success" for four seconds after the user has used the kate
1097  // command line)
1098  connect(m_view, SIGNAL(focusOut(KTextEditor::View *)), m_hideTimer, SLOT(stop()));
1099 }
1100 
1101 void KateCmdLineEdit::hideEvent(QHideEvent *e)
1102 {
1103  Q_UNUSED(e);
1104 }
1105 
1106 QString KateCmdLineEdit::helptext(const QPoint &) const
1107 {
1108  const QString beg = QStringLiteral("<qt background=\"white\"><div><table width=\"100%\"><tr><td bgcolor=\"brown\"><font color=\"white\"><b>Help: <big>");
1109  const QString mid = QStringLiteral("</big></b></font></td></tr><tr><td>");
1110  const QString end = QStringLiteral("</td></tr></table></div><qt>");
1111 
1112  const QString t = text();
1113  static const QRegularExpression re(QStringLiteral("\\s*help\\s+(.*)"));
1114  auto match = re.match(t);
1115  if (match.hasMatch()) {
1116  QString s;
1117  // get help for command
1118  const QString name = match.captured(1);
1119  if (name == QLatin1String("list")) {
1120  return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(QLatin1Char(' ')) + i18n("<p>For help on individual commands, do <code>'help &lt;command&gt;'</code></p>") + end;
1121  } else if (!name.isEmpty()) {
1122  KTextEditor::Command *cmd = KateCmd::self()->queryCommand(name);
1123  if (cmd) {
1124  if (cmd->help(m_view, name, s)) {
1125  return beg + name + mid + s + end;
1126  } else {
1127  return beg + name + mid + i18n("No help for '%1'", name) + end;
1128  }
1129  } else {
1130  return beg + mid + i18n("No such command <b>%1</b>", name) + end;
1131  }
1132  }
1133  }
1134 
1135  return beg + mid +
1136  i18n("<p>This is the Katepart <b>command line</b>.<br />"
1137  "Syntax: <code><b>command [ arguments ]</b></code><br />"
1138  "For a list of available commands, enter <code><b>help list</b></code><br />"
1139  "For help for individual commands, enter <code><b>help &lt;command&gt;</b></code></p>") +
1140  end;
1141 }
1142 
1143 bool KateCmdLineEdit::event(QEvent *e)
1144 {
1145  if (e->type() == QEvent::QueryWhatsThis) {
1146  setWhatsThis(helptext(QPoint()));
1147  e->accept();
1148  return true;
1149  }
1150  return KLineEdit::event(e);
1151 }
1152 
1178 void KateCmdLineEdit::slotReturnPressed(const QString &text)
1179 {
1180  static const QRegularExpression focusChangingCommands(QStringLiteral("^(buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e)$"));
1181 
1182  if (text.isEmpty()) {
1183  return;
1184  }
1185  // silently ignore leading space characters
1186  uint n = 0;
1187  const uint textlen = text.length();
1188  while ((n < textlen) && (text[n].isSpace())) {
1189  n++;
1190  }
1191 
1192  if (n >= textlen) {
1193  return;
1194  }
1195 
1196  QString cmd = text.mid(n);
1197 
1198  // Parse any leading range expression, and strip it (and maybe do some other transforms on the command).
1199  QString leadingRangeExpression;
1200  KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd);
1201 
1202  // Built in help: if the command starts with "help", [try to] show some help
1203  if (cmd.startsWith(QLatin1String("help"))) {
1204  QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint()));
1205  clear();
1206  KateCmd::self()->appendHistory(cmd);
1207  m_histpos = KateCmd::self()->historyLength();
1208  m_oldText.clear();
1209  return;
1210  }
1211 
1212  if (cmd.length() > 0) {
1213  KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd);
1214 
1215  m_oldText = leadingRangeExpression + cmd;
1216  m_msgMode = true;
1217 
1218  // if the command changes the focus itself, the bar should be hidden before execution.
1219  if (focusChangingCommands.match(cmd.leftRef(cmd.indexOf(QLatin1Char(' ')))).hasMatch()) {
1220  emit hideRequested();
1221  }
1222 
1223  if (!p) {
1224  setText(i18n("No such command: \"%1\"", cmd));
1225  } else if (range.isValid() && !p->supportsRange(cmd)) {
1226  // Raise message, when the command does not support ranges.
1227  setText(i18n("Error: No range allowed for command \"%1\".", cmd));
1228  } else {
1229  QString msg;
1230  if (p->exec(m_view, cmd, msg, range)) {
1231  // append command along with range (will be empty if none given) to history
1232  KateCmd::self()->appendHistory(leadingRangeExpression + cmd);
1233  m_histpos = KateCmd::self()->historyLength();
1234  m_oldText.clear();
1235 
1236  if (msg.length() > 0) {
1237  setText(i18n("Success: ") + msg);
1238  } else if (isVisible()) {
1239  // always hide on success without message
1240  emit hideRequested();
1241  }
1242  } else {
1243  if (msg.length() > 0) {
1244  if (msg.contains(QLatin1Char('\n'))) {
1245  // multiline error, use widget with more space
1246  QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), msg);
1247  } else {
1248  setText(msg);
1249  }
1250  } else {
1251  setText(i18n("Command \"%1\" failed.", cmd));
1252  }
1253  }
1254  }
1255  }
1256 
1257  // clean up
1258  if (completionObject() != KateCmd::self()->commandCompletionObject()) {
1259  KCompletion *c = completionObject();
1260  setCompletionObject(KateCmd::self()->commandCompletionObject());
1261  delete c;
1262  }
1263  m_command = nullptr;
1264  m_cmdend = 0;
1265 
1266  if (!focusChangingCommands.match(cmd.leftRef(cmd.indexOf(QLatin1Char(' ')))).hasMatch()) {
1267  m_view->setFocus();
1268  }
1269 
1270  if (isVisible()) {
1271  m_hideTimer->start(4000);
1272  }
1273 }
1274 
1275 void KateCmdLineEdit::hideLineEdit() // unless i have focus ;)
1276 {
1277  if (!hasFocus()) {
1278  emit hideRequested();
1279  }
1280 }
1281 
1282 void KateCmdLineEdit::focusInEvent(QFocusEvent *ev)
1283 {
1284  if (m_msgMode) {
1285  m_msgMode = false;
1286  setText(m_oldText);
1287  selectAll();
1288  }
1289 
1291 }
1292 
1293 void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev)
1294 {
1295  if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) {
1296  m_view->setFocus();
1297  hideLineEdit();
1298  clear();
1299  } else if (ev->key() == Qt::Key_Up) {
1300  fromHistory(true);
1301  } else if (ev->key() == Qt::Key_Down) {
1302  fromHistory(false);
1303  }
1304 
1305  uint cursorpos = cursorPosition();
1307 
1308  // during typing, let us see if we have a valid command
1309  if (!m_cmdend || cursorpos <= m_cmdend) {
1310  QChar c;
1311  if (!ev->text().isEmpty()) {
1312  c = ev->text().at(0);
1313  }
1314 
1315  if (!m_cmdend && !c.isNull()) { // we have no command, so lets see if we got one
1316  if (!c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) {
1317  m_command = KateCmd::self()->queryCommand(text().trimmed());
1318  if (m_command) {
1319  // qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<<m_command<<". text is '"<<text()<<"'";
1320  // if the typed character is ":",
1321  // we try if the command has flag completions
1322  m_cmdend = cursorpos;
1323  // qCDebug(LOG_KTE)<<"keypress in commandline: Set m_cmdend to "<<m_cmdend;
1324  } else {
1325  m_cmdend = 0;
1326  }
1327  }
1328  } else { // since cursor is inside the command name, we reconsider it
1329  // qCDebug(LOG_KTE) << "keypress in commandline: \\W -- text is " << text();
1330  m_command = KateCmd::self()->queryCommand(text().trimmed());
1331  if (m_command) {
1332  // qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<<m_command;
1333  QString t = text();
1334  m_cmdend = 0;
1335  bool b = false;
1336  for (; (int)m_cmdend < t.length(); m_cmdend++) {
1337  if (t[m_cmdend].isLetter()) {
1338  b = true;
1339  }
1340  if (b && (!t[m_cmdend].isLetterOrNumber() && t[m_cmdend] != QLatin1Char('-') && t[m_cmdend] != QLatin1Char('_'))) {
1341  break;
1342  }
1343  }
1344 
1345  if (c == QLatin1Char(':') && cursorpos == m_cmdend) {
1346  // check if this command wants to complete flags
1347  // qCDebug(LOG_KTE)<<"keypress in commandline: Checking if flag completion is desired!";
1348  }
1349  } else {
1350  // clean up if needed
1351  if (completionObject() != KateCmd::self()->commandCompletionObject()) {
1352  KCompletion *c = completionObject();
1353  setCompletionObject(KateCmd::self()->commandCompletionObject());
1354  delete c;
1355  }
1356 
1357  m_cmdend = 0;
1358  }
1359  }
1360 
1361  // if we got a command, check if it wants to do something.
1362  if (m_command) {
1363  KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed());
1364  if (cmpl) {
1365  // We need to prepend the current command name + flag string
1366  // when completion is done
1367  // qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!";
1368 
1369  setCompletionObject(cmpl);
1370  }
1371  }
1372  } else if (m_command && !ev->text().isEmpty()) {
1373  // check if we should call the commands processText()
1374  if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) {
1375  m_command->processText(m_view, text());
1376  }
1377  }
1378 }
1379 
1380 void KateCmdLineEdit::fromHistory(bool up)
1381 {
1382  if (!KateCmd::self()->historyLength()) {
1383  return;
1384  }
1385 
1386  QString s;
1387 
1388  if (up) {
1389  if (m_histpos > 0) {
1390  m_histpos--;
1391  s = KateCmd::self()->fromHistory(m_histpos);
1392  }
1393  } else {
1394  if (m_histpos < (KateCmd::self()->historyLength() - 1)) {
1395  m_histpos++;
1396  s = KateCmd::self()->fromHistory(m_histpos);
1397  } else {
1398  m_histpos = KateCmd::self()->historyLength();
1399  setText(m_oldText);
1400  }
1401  }
1402  if (!s.isEmpty()) {
1403  // Select the argument part of the command, so that it is easy to overwrite
1404  setText(s);
1405  static const QRegularExpression reCmd(QStringLiteral("^[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)"));
1406  const auto match = reCmd.match(text());
1407  if (match.hasMatch()) {
1408  setSelection(text().length() - match.capturedLength(1), match.capturedLength(1));
1409  }
1410  }
1411 }
1412 // END KateCmdLineEdit
1413 
1414 // BEGIN KateIconBorder
1415 using namespace KTextEditor;
1416 
1417 KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent)
1418  : QWidget(parent)
1419  , m_view(internalView->m_view)
1420  , m_doc(internalView->doc())
1421  , m_viewInternal(internalView)
1422  , m_iconBorderOn(false)
1423  , m_lineNumbersOn(false)
1424  , m_relLineNumbersOn(false)
1425  , m_updateRelLineNumbers(false)
1426  , m_foldingMarkersOn(false)
1427  , m_dynWrapIndicatorsOn(false)
1428  , m_annotationBorderOn(false)
1429  , m_updatePositionToArea(true)
1430  , m_annotationItemDelegate(new KateAnnotationItemDelegate(m_viewInternal, this))
1431 {
1432  setAcceptDrops(true);
1433  setAttribute(Qt::WA_StaticContents);
1434 
1435  // See: https://doc.qt.io/qt-5/qwidget.html#update. As this widget does not
1436  // have a background, there's no need for Qt to erase the widget's area
1437  // before repainting. Enabling this prevents flickering when the widget is
1438  // repainted.
1439  setAttribute(Qt::WA_OpaquePaintEvent);
1440 
1441  setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
1442  setMouseTracking(true);
1443  m_doc->setMarkDescription(MarkInterface::markType01, i18n("Bookmark"));
1444  m_doc->setMarkIcon(MarkInterface::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")));
1445 
1446  connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
1447 
1448  updateFont();
1449 
1450  m_antiFlickerTimer.setSingleShot(true);
1451  m_antiFlickerTimer.setInterval(300);
1452  connect(&m_antiFlickerTimer, &QTimer::timeout, this, &KateIconBorder::highlightFolding);
1453 
1454  // user interaction (scrolling) hides e.g. preview
1455  connect(m_view, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate *)), this, SLOT(displayRangeChanged()));
1456 }
1457 
1458 KateIconBorder::~KateIconBorder()
1459 {
1460  delete m_foldingPreview;
1461  delete m_foldingRange;
1462 }
1463 
1464 void KateIconBorder::setIconBorderOn(bool enable)
1465 {
1466  if (enable == m_iconBorderOn) {
1467  return;
1468  }
1469 
1470  m_iconBorderOn = enable;
1471 
1472  m_updatePositionToArea = true;
1473 
1474  QTimer::singleShot(0, this, SLOT(update()));
1475 }
1476 
1477 void KateIconBorder::setAnnotationBorderOn(bool enable)
1478 {
1479  if (enable == m_annotationBorderOn) {
1480  return;
1481  }
1482 
1483  m_annotationBorderOn = enable;
1484 
1485  // make sure the tooltip is hidden
1486  if (!m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
1487  m_hoveredAnnotationGroupIdentifier.clear();
1488  hideAnnotationTooltip();
1489  }
1490 
1491  emit m_view->annotationBorderVisibilityChanged(m_view, enable);
1492 
1493  m_updatePositionToArea = true;
1494 
1495  QTimer::singleShot(0, this, SLOT(update()));
1496 }
1497 
1498 void KateIconBorder::removeAnnotationHovering()
1499 {
1500  // remove hovering if it's still there
1501  if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
1502  m_hoveredAnnotationGroupIdentifier.clear();
1503  QTimer::singleShot(0, this, SLOT(update()));
1504  }
1505 }
1506 
1507 void KateIconBorder::setLineNumbersOn(bool enable)
1508 {
1509  if (enable == m_lineNumbersOn) {
1510  return;
1511  }
1512 
1513  m_lineNumbersOn = enable;
1514  m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators;
1515 
1516  m_updatePositionToArea = true;
1517 
1518  QTimer::singleShot(0, this, SLOT(update()));
1519 }
1520 
1521 void KateIconBorder::setRelLineNumbersOn(bool enable)
1522 {
1523  if (enable == m_relLineNumbersOn) {
1524  return;
1525  }
1526 
1527  m_relLineNumbersOn = enable;
1528  /*
1529  * We don't have to touch the m_dynWrapIndicatorsOn because
1530  * we already got it right from the m_lineNumbersOn
1531  */
1532  m_updatePositionToArea = true;
1533 
1534  QTimer::singleShot(0, this, SLOT(update()));
1535 }
1536 
1537 void KateIconBorder::updateForCursorLineChange()
1538 {
1539  if (m_relLineNumbersOn) {
1540  m_updateRelLineNumbers = true;
1541  }
1542 
1543  // always do normal update, e.g. for different current line color!
1544  update();
1545 }
1546 
1547 void KateIconBorder::setDynWrapIndicators(int state)
1548 {
1549  if (state == m_dynWrapIndicators) {
1550  return;
1551  }
1552 
1553  m_dynWrapIndicators = state;
1554  m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state;
1555 
1556  m_updatePositionToArea = true;
1557 
1558  QTimer::singleShot(0, this, SLOT(update()));
1559 }
1560 
1561 void KateIconBorder::setFoldingMarkersOn(bool enable)
1562 {
1563  if (enable == m_foldingMarkersOn) {
1564  return;
1565  }
1566 
1567  m_foldingMarkersOn = enable;
1568 
1569  m_updatePositionToArea = true;
1570 
1571  QTimer::singleShot(0, this, SLOT(update()));
1572 }
1573 
1575 {
1576  int w = 1; // Must be any value != 0 or we will never painted!
1577 
1578  const int i = m_positionToArea.size();
1579  if (i > 0) {
1580  w = m_positionToArea.at(i - 1).first;
1581  }
1582 
1583  return QSize(w, 0);
1584 }
1585 
1586 // This function (re)calculates the maximum width of any of the digit characters (0 -> 9)
1587 // for graceful handling of variable-width fonts as the linenumber font.
1588 void KateIconBorder::updateFont()
1589 {
1590  const QFontMetricsF &fm = m_view->renderer()->currentFontMetrics();
1591  m_maxCharWidth = 0.0;
1592  // Loop to determine the widest numeric character in the current font.
1593  // 48 is ascii '0'
1594  for (int i = 48; i < 58; i++) {
1595  const qreal charWidth = ceil(fm.boundingRect(QChar(i)).width());
1596  m_maxCharWidth = qMax(m_maxCharWidth, charWidth);
1597  }
1598 
1599  // NOTE/TODO(or not) Take size of m_dynWrapIndicatorChar into account.
1600  // It's a multi-char and it's reported size is, even with a mono-space font,
1601  // bigger than each digit, e.g. 10 vs 12. Currently it seems to work even with
1602  // "Line Numbers Off" but all these width calculating looks slightly hacky
1603 
1604  // the icon pane scales with the font...
1605  m_iconAreaWidth = fm.height();
1606 
1607  // Only for now, later may that become an own value
1608  m_foldingAreaWidth = m_iconAreaWidth;
1609 
1610  calcAnnotationBorderWidth();
1611 
1612  m_updatePositionToArea = true;
1613 
1614  QTimer::singleShot(0, this, SLOT(update()));
1615 }
1616 
1617 int KateIconBorder::lineNumberWidth() const
1618 {
1619  int width = 0;
1620  // Avoid unneeded expensive calculations ;-)
1621  if (m_lineNumbersOn) {
1622  // width = (number of digits + 1) * char width
1623  const int digits = (int)ceil(log10((double)(m_view->doc()->lines() + 1)));
1624  width = (int)ceil((digits + 1) * m_maxCharWidth);
1625  }
1626 
1627  if ((width < 1) && m_dynWrapIndicatorsOn && m_view->config()->dynWordWrap()) {
1628  // FIXME Why 2x? because of above (number of digits + 1)
1629  // -> looks to me like a hint for bad calculation elsewhere
1630  width = 2 * m_maxCharWidth;
1631  }
1632 
1633  return width;
1634 }
1635 
1636 void KateIconBorder::dragEnterEvent(QDragEnterEvent *event)
1637 {
1638  m_view->m_viewInternal->dragEnterEvent(event);
1639 }
1640 
1641 void KateIconBorder::dragMoveEvent(QDragMoveEvent *event)
1642 {
1643  // FIXME Just calling m_view->m_viewInternal->dragMoveEvent(e) don't work
1644  // as intended, we need to set the cursor at column 1
1645  // Is there a way to change the pos of the event?
1646  QPoint pos(0, event->pos().y());
1647  // Code copy of KateViewInternal::dragMoveEvent
1648  m_view->m_viewInternal->placeCursor(pos, true, false);
1649  m_view->m_viewInternal->fixDropEvent(event);
1650 }
1651 
1652 void KateIconBorder::dropEvent(QDropEvent *event)
1653 {
1654  m_view->m_viewInternal->dropEvent(event);
1655 }
1656 
1657 void KateIconBorder::paintEvent(QPaintEvent *e)
1658 {
1659  paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height());
1660 }
1661 
1662 static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open)
1663 {
1664  painter.setRenderHint(QPainter::Antialiasing);
1665 
1666  qreal size = qMin(width, height);
1667 
1668  if (open) {
1669  // Paint unfolded icon less pushy
1670  if (KColorUtils::luma(c) < 0.25) {
1671  c = KColorUtils::darken(c);
1672  } else {
1673  c = KColorUtils::shade(c, 0.1);
1674  }
1675 
1676  } else {
1677  // Paint folded icon in contrast to popup highlighting
1678  if (KColorUtils::luma(c) > 0.25) {
1679  c = KColorUtils::darken(c);
1680  } else {
1681  c = KColorUtils::shade(c, 0.1);
1682  }
1683  }
1684 
1685  QPen pen;
1686  pen.setJoinStyle(Qt::RoundJoin);
1687  pen.setColor(c);
1688  pen.setWidthF(1.5);
1689  painter.setPen(pen);
1690  painter.setBrush(c);
1691 
1692  // let some border, if possible
1693  size *= 0.6;
1694 
1695  qreal halfSize = size / 2;
1696  qreal halfSizeP = halfSize * 0.6;
1697  QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2);
1698 
1699  if (open) {
1700  QPointF points[3] = {middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP)};
1701  painter.drawConvexPolygon(points, 3);
1702  } else {
1703  QPointF points[3] = {middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0)};
1704  painter.drawConvexPolygon(points, 3);
1705  }
1706 
1707  painter.setRenderHint(QPainter::Antialiasing, false);
1708 }
1709 
1715 class KateAnnotationGroupIdentifier
1716 {
1717 public:
1718  KateAnnotationGroupIdentifier(const QVariant &identifier)
1719  : m_isValid(identifier.isValid() && identifier.canConvert<QString>())
1720  , m_id(m_isValid ? identifier.toString() : QString())
1721  {
1722  }
1723  KateAnnotationGroupIdentifier() = default;
1724  KateAnnotationGroupIdentifier(const KateAnnotationGroupIdentifier &rhs) = default;
1725 
1726  KateAnnotationGroupIdentifier &operator=(const KateAnnotationGroupIdentifier &rhs)
1727  {
1728  m_isValid = rhs.m_isValid;
1729  m_id = rhs.m_id;
1730  return *this;
1731  }
1732  KateAnnotationGroupIdentifier &operator=(const QVariant &identifier)
1733  {
1734  m_isValid = (identifier.isValid() && identifier.canConvert<QString>());
1735  if (m_isValid) {
1736  m_id = identifier.toString();
1737  } else {
1738  m_id.clear();
1739  }
1740  return *this;
1741  }
1742 
1743  bool operator==(const KateAnnotationGroupIdentifier &rhs) const
1744  {
1745  return (m_isValid == rhs.m_isValid) && (!m_isValid || (m_id == rhs.m_id));
1746  }
1747  bool operator!=(const KateAnnotationGroupIdentifier &rhs) const
1748  {
1749  return (m_isValid != rhs.m_isValid) || (m_isValid && (m_id != rhs.m_id));
1750  }
1751 
1752  void clear()
1753  {
1754  m_isValid = false;
1755  m_id.clear();
1756  }
1757  bool isValid() const
1758  {
1759  return m_isValid;
1760  }
1761  const QString &id() const
1762  {
1763  return m_id;
1764  }
1765 
1766 private:
1767  bool m_isValid = false;
1768  QString m_id;
1769 };
1770 
1775 class KateAnnotationGroupPositionState
1776 {
1777 public:
1783  KateAnnotationGroupPositionState(KateViewInternal *viewInternal, const KTextEditor::AnnotationModel *model, const QString &hoveredAnnotationGroupIdentifier, uint startz, bool isUsed);
1789  void nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine);
1790 
1791 private:
1792  KateViewInternal *m_viewInternal;
1793  const KTextEditor::AnnotationModel *const m_model;
1794  const QString m_hoveredAnnotationGroupIdentifier;
1795 
1796  int m_visibleWrappedLineInAnnotationGroup = -1;
1797  KateAnnotationGroupIdentifier m_lastAnnotationGroupIdentifier;
1798  KateAnnotationGroupIdentifier m_nextAnnotationGroupIdentifier;
1799  bool m_isSameAnnotationGroupsSinceLast = false;
1800 };
1801 
1802 KateAnnotationGroupPositionState::KateAnnotationGroupPositionState(KateViewInternal *viewInternal, const KTextEditor::AnnotationModel *model, const QString &hoveredAnnotationGroupIdentifier, uint startz, bool isUsed)
1803  : m_viewInternal(viewInternal)
1804  , m_model(model)
1805  , m_hoveredAnnotationGroupIdentifier(hoveredAnnotationGroupIdentifier)
1806 {
1807  if (!isUsed) {
1808  return;
1809  }
1810 
1811  if (!m_model || (static_cast<int>(startz) >= m_viewInternal->cache()->viewCacheLineCount())) {
1812  return;
1813  }
1814 
1815  const auto realLineAtStart = m_viewInternal->cache()->viewLine(startz).line();
1816  m_nextAnnotationGroupIdentifier = m_model->data(realLineAtStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1817  if (m_nextAnnotationGroupIdentifier.isValid()) {
1818  // estimate state of annotation group before first rendered line
1819  if (startz == 0) {
1820  if (realLineAtStart > 0) {
1821  // TODO: here we would want to scan until the next line that would be displayed,
1822  // to see if there are any group changes until then
1823  // for now simply taking neighbour line into account, not a grave bug on the first displayed line
1824  m_lastAnnotationGroupIdentifier = m_model->data(realLineAtStart - 1, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1825  m_isSameAnnotationGroupsSinceLast = (m_lastAnnotationGroupIdentifier == m_nextAnnotationGroupIdentifier);
1826  }
1827  } else {
1828  const auto realLineBeforeStart = m_viewInternal->cache()->viewLine(startz - 1).line();
1829  m_lastAnnotationGroupIdentifier = m_model->data(realLineBeforeStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1830  if (m_lastAnnotationGroupIdentifier.isValid()) {
1831  if (m_lastAnnotationGroupIdentifier.id() == m_nextAnnotationGroupIdentifier.id()) {
1832  m_isSameAnnotationGroupsSinceLast = true;
1833  // estimate m_visibleWrappedLineInAnnotationGroup from lines before startz
1834  for (uint z = startz; z > 0; --z) {
1835  const auto realLine = m_viewInternal->cache()->viewLine(z - 1).line();
1836  const KateAnnotationGroupIdentifier identifier = m_model->data(realLine, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1837  if (identifier != m_lastAnnotationGroupIdentifier) {
1838  break;
1839  }
1840  ++m_visibleWrappedLineInAnnotationGroup;
1841  }
1842  }
1843  }
1844  }
1845  }
1846 }
1847 
1848 void KateAnnotationGroupPositionState::nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine)
1849 {
1850  styleOption.wrappedLine = m_viewInternal->cache()->viewLine(z).viewLine();
1851  styleOption.wrappedLineCount = m_viewInternal->cache()->viewLineCount(realLine);
1852 
1853  // Estimate position in group
1854  const KateAnnotationGroupIdentifier annotationGroupIdentifier = m_nextAnnotationGroupIdentifier;
1855  bool isSameAnnotationGroupsSinceThis = false;
1856  // Calculate next line's group identifier
1857  // shortcut: assuming wrapped lines are always displayed together, test is simple
1858  if (styleOption.wrappedLine + 1 < styleOption.wrappedLineCount) {
1859  m_nextAnnotationGroupIdentifier = annotationGroupIdentifier;
1860  isSameAnnotationGroupsSinceThis = true;
1861  } else {
1862  if (static_cast<int>(z + 1) < m_viewInternal->cache()->viewCacheLineCount()) {
1863  const int realLineAfter = m_viewInternal->cache()->viewLine(z + 1).line();
1864  // search for any realLine with a different group id, also the non-displayed
1865  int rl = realLine + 1;
1866  for (; rl <= realLineAfter; ++rl) {
1867  m_nextAnnotationGroupIdentifier = m_model->data(rl, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1868  if (!m_nextAnnotationGroupIdentifier.isValid() || (m_nextAnnotationGroupIdentifier.id() != annotationGroupIdentifier.id())) {
1869  break;
1870  }
1871  }
1872  isSameAnnotationGroupsSinceThis = (rl > realLineAfter);
1873  if (rl < realLineAfter) {
1874  m_nextAnnotationGroupIdentifier = m_model->data(realLineAfter, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1875  }
1876  } else {
1877  // TODO: check next line after display end
1878  m_nextAnnotationGroupIdentifier.clear();
1879  isSameAnnotationGroupsSinceThis = false;
1880  }
1881  }
1882 
1883  if (annotationGroupIdentifier.isValid()) {
1884  if (m_hoveredAnnotationGroupIdentifier == annotationGroupIdentifier.id()) {
1885  styleOption.state |= QStyle::State_MouseOver;
1886  } else {
1887  styleOption.state &= ~QStyle::State_MouseOver;
1888  }
1889 
1890  if (m_isSameAnnotationGroupsSinceLast) {
1891  ++m_visibleWrappedLineInAnnotationGroup;
1892  } else {
1893  m_visibleWrappedLineInAnnotationGroup = 0;
1894  }
1895 
1896  styleOption.annotationItemGroupingPosition = StyleOptionAnnotationItem::InGroup;
1897  if (!m_isSameAnnotationGroupsSinceLast) {
1898  styleOption.annotationItemGroupingPosition |= StyleOptionAnnotationItem::GroupBegin;
1899  }
1900  if (!isSameAnnotationGroupsSinceThis) {
1901  styleOption.annotationItemGroupingPosition |= StyleOptionAnnotationItem::GroupEnd;
1902  }
1903  } else {
1904  m_visibleWrappedLineInAnnotationGroup = 0;
1905  }
1906  styleOption.visibleWrappedLineInGroup = m_visibleWrappedLineInAnnotationGroup;
1907 
1908  m_lastAnnotationGroupIdentifier = m_nextAnnotationGroupIdentifier;
1909  m_isSameAnnotationGroupsSinceLast = isSameAnnotationGroupsSinceThis;
1910 }
1911 
1912 void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height)
1913 {
1914  const uint h = m_view->renderer()->lineHeight();
1915  const uint startz = (y / h);
1916  const uint endz = qMin(startz + 1 + (height / h), static_cast<uint>(m_viewInternal->cache()->viewCacheLineCount()));
1917  const uint currentLine = m_view->cursorPosition().line();
1918 
1919  // center the folding boxes
1920  int m_px = (h - 11) / 2;
1921  if (m_px < 0) {
1922  m_px = 0;
1923  }
1924 
1925  // Ensure we miss no change of the count of line number digits
1926  const int newNeededWidth = lineNumberWidth();
1927 
1928  if (m_updatePositionToArea || (newNeededWidth != m_lineNumberAreaWidth)) {
1929  m_lineNumberAreaWidth = newNeededWidth;
1930  m_updatePositionToArea = true;
1931  m_positionToArea.clear();
1932  }
1933 
1934  QPainter p(this);
1935  p.setRenderHints(QPainter::TextAntialiasing);
1936  p.setFont(m_view->renderer()->currentFont()); // for line numbers
1937 
1938  KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
1939  KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, m_hoveredAnnotationGroupIdentifier, startz, m_annotationBorderOn);
1940 
1941  // Fetch often used data only once, improve readability
1942  const int w = width();
1943  const QColor iconBarColor = m_view->renderer()->config()->iconBarColor(); // Effective our background
1944  const QColor lineNumberColor = m_view->renderer()->config()->lineNumberColor();
1945  const QColor backgroundColor = m_view->renderer()->config()->backgroundColor(); // Of the edit area
1946 
1947  // Paint the border in chunks line by line
1948  for (uint z = startz; z < endz; z++) {
1949  // Painting coordinates, lineHeight * lineNumber
1950  const uint y = h * z;
1951  // Paint the border in chunks left->right, remember used width
1952  uint lnX = 0;
1953 
1954  // Paint background over full width...
1955  p.fillRect(lnX, y, w, h, iconBarColor);
1956  // ...and overpaint again the end to simulate some margin to the edit area,
1957  // so that the text not looks like stuck to the border
1958  p.fillRect(w - m_separatorWidth, y, w, h, backgroundColor);
1959 
1960  const KateTextLayout lineLayout = m_viewInternal->cache()->viewLine(z);
1961  int realLine = lineLayout.line();
1962  if (realLine < 0) {
1963  // We have reached the end of the document, just paint background
1964  continue;
1965  }
1966 
1967  // icon pane
1968  if (m_iconBorderOn) {
1969  p.setPen(m_view->renderer()->config()->separatorColor());
1970  p.setBrush(m_view->renderer()->config()->separatorColor());
1971  p.drawLine(lnX + m_iconAreaWidth, y, lnX + m_iconAreaWidth, y + h);
1972 
1973  const uint mrk(m_doc->mark(realLine)); // call only once
1974  if (mrk && lineLayout.startCol() == 0) {
1975  for (uint bit = 0; bit < 32; bit++) {
1976  MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit);
1977  if (mrk & markType) {
1978  const QIcon markIcon = m_doc->markIcon(markType);
1979 
1980  if (!markIcon.isNull() && h > 0 && m_iconAreaWidth > 0) {
1981  const int s = qMin(m_iconAreaWidth, static_cast<int>(h)) - 2;
1982 
1983  // center the mark pixmap
1984  const int x_px = qMax(m_iconAreaWidth - s, 0) / 2;
1985  const int y_px = qMax(static_cast<int>(h) - s, 0) / 2;
1986 
1987  markIcon.paint(&p, lnX + x_px, y + y_px, s, s);
1988  }
1989  }
1990  }
1991  }
1992 
1993  lnX += m_iconAreaWidth + m_separatorWidth;
1994  if (m_updatePositionToArea) {
1995  m_positionToArea.append(AreaPosition(lnX, IconBorder));
1996  }
1997  }
1998 
1999  // annotation information
2000  if (m_annotationBorderOn) {
2001  // Draw a border line between annotations and the line numbers
2002  p.setPen(lineNumberColor);
2003  p.setBrush(lineNumberColor);
2004 
2005  const qreal borderX = lnX + m_annotationAreaWidth + 0.5;
2006  p.drawLine(QPointF(borderX, y + 0.5), QPointF(borderX, y + h - 0.5));
2007 
2008  if (model) {
2010  initStyleOption(&styleOption);
2011  styleOption.rect.setRect(lnX, y, m_annotationAreaWidth, h);
2012  annotationGroupPositionState.nextLine(styleOption, z, realLine);
2013 
2014  m_annotationItemDelegate->paint(&p, styleOption, model, realLine);
2015  }
2016 
2017  lnX += m_annotationAreaWidth + m_separatorWidth;
2018  if (m_updatePositionToArea) {
2019  m_positionToArea.append(AreaPosition(lnX, AnnotationBorder));
2020  }
2021  }
2022 
2023  // line number
2024  if (m_lineNumbersOn || m_dynWrapIndicatorsOn) {
2025  QColor usedLineNumberColor;
2026  const int distanceToCurrent = abs(realLine - static_cast<int>(currentLine));
2027  if (distanceToCurrent == 0) {
2028  usedLineNumberColor = m_view->renderer()->config()->currentLineNumberColor();
2029  } else {
2030  usedLineNumberColor = lineNumberColor;
2031  }
2032  p.setPen(usedLineNumberColor);
2033  p.setBrush(usedLineNumberColor);
2034 
2035  if (lineLayout.startCol() == 0) {
2036  if (m_relLineNumbersOn) {
2037  if (distanceToCurrent == 0) {
2038  p.drawText(lnX + m_maxCharWidth / 2, y, m_lineNumberAreaWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignLeft | Qt::AlignVCenter, QString::number(realLine + 1));
2039  } else {
2040  p.drawText(lnX + m_maxCharWidth / 2, y, m_lineNumberAreaWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, QString::number(distanceToCurrent));
2041  }
2042  if (m_updateRelLineNumbers) {
2043  m_updateRelLineNumbers = false;
2044  update();
2045  }
2046  } else if (m_lineNumbersOn) {
2047  p.drawText(lnX + m_maxCharWidth / 2, y, m_lineNumberAreaWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, QString::number(realLine + 1));
2048  }
2049  } else if (m_dynWrapIndicatorsOn) {
2050  p.drawText(lnX + m_maxCharWidth / 2, y, m_lineNumberAreaWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, m_dynWrapIndicatorChar);
2051  }
2052 
2053  lnX += m_lineNumberAreaWidth + m_separatorWidth;
2054  if (m_updatePositionToArea) {
2055  m_positionToArea.append(AreaPosition(lnX, LineNumbers));
2056  }
2057  }
2058 
2059  // modified line system
2060  if (m_view->config()->lineModification() && !m_doc->url().isEmpty()) {
2061  const Kate::TextLine tl = m_doc->plainKateTextLine(realLine);
2062  if (tl->markedAsModified()) {
2063  p.fillRect(lnX, y, m_modAreaWidth, h, m_view->renderer()->config()->modifiedLineColor());
2064  } else if (tl->markedAsSavedOnDisk()) {
2065  p.fillRect(lnX, y, m_modAreaWidth, h, m_view->renderer()->config()->savedLineColor());
2066  } else {
2067  p.fillRect(lnX, y, m_modAreaWidth, h, iconBarColor);
2068  }
2069 
2070  lnX += m_modAreaWidth; // No m_separatorWidth
2071  if (m_updatePositionToArea) {
2072  m_positionToArea.append(AreaPosition(lnX, None));
2073  }
2074  }
2075 
2076  // folding markers
2077  if (m_foldingMarkersOn) {
2078  const QColor foldingColor(m_view->renderer()->config()->foldingColor());
2079  // possible additional folding highlighting
2080  if (m_foldingRange && m_foldingRange->overlapsLine(realLine)) {
2081  p.fillRect(lnX, y, m_foldingAreaWidth, h, foldingColor);
2082  }
2083 
2084  if (lineLayout.startCol() == 0) {
2085  QVector<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>> startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine);
2086  bool anyFolded = false;
2087  for (int i = 0; i < startingRanges.size(); ++i) {
2088  if (startingRanges[i].second & Kate::TextFolding::Folded) {
2089  anyFolded = true;
2090  }
2091  }
2092  const Kate::TextLine tl = m_doc->kateTextLine(realLine);
2093  if (!startingRanges.isEmpty() || tl->markedAsFoldingStart()) {
2094  if (anyFolded) {
2095  paintTriangle(p, foldingColor, lnX, y, m_foldingAreaWidth, h, false);
2096  } else {
2097  // Don't try to use currentLineNumberColor, the folded icon gets also not highligted
2098  paintTriangle(p, lineNumberColor, lnX, y, m_foldingAreaWidth, h, true);
2099  }
2100  }
2101  }
2102 
2103  lnX += m_foldingAreaWidth;
2104  if (m_updatePositionToArea) {
2105  m_positionToArea.append(AreaPosition(lnX, FoldingMarkers));
2106  }
2107  }
2108 
2109  if (m_updatePositionToArea) {
2110  m_updatePositionToArea = false;
2111  // Don't forget our "text-stuck-to-border" protector
2112  lnX += m_separatorWidth;
2113  m_positionToArea.append(AreaPosition(lnX, None));
2114  // Now that we know our needed space, ensure we are painted properly
2115  updateGeometry();
2116  update();
2117  return;
2118  }
2119  }
2120 }
2121 
2122 KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const
2123 {
2124  for (int i = 0; i < m_positionToArea.size(); ++i) {
2125  if (p.x() <= m_positionToArea.at(i).first) {
2126  return m_positionToArea.at(i).second;
2127  }
2128  }
2129 
2130  return None;
2131 }
2132 
2133 void KateIconBorder::mousePressEvent(QMouseEvent *e)
2134 {
2135  const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y());
2136  if (t.isValid()) {
2137  m_lastClickedLine = t.line();
2138  const auto area = positionToArea(e->pos());
2139  // IconBorder and AnnotationBorder have their own behavior; don't forward to view
2140  if (area != IconBorder && area != AnnotationBorder) {
2141  const auto pos = QPoint(0, e->y());
2142  if (area == LineNumbers && e->button() == Qt::LeftButton && !(e->modifiers() & Qt::ShiftModifier)) {
2143  // setup view so the following mousePressEvent will select the line
2144  m_viewInternal->beginSelectLine(pos);
2145  }
2146  QMouseEvent forward(QEvent::MouseButtonPress, pos, e->button(), e->buttons(), e->modifiers());
2147  m_viewInternal->mousePressEvent(&forward);
2148  }
2149  return e->accept();
2150  }
2151 
2153 }
2154 
2155 void KateIconBorder::highlightFoldingDelayed(int line)
2156 {
2157  if ((line == m_currentLine) || (line >= m_doc->buffer().lines())) {
2158  return;
2159  }
2160 
2161  m_currentLine = line;
2162 
2163  if (m_foldingRange) {
2164  // We are for a while in the folding area, no need for delay
2165  highlightFolding();
2166 
2167  } else if (!m_antiFlickerTimer.isActive()) {
2168  m_antiFlickerTimer.start();
2169  }
2170 }
2171 
2172 void KateIconBorder::highlightFolding()
2173 {
2179  for (int line = m_currentLine; line >= qMax(0, m_currentLine - 1024); --line) {
2183  KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line);
2184  if (!foldingRange.isValid()) {
2185  continue;
2186  }
2187 
2191  if (foldingRange.overlapsLine(m_currentLine)) {
2192  newRange = foldingRange;
2193  break;
2194  }
2195  }
2196 
2197  if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) {
2198  // new range equals the old one, nothing to do.
2199  return;
2200  }
2201 
2202  // the ranges differ, delete the old, if it exists
2203  delete m_foldingRange;
2204  m_foldingRange = nullptr;
2205  // New range, new preview!
2206  delete m_foldingPreview;
2207 
2208  bool showPreview = false;
2209 
2210  if (newRange.isValid()) {
2211  // When next line is not visible we have a folded range, only then we want a preview!
2212  showPreview = !m_view->textFolding().isLineVisible(newRange.start().line() + 1);
2213 
2214  // qCDebug(LOG_KTE) << "new folding hl-range:" << newRange;
2215  m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight);
2217 
2222  attr->setBackground(QBrush(m_view->renderer()->config()->foldingColor()));
2223 
2224  m_foldingRange->setView(m_view);
2225  // use z depth defined in moving ranges interface
2226  m_foldingRange->setZDepth(-100.0);
2227  m_foldingRange->setAttribute(attr);
2228  }
2229 
2230  // show text preview, if a folded region starts here...
2231  // ...but only when main window is active (#392396)
2232  const bool isWindowActive = !window() || window()->isActiveWindow();
2233  if (showPreview && m_view->config()->foldingPreview() && isWindowActive) {
2234  m_foldingPreview = new KateTextPreview(m_view, this);
2235  m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating);
2236  m_foldingPreview->setFrameStyle(QFrame::StyledPanel);
2237 
2238  // Calc how many lines can be displayed in the popup
2239  const int lineHeight = m_view->renderer()->lineHeight();
2240  const int foldingStartLine = m_foldingRange->start().line();
2241  // FIXME Is there really no easier way to find lineInDisplay?
2242  const QPoint pos = m_viewInternal->mapFrom(m_view, m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0)));
2243  const int lineInDisplay = pos.y() / lineHeight;
2244  // Allow slightly overpainting of the view bottom to proper cover all lines
2245  const int extra = (m_viewInternal->height() % lineHeight) > (lineHeight * 0.6) ? 1 : 0;
2246  const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, m_viewInternal->linesDisplayed() - lineInDisplay + extra);
2247 
2248  m_foldingPreview->resize(m_viewInternal->width(), lineCount * lineHeight + 2 * m_foldingPreview->frameWidth());
2249  const int xGlobal = mapToGlobal(QPoint(width(), 0)).x();
2250  const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0))).y();
2251  m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft());
2252  m_foldingPreview->setLine(foldingStartLine);
2253  m_foldingPreview->setCenterView(false);
2254  m_foldingPreview->setShowFoldedLines(true);
2255  m_foldingPreview->raise();
2256  m_foldingPreview->show();
2257  }
2258 }
2259 
2260 void KateIconBorder::hideFolding()
2261 {
2262  if (m_antiFlickerTimer.isActive()) {
2263  m_antiFlickerTimer.stop();
2264  }
2265 
2266  m_currentLine = -1;
2267  delete m_foldingRange;
2268  m_foldingRange = nullptr;
2269 
2270  delete m_foldingPreview;
2271 }
2272 
2273 void KateIconBorder::leaveEvent(QEvent *event)
2274 {
2275  hideFolding();
2276  removeAnnotationHovering();
2277 
2278  QWidget::leaveEvent(event);
2279 }
2280 
2281 void KateIconBorder::mouseMoveEvent(QMouseEvent *e)
2282 {
2283  const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y());
2284  if (!t.isValid()) {
2285  // Cleanup everything which may be shown
2286  removeAnnotationHovering();
2287  hideFolding();
2288 
2289  } else {
2290  const BorderArea area = positionToArea(e->pos());
2291  if (area == FoldingMarkers) {
2292  highlightFoldingDelayed(t.line());
2293  } else {
2294  hideFolding();
2295  }
2296  if (area == AnnotationBorder) {
2297  KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2298  if (model) {
2299  m_hoveredAnnotationGroupIdentifier = model->data(t.line(), (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole).toString();
2300  const QPoint viewRelativePos = m_view->mapFromGlobal(e->globalPos());
2301  QHelpEvent helpEvent(QEvent::ToolTip, viewRelativePos, e->globalPos());
2303  initStyleOption(&styleOption);
2304  styleOption.rect = annotationLineRectInView(t.line());
2305  setStyleOptionLineData(&styleOption, e->y(), t.line(), model, m_hoveredAnnotationGroupIdentifier);
2306  m_annotationItemDelegate->helpEvent(&helpEvent, m_view, styleOption, model, t.line());
2307 
2308  QTimer::singleShot(0, this, SLOT(update()));
2309  }
2310  } else {
2311  if (area == IconBorder) {
2312  m_doc->requestMarkTooltip(t.line(), e->globalPos());
2313  }
2314 
2315  m_hoveredAnnotationGroupIdentifier.clear();
2316  QTimer::singleShot(0, this, SLOT(update()));
2317  }
2318  if (area != IconBorder) {
2319  QPoint p = m_viewInternal->mapFromGlobal(e->globalPos());
2320  QMouseEvent forward(QEvent::MouseMove, p, e->button(), e->buttons(), e->modifiers());
2321  m_viewInternal->mouseMoveEvent(&forward);
2322  }
2323  }
2324 
2326 }
2327 
2328 void KateIconBorder::mouseReleaseEvent(QMouseEvent *e)
2329 {
2330  const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line();
2331  if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) {
2332  const BorderArea area = positionToArea(e->pos());
2333  if (area == IconBorder) {
2334  if (e->button() == Qt::LeftButton) {
2335  if (!m_doc->handleMarkClick(cursorOnLine)) {
2336  KateViewConfig *config = m_view->config();
2337  const uint editBits = m_doc->editableMarks();
2338  // is the default or the only editable mark
2339  const uint singleMark = qPopulationCount(editBits) > 1 ? editBits & config->defaultMarkType() : editBits;
2340  if (singleMark) {
2341  if (m_doc->mark(cursorOnLine) & singleMark) {
2342  m_doc->removeMark(cursorOnLine, singleMark);
2343  } else {
2344  m_doc->addMark(cursorOnLine, singleMark);
2345  }
2346  } else if (config->allowMarkMenu()) {
2347  showMarkMenu(cursorOnLine, QCursor::pos());
2348  }
2349  }
2350  } else if (e->button() == Qt::RightButton) {
2351  showMarkMenu(cursorOnLine, QCursor::pos());
2352  }
2353  }
2354 
2355  if (area == FoldingMarkers) {
2356  // Prefer the highlighted range over the exact clicked line
2357  const int lineToToggle = m_foldingRange ? m_foldingRange->toRange().start().line() : cursorOnLine;
2358  if (e->button() == Qt::LeftButton) {
2359  m_view->toggleFoldingOfLine(lineToToggle);
2360  } else if (e->button() == Qt::RightButton) {
2361  m_view->toggleFoldingsInRange(lineToToggle);
2362  }
2363 
2364  delete m_foldingPreview;
2365  }
2366 
2367  if (area == AnnotationBorder) {
2368  const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this);
2369  if (e->button() == Qt::LeftButton && singleClick) {
2370  emit m_view->annotationActivated(m_view, cursorOnLine);
2371  } else if (e->button() == Qt::RightButton) {
2372  showAnnotationMenu(cursorOnLine, e->globalPos());
2373  }
2374  }
2375  }
2376 
2377  QMouseEvent forward(QEvent::MouseButtonRelease, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers());
2378  m_viewInternal->mouseReleaseEvent(&forward);
2379 }
2380 
2381 void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e)
2382 {
2383  int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line();
2384 
2385  if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) {
2386  const BorderArea area = positionToArea(e->pos());
2387  const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this);
2388  if (area == AnnotationBorder && !singleClick) {
2389  emit m_view->annotationActivated(m_view, cursorOnLine);
2390  }
2391  }
2392  QMouseEvent forward(QEvent::MouseButtonDblClick, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers());
2393  m_viewInternal->mouseDoubleClickEvent(&forward);
2394 }
2395 
2396 void KateIconBorder::wheelEvent(QWheelEvent *e)
2397 {
2398  QCoreApplication::sendEvent(m_viewInternal, e);
2399 }
2400 
2401 void KateIconBorder::showMarkMenu(uint line, const QPoint &pos)
2402 {
2403  if (m_doc->handleMarkContextMenu(line, pos)) {
2404  return;
2405  }
2406 
2407  if (!m_view->config()->allowMarkMenu()) {
2408  return;
2409  }
2410 
2411  QMenu markMenu;
2412  QMenu selectDefaultMark;
2413  auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark);
2414 
2415  QVector<int> vec(33);
2416  int i = 1;
2417 
2418  for (uint bit = 0; bit < 32; bit++) {
2419  MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit);
2420  if (!(m_doc->editableMarks() & markType)) {
2421  continue;
2422  }
2423 
2424  QAction *mA;
2425  QAction *dMA;
2426  const QIcon icon = m_doc->markIcon(markType);
2427  if (!m_doc->markDescription(markType).isEmpty()) {
2428  mA = markMenu.addAction(icon, m_doc->markDescription(markType));
2429  dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType));
2430  } else {
2431  mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1));
2432  dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1));
2433  }
2434  selectDefaultMarkActionGroup->addAction(dMA);
2435  mA->setData(i);
2436  mA->setCheckable(true);
2437  dMA->setData(i + 100);
2438  dMA->setCheckable(true);
2439  if (m_doc->mark(line) & markType) {
2440  mA->setChecked(true);
2441  }
2442 
2443  if (markType & KateViewConfig::global()->defaultMarkType()) {
2444  dMA->setChecked(true);
2445  }
2446 
2447  vec[i++] = markType;
2448  }
2449 
2450  if (markMenu.actions().count() == 0) {
2451  return;
2452  }
2453 
2454  if (markMenu.actions().count() > 1) {
2455  markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark);
2456  }
2457 
2458  QAction *rA = markMenu.exec(pos);
2459  if (!rA) {
2460  return;
2461  }
2462  int result = rA->data().toInt();
2463  if (result > 100) {
2464  KateViewConfig::global()->setValue(KateViewConfig::DefaultMarkType, vec[result - 100]);
2465  } else {
2466  MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)vec[result];
2467  if (m_doc->mark(line) & markType) {
2468  m_doc->removeMark(line, markType);
2469  } else {
2470  m_doc->addMark(line, markType);
2471  }
2472  }
2473 }
2474 
2475 KTextEditor::AbstractAnnotationItemDelegate *KateIconBorder::annotationItemDelegate() const
2476 {
2477  return m_annotationItemDelegate;
2478 }
2479 
2480 void KateIconBorder::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate)
2481 {
2482  if (delegate == m_annotationItemDelegate) {
2483  return;
2484  }
2485 
2486  // reset to default, but already on that?
2487  if (!delegate && m_isDefaultAnnotationItemDelegate) {
2488  // nothing to do
2489  return;
2490  }
2491 
2492  // make sure the tooltip is hidden
2493  if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
2494  m_hoveredAnnotationGroupIdentifier.clear();
2495  hideAnnotationTooltip();
2496  }
2497 
2498  disconnect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2499  if (!m_isDefaultAnnotationItemDelegate) {
2500  disconnect(m_annotationItemDelegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2501  }
2502 
2503  if (!delegate) {
2504  // reset to a default delegate
2505  m_annotationItemDelegate = new KateAnnotationItemDelegate(m_viewInternal, this);
2506  m_isDefaultAnnotationItemDelegate = true;
2507  } else {
2508  // drop any default delegate
2509  if (m_isDefaultAnnotationItemDelegate) {
2510  delete m_annotationItemDelegate;
2511  m_isDefaultAnnotationItemDelegate = false;
2512  }
2513 
2514  m_annotationItemDelegate = delegate;
2515  // catch delegate being destroyed
2516  connect(delegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2517  }
2518 
2519  connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2520 
2521  if (m_annotationBorderOn) {
2522  updateGeometry();
2523 
2524  QTimer::singleShot(0, this, SLOT(update()));
2525  }
2526 }
2527 
2528 void KateIconBorder::handleDestroyedAnnotationItemDelegate()
2529 {
2530  setAnnotationItemDelegate(nullptr);
2531 }
2532 
2533 void KateIconBorder::initStyleOption(KTextEditor::StyleOptionAnnotationItem *styleOption) const
2534 {
2535  styleOption->initFrom(this);
2536  styleOption->view = m_view;
2537  styleOption->decorationSize = QSize(m_iconAreaWidth, m_iconAreaWidth);
2538  styleOption->contentFontMetrics = m_view->renderer()->currentFontMetrics();
2539 }
2540 
2541 void KateIconBorder::setStyleOptionLineData(KTextEditor::StyleOptionAnnotationItem *styleOption, int y, int realLine, const KTextEditor::AnnotationModel *model, const QString &annotationGroupIdentifier) const
2542 {
2543  // calculate rendered displayed line
2544  const uint h = m_view->renderer()->lineHeight();
2545  const uint z = (y / h);
2546 
2547  KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, annotationGroupIdentifier, z, true);
2548  annotationGroupPositionState.nextLine(*styleOption, z, realLine);
2549 }
2550 
2551 QRect KateIconBorder::annotationLineRectInView(int line) const
2552 {
2553  int x = 0;
2554  if (m_iconBorderOn) {
2555  x += m_iconAreaWidth + 2;
2556  }
2557  const int y = m_view->m_viewInternal->lineToY(line);
2558 
2559  return QRect(x, y, m_annotationAreaWidth, m_view->renderer()->lineHeight());
2560 }
2561 
2562 void KateIconBorder::updateAnnotationLine(int line)
2563 {
2564  // TODO: why has the default value been 8, where is that magic number from?
2565  int width = 8;
2566  KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2567 
2568  if (model) {
2570  initStyleOption(&styleOption);
2571  width = m_annotationItemDelegate->sizeHint(styleOption, model, line).width();
2572  }
2573 
2574  if (width > m_annotationAreaWidth) {
2575  m_annotationAreaWidth = width;
2576  m_updatePositionToArea = true;
2577 
2578  QTimer::singleShot(0, this, SLOT(update()));
2579  }
2580 }
2581 
2582 void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos)
2583 {
2584  QMenu menu;
2585  QAction a(i18n("Disable Annotation Bar"), &menu);
2586  a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2587  menu.addAction(&a);
2588  emit m_view->annotationContextMenuAboutToShow(m_view, &menu, line);
2589  if (menu.exec(pos) == &a) {
2590  m_view->setAnnotationBorderVisible(false);
2591  }
2592 }
2593 
2594 void KateIconBorder::hideAnnotationTooltip()
2595 {
2596  m_annotationItemDelegate->hideTooltip(m_view);
2597 }
2598 
2599 void KateIconBorder::updateAnnotationBorderWidth()
2600 {
2601  calcAnnotationBorderWidth();
2602 
2603  m_updatePositionToArea = true;
2604 
2605  QTimer::singleShot(0, this, SLOT(update()));
2606 }
2607 
2608 void KateIconBorder::calcAnnotationBorderWidth()
2609 {
2610  // TODO: another magic number, not matching the one in updateAnnotationLine()
2611  m_annotationAreaWidth = 6;
2612  KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2613 
2614  if (model) {
2616  initStyleOption(&styleOption);
2617 
2618  const int lineCount = m_view->doc()->lines();
2619  if (lineCount > 0) {
2620  const int checkedLineCount = m_hasUniformAnnotationItemSizes ? 1 : lineCount;
2621  for (int i = 0; i < checkedLineCount; ++i) {
2622  const int curwidth = m_annotationItemDelegate->sizeHint(styleOption, model, i).width();
2623  if (curwidth > m_annotationAreaWidth) {
2624  m_annotationAreaWidth = curwidth;
2625  }
2626  }
2627  }
2628  }
2629 }
2630 
2631 void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel)
2632 {
2633  if (oldmodel) {
2634  oldmodel->disconnect(this);
2635  }
2636  if (newmodel) {
2637  connect(newmodel, SIGNAL(reset()), this, SLOT(updateAnnotationBorderWidth()));
2638  connect(newmodel, SIGNAL(lineChanged(int)), this, SLOT(updateAnnotationLine(int)));
2639  }
2640  updateAnnotationBorderWidth();
2641 }
2642 
2643 void KateIconBorder::displayRangeChanged()
2644 {
2645  hideFolding();
2646  removeAnnotationHovering();
2647 }
2648 
2649 // END KateIconBorder
2650 
2651 // BEGIN KateViewEncodingAction
2652 // According to https://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
2653 // the default/unknown mib value is 2.
2654 #define MIB_DEFAULT 2
2655 
2656 bool lessThanAction(KSelectAction *a, KSelectAction *b)
2657 {
2658  return a->text() < b->text();
2659 }
2660 
2661 void KateViewEncodingAction::Private::init()
2662 {
2663  QList<KSelectAction *> actions;
2664 
2665  q->setToolBarMode(MenuMode);
2666 
2667  int i;
2668  const auto encodingsByScript = KCharsets::charsets()->encodingsByScript();
2669  for (const QStringList &encodingsForScript : encodingsByScript) {
2670  KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), q);
2671 
2672  for (i = 1; i < encodingsForScript.size(); ++i) {
2673  tmp->addAction(encodingsForScript.at(i));
2674  }
2675  q->connect(tmp, SIGNAL(triggered(QAction *)), q, SLOT(_k_subActionTriggered(QAction *)));
2676  // tmp->setCheckable(true);
2677  actions << tmp;
2678  }
2679  std::sort(actions.begin(), actions.end(), lessThanAction);
2680  for (KSelectAction *action : qAsConst(actions)) {
2681  q->addAction(action);
2682  }
2683 }
2684 
2685 void KateViewEncodingAction::Private::_k_subActionTriggered(QAction *action)
2686 {
2687  if (currentSubAction == action) {
2688  return;
2689  }
2690  currentSubAction = action;
2691  bool ok = false;
2692  int mib = q->mibForName(action->text(), &ok);
2693  if (ok) {
2694  emit q->KSelectAction::triggered(action->text());
2695  emit q->triggered(q->codecForMib(mib));
2696  }
2697 }
2698 
2699 KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode)
2700  : KSelectAction(text, parent)
2701  , doc(_doc)
2702  , view(_view)
2703  , d(new Private(this))
2704  , m_saveAsMode(saveAsMode)
2705 {
2706  d->init();
2707 
2708  connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow()));
2709  connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString)));
2710 }
2711 
2712 KateViewEncodingAction::~KateViewEncodingAction()
2713 {
2714  delete d;
2715 }
2716 
2717 void KateViewEncodingAction::slotAboutToShow()
2718 {
2719  setCurrentCodec(doc->config()->encoding());
2720 }
2721 
2722 void KateViewEncodingAction::setEncoding(const QString &e)
2723 {
2727  if (m_saveAsMode) {
2728  doc->documentSaveAsWithEncoding(e);
2729  return;
2730  }
2731 
2735  doc->userSetEncodingForNextReload();
2736  doc->setEncoding(e);
2737  view->reloadFile();
2738 }
2739 
2740 int KateViewEncodingAction::mibForName(const QString &codecName, bool *ok) const
2741 {
2742  // FIXME logic is good but code is ugly
2743 
2744  bool success = false;
2745  int mib = MIB_DEFAULT;
2747 
2748  QTextCodec *codec = charsets->codecForName(codecName, success);
2749  if (!success) {
2750  // Maybe we got a description name instead
2751  codec = charsets->codecForName(charsets->encodingForName(codecName), success);
2752  }
2753 
2754  if (codec) {
2755  mib = codec->mibEnum();
2756  }
2757 
2758  if (ok) {
2759  *ok = success;
2760  }
2761 
2762  if (success) {
2763  return mib;
2764  }
2765 
2766  qCWarning(LOG_KTE) << "Invalid codec name: " << codecName;
2767  return MIB_DEFAULT;
2768 }
2769 
2770 QTextCodec *KateViewEncodingAction::codecForMib(int mib) const
2771 {
2772  if (mib == MIB_DEFAULT) {
2773  // FIXME offer to change the default codec
2774  return QTextCodec::codecForLocale();
2775  } else {
2776  return QTextCodec::codecForMib(mib);
2777  }
2778 }
2779 
2780 QTextCodec *KateViewEncodingAction::currentCodec() const
2781 {
2782  return codecForMib(currentCodecMib());
2783 }
2784 
2785 bool KateViewEncodingAction::setCurrentCodec(QTextCodec *codec)
2786 {
2787  disconnect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString)));
2788 
2789  int i, j;
2790  for (i = 0; i < actions().size(); ++i) {
2791  if (actions().at(i)->menu()) {
2792  for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) {
2793  if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) {
2794  continue;
2795  }
2796  if (actions().at(i)->menu()->actions().at(j)->isSeparator()) {
2797  continue;
2798  }
2799 
2800  if (codec == KCharsets::charsets()->codecForName(actions().at(i)->menu()->actions().at(j)->text())) {
2801  d->currentSubAction = actions().at(i)->menu()->actions().at(j);
2802  d->currentSubAction->setChecked(true);
2803  } else {
2804  actions().at(i)->menu()->actions().at(j)->setChecked(false);
2805  }
2806  }
2807  }
2808  }
2809 
2810  connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString)));
2811  return true;
2812 }
2813 
2814 QString KateViewEncodingAction::currentCodecName() const
2815 {
2816  return d->currentSubAction->text();
2817 }
2818 
2819 bool KateViewEncodingAction::setCurrentCodec(const QString &codecName)
2820 {
2821  return setCurrentCodec(KCharsets::charsets()->codecForName(codecName));
2822 }
2823 
2824 int KateViewEncodingAction::currentCodecMib() const
2825 {
2826  return mibForName(currentCodecName());
2827 }
2828 
2829 bool KateViewEncodingAction::setCurrentCodec(int mib)
2830 {
2831  return setCurrentCodec(codecForMib(mib));
2832 }
2833 // END KateViewEncodingAction
2834 
2835 // BEGIN KateViewBar related classes
2836 
2837 KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent)
2838  : QWidget(parent)
2839  , m_viewBar(nullptr)
2840 {
2841  QHBoxLayout *layout = new QHBoxLayout(this);
2842 
2843  // NOTE: Here be cosmetics.
2844  layout->setContentsMargins(0, 0, 0, 0);
2845 
2846  // hide button
2847  if (addCloseButton) {
2848  QToolButton *hideButton = new QToolButton(this);
2849  hideButton->setAutoRaise(true);
2850  hideButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2851  connect(hideButton, SIGNAL(clicked()), SIGNAL(hideMe()));
2852  layout->addWidget(hideButton);
2853  layout->setAlignment(hideButton, Qt::AlignLeft | Qt::AlignTop);
2854  }
2855 
2856  // widget to be used as parent for the real content
2857  m_centralWidget = new QWidget(this);
2858  layout->addWidget(m_centralWidget);
2859 
2860  setLayout(layout);
2861  setFocusProxy(m_centralWidget);
2862 }
2863 
2864 KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view)
2865  : QWidget(parent)
2866  , m_external(external)
2867  , m_view(view)
2868  , m_permanentBarWidget(nullptr)
2869 
2870 {
2871  m_layout = new QVBoxLayout(this);
2872  m_stack = new QStackedWidget(this);
2873  m_layout->addWidget(m_stack);
2874  m_layout->setContentsMargins(0, 0, 0, 0);
2875 
2876  m_stack->hide();
2877  hide();
2878 }
2879 
2880 void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget)
2881 {
2882  // just ignore additional adds for already existing widgets
2883  if (hasBarWidget(newBarWidget)) {
2884  return;
2885  }
2886 
2887  // add new widget, invisible...
2888  newBarWidget->hide();
2889  m_stack->addWidget(newBarWidget);
2890  newBarWidget->setAssociatedViewBar(this);
2891  connect(newBarWidget, SIGNAL(hideMe()), SLOT(hideCurrentBarWidget()));
2892 }
2893 
2894 void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget)
2895 {
2896  // remove only if there
2897  if (!hasBarWidget(barWidget)) {
2898  return;
2899  }
2900 
2901  m_stack->removeWidget(barWidget);
2902  barWidget->setAssociatedViewBar(nullptr);
2903  barWidget->hide();
2904  disconnect(barWidget, nullptr, this, nullptr);
2905 }
2906 
2907 void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget)
2908 {
2909  Q_ASSERT(barWidget);
2910  Q_ASSERT(!m_permanentBarWidget);
2911 
2912  m_stack->addWidget(barWidget);
2913  m_stack->setCurrentWidget(barWidget);
2914  m_stack->show();
2915  m_permanentBarWidget = barWidget;
2916  m_permanentBarWidget->show();
2917 
2918  setViewBarVisible(true);
2919 }
2920 
2921 void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget)
2922 {
2923  Q_ASSERT(m_permanentBarWidget == barWidget);
2924  Q_UNUSED(barWidget);
2925 
2926  const bool hideBar = m_stack->currentWidget() == m_permanentBarWidget;
2927 
2928  m_permanentBarWidget->hide();
2929  m_stack->removeWidget(m_permanentBarWidget);
2930  m_permanentBarWidget = nullptr;
2931 
2932  if (hideBar) {
2933  m_stack->hide();
2934  setViewBarVisible(false);
2935  }
2936 }
2937 
2938 bool KateViewBar::hasPermanentWidget(KateViewBarWidget *barWidget) const
2939 {
2940  return (m_permanentBarWidget == barWidget);
2941 }
2942 
2943 void KateViewBar::showBarWidget(KateViewBarWidget *barWidget)
2944 {
2945  Q_ASSERT(barWidget != nullptr);
2946 
2947  if (barWidget != qobject_cast<KateViewBarWidget *>(m_stack->currentWidget())) {
2948  hideCurrentBarWidget();
2949  }
2950 
2951  // raise correct widget
2952  m_stack->setCurrentWidget(barWidget);
2953  barWidget->show();
2954  barWidget->setFocus(Qt::ShortcutFocusReason);
2955  m_stack->show();
2956  setViewBarVisible(true);
2957 }
2958 
2959 bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const
2960 {
2961  return m_stack->indexOf(barWidget) != -1;
2962 }
2963 
2964 void KateViewBar::hideCurrentBarWidget()
2965 {
2966  KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
2967  if (current) {
2968  current->closed();
2969  }
2970 
2971  // if we have any permanent widget, make it visible again
2972  if (m_permanentBarWidget) {
2973  m_stack->setCurrentWidget(m_permanentBarWidget);
2974  } else {
2975  // else: hide the bar
2976  m_stack->hide();
2977  setViewBarVisible(false);
2978  }
2979 
2980  m_view->setFocus();
2981 }
2982 
2983 void KateViewBar::setViewBarVisible(bool visible)
2984 {
2985  if (m_external) {
2986  if (visible) {
2987  m_view->mainWindow()->showViewBar(m_view);
2988  } else {
2989  m_view->mainWindow()->hideViewBar(m_view);
2990  }
2991  } else {
2992  setVisible(visible);
2993  }
2994 }
2995 
2996 bool KateViewBar::hiddenOrPermanent() const
2997 {
2998  KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
2999  if (!isVisible() || (m_permanentBarWidget && m_permanentBarWidget == current)) {
3000  return true;
3001  }
3002  return false;
3003 }
3004 
3005 void KateViewBar::keyPressEvent(QKeyEvent *event)
3006 {
3007  if (event->key() == Qt::Key_Escape) {
3008  hideCurrentBarWidget();
3009  return;
3010  }
3011  QWidget::keyPressEvent(event);
3012 }
3013 
3014 void KateViewBar::hideEvent(QHideEvent *event)
3015 {
3016  Q_UNUSED(event);
3017  // if (!event->spontaneous())
3018  // m_view->setFocus();
3019 }
3020 
3021 // END KateViewBar related classes
3022 
3023 KatePasteMenu::KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view)
3024  : KActionMenu(text, view)
3025  , m_view(view)
3026 {
3027  connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow()));
3028 }
3029 
3030 void KatePasteMenu::slotAboutToShow()
3031 {
3032  menu()->clear();
3033 
3037  int i = 0;
3038  for (const QString &text : KTextEditor::EditorPrivate::self()->clipboardHistory()) {
3042  QString leftPart = (text.size() > 48) ? (text.left(48) + QLatin1String("...")) : text;
3043  QAction *a = menu()->addAction(leftPart.replace(QLatin1Char('\n'), QLatin1Char(' ')), this, SLOT(paste()));
3044  a->setData(i++);
3045  }
3046 }
3047 
3048 void KatePasteMenu::paste()
3049 {
3050  if (!sender()) {
3051  return;
3052  }
3053 
3054  QAction *action = qobject_cast<QAction *>(sender());
3055  if (!action) {
3056  return;
3057  }
3058 
3059  // get index
3060  int i = action->data().toInt();
3061  if (i >= KTextEditor::EditorPrivate::self()->clipboardHistory().size()) {
3062  return;
3063  }
3064 
3065  // paste
3066  m_view->paste(&KTextEditor::EditorPrivate::self()->clipboardHistory()[i]);
3067 }
bool canConvert(int targetTypeId) const const
QTextCodec * codecForName(const QString &name) const
void showText(const QPoint &pos, const QString &text, QWidget *w)
void setBottom(int y)
show message as view overlay in the center of the view.
Definition: message.h:142
KGUIADDONS_EXPORT qreal hue(const QColor &)
Qt::KeyboardModifiers modifiers() const const
virtual QSize sizeHint() const const override
QEvent::Type type() const const
void setContentsMargins(int left, int top, int right, int bottom)
KGUIADDONS_EXPORT QColor darken(const QColor &, qreal amount=0.5, qreal chromaGain=1.0)
int width() const const
void keyPressEvent(QKeyEvent *) override
virtual bool help(KTextEditor::View *view, const QString &cmd, QString &msg)=0
Shows help for the given view and cmd string.
bool end()
void fillRect(const QRectF &rectangle, const QBrush &brush)
A class which provides customized text decorations.
Definition: attribute.h:156
void setRenderHint(QPainter::RenderHint hint, bool on)
void fill(const QColor &color)
int right() const const
const Key & key() const const
virtual void setGeometry(const QRect &r)=0
void setChecked(bool)
int value() const const
bool hasNext() const const
QVariant data() const const
int y() const const
const T & at(int i) const const
int size() const const
QPoint pos() const const
virtual void mousePressEvent(QMouseEvent *e) override
KIOFILEWIDGETS_EXPORT void add(const QString &fileClass, const QString &directory)
AnnotationItemGroupPositions annotationItemGroupingPosition
Relative position of the real line in the row of consecutive displayed lines which belong to the same...
void invokeHelp(const QString &anchor=QString(), const QString &appname=QString())
void setMenu(QMenu *menu)
void setJoinStyle(Qt::PenJoinStyle style)
void setAlpha(int alpha)
The style option set for an annotation item, as painted by AbstractAnnotationItemDelegate.
int height() const const
int x() const const
int y() const const
bool event(QEvent *) override
A delegate for rendering line annotation information and handling events.
Qt::MouseButtons buttons() const const
void drawLine(const QLineF &line)
The Cursor represents a position in a Document.
Definition: cursor.h:85
bool canConvert(const QVariant &value)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
constexpr bool isValid() const Q_DECL_NOEXCEPT
Validity check.
void showText(const QPoint &pos, const QString &text, QWidget *w)
int hue() const const
void setIcon(const QIcon &icon)
virtual void paintEvent(QPaintEvent *) override
virtual void mouseReleaseEvent(QMouseEvent *e) override
QAction * addAction(const QString &text)
void getHsl(int *h, int *s, int *l, int *a) const const
QTextCodec * codecForLocale()
int x() const const
int y() const const
constexpr bool contains(const Range &range) const Q_DECL_NOEXCEPT
Check whether the this range wholly encompasses range.
int size() const const
KSharedConfigPtr config()
QString name(StandardShortcut id)
bool operator==(const Qt3DRender::QGraphicsApiFilter &reference, const Qt3DRender::QGraphicsApiFilter &sample)
QList< QStringList > encodingsByScript() const
int lightness() const const
void clear()
void initFrom(const QWidget *widget)
virtual bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range=KTextEditor::Range::invalid())=0
Execute the command for the given view and cmd string.
KGUIADDONS_EXPORT qreal luma(const QColor &)
const QRect & rect() const const
void timeout()
void drawRect(const QRectF &rectangle)
void clear()
QColor color() const const
Default for normal text and source code.
Definition: attribute.h:45
virtual QColor foregroundColor() const const override
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
virtual void showEvent(QShowEvent *event)
QString number(int n, int base)
int count(const T &value) const const
QSize decorationSize
Recommended size for icons or other symbols that will be rendered by the delegate.
QPoint globalPos() const const
KGuiItem clear()
void drawPoint(const QPointF &position)
QHash::const_iterator constEnd() const const
int viewLine() const
Return the index of this visual line inside the document line (KateLineLayout).
virtual void mousePressEvent(QMouseEvent *event)
int toInt(bool *ok) const const
virtual QColor backgroundColor() const const override
KGuiItem stop()
int top() const const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
A KParts derived class representing a text document.
Definition: document.h:199
void setPen(const QColor &color)
QRectF boundingRect(const QString &text) const const
void setTop(int y)
int left() const const
Qt::MouseButton button() const const
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
virtual void mouseMoveEvent(QMouseEvent *event)
void setWidth(int width)
virtual QWindow * window() const const override
bool isEmpty() const const
bool sendEvent(QObject *receiver, QEvent *event)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString text() const const
void setWidthF(qreal width)
void setBrush(const QBrush &brush)
KIOWIDGETS_EXPORT PasteJob * paste(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags=DefaultFlags)
KCharsets * charsets()
QPoint center() const const
QString encodingForName(const QString &descriptiveName) const
constexpr bool overlapsLine(int line) const Q_DECL_NOEXCEPT
Check whether the range overlaps at least part of line.
Range is folded away.
void addAction(QAction *action)
KGUIADDONS_EXPORT QColor shade(const QColor &, qreal lumaAmount, qreal chromaAmount=0.0)
void setColor(const QColor &color)
void moveTop(int y)
void setAutoRaise(bool enable)
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
constexpr Cursor start() const Q_DECL_NOEXCEPT
Get the start position of this range.
QAction * exec()
void paint(QPainter *painter, const QRect &rect, Qt::Alignment alignment, QIcon::Mode mode, QIcon::State state) const const
An model for providing line annotation information.
MessagePosition
Message position used to place the message either above or below of the KTextEditor::View.
Definition: message.h:131
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
const QList< QKeySequence > & forward()
ushort unicode() const const
An object representing a section of text, from one Cursor to another.
Qt::KeyboardModifiers modifiers() const const
An Editor command line command.
bool isNull() const const
void setData(const QVariant &userData)
QList::iterator end()
const T value(const Key &key) const const
int key() const const
void accept()
QHashIterator::Item next()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
static KCharsets * charsets()
virtual int mibEnum() const const =0
QColor lighter(int factor) const const
void hideText()
void setCheckable(bool)
QWidget * widget() const const
virtual QVariant data(int line, Qt::ItemDataRole role) const =0
data() is used to retrieve the information needed to present the annotation information from the anno...
virtual QRect rect() const const override
Expand to encapsulate new characters to the right of the range.
Definition: movingrange.h:171
QString i18n(const char *text, const TYPE &arg...)
MarkTypes
Predefined mark types.
bool setAlignment(QWidget *w, Qt::Alignment alignment)
QString & replace(int position, int n, QChar after)
const QList< QKeySequence > & end()
virtual void focusInEvent(QFocusEvent *e) override
QHash::const_iterator constBegin() const const
int wrappedLineCount
Number of wrapped lines for the given real line.
virtual bool supportsRange(const QString &cmd)
Find out if a given command can act on a range.
bool contains(const QRect &rectangle, bool proper) const const
This class represents one visible line of text; with dynamic wrapping, many KateTextLayouts can be ne...
int width() const const
qreal width() const const
QString mid(int position, int n) const const
QPoint pos()
char * toString(const T &value)
void setWidth(int width)
KTextEditor::View * view
The view where the annotation is shown.
void setX(int x)
bool isEmpty() const const
bool isNull() const const
void setHeight(int height)
void drawConvexPolygon(const QPointF *points, int pointCount)
const QChar at(int position) const const
QTextCodec * codecForMib(int mib)
show message as view overlay in the top right corner.
Definition: message.h:137
int height() const const
void adjust(int dx1, int dy1, int dx2, int dy2)
virtual void keyPressEvent(QKeyEvent *event)
int bottom() const const
constexpr int line() const Q_DECL_NOEXCEPT
Retrieve the line on which this cursor is situated.
Definition: cursor.h:199
int length() const const
void init(const QWidget *widget)
static constexpr Range invalid() Q_DECL_NOEXCEPT
Returns an invalid range.
void setHsl(int h, int s, int l, int a)
void setHsv(int h, int s, int v, int a)
QString left(int n) const const
virtual QAccessibleInterface * parent() const const override
void update(Part *part, const QByteArray &data, qint64 dataSize)
bool isValid() const const
QIcon fromTheme(const QString &name)
int wrappedLine
Index of the displayed line in the wrapped lines for the given real line.
KGuiItem reset()
QPoint pos() const const
QTextStream & left(QTextStream &stream)
virtual void resizeEvent(QResizeEvent *event)
const T & value() const const
void moveCenter(const QPoint &position)
const QList< QKeySequence > & selectAll()
A text widget with KXMLGUIClient that represents a Document.
Definition: view.h:155
QList< QAction * > actions() const const
static KTextEditor::EditorPrivate * self()
Kate Part Internal stuff ;)
Definition: kateglobal.cpp:373
QFontMetricsF contentFontMetrics
The metrics of the font used for rendering the text document.
int size() const const
bool begin(QPaintDevice *device)
virtual void leaveEvent(QEvent *event)
show message as view overlay in the bottom right corner.
Definition: message.h:139
virtual void setGeometry(const QRect &r) override
const QPen & pen() const const
QString toString() const const
virtual QSize sizeHint() const const =0
bool operator!=(const Qt3DRender::QGraphicsApiFilter &reference, const Qt3DRender::QGraphicsApiFilter &sample)
QList::iterator begin()
int visibleWrappedLineInGroup
Index of the displayed line in the displayed lines for the same group.
void destroyed(QObject *obj)
virtual void sliderChange(QAbstractSlider::SliderChange change) override
qreal height() const const
virtual bool isValid() const const override
virtual void mouseMoveEvent(QMouseEvent *e) override
bool isLetterOrNumber() const const
MESSAGEVIEWER_EXPORT const QTextCodec * codecForName(const QByteArray &_str)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun May 31 2020 23:13:21 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.