1 /****************************************************************************
2 **
3 ** Copyright (C) 2006-2009 fullmetalcoder <fullmetalcoder@hotmail.fr>
4 **
5 ** This file is part of the Edyuk project <http://edyuk.org>
6 **
7 ** This file may be used under the terms of the GNU General Public License
8 ** version 3 as published by the Free Software Foundation and appearing in the
9 ** file GPL.txt included in the packaging of this file.
10 **
11 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
12 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13 **
14 ****************************************************************************/
15
16 #include "qfoldpanel.h"
17
18 /*!
19 \file qfoldpanel.cpp
20 \brief Implementation of the QFoldPanel class.
21
22 \see QFoldPanel
23 */
24
25 #include "qeditor.h"
26
27 #include "qdocument.h"
28 #include "qdocumentline.h"
29
30 #include "qlanguagedefinition.h"
31 #include <QPainterPath>
32
33 /*!
34 \ingroup widgets
35 @{
36 */
37
38 /*!
39 \class QFoldPanel
40 \brief A panel that draw fold indicators and provide fold/unfold actions to the user
41 */
42
QCE_AUTO_REGISTER(QFoldPanel)43 QCE_AUTO_REGISTER(QFoldPanel)
44
45 /*!
46 \brief Constructor
47 */
48 QFoldPanel::QFoldPanel(QWidget *p)
49 : QPanel(p), m_width(0)
50 {
51 setWidth(12);
52 setObjectName("foldPanel");
53 setMouseTracking(true);
54 m_lastMouseLine = -1;
55 }
56
57 /*!
58 \brief Empty destructor
59 */
~QFoldPanel()60 QFoldPanel::~QFoldPanel()
61 {
62
63 }
64
65 /*!
66
67 */
type() const68 QString QFoldPanel::type() const
69 {
70 return "Fold indicators";
71 }
72
setWidth(int w)73 void QFoldPanel::setWidth(int w)
74 {
75 w = qMax(w, 5);
76 if (w != m_width) {
77 m_width = w;
78 setFixedWidth(w);
79 }
80 }
81
82 /*!
83
84 */
mousePressEvent(QMouseEvent * e)85 void QFoldPanel::mousePressEvent(QMouseEvent *e)
86 {
87 if ( !editor() || !editor()->languageDefinition() || (e->button() != Qt::LeftButton) )
88 {
89 QPanel::mousePressEvent(e);
90 return;
91 }
92
93
94 QDocument *doc = editor()->document();
95 QLanguageDefinition *def = editor()->languageDefinition();
96
97 int ln = mapRectPosToLine(e->pos());
98 if ( ln != -1 ){
99 QDocumentLine b = doc->line(ln);
100
101 if ( b.hasFlag(QDocumentLine::CollapsedBlockStart) )
102 def->expand(doc, ln);
103 else //if ( def->blockFlags(doc, ln, 0) & QLanguageDefinition::Collapsible ) collapse checks if it can collapse the line
104 def->collapse(doc, ln);
105 editor()->setFocus();
106 } else
107 QPanel::mousePressEvent(e);
108
109 }
110
mouseMoveEvent(QMouseEvent * e)111 void QFoldPanel::mouseMoveEvent(QMouseEvent *e)
112 {
113 if ( !editor() || !editor()->languageDefinition() )
114 {
115 QPanel::mousePressEvent(e);
116 return;
117 }
118
119 int ln = mapRectPosToLine(e->pos());
120 if (ln != m_lastMouseLine){
121 m_lastMouseLine = ln;
122 repaint();
123 } else
124 QPanel::mousePressEvent(e);
125 m_lastMouseLine = ln;
126 }
127
leaveEvent(QEvent *)128 void QFoldPanel::leaveEvent(QEvent *)
129 {
130 if (m_lastMouseLine > -1) {
131 m_lastMouseLine = -1;
132 repaint();
133 }
134 }
135
136 /*!
137
138 */
contextMenuEvent(QContextMenuEvent * e)139 void QFoldPanel::contextMenuEvent(QContextMenuEvent *e)
140 {
141 if (!editor() || !editor()->languageDefinition()) {
142 QPanel::contextMenuEvent(e);
143 return;
144 }
145
146 int line=editor()->document()->lineNumber(editor()->verticalOffset()+e->y());
147 if (editor()->document()->line(line).isValid())
148 emit contextMenuRequested(line, e->globalPos());
149 }
150
151
152 /*!
153
154 */
paint(QPainter * p,QEditor * e)155 bool QFoldPanel::paint(QPainter *p, QEditor *e)
156 {
157 QDocument *doc = editor()->document();
158 QLanguageDefinition *def = e->languageDefinition();
159
160 if ( !def || !doc )
161 {
162 return true;
163 }
164 m_rects.clear();
165 m_lines.clear();
166
167 bool bVisible = false; //,
168 // inCursorBlock = false;
169
170 int endHighlightLineNr = -1;
171
172 qreal pos,
173 max = doc->lines(),
174 ls = doc->getLineSpacing(),
175 pageBottom = e->viewport()->height(),
176 contentsY = e->verticalOffset();
177
178 qreal xMid = m_width / 2;
179 qreal iconSize = (m_width * 3)/ 4;
180 qreal xIconOffset = (m_width - iconSize) / 2;
181 qreal yIconOffset = (ls - iconSize) / 2;
182
183 pos = - contentsY;
184
185 //qDebug("beg pos : %i", pos);
186
187 p->save();
188 QPen linePen(QColor(128,0,128));
189 qreal lineWidth = m_width / 5;
190
191 linePen.setWidth(lineWidth);
192 linePen.setCapStyle(Qt::FlatCap);
193 p->setPen(linePen);
194
195 QFoldedLineIterator fli = def->foldedLineIterator(doc);
196
197 for (; fli.lineNr<max; ++fli) {
198 if ( pos > pageBottom )
199 break;
200
201 const QDocumentLine &line=fli.line;
202
203 if ( fli.lineFlagsInvalid() ){
204 //correct folding when the folding of the current line is invalid
205 //problems: slow (but O(n) like the paint method is anyways), doesn't work if panel is hidden
206 //pro: simple, doesn't correct invalid, but invisible folding (e.g. like folding that is only temporary invalid, until the user writes a closing bracket; otherwise writing $$ would expand every folded $-block)
207 doc->correctFolding(fli.lineNr, doc->lines()); //this will again call paint
208 break;
209 }
210
211 if ( line.isHidden() ) {
212 continue;
213 }
214
215 qreal len = ls * line.lineSpan();
216
217 bVisible = ((pos + len) >= 0);
218
219 if (bVisible) {
220
221 if ( fli.open ) {
222 bool isCollapsed = line.hasFlag(QDocumentLine::CollapsedBlockStart);
223 qreal topLineEnd = yIconOffset;
224 qreal bottomLineStart = yIconOffset + iconSize;
225 if (isCollapsed) { // a bit more space for collapsed icons
226 topLineEnd -= 1;
227 bottomLineStart += 1;
228 }
229
230 // line above icon
231 if (topLineEnd > 0 && fli.lineNr <= endHighlightLineNr)
232 p->drawLine(QPointF(xMid, pos), QPointF(xMid, pos + topLineEnd));
233
234 // draw icon
235 m_lines << fli.lineNr;
236 m_rects << QRectF(0, pos, m_width, ls);
237 drawIcon(p, e, xIconOffset, pos + yIconOffset, iconSize, isCollapsed, fli.lineNr == m_lastMouseLine);
238
239 if (!isCollapsed && fli.lineNr == m_lastMouseLine) {
240 // found the line with the mouse -> determine end of highlighting
241 QFoldedLineIterator findEnd = fli;
242 findEnd.incrementUntilBlockEnd();
243 endHighlightLineNr = findEnd.lineNr;
244 }
245
246 // line below icon
247 if (bottomLineStart < len && fli.lineNr < endHighlightLineNr)
248 p->drawLine(QPointF(xMid, pos + bottomLineStart), QPointF(xMid, pos + len));
249 } else if (fli.lineNr <= endHighlightLineNr) {
250 if ( fli.lineNr == endHighlightLineNr ) {
251 qreal mid = pos + len - ls / 6;
252 p->drawLine(QPointF(xMid, pos), QPointF(xMid, mid)); // line ending here
253 } else {
254 p->drawLine(QPointF(xMid, pos), QPointF(xMid, pos + len)); // line continues
255 }
256 }
257 }
258 pos += len;
259 }
260
261 p->restore();
262 return true;
263 }
264
event(QEvent * e)265 bool QFoldPanel::event(QEvent *e) {
266 if (e->type() == QEvent::ToolTip) {
267 QDocument *doc = editor()->document();
268 if (doc) {
269 QLanguageDefinition *def = doc->languageDefinition();
270
271 QHelpEvent* helpEvent = static_cast<QHelpEvent*>(e);
272 int line = mapRectPosToLine(helpEvent->pos());
273 if ( def && line != -1){
274 QFoldedLineIterator it = def->foldedLineIterator(doc, line);
275 it.incrementUntilBlockEnd();
276 if (doc->line(line).hasFlag(QDocumentLine::CollapsedBlockStart) || (editor()->getLastVisibleLine() < it.lineNr)) {
277 QString tooltip;
278
279
280 int lineWidth = 80;
281 int maxShownLines = 9;
282 int wrapCount = 2;
283 if (editor()->flag(QEditor::HardLineWrap)) {
284 lineWidth = -1; // rely on wrapping of editor
285 maxShownLines = 15;
286 wrapCount = 0;
287 }
288
289 if (it.lineNr - line < maxShownLines)
290 tooltip = doc->exportAsHtml(doc->cursor(line,0,it.lineNr),true,true,lineWidth,wrapCount);
291 else {
292 tooltip = doc->exportAsHtml(doc->cursor(line,0,line+maxShownLines/2),true,true,lineWidth,wrapCount);
293 tooltip.replace("</body></html>","");
294 tooltip += "<br>...<br>";
295 tooltip += doc->exportAsHtml(doc->cursor(it.lineNr-maxShownLines/2,0,it.lineNr),false,true,lineWidth,wrapCount);
296 tooltip += "</body></html>";
297 }
298 if (tooltip.isEmpty()) QToolTip::hideText();
299 else QToolTip::showText(helpEvent->globalPos(), tooltip);
300 e->setAccepted(true);
301 }
302 }
303 }
304 }
305 return QWidget::event(e);
306 }
307
mapRectPosToLine(const QPointF & p)308 int QFoldPanel::mapRectPosToLine(const QPointF& p){
309 for ( int i = 0; i < m_rects.count(); ++i )
310 {
311 if ( !m_rects.at(i).contains(p) )
312 continue;
313
314 return m_lines.at(i);
315 }
316 return -1;
317 }
318
319
drawIcon(QPainter * p,QEditor *,qreal x,qreal y,int iconSize,bool toExpand,bool highlight)320 QRectF QFoldPanel::drawIcon( QPainter *p, QEditor *,
321 qreal x, qreal y, int iconSize, bool toExpand, bool highlight)
322 {
323 int tailSpacing = iconSize / 4;
324 QRectF symbolRect(x, y, iconSize, iconSize);
325
326 p->save();
327
328 p->setRenderHint(QPainter::Antialiasing);
329
330 if (toExpand) {
331 // rightarrow
332 p->translate(tailSpacing, 0);
333 QPainterPath path;
334 path.moveTo(x, y);
335 path.lineTo(x, y+iconSize);
336 path.lineTo(x+float(iconSize)/2, y+float(iconSize)/2);
337 path.lineTo(x, y);
338
339 p->fillPath(path, highlight ? QColor(128,0,128) : QColor(96,96,96));
340 } else {
341 // downarrow
342 p->translate(0, tailSpacing);
343 QPainterPath path;
344 path.moveTo(x, y);
345 path.lineTo(x+iconSize, y);
346 path.lineTo(x+float(iconSize)/2, y+float(iconSize)/2);
347 path.lineTo(x, y);
348
349 p->fillPath(path, highlight ? QColor(128,0,128) : QColor(160,160,160));
350 }
351
352 p->restore();
353 return symbolRect;
354 }
355
setFont_slot(const QFont & font)356 void QFoldPanel::setFont_slot(const QFont &font)
357 {
358 setWidth(font.pointSize() + 2);
359 setFont(font);
360 }
361
editorChange(QEditor * e)362 void QFoldPanel::editorChange(QEditor *e)
363 {
364 if ( editor() )
365 {
366 disconnect( editor()->document(), SIGNAL( fontChanged(QFont) ),
367 this , SLOT ( setFont_slot(QFont) ) );
368 }
369
370 if ( e )
371 {
372 connect(e->document(), SIGNAL( fontChanged(QFont) ),
373 this, SLOT ( setFont_slot(QFont) ) );
374 }
375 }
376
377
378 /*! @} */
379