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