1 //===========================================================================
2 //
3 //   File : KviIrcView.cpp
4 //   Creation date : Tue Jul 6 1999 14:45:20 by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 1999-2010 Szymon Stefanek (pragma at kvirc dot net)
8 //
9 //   This program is FREE software. You can redistribute it and/or
10 //   modify it under the terms of the GNU General Public License
11 //   as published by the Free Software Foundation; either version 2
12 //   of the License, or (at your option) any later version.
13 //
14 //   This program is distributed in the HOPE that it will be USEFUL,
15 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 //   See the GNU General Public License for more details.
18 //
19 //   You should have received a copy of the GNU General Public License
20 //   along with this program. If not, write to the Free Software Foundation,
21 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //===========================================================================
24 
25 // Damn complex class ...but it works :)
26 // #include <brain.h>
27 //
28 // #define HOPE_THAT_IT_WILL_NEVER_NEED_TO_BE_MODIFIED :)
29 
30 // 07 May 1999,
31 //      Already forgot how this damn thing works,
32 //      and spent 1 hour over a stupid bug.
33 //      I had to recreate the whole thing in my mind......ooooouh...
34 //      How did I wrote it ?
35 //      Just take a look to paintEvent() or to calculateLineWraps()...
36 //      Anyway...I've solved the bug.
37 
38 // 23 Nov 1999,
39 //      Well, not so bad...I seem to still remember how it works
40 //      So just for fun, complicated the things a little bit more.
41 //      Added pre-calculation of the text blocks and word wrapping
42 //      and a fast scrolling mode (3 lines at once) for consecutive
43 //      appendText() calls.
44 //      Now the code becomes really not understandable...:)
45 
46 // 29 Jun 2000 21:02,
47 //      Here we go again... I have to adjust this stuff for 3.0.0
48 //      Will I make this thingie work?
49 // 01 Jul 2000 04:20 (AM!),
50 //      Yes....I got it to work just now
51 //      and YES, complicated the things yet more.
52 //      This time made some paint event code completely unreadable
53 //      by placing two monster macros...
54 //      I hope that you have a smart compiler (such as gcc is).
55 
56 // 09 Dec 2000
57 //      This is my C-asm-optimisation-hack playground
58 //      Expect Bad Programming(tm), Ugly Code(tm), Unreadable Macros (tm)
59 //      and massive usage of the Evil(tm) goto.
60 
61 // 25 Sep 2001
62 //      This stuff is going to be ported to Windoze
63 //      A conditionally compiled code will use only Qt calls...let's see :)
64 //
65 
66 //
67 // Here we go... a huge set of includes
68 //
69 
70 #include "KviIrcView.h"
71 #include "KviIrcView_tools.h"
72 #include "KviIrcView_private.h"
73 #include "kvi_debug.h"
74 #include "KviApplication.h"
75 #include "kvi_settings.h"
76 #include "KviOptions.h"
77 #include "KviControlCodes.h"
78 #include "kvi_defaults.h"
79 #include "KviWindow.h"
80 #include "KviLocale.h"
81 #include "KviMainWindow.h"
82 #include "KviMemory.h"
83 #include "KviIconManager.h"
84 #include "kvi_out.h"
85 #include "KviConsoleWindow.h"
86 #include "KviIrcUserDataBase.h"
87 #include "KviChannelWindow.h"
88 #include "KviFileDialog.h"
89 #include "KviMessageBox.h"
90 #include "KviTextIconManager.h"
91 #include "KviIrcConnection.h"
92 #include "KviWindowStack.h"
93 #include "KviUserInput.h"
94 #include "KviAnimatedPixmap.h"
95 #include "KviPixmapUtils.h"
96 #include "KviTrayIcon.h"
97 
98 #include <QPainter>
99 #include <QRegExp>
100 #include <QFontMetrics>
101 #include <QMessageBox>
102 #include <QPaintEvent>
103 #include <QDateTime>
104 #include <QScrollBar>
105 #include <QFontDialog>
106 #include <QByteArray>
107 #include <QMenu>
108 #include <QWindow>
109 
110 #include <ctime>
111 
112 #ifdef COMPILE_ON_WINDOWS
113 #pragma warning(disable : 4102)
114 #endif
115 
116 #ifdef __STRICT_ANSI__
117 #ifdef COMPILE_USE_DYNAMIC_LABELS
118 // incompatible with -ansi
119 
120 #endif
121 #endif
122 //#undef COMPILE_USE_DYNAMIC_LABELS
123 
124 #define KVI_DEF_BACK 200
125 
126 //
127 // Globals
128 //
129 
130 // Stuff declared in KviApplication.cpp and managed by KviApplication class
131 
132 #ifdef COMPILE_PSEUDO_TRANSPARENCY
133 extern QPixmap * g_pShadedChildGlobalDesktopBackground;
134 #endif
135 
136 //
137 // Internal constants
138 //
139 
140 // Maximum size of the internal buffer for each window
141 // This is the default value
142 //#define KVI_IRCVIEW_MAX_LINES 1024
143 // A little bit more than the scroll-bar...
144 // Qt+X have strange interactions that I can not understand when I try to move the splitter
145 // to the maximum on the left, Maybe the cache pixmap size becomes negative ? (I don't think so)
146 // Anyway, when the scroll bar position becomes negative (or the IrcView has smaller width than
147 // the scroll bar) X aborts with a funny
148 // X Error: BadDrawable (invalid Pixmap or Window parameter) 9
149 //   Major opcode:  55
150 // Program received signal SIGABRT, Aborted.
151 // Do not change unless you're sure that it will not happen :)
152 #define KVI_IRCVIEW_MINIMUM_WIDTH 22
153 // 16+4+(2*4) * Do not change
154 // this is mostly needed to avoid collapsing in slit view
155 #define KVI_IRCVIEW_MINIMUM_HEIGHT 22
156 #define KVI_IRCVIEW_PIXMAP_AND_SEPARATOR 20
157 #define KVI_IRCVIEW_DOUBLEBORDER_WIDTH 8
158 #define KVI_IRCVIEW_SIZEHINT_WIDTH 150
159 #define KVI_IRCVIEW_SIZEHINT_HEIGHT 150
160 
161 #define KVI_IRCVIEW_BLOCK_SELECTION_TOTAL 0
162 #define KVI_IRCVIEW_BLOCK_SELECTION_LEFT 1
163 #define KVI_IRCVIEW_BLOCK_SELECTION_RIGHT 2
164 #define KVI_IRCVIEW_BLOCK_SELECTION_CENTRAL 3
165 #define KVI_IRCVIEW_BLOCK_SELECTION_ICON 4
166 
167 #define KVI_IRCVIEW_PIXMAP_SIZE 16
168 
169 #define KVI_IRCVIEW_ESCAPE_TAG_URLLINK 'u'
170 #define KVI_IRCVIEW_ESCAPE_TAG_NICKLINK 'n'
171 #define KVI_IRCVIEW_ESCAPE_TAG_SERVERLINK 's'
172 #define KVI_IRCVIEW_ESCAPE_TAG_HOSTLINK 'h'
173 #define KVI_IRCVIEW_ESCAPE_TAG_GENERICESCAPE '['
174 
175 //
176 // Info about escape syntax
177 //
178 
179 // escape commands:
180 //
181 //  <cr>!<escape_command><cr><visible parameters<cr>
182 //
183 //  <escape_command> ::= u        <--- URL link
184 //  <escape_command> ::= n        <--- nick link
185 //  <escape_command> ::= s        <--- server link
186 //  <escape_command> ::= h        <--- host link
187 //  <escape_command> ::= [...     <--- generic escape "rbt" | "mbt" | "dbl" | "txt"
188 //
189 
190 //
191 // The IrcView : construct and destroy
192 //
193 
KviIrcView(QWidget * parent,KviWindow * pWnd)194 KviIrcView::KviIrcView(QWidget * parent, KviWindow * pWnd)
195     : QWidget(parent)
196 {
197 	setObjectName("irc_view");
198 	// Ok...here we go
199 	// initialize the initializable
200 
201 	setAttribute(Qt::WA_NoSystemBackground); // This disables automatic qt double buffering
202 	                                         // 	setAttribute(Qt::WA_OpaquePaintEvent);
203 	                                         // 	setAttribute(Qt::WA_PaintOnScreen); // disable qt backing store (that would force us to trigger repaint() instead of the 10 times faster paintEvent(0))
204 
205 	m_iFlushTimer = 0;
206 	m_pToolsPopup = nullptr;
207 	m_pFirstLine = nullptr;
208 	m_pCurLine = nullptr;
209 	m_pLastLine = nullptr;
210 	m_pCursorLine = nullptr;
211 	m_uLineMarkLineIndex = KVI_IRCVIEW_INVALID_LINE_MARK_INDEX;
212 	m_bHaveUnreadedHighlightedMessages = false;
213 	m_bHaveUnreadedMessages = false;
214 	m_iNumLines = 0;
215 	m_iMaxLines = KVI_OPTION_UINT(KviOption_uintIrcViewMaxBufferSize);
216 
217 	m_uNextLineIndex = 0;
218 	m_pSelectionInitLine = nullptr;
219 	m_pSelectionEndLine = nullptr;
220 	m_iSelectionInitCharIndex = 0;
221 	m_iSelectionEndCharIndex = 0;
222 	m_iSelectTimer = 0;
223 
224 	if(m_iMaxLines < 32)
225 	{
226 		m_iMaxLines = 32;
227 		KVI_OPTION_UINT(KviOption_uintIrcViewMaxBufferSize) = 32;
228 	}
229 
230 	m_bMouseIsDown = false;
231 
232 	//m_bShowImages            = KVI_OPTION_BOOL(KviOption_boolIrcViewShowImages);
233 
234 	m_iMouseTimer = 0;
235 	m_pLastEvent = nullptr;
236 	m_iLastMouseClickTime = QDateTime::currentMSecsSinceEpoch();
237 
238 	m_bAcceptDrops = false;
239 	m_pPrivateBackgroundPixmap = nullptr;
240 	m_bSkipScrollBarRepaint = false;
241 	m_pLogFile = nullptr;
242 	m_pKviWindow = pWnd;
243 
244 	m_iUnprocessedPaintEventRequests = 0;
245 	m_bPostedPaintEventPending = false;
246 
247 	m_pLastLinkUnderMouse = nullptr;
248 	m_iLastLinkRectTop = -1;
249 	m_iLastLinkRectHeight = -1;
250 
251 	m_pMasterView = nullptr;
252 
253 	m_pToolWidget = nullptr;
254 
255 	m_pWrappedBlockSelectionInfo = new KviIrcViewWrappedBlockSelectionInfo;
256 
257 	// say qt to avoid erasing on repaint
258 	setAutoFillBackground(false);
259 
260 	m_pFm = nullptr; // will be updated in the first paint event
261 	m_iFontDescent = 0;
262 	m_iFontLineSpacing = 0;
263 	m_iFontLineWidth = 0;
264 
265 	m_pToolTip = new KviIrcViewToolTip(this);
266 
267 	// Create the scroll bar
268 	m_pScrollBar = new QScrollBar(Qt::Vertical, this);
269 	m_pScrollBar->setAutoFillBackground(true);
270 	m_pScrollBar->setMaximum(0);
271 	m_pScrollBar->setMinimum(0);
272 	m_pScrollBar->setSingleStep(1);
273 	m_pScrollBar->setPageStep(10);
274 	m_pScrollBar->setValue(0);
275 	m_pScrollBar->setObjectName("irc_view_scrollbar");
276 	m_pScrollBar->setTracking(true);
277 	m_pScrollBar->show();
278 	m_pScrollBar->setFocusProxy(this);
279 
280 	m_pToolsButton = new QToolButton(this);
281 	m_pToolsButton->setObjectName("btntools");
282 	m_pToolsButton->setAutoFillBackground(true);
283 
284 	QIcon is1(*(g_pIconManager->getSmallIcon(KviIconManager::PopupMenu)));
285 	m_pToolsButton->setAutoRaise(true);
286 	m_pToolsButton->setIcon(is1);
287 
288 	KviTalToolTip::add(m_pToolsButton, __tr2qs("Output view tools"));
289 	m_pToolsButton->setFocusProxy(this);
290 
291 	connect(m_pToolsButton, SIGNAL(clicked()), this, SLOT(showToolsPopup()));
292 	m_pToolsButton->show();
293 
294 	connect(m_pScrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
295 	m_iLastScrollBarValue = 0;
296 
297 	// set the minimum size
298 	setMinimumSize(KVI_IRCVIEW_MINIMUM_WIDTH + m_pScrollBar->sizeHint().width(), KVI_IRCVIEW_MINIMUM_HEIGHT);
299 	// and catch all mouse events
300 	setMouseTracking(true);
301 	// let's go!
302 	applyOptions();
303 
304 	if(KVI_OPTION_UINT(KviOption_uintAutoFlushLogs)) //m_iFlushTimer
305 	{
306 		m_iFlushTimer = startTimer(KVI_OPTION_UINT(KviOption_uintAutoFlushLogs) * 60 * 1000);
307 	}
308 
309 	//	if(pWnd->input()) setFocusProxy(pWnd->input());
310 
311 	QSizePolicy oSizePolicy = sizePolicy();
312 	oSizePolicy.setHorizontalPolicy(QSizePolicy::Expanding);
313 	setSizePolicy(oSizePolicy);
314 }
315 
delete_text_line(KviIrcViewLine * line,QHash<KviIrcViewLine *,KviAnimatedPixmap * > * animatedSmiles)316 static inline void delete_text_line(KviIrcViewLine * line, QHash<KviIrcViewLine *, KviAnimatedPixmap *> * animatedSmiles)
317 {
318 	QMultiHash<KviIrcViewLine *, KviAnimatedPixmap *>::iterator it = animatedSmiles->find(line);
319 	while(it != animatedSmiles->end() && it.key() == line)
320 	{
321 		it = animatedSmiles->erase(it);
322 	}
323 	for(size_t i{}; i < line->uChunkCount; i++)
324 	{
325 		if((line->pChunks[i].type == KviControlCodes::Escape) || (line->pChunks[i].type == KviControlCodes::Icon))
326 		{
327 			if((line->pChunks[i].type == KviControlCodes::Icon) && (line->pChunks[i].szPayload != line->pChunks[i].szSmileId))
328 				KviMemory::free(line->pChunks[i].szSmileId);
329 			KviMemory::free(line->pChunks[i].szPayload);
330 		}
331 	}
332 	KviMemory::free(line->pChunks); // free attributes data
333 	if(line->iBlockCount)
334 		KviMemory::free(line->pBlocks);
335 	delete line;
336 }
337 
~KviIrcView()338 KviIrcView::~KviIrcView()
339 {
340 	// kill any pending timer
341 	if(m_iFlushTimer)
342 		killTimer(m_iFlushTimer);
343 	if(m_iSelectTimer)
344 		killTimer(m_iSelectTimer);
345 	if(m_iMouseTimer)
346 		killTimer(m_iMouseTimer);
347 
348 	// and close the log file (flush!)
349 	stopLogging();
350 
351 	if(m_pToolWidget)
352 		delete m_pToolWidget;
353 
354 	// don't forget the background pixmap!
355 	if(m_pPrivateBackgroundPixmap)
356 		delete m_pPrivateBackgroundPixmap;
357 
358 	// and to remove all the text lines
359 	emptyBuffer(false);
360 
361 	// the pending ones too!
362 	for(const auto & l : m_pMessagesStoppedWhileSelecting)
363 		delete_text_line(l, &m_hAnimatedSmiles);
364 
365 	m_pMessagesStoppedWhileSelecting.clear();
366 
367 	if(m_pFm)
368 		delete m_pFm;
369 
370 	delete m_pToolTip;
371 	delete m_pWrappedBlockSelectionInfo;
372 }
373 
showEvent(QShowEvent * e)374 void KviIrcView::showEvent(QShowEvent * e)
375 {
376 	QWindow * pWin = topLevelWidget()->windowHandle();
377 	if(!pWin)
378 		return; // huh ?
379 
380 	QObject::disconnect(pWin,SIGNAL(screenChanged(QScreen *)),this,SLOT(screenChanged(QScreen *)));
381 	QObject::connect(pWin,SIGNAL(screenChanged(QScreen *)),this,SLOT(screenChanged(QScreen *)));
382 }
383 
screenChanged(QScreen *)384 void KviIrcView::screenChanged(QScreen *)
385 {
386 	// Changing screen can change DPI. Reset font so metrics are recomputed.
387 	setFont(font());
388 }
389 
390 
391 //
392 // The IrcView : options
393 //
394 
setFont(const QFont & f)395 void KviIrcView::setFont(const QFont & f)
396 {
397 	if(m_pFm)
398 	{
399 		// force an update to the font variables
400 		delete m_pFm;
401 		m_pFm = nullptr;
402 	}
403 	KviIrcViewLine * l = m_pFirstLine;
404 	while(l)
405 	{
406 		l->iMaxLineWidth = -1;
407 		l = l->pNext;
408 	}
409 
410 	QFont newFont(f);
411 	newFont.setKerning(false);
412 	newFont.setStyleStrategy(QFont::StyleStrategy(newFont.styleStrategy() | QFont::ForceIntegerMetrics));
413 	QWidget::setFont(newFont);
414 	update();
415 }
416 
applyOptions()417 void KviIrcView::applyOptions()
418 {
419 	flushLog();
420 
421 	if(m_iFlushTimer)
422 		killTimer(m_iFlushTimer);
423 
424 	if(KVI_OPTION_UINT(KviOption_uintAutoFlushLogs))
425 		m_iFlushTimer = startTimer(KVI_OPTION_UINT(KviOption_uintAutoFlushLogs) * 60 * 1000);
426 
427 	// Will do nothing if the view is still empty (i.e. called from the constructor)
428 	reapplyMessageColors();
429 
430 	setFont(KVI_OPTION_FONT(KviOption_fontIrcView));
431 }
432 
433 //
434 // The IrcView : DnD - 2005.Resurrection by Grifisx & Noldor
435 //
436 
enableDnd(bool bEnable)437 void KviIrcView::enableDnd(bool bEnable)
438 {
439 	setAcceptDrops(bEnable);
440 	m_bAcceptDrops = bEnable;
441 }
442 
clearBuffer()443 void KviIrcView::clearBuffer()
444 {
445 	emptyBuffer(true);
446 	m_pScrollBar->setRange(0, m_iNumLines);
447 }
448 
saveBuffer(const char * pcFilename)449 bool KviIrcView::saveBuffer(const char * pcFilename)
450 {
451 	QFile f(QString::fromUtf8(pcFilename));
452 	if(!f.open(QIODevice::WriteOnly | QIODevice::Truncate))
453 		return false;
454 	QString szTmp;
455 	getTextBuffer(szTmp);
456 	QByteArray tmpx = szTmp.toUtf8();
457 	f.write(tmpx.data(), tmpx.length());
458 	f.close();
459 	return true;
460 }
461 
prevLine()462 void KviIrcView::prevLine() { m_pScrollBar->triggerAction(QAbstractSlider::SliderSingleStepSub); }
nextLine()463 void KviIrcView::nextLine() { m_pScrollBar->triggerAction(QAbstractSlider::SliderSingleStepAdd); }
prevPage()464 void KviIrcView::prevPage() { m_pScrollBar->triggerAction(QAbstractSlider::SliderPageStepSub); }
nextPage()465 void KviIrcView::nextPage() { m_pScrollBar->triggerAction(QAbstractSlider::SliderPageStepAdd); }
scrollTop()466 void KviIrcView::scrollTop() { m_pScrollBar->triggerAction(QAbstractSlider::SliderToMinimum); }
scrollBottom()467 void KviIrcView::scrollBottom() { m_pScrollBar->triggerAction(QAbstractSlider::SliderToMaximum); }
468 
setPrivateBackgroundPixmap(const QPixmap & pixmap,bool bRepaint)469 void KviIrcView::setPrivateBackgroundPixmap(const QPixmap & pixmap, bool bRepaint)
470 {
471 	if(m_pPrivateBackgroundPixmap)
472 	{
473 		delete m_pPrivateBackgroundPixmap;
474 		m_pPrivateBackgroundPixmap = nullptr;
475 	}
476 	if(!pixmap.isNull())
477 		m_pPrivateBackgroundPixmap = new QPixmap(pixmap);
478 
479 	if(bRepaint)
480 		update();
481 }
482 
emptyBuffer(bool bRepaint)483 void KviIrcView::emptyBuffer(bool bRepaint)
484 {
485 	while(m_pLastLine != nullptr)
486 		removeHeadLine();
487 	if(bRepaint)
488 		update();
489 }
490 
clearLineMark(bool bRepaint)491 void KviIrcView::clearLineMark(bool bRepaint)
492 {
493 	m_uLineMarkLineIndex = KVI_IRCVIEW_INVALID_LINE_MARK_INDEX;
494 	clearUnreaded();
495 	if(bRepaint)
496 		update();
497 }
498 
clearUnreaded()499 void KviIrcView::clearUnreaded()
500 {
501 	m_bHaveUnreadedHighlightedMessages = false;
502 	m_bHaveUnreadedMessages = false;
503 
504 	if(g_pMainWindow->trayIcon())
505 		g_pMainWindow->trayIcon()->refresh();
506 }
507 
setMaxBufferSize(int maxBufSize,bool bRepaint)508 void KviIrcView::setMaxBufferSize(int maxBufSize, bool bRepaint)
509 {
510 	if(maxBufSize < 32)
511 		maxBufSize = 32;
512 	m_iMaxLines = maxBufSize;
513 	while(m_iNumLines > m_iMaxLines)
514 		removeHeadLine();
515 	m_pScrollBar->setRange(0, m_iNumLines);
516 	if(bRepaint)
517 		update();
518 }
519 
520 /*
521 void KviIrcView::setTimestamp(bool bTimestamp)
522 {
523 	m_bTimestamp = bTimestamp;
524 
525 
526 // STATS FOR A BUFFER FULL OF HIGHLY COLORED STRINGS, HIGHLY WRAPPED
527 //
528 // Lines = 1024 (322425 bytes - 314 KB) (avg 314 bytes per line), well :)
529 // string bytes = 87745 (85 KB)
530 // attributes = 3576 (42912 bytes - 41 KB)
531 // blocks = 12226 (146712 bytes - 143 KB)
532 //
533 //	unsigned long int nAlloc = 0;
534 //	unsigned long int nLines = 0;
535 //	unsigned long int nStringBytes = 0;
536 //	unsigned long int nAttrBytes = 0;
537 //	unsigned long int nBlockBytes = 0;
538 //	unsigned long int nBlocks = 0;
539 //	unsigned long int nAttributes = 0;
540 //	KviIrcViewLine * l=m_pFirstLine;
541 //	while(l){
542 //		nLines++;
543 //		nAlloc += sizeof(KviIrcViewLine);
544 //		nStringBytes += l->data_len + 1;
545 //		nAlloc += l->data_len + 1;
546 //		nAlloc += (l->uChunkCount * sizeof(KviIrcViewLineChunk));
547 //		nAttrBytes +=(l->uChunkCount * sizeof(KviIrcViewLineChunk));
548 //		nAlloc += (l->iBlockCount * sizeof(KviIrcViewLineChunk));
549 //		nBlockBytes += (l->iBlockCount * sizeof(KviIrcViewLineChunk));
550 //		nBlocks += (l->iBlockCount);
551 //		nAttributes += (l->uChunkCount);
552 //		l = l->pNext;
553 //	}
554 //	qDebug("\n\nLines = %u (%u bytes - %u KB) (avg %u bytes per line)",nLines,nAlloc,nAlloc / 1024,nLines ? (nAlloc / nLines) : 0);
555 //	qDebug("string bytes = %u (%u KB)",nStringBytes,nStringBytes / 1024);
556 //	qDebug("attributes = %u (%u bytes - %u KB)",nAttributes,nAttrBytes,nAttrBytes / 1024);
557 //	qDebug("blocks = %u (%u bytes - %u KB)\n",nBlocks,nBlockBytes,nBlockBytes / 1024);
558 
559 }
560 */
scrollBarPositionChanged(int newValue)561 void KviIrcView::scrollBarPositionChanged(int newValue)
562 {
563 	if(!m_pCurLine)
564 		return;
565 	if(newValue > m_iLastScrollBarValue)
566 	{
567 		while(newValue > m_iLastScrollBarValue)
568 		{
569 			if(m_pCurLine->pNext)
570 			{
571 				m_pCurLine = m_pCurLine->pNext;
572 			}
573 			m_iLastScrollBarValue++;
574 		}
575 	}
576 	else
577 	{
578 		while(newValue < m_iLastScrollBarValue)
579 		{
580 			if(m_pCurLine->pPrev)
581 				m_pCurLine = m_pCurLine->pPrev;
582 			m_iLastScrollBarValue--;
583 		}
584 	}
585 	if(!m_bSkipScrollBarRepaint)
586 		repaint();
587 }
588 
postUpdateEvent()589 void KviIrcView::postUpdateEvent()
590 {
591 	// This will post a QEvent with a full repaint request
592 	if(!m_bPostedPaintEventPending)
593 	{
594 		m_bPostedPaintEventPending = true;
595 		QEvent * e = new QEvent(QEvent::User);
596 		g_pApp->postEvent(this, e); // queue a repaint
597 	}
598 
599 	m_iUnprocessedPaintEventRequests++; // paintEvent() will set it to 0
600 
601 	if(m_iUnprocessedPaintEventRequests == 3)
602 	{
603 // Three unprocessed paint events...do it now
604 #ifdef COMPILE_PSEUDO_TRANSPARENCY
605 		if(!((KVI_OPTION_PIXMAP(KviOption_pixmapIrcViewBackground).pixmap()) || m_pPrivateBackgroundPixmap || g_pShadedChildGlobalDesktopBackground || KVI_OPTION_BOOL(KviOption_boolUseCompositingForTransparency)))
606 			fastScroll(3);
607 #else
608 		if(!((KVI_OPTION_PIXMAP(KviOption_pixmapIrcViewBackground).pixmap()) || m_pPrivateBackgroundPixmap))
609 			fastScroll(3);
610 #endif
611 		else
612 			repaint();
613 	}
614 }
615 
appendLine(KviIrcViewLine * ptr,const QDateTime & date,bool bRepaint)616 void KviIrcView::appendLine(KviIrcViewLine * ptr, const QDateTime & date, bool bRepaint)
617 {
618 	// This one appends a KviIrcViewLine to
619 	// the buffer list (at the end)
620 
621 	if(m_bMouseIsDown)
622 	{
623 		// Do not move the view!
624 		// So we append the text line to a temp queue
625 		// and then we'll add it when the mouse button is released
626 		m_pMessagesStoppedWhileSelecting.push_back(ptr);
627 		return;
628 	}
629 
630 	// First log the line and assign the index
631 	// Don't use add2log here!...we must go as fast as possible, so we avoid some push and pop calls, and also a couple of branches
632 	if(m_pLogFile && KVI_OPTION_BOOL(KviOption_boolStripControlCodesInLogs))
633 	{
634 		// a slave view has no log files!
635 		if(KVI_OPTION_MSGTYPE(ptr->iMsgType).logEnabled())
636 		{
637 			add2Log(ptr->szText, date, ptr->iMsgType, false);
638 			// If we fail...this has been already reported!
639 		}
640 
641 		// mmh.. when this overflows... we have problems (find doesn't work anymore :()
642 		// but it overflows at 2^32 lines... 2^32 = 4.294.967.296 lines
643 		// to spit it out in a year you'd need to print 1360 lines per second... that's insane :D
644 		// a really fast but reasonable rate of printed lines might be 10 per second
645 		// thus 429.496.730 seconds would be needed to make this var overflow
646 		// that means more or less 13 years of text spitting at full rate :D
647 		// I think that we can safely assume that this will NOT overflow ... your cpu (or you)
648 		// will get mad before. Well.. it is not that dangerous after all...
649 		ptr->uIndex = m_uNextLineIndex;
650 		m_uNextLineIndex++;
651 	}
652 	else
653 	{
654 		// no log: we could have master view!
655 		if(m_pMasterView)
656 		{
657 			if(m_pMasterView->m_pLogFile && KVI_OPTION_BOOL(KviOption_boolStripControlCodesInLogs))
658 			{
659 				if(KVI_OPTION_MSGTYPE(ptr->iMsgType).logEnabled())
660 					m_pMasterView->add2Log(ptr->szText, date, ptr->iMsgType, false);
661 			}
662 			ptr->uIndex = m_pMasterView->m_uNextLineIndex;
663 			m_pMasterView->m_uNextLineIndex++;
664 		}
665 		else
666 		{
667 			ptr->uIndex = m_uNextLineIndex;
668 			m_uNextLineIndex++;
669 		}
670 	}
671 
672 	if(m_pLastLine)
673 	{
674 		// There is at least one line in the view
675 		m_pLastLine->pNext = ptr;
676 		ptr->pPrev = m_pLastLine;
677 		ptr->pNext = nullptr;
678 		m_iNumLines++;
679 
680 		if(m_iNumLines > m_iMaxLines)
681 		{
682 			// Too many lines in the view...remove one
683 			removeHeadLine();
684 			if(m_pCurLine == m_pLastLine)
685 			{
686 				m_pCurLine = ptr;
687 				if(bRepaint)
688 					postUpdateEvent();
689 			}
690 			else
691 			{
692 				// the cur line remains the same
693 				// the scroll bar must move up one place to be in sync
694 				m_bSkipScrollBarRepaint = true;
695 				if(m_pScrollBar->value() > 0)
696 				{
697 					m_iLastScrollBarValue--;
698 					KVI_ASSERT(m_iLastScrollBarValue >= 0);
699 					m_pScrollBar->triggerAction(QAbstractSlider::SliderSingleStepSub);
700 				} // else will stay in sync
701 				m_bSkipScrollBarRepaint = false;
702 			}
703 		}
704 		else
705 		{
706 			// Just append
707 			m_pScrollBar->setRange(0, m_iNumLines);
708 			if(m_pCurLine == m_pLastLine)
709 			{
710 				m_bSkipScrollBarRepaint = true;
711 				m_pScrollBar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
712 				m_bSkipScrollBarRepaint = false;
713 				if(bRepaint)
714 					postUpdateEvent();
715 			}
716 		}
717 		m_pLastLine = ptr;
718 	}
719 	else
720 	{
721 		//First line
722 		m_pLastLine = ptr;
723 		m_pFirstLine = ptr;
724 		m_pCurLine = ptr;
725 		ptr->pPrev = nullptr;
726 		ptr->pNext = nullptr;
727 		m_iNumLines = 1;
728 		m_pScrollBar->setRange(0, 1);
729 		m_pScrollBar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
730 		if(bRepaint)
731 			postUpdateEvent();
732 	}
733 }
734 
735 //
736 // removeHeadLine
737 //
738 
removeHeadLine(bool bRepaint)739 void KviIrcView::removeHeadLine(bool bRepaint)
740 {
741 	//Removes the first line of the text buffer
742 	if(!m_pLastLine)
743 		return;
744 	if(m_pFirstLine == m_pCursorLine)
745 		m_pCursorLine = nullptr;
746 
747 	if(m_pFirstLine->pNext)
748 	{
749 		KviIrcViewLine * aux_ptr = m_pFirstLine->pNext; // get the next line
750 		aux_ptr->pPrev = nullptr;                       // becomes the first
751 		if(m_pFirstLine == m_pCurLine)
752 			m_pCurLine = aux_ptr;                       // move the cur line if necessary
753 		delete_text_line(m_pFirstLine, &m_hAnimatedSmiles); // delete the struct
754 		m_pFirstLine = aux_ptr;                             // set the last
755 		m_iNumLines--;                                      // and decrement the count
756 	}
757 	else
758 	{	// unique line
759 		m_pCurLine = nullptr;
760 		delete_text_line(m_pFirstLine, &m_hAnimatedSmiles);
761 		m_pFirstLine = nullptr;
762 		m_iNumLines = 0;
763 		m_pLastLine = nullptr;
764 	}
765 	if(bRepaint)
766 		repaint();
767 }
768 
messageShouldGoToMessageView(int iMsgType)769 bool KviIrcView::messageShouldGoToMessageView(int iMsgType)
770 {
771 	switch(iMsgType)
772 	{
773 		case KVI_OUT_CHANPRIVMSG:
774 		case KVI_OUT_CHANPRIVMSGCRYPTED:
775 		case KVI_OUT_CHANNELNOTICE:
776 		case KVI_OUT_CHANNELNOTICECRYPTED:
777 		case KVI_OUT_ACTION:
778 		case KVI_OUT_ACTIONCRYPTED:
779 		case KVI_OUT_OWNACTION:
780 		case KVI_OUT_OWNACTIONCRYPTED:
781 		case KVI_OUT_OWNPRIVMSG:
782 		case KVI_OUT_OWNPRIVMSGCRYPTED:
783 		case KVI_OUT_HIGHLIGHT:
784 			return true;
785 			break;
786 		default:
787 			break; // fall down
788 	}
789 
790 	return false;
791 }
792 
splitMessagesTo(KviIrcView * v)793 void KviIrcView::splitMessagesTo(KviIrcView * v)
794 {
795 	v->emptyBuffer(false);
796 
797 	KviIrcViewLine * l = m_pFirstLine;
798 	KviIrcViewLine * tmp;
799 	while(l)
800 	{
801 		if(messageShouldGoToMessageView(l->iMsgType))
802 		{
803 			m_iNumLines--;
804 			v->m_iNumLines++;
805 
806 			if(l->pNext)
807 				l->pNext->pPrev = l->pPrev;
808 			if(l->pPrev)
809 				l->pPrev->pNext = l->pNext;
810 			if(l == m_pFirstLine)
811 				m_pFirstLine = l->pNext;
812 			if(l == m_pLastLine)
813 				m_pLastLine = l->pPrev;
814 			if(v->m_pLastLine)
815 			{
816 				v->m_pLastLine->pNext = l;
817 				l->pPrev = v->m_pLastLine;
818 				v->m_pLastLine = l;
819 			}
820 			else
821 			{
822 				v->m_pFirstLine = l;
823 				l->pPrev = nullptr;
824 				v->m_pLastLine = l;
825 			}
826 			tmp = l->pNext;
827 			l->pNext = nullptr;
828 			l = tmp;
829 		}
830 		else
831 		{
832 			l = l->pNext;
833 		}
834 	}
835 
836 	v->m_pCurLine = v->m_pLastLine;
837 	m_pCurLine = m_pLastLine;
838 
839 	v->m_pCursorLine = nullptr;
840 	m_pCursorLine = nullptr;
841 
842 	m_iLastScrollBarValue = m_iNumLines;
843 	m_pScrollBar->setRange(0, m_iNumLines);
844 	m_pScrollBar->setValue(m_iNumLines);
845 
846 	repaint();
847 
848 	v->m_iLastScrollBarValue = v->m_iNumLines;
849 	v->m_pScrollBar->setRange(0, v->m_iNumLines);
850 	v->m_pScrollBar->setValue(v->m_iNumLines);
851 	v->repaint();
852 }
853 
appendMessagesFrom(KviIrcView * v)854 void KviIrcView::appendMessagesFrom(KviIrcView * v)
855 {
856 	if(!m_pLastLine)
857 	{
858 		m_pFirstLine = v->m_pFirstLine;
859 	}
860 	else
861 	{
862 		m_pLastLine->pNext = v->m_pFirstLine;
863 		v->m_pFirstLine->pPrev = m_pLastLine;
864 	}
865 	m_pLastLine = v->m_pLastLine;
866 	m_pCurLine = m_pLastLine;
867 	m_pCursorLine = nullptr;
868 	v->m_pFirstLine = nullptr;
869 	v->m_pLastLine = nullptr;
870 	v->m_pCurLine = nullptr;
871 	v->m_pCursorLine = nullptr;
872 	m_iNumLines += v->m_iNumLines;
873 	v->m_iNumLines = 0;
874 	//	v->m_pScrollBar->setRange(0,0);
875 	//	v->m_pScrollBar->setValue(0);
876 	m_iLastScrollBarValue = m_iNumLines;
877 	m_pScrollBar->setRange(0, m_iNumLines);
878 	m_pScrollBar->setValue(m_iNumLines);
879 
880 	repaint();
881 }
882 
joinMessagesFrom(KviIrcView * v)883 void KviIrcView::joinMessagesFrom(KviIrcView * v)
884 {
885 	KviIrcViewLine * l1 = m_pFirstLine;
886 	KviIrcViewLine * l2 = v->m_pFirstLine;
887 	KviIrcViewLine * tmp;
888 
889 	while(l2)
890 	{
891 		if(l1)
892 		{
893 			if(l2->uIndex < l1->uIndex)
894 			{
895 				// the external message is older than the current internal one
896 				l2->pPrev = l1->pPrev;
897 				if(l1->pPrev)
898 					l1->pPrev->pNext = l2;
899 				else
900 					m_pFirstLine = l2;
901 				l1->pPrev = l2;
902 				tmp = l2->pNext;
903 				l2->pNext = l1;
904 				l2 = tmp;
905 			}
906 			else
907 			{
908 				// the external message is younger than the current internal one
909 				l1 = l1->pNext;
910 			}
911 		}
912 		else
913 		{
914 			// There is no current internal message (ran over the end)
915 			// merge at the end then
916 			if(m_pFirstLine)
917 			{
918 				m_pLastLine->pNext = l2;
919 				l2->pPrev = m_pLastLine;
920 			}
921 			else
922 			{
923 				m_pFirstLine = l2;
924 				l2->pPrev = nullptr;
925 			}
926 			tmp = l2->pNext;
927 			l2->pNext = nullptr;
928 			m_pLastLine = l2;
929 			l2 = tmp;
930 		}
931 	}
932 
933 	m_pCurLine = m_pLastLine;
934 	m_pCursorLine = nullptr;
935 	v->m_pFirstLine = nullptr;
936 	v->m_pLastLine = nullptr;
937 	v->m_pCurLine = nullptr;
938 	v->m_pCursorLine = nullptr;
939 	m_iNumLines += v->m_iNumLines;
940 	v->m_iNumLines = 0;
941 	//	v->m_pScrollBar->setRange(0,0);
942 	//	v->m_pScrollBar->setValue(0);
943 	m_iLastScrollBarValue = m_iNumLines;
944 	m_pScrollBar->setRange(0, m_iNumLines);
945 	m_pScrollBar->setValue(m_iNumLines);
946 
947 	repaint();
948 }
949 
getLinkEscapeCommand(QString & buffer,const QString & szPayload,const QString & escape_label)950 void KviIrcView::getLinkEscapeCommand(QString & buffer, const QString & szPayload, const QString & escape_label)
951 {
952 	if(szPayload.isEmpty())
953 		return;
954 
955 	int idx = szPayload.indexOf(escape_label, Qt::CaseInsensitive);
956 	if(idx == -1)
957 		return;
958 	idx += escape_label.length();
959 
960 	int idx2 = szPayload.indexOf("[!", idx, Qt::CaseInsensitive);
961 	int len = idx2 == -1 ? szPayload.length() - idx : idx2 - idx;
962 
963 	buffer = szPayload.mid(idx, len);
964 }
965 
fastScroll(int lines)966 void KviIrcView::fastScroll(int lines)
967 {
968 	m_iUnprocessedPaintEventRequests = 0;
969 
970 #ifdef COMPILE_ON_MAC
971 	// fastScroll() is currently broken for macosx, ticket #791
972 	update();
973 	return;
974 #endif
975 
976 	if(!isVisible())
977 		return;
978 
979 	if(!m_pFm)
980 	{
981 		repaint(); // We must get the metrics from a real paint event :/
982 		return;	   // must do a full repaint to get them..
983 	}
984 
985 	// Ok...the current line is the last one here
986 	// It is the only one that needs to be repainted
987 	int widgetWidth = width() - m_pScrollBar->width();
988 
989 	if(widgetWidth < KVI_IRCVIEW_PIXMAP_AND_SEPARATOR + KVI_IRCVIEW_DOUBLEBORDER_WIDTH + 10)
990 		return; // can't show stuff here
991 
992 	int widgetHeight = height();
993 	int maxLineWidth = widgetWidth - KVI_IRCVIEW_DOUBLEBORDER_WIDTH;
994 
995 	if(KVI_OPTION_BOOL(KviOption_boolIrcViewShowImages))
996 	{
997 		maxLineWidth -= KVI_IRCVIEW_PIXMAP_AND_SEPARATOR;
998 	}
999 
1000 	int heightToPaint = 1;
1001 	KviIrcViewLine * l = m_pCurLine;
1002 	while(lines > 0)
1003 	{
1004 		if(l)
1005 		{
1006 			if(maxLineWidth != l->iMaxLineWidth)
1007 				calculateLineWraps(l, maxLineWidth);
1008 			heightToPaint += l->uLineWraps * m_iFontLineSpacing;
1009 			heightToPaint += (m_iFontLineSpacing + m_iFontDescent);
1010 			lines--;
1011 			l = l->pPrev;
1012 		}
1013 		else
1014 			lines = 0;
1015 	}
1016 
1017 	scroll(0, -(heightToPaint - 1), QRect(1, 1, widgetWidth - 2, widgetHeight - 2));
1018 
1019 	if(m_iLastLinkRectHeight > -1)
1020 	{	// need to kill the last highlighted link
1021 		m_iLastLinkRectTop -= heightToPaint;
1022 		if(m_iLastLinkRectTop < 0)
1023 		{
1024 			m_iLastLinkRectHeight += m_iLastLinkRectTop;
1025 			m_iLastLinkRectTop = 0;
1026 		}
1027 	}
1028 }
1029 
1030 //
1031 // The IrcView : THE paint event
1032 //
1033 
paintEvent(QPaintEvent * p)1034 void KviIrcView::paintEvent(QPaintEvent * p)
1035 {
1036 	// THIS FUNCTION IS A MONSTER
1037 
1038 	/*
1039 	 * Profane description: this is ircview's most important function. It takes a lot of cpu cycles to complete, so we want to be sure
1040 	 * it's well optimized. First, we want to skip this method every time it's useless: it we're too short or we're covered by other windows.
1041 	 */
1042 
1043 	if(!isVisible())
1044 	{
1045 		m_iUnprocessedPaintEventRequests = 0; // assume a full repaint when this widget is shown...
1046 		return;                               // can't show stuff here
1047 	}
1048 
1049 	int scrollbarWidth = m_pScrollBar->width();
1050 	int toolWidgetHeight = (m_pToolWidget && m_pToolWidget->isVisible()) ? m_pToolWidget->sizeHint().height() : 0;
1051 	int widgetWidth = width() - scrollbarWidth;
1052 	int widgetHeight = height() - toolWidgetHeight;
1053 
1054 	static QRect r; // static: avoid calling constructor and destructor every time...
1055 
1056 	if(p)
1057 	{
1058 		r = p->rect(); // app triggered, or self triggered from fastScroll (in that case m_iUnprocessedPaintEventRequests is set to 0 there)
1059 		if(r == rect())
1060 			m_iUnprocessedPaintEventRequests = 0; // only full repaints reset
1061 	}
1062 	else
1063 	{
1064 		// A self triggered event
1065 		m_iUnprocessedPaintEventRequests = 0; // only full repaints reset
1066 		r = rect();
1067 	}
1068 
1069 	/*
1070 	 * Profane description: we start the real paint here: set some geometry, a font, and paint the background
1071 	 */
1072 	int rectTop = r.y();
1073 	int rectHeight = r.height();
1074 	int rectBottom = rectTop + rectHeight;
1075 
1076 	QFont newFont;
1077 
1078 	QPainter pa(this);
1079 
1080 	SET_ANTI_ALIASING(pa);
1081 
1082 	pa.setFont(font());
1083 	QFont::Style normalFontStyle = pa.font().style();
1084 
1085 	if(!m_pFm)
1086 	{
1087 		// note that QFontMetrics(pa.font()) may be not the same as QFontMetrics(font())
1088 		// because the painter might effectively use an approximation of the QFont specified
1089 		// by font().
1090 		recalcFontVariables(pa.font(), pa.fontInfo());
1091 	}
1092 
1093 #ifdef COMPILE_PSEUDO_TRANSPARENCY
1094 	if(KVI_OPTION_BOOL(KviOption_boolUseCompositingForTransparency) && g_pApp->supportsCompositing())
1095 	{
1096 		pa.save();
1097 		pa.setCompositionMode(QPainter::CompositionMode_Source);
1098 		QColor col = KVI_OPTION_COLOR(KviOption_colorGlobalTransparencyFade);
1099 		col.setAlphaF((float)((float)KVI_OPTION_UINT(KviOption_uintGlobalTransparencyChildFadeFactor) / (float)100));
1100 		pa.fillRect(rect(), col);
1101 		pa.restore();
1102 	}
1103 	else if(g_pShadedChildGlobalDesktopBackground)
1104 	{
1105 		QPoint pnt = m_pKviWindow->isDocked() ? mapTo(g_pMainWindow, r.topLeft()) : mapTo(m_pKviWindow, r.topLeft());
1106 		pa.drawTiledPixmap(r, *(g_pShadedChildGlobalDesktopBackground), pnt);
1107 	}
1108 	else
1109 	{
1110 #endif
1111 		pa.fillRect(r, KVI_OPTION_COLOR(KviOption_colorIrcViewBackground));
1112 
1113 		QPixmap * pix = m_pPrivateBackgroundPixmap;
1114 
1115 		if(!pix)
1116 			pix = KVI_OPTION_PIXMAP(KviOption_pixmapIrcViewBackground).pixmap();
1117 		if(pix)
1118 			KviPixmapUtils::drawPixmapWithPainter(&pa, pix, KVI_OPTION_UINT(KviOption_uintIrcViewPixmapAlign), r, widgetWidth, widgetHeight);
1119 #ifdef COMPILE_PSEUDO_TRANSPARENCY
1120 	}
1121 #endif
1122 
1123 	if(widgetWidth < 20)
1124 	{
1125 		m_iUnprocessedPaintEventRequests = 0; // assume a full repaint when this widget is shown...
1126 		return;                               // can't show stuff here
1127 	}
1128 
1129 	/*
1130 	 * Profane description: after the background, start to paint the contents (a list of text lines with "dynamic contents", correctly
1131 	 * wrapped at the right edge of this control).
1132 	 */
1133 
1134 	// Have lines visible
1135 	int curBottomCoord = widgetHeight - KVI_IRCVIEW_VERTICAL_BORDER;
1136 	int maxLineWidth = widgetWidth - KVI_IRCVIEW_DOUBLEBORDER_WIDTH;
1137 	int defLeftCoord = KVI_IRCVIEW_HORIZONTAL_BORDER;
1138 	int lineWrapsHeight;
1139 
1140 	// if we draw an icon as a line preamble, we have to change borders geometry accordingly
1141 	if(KVI_OPTION_BOOL(KviOption_boolIrcViewShowImages))
1142 	{
1143 		maxLineWidth -= KVI_IRCVIEW_PIXMAP_AND_SEPARATOR;
1144 		defLeftCoord += KVI_IRCVIEW_PIXMAP_AND_SEPARATOR;
1145 	}
1146 
1147 	KviIrcViewLine * pCurTextLine = m_pCurLine;
1148 
1149 	// Make sure that we have enough space to paint something...
1150 	if(maxLineWidth < m_iMinimumPaintWidth)
1151 		pCurTextLine = nullptr;
1152 
1153 	bool bLineMarkPainted = !KVI_OPTION_BOOL(KviOption_boolTrackLastReadTextViewLine);
1154 	int iLinesPerPage = 0;
1155 
1156 	// And loop through lines until we not run over the upper bound of the view
1157 	while((curBottomCoord >= KVI_IRCVIEW_VERTICAL_BORDER) && pCurTextLine)
1158 	{
1159 		// Paint pCurTextLine
1160 		if(maxLineWidth != pCurTextLine->iMaxLineWidth)
1161 		{
1162 			// Width of the widget or the font has been changed
1163 			// from the last time that this line was painted
1164 			calculateLineWraps(pCurTextLine, maxLineWidth);
1165 		}
1166 
1167 		// the evil multiplication
1168 		// in an i486 it can get up to 42 clock cycles
1169 		lineWrapsHeight = (pCurTextLine->uLineWraps) * m_iFontLineSpacing;
1170 		curBottomCoord -= lineWrapsHeight;
1171 
1172 		if((curBottomCoord - m_iFontLineSpacing) > rectBottom)
1173 		{
1174 			// not in update rect... skip
1175 			curBottomCoord -= (m_iFontLineSpacing + m_iFontDescent);
1176 			pCurTextLine = pCurTextLine->pPrev;
1177 			continue;
1178 		}
1179 
1180 		if(KVI_OPTION_BOOL(KviOption_boolIrcViewShowImages))
1181 		{
1182 			// Paint the pixmap first
1183 			// Calculate the position of the image
1184 			//imageYPos = curBottomCoord - (pixmapHeight(16) + ((m_iFontLineSpacing - 16)/2) );
1185 			int imageYPos = curBottomCoord - m_iRelativePixmapY;
1186 			//Set the mask if needed
1187 			int iPixId = KVI_OPTION_MSGTYPE(pCurTextLine->iMsgType).pixId();
1188 			if(iPixId > 0)
1189 				pa.drawPixmap(KVI_IRCVIEW_HORIZONTAL_BORDER, imageYPos, *(g_pIconManager->getSmallIcon(iPixId)));
1190 		}
1191 
1192 		// Initialize for drawing this line of text
1193 		// The first block is always an attribute block
1194 		char defaultBack = pCurTextLine->pBlocks->pChunk->colors.back;
1195 		char defaultFore = pCurTextLine->pBlocks->pChunk->colors.fore;
1196 		bool curBold = false;
1197 		bool curItalic = false;
1198 		bool curUnderline = false;
1199 		char foreBeforeEscape = KviControlCodes::Black;
1200 		bool curLink = false;
1201 		bool bacWasTransp = false;
1202 		char curFore = defaultFore;
1203 		char curBack = defaultBack;
1204 		int curLeftCoord = defLeftCoord;
1205 		curBottomCoord -= m_iFontDescent; //rise up the text...
1206 
1207 		//
1208 		// Single text line loop (paint all text blocks)
1209 		// (May correspond to more physical lines on the display if the text is wrapped)
1210 		//
1211 
1212 		for(int i = 0; i < pCurTextLine->iBlockCount; i++)
1213 		{
1214 			KviIrcViewWrappedBlock * block = &(pCurTextLine->pBlocks[i]);
1215 
1216 			// Play with the attributes
1217 			if(block->pChunk)
1218 			{
1219 				// normal block
1220 				switch(block->pChunk->type)
1221 				{
1222 					case KviControlCodes::Color:
1223 						if(block->pChunk->colors.fore != KviControlCodes::NoChange)
1224 						{
1225 							curFore = block->pChunk->colors.fore;
1226 							if(block->pChunk->colors.back != KviControlCodes::NoChange)
1227 								curBack = block->pChunk->colors.back;
1228 						}
1229 						else
1230 						{
1231 							/*
1232 							* When KVIrc encounters a Ctrl+K code without any trailing numbers, we then
1233 							* use KVIrc's default color value defined by the user in the Options dialog.
1234 							*/
1235 							curFore = defaultFore;
1236 							curBack = defaultBack;
1237 						}
1238 						break;
1239 					case KviControlCodes::Escape:
1240 						foreBeforeEscape = curFore;
1241 						if(block->pChunk->colors.fore != KviControlCodes::NoChange)
1242 							curFore = block->pChunk->colors.fore;
1243 						if(m_pLastLinkUnderMouse == block)
1244 							curLink = true;
1245 						break;
1246 					case KviControlCodes::UnEscape:
1247 						curLink = false;
1248 						curFore = foreBeforeEscape;
1249 						break;
1250 					case KviControlCodes::Bold:
1251 						curBold = !curBold;
1252 						break;
1253 					case KviControlCodes::Italic:
1254 						curItalic = !curItalic;
1255 						break;
1256 					case KviControlCodes::Underline:
1257 						curUnderline = !curUnderline;
1258 						break;
1259 					case KviControlCodes::Reset:
1260 						curBold = false;
1261 						curItalic = false;
1262 						curUnderline = false;
1263 						bacWasTransp = false;
1264 						curFore = defaultFore;
1265 						curBack = defaultBack;
1266 						break;
1267 					case KviControlCodes::Reverse:
1268 						// this should be "reversed colors"
1269 						char aux = curBack;
1270 						if(bacWasTransp)
1271 						{
1272 							curBack = KviControlCodes::Transparent;
1273 						}
1274 						else
1275 						{
1276 							curBack = curFore;
1277 						}
1278 						if(aux == KviControlCodes::Transparent)
1279 						{
1280 							curFore = (char)KVI_DEF_BACK;
1281 						}
1282 						else
1283 						{
1284 							curFore = aux;
1285 						}
1286 						bacWasTransp = (aux == KviControlCodes::Transparent);
1287 						break;
1288 					//case KviControlCodes::Icon:
1289 					//case KviControlCodes::UnIcon:
1290 						// does nothing
1291 						//qDebug("Have a block with ICON/UNICON attr");
1292 						//break;
1293 				}
1294 			}
1295 			else
1296 			{
1297 				// no attributes, it is a line wrap
1298 				curLeftCoord = defLeftCoord;
1299 				if(KVI_OPTION_BOOL(KviOption_boolIrcViewWrapMargin))
1300 					curLeftCoord += m_iWrapMargin;
1301 				curBottomCoord += m_iFontLineSpacing;
1302 			}
1303 
1304 //
1305 // Here we run really out of bounds :)))))
1306 // A couple of macros that could work well as functions...
1307 // but since there are really many params to be passed
1308 // and push & pop calls take clock cycles
1309 // my paranoid mind decided to go for the macro way.
1310 // This is NOT good programming
1311 //
1312 
1313 #define SET_PEN(_color, _custom)                                                             \
1314 	if(((unsigned char)_color) <= KVI_EXTCOLOR_MAX)                                          \
1315 	{                                                                                        \
1316 		pa.setPen(getMircColor((unsigned char)_color));                                      \
1317 	}                                                                                        \
1318 	else                                                                                     \
1319 	{                                                                                        \
1320 		switch((unsigned char)_color)                                                        \
1321 		{                                                                                    \
1322 			case KVI_COLOR_EXT_USER_OP:                                                      \
1323 				pa.setPen(KVI_OPTION_COLOR(KviOption_colorUserListViewOpForeground));        \
1324 				break;                                                                       \
1325 			case KVI_COLOR_EXT_USER_HALFOP:                                                  \
1326 				pa.setPen(KVI_OPTION_COLOR(KviOption_colorUserListViewHalfOpForeground));    \
1327 				break;                                                                       \
1328 			case KVI_COLOR_EXT_USER_ADMIN:                                                   \
1329 				pa.setPen(KVI_OPTION_COLOR(KviOption_colorUserListViewChanAdminForeground)); \
1330 				break;                                                                       \
1331 			case KVI_COLOR_EXT_USER_OWNER:                                                   \
1332 				pa.setPen(KVI_OPTION_COLOR(KviOption_colorUserListViewChanOwnerForeground)); \
1333 				break;                                                                       \
1334 			case KVI_COLOR_EXT_USER_VOICE:                                                   \
1335 				pa.setPen(KVI_OPTION_COLOR(KviOption_colorUserListViewVoiceForeground));     \
1336 				break;                                                                       \
1337 			case KVI_COLOR_EXT_USER_USEROP:                                                  \
1338 				pa.setPen(KVI_OPTION_COLOR(KviOption_colorUserListViewUserOpForeground));    \
1339 				break;                                                                       \
1340 			case KVI_COLOR_EXT_USER_NORMAL:                                                  \
1341 				pa.setPen(KVI_OPTION_COLOR(KviOption_colorUserListViewNormalForeground));    \
1342 				break;                                                                       \
1343 			case KVI_DEF_BACK:                                                               \
1344 				pa.setPen(KVI_OPTION_COLOR(KviOption_colorIrcViewBackground));               \
1345 				break;                                                                       \
1346 			case KVI_COLOR_CUSTOM:                                                           \
1347 				pa.setPen(_custom);                                                          \
1348 				break;                                                                       \
1349 			case KVI_COLOR_OWN:                                                              \
1350 				pa.setPen(KVI_OPTION_COLOR(KviOption_colorUserListViewOwnForeground));       \
1351 				break;                                                                       \
1352 		}                                                                                    \
1353 	}
1354 
1355 #define DRAW_SELECTED_TEXT(_text_str, _text_idx, _text_len, _text_width)                                                                                                               \
1356 	SET_PEN(KVI_OPTION_MSGTYPE(KVI_OUT_SELECT).fore(), block->pChunk ? block->pChunk->customFore : QColor());                                                                          \
1357 	{                                                                                                                                                                                  \
1358 		int theWdth = _text_width;                                                                                                                                                     \
1359 		if(theWdth < 0)                                                                                                                                                                \
1360 			theWdth = width() - (curLeftCoord + KVI_IRCVIEW_HORIZONTAL_BORDER + scrollbarWidth);                                                                                       \
1361 		pa.fillRect(curLeftCoord, curBottomCoord - m_iFontLineSpacing + m_iFontDescent, theWdth, m_iFontLineSpacing, getMircColor(KVI_OPTION_MSGTYPE(KVI_OUT_SELECT).back())); \
1362 	}                                                                                                                                                                                  \
1363 	pPenFont.setStyle(curItalic ^ (normalFontStyle != QFont::StyleNormal) ? QFont::StyleItalic : QFont::StyleNormal);                                                 \
1364 	if (m_bUseRealBold)                                                                                                                                             \
1365 		pPenFont.setBold(curBold);                                                                                                                                  \
1366 	pa.setFont(pPenFont);                                                                                                                                           \
1367 	pa.drawText(curLeftCoord, curBottomCoord, _text_str.mid(_text_idx, _text_len));                                                                                 \
1368 	if(curBold && !m_bUseRealBold)                                                                                                                                  \
1369 		pa.drawText(curLeftCoord + 1, curBottomCoord, _text_str.mid(_text_idx, _text_len));                                                                         \
1370 	curLeftCoord += _text_width;
1371 
1372 #define DRAW_NORMAL_TEXT(_text_str, _text_idx, _text_len, _text_width)                                                                                              \
1373 	SET_PEN(curFore, block->pChunk ? block->pChunk->customFore : QColor());                                                                                         \
1374 	if(curBack != KviControlCodes::Transparent)                                                                                                                     \
1375 	{                                                                                                                                                               \
1376 		int theWdth = _text_width;                                                                                                                                  \
1377 		if(theWdth < 0)                                                                                                                                             \
1378 			theWdth = width() - (curLeftCoord + KVI_IRCVIEW_HORIZONTAL_BORDER + scrollbarWidth);                                                                    \
1379 		pa.fillRect(curLeftCoord, curBottomCoord - m_iFontLineSpacing + m_iFontDescent, theWdth, m_iFontLineSpacing, getMircColor((unsigned char)curBack));         \
1380 	}                                                                                                                                                               \
1381 	pPenFont.setStyle(curItalic ^ (normalFontStyle != QFont::StyleNormal) ? QFont::StyleItalic : QFont::StyleNormal);                                                 \
1382 	if (m_bUseRealBold)                                                                                                                                             \
1383 	    pPenFont.setBold(curBold);                                                                                                                                  \
1384 	pa.setFont(pPenFont);                                                                                                                                           \
1385 	pa.drawText(curLeftCoord, curBottomCoord, _text_str.mid(_text_idx, _text_len));                                                                                 \
1386 	if(curBold && !m_bUseRealBold)                                                                                                                                  \
1387 		pa.drawText(curLeftCoord + 1, curBottomCoord, _text_str.mid(_text_idx, _text_len));                                                                         \
1388 	if(curUnderline)                                                                                                                                                \
1389 	{                                                                                                                                                               \
1390 		int theWdth = _text_width;                                                                                                                                  \
1391 		if(theWdth < 0)                                                                                                                                             \
1392 			theWdth = width() - (curLeftCoord + KVI_IRCVIEW_HORIZONTAL_BORDER + scrollbarWidth);                                                                    \
1393 		pa.drawLine(curLeftCoord, curBottomCoord + 2, curLeftCoord + theWdth, curBottomCoord + 2);                                                                  \
1394 	}                                                                                                                                                               \
1395 	curLeftCoord += _text_width;
1396 
1397 	// EOF macro declarations
1398 
1399 			if(pCurTextLine == m_pCursorLine)
1400 			{
1401 				// this line is currently highlighted by the ircview "find" method.
1402 				curBack = KVI_OPTION_MSGTYPE(KVI_OUT_SEARCH).back();
1403 				curFore = KVI_OPTION_MSGTYPE(KVI_OUT_SEARCH).fore();
1404 			}
1405 
1406 			if(m_bMouseIsDown)
1407 			{
1408 				QFont pPenFont = pa.font();
1409 				// Check if the block or a part of it is selected
1410 				if(checkSelectionBlock(pCurTextLine, i))
1411 				{
1412 					switch(m_pWrappedBlockSelectionInfo->selection_type)
1413 					{
1414 						case KVI_IRCVIEW_BLOCK_SELECTION_TOTAL:
1415 							DRAW_SELECTED_TEXT(pCurTextLine->szText, block->block_start,
1416 							    block->block_len, block->block_width)
1417 							break;
1418 						case KVI_IRCVIEW_BLOCK_SELECTION_LEFT:
1419 							DRAW_SELECTED_TEXT(pCurTextLine->szText, block->block_start,
1420 							    m_pWrappedBlockSelectionInfo->part_1_length,
1421 							    m_pWrappedBlockSelectionInfo->part_1_width)
1422 							DRAW_NORMAL_TEXT(pCurTextLine->szText, block->block_start + m_pWrappedBlockSelectionInfo->part_1_length,
1423 							    m_pWrappedBlockSelectionInfo->part_2_length,
1424 							    m_pWrappedBlockSelectionInfo->part_2_width)
1425 							break;
1426 						case KVI_IRCVIEW_BLOCK_SELECTION_RIGHT:
1427 							DRAW_NORMAL_TEXT(pCurTextLine->szText, block->block_start,
1428 							    m_pWrappedBlockSelectionInfo->part_1_length,
1429 							    m_pWrappedBlockSelectionInfo->part_1_width)
1430 							DRAW_SELECTED_TEXT(pCurTextLine->szText, block->block_start + m_pWrappedBlockSelectionInfo->part_1_length,
1431 							    m_pWrappedBlockSelectionInfo->part_2_length,
1432 							    m_pWrappedBlockSelectionInfo->part_2_width)
1433 							break;
1434 						case KVI_IRCVIEW_BLOCK_SELECTION_CENTRAL:
1435 							DRAW_NORMAL_TEXT(pCurTextLine->szText, block->block_start,
1436 							    m_pWrappedBlockSelectionInfo->part_1_length,
1437 							    m_pWrappedBlockSelectionInfo->part_1_width)
1438 							DRAW_SELECTED_TEXT(pCurTextLine->szText, block->block_start + m_pWrappedBlockSelectionInfo->part_1_length,
1439 							    m_pWrappedBlockSelectionInfo->part_2_length,
1440 							    m_pWrappedBlockSelectionInfo->part_2_width)
1441 							DRAW_NORMAL_TEXT(pCurTextLine->szText, block->block_start + m_pWrappedBlockSelectionInfo->part_1_length + m_pWrappedBlockSelectionInfo->part_2_length,
1442 							    m_pWrappedBlockSelectionInfo->part_3_length,
1443 							    m_pWrappedBlockSelectionInfo->part_3_width)
1444 							break;
1445 						case KVI_IRCVIEW_BLOCK_SELECTION_ICON:
1446 						{
1447 							int theWdth = block->block_width;
1448 							if(theWdth < 0)
1449 								theWdth = width() - (curLeftCoord + KVI_IRCVIEW_HORIZONTAL_BORDER + scrollbarWidth);
1450 							pa.fillRect(curLeftCoord, curBottomCoord - m_iFontLineSpacing + m_iFontDescent, theWdth, m_iFontLineSpacing,
1451 								getMircColor(KVI_OPTION_MSGTYPE(KVI_OUT_SELECT).back()));
1452 							goto no_selection_paint;
1453 						}
1454 						break;
1455 					}
1456 				}
1457 				else
1458 				{
1459 					if(block->pChunk && block->pChunk->type == KviControlCodes::Icon)
1460 						goto no_selection_paint;
1461 					int wdth = block->block_width;
1462 					if(wdth == 0)
1463 					{
1464 						// Last block before a word wrap, or a zero characters attribute block ?
1465 						if(i < (pCurTextLine->iBlockCount - 1))
1466 						{
1467 							// There is another block...
1468 							// Check if it is a wrap...
1469 							if(pCurTextLine->pBlocks[i + 1].pChunk == nullptr)
1470 								wdth = widgetWidth - (curLeftCoord + KVI_IRCVIEW_HORIZONTAL_BORDER);
1471 						}
1472 						// else simply a zero characters block
1473 					}
1474 					DRAW_NORMAL_TEXT(pCurTextLine->szText, block->block_start, block->block_len, wdth)
1475 				}
1476 			}
1477 			else
1478 			{
1479 			// No selection ...go fast!
1480 			no_selection_paint:
1481 				if(block->pChunk && block->pChunk->type == KviControlCodes::Icon)
1482 				{
1483 					int wdth = block->block_width;
1484 					if(wdth < 0)
1485 						wdth = widgetWidth - (curLeftCoord + KVI_IRCVIEW_HORIZONTAL_BORDER);
1486 					int imageYPos = curBottomCoord - m_iRelativePixmapY;
1487 					// Set the mask if needed
1488 					if(curBack != KviControlCodes::Transparent && curBack <= KVI_EXTCOLOR_MAX)
1489 					{
1490 						pa.fillRect(curLeftCoord, curBottomCoord - m_iFontLineSpacing + m_iFontDescent, wdth, m_iFontLineSpacing, getMircColor((unsigned char)curBack));
1491 					}
1492 					QString tmpQ;
1493 					tmpQ.setUtf16(block->pChunk->szSmileId, kvi_wstrlen(block->pChunk->szSmileId));
1494 					QPixmap * daIcon = nullptr;
1495 					KviTextIcon * pIcon = g_pTextIconManager->lookupTextIcon(tmpQ);
1496 					if(pIcon)
1497 					{
1498 						daIcon = pIcon->animatedPixmap() ? pIcon->animatedPixmap()->pixmap() : pIcon->pixmap();
1499 					}
1500 					if(!daIcon)
1501 					{
1502 						// this should never happen since we do a check
1503 						// when building the text icon block, but.. better safe than sorry:
1504 						// so... we lost some icons ? wrong associations ?
1505 						// recover it by displaying the "question mark" icon
1506 						daIcon = g_pIconManager->getSmallIcon(KviIconManager::Help); // must be there, eventually null pixmap :D
1507 					}
1508 					int moredown = 1; // used to center image vertically (pixels which the image is moved more down)
1509 					moredown += ((m_iFontLineSpacing - daIcon->height()) / 2);
1510 					pa.drawPixmap(curLeftCoord + m_iIconSideSpacing, imageYPos + moredown, *(daIcon));
1511 
1512 					//qDebug("SHifting by %d",block->block_width);
1513 					curLeftCoord += block->block_width;
1514 				}
1515 				else
1516 				{
1517 					int wdth = block->block_width;
1518 					if(wdth < 0)
1519 						wdth = widgetWidth - (curLeftCoord + KVI_IRCVIEW_HORIZONTAL_BORDER);
1520 
1521 					// FIXME: We could avoid this XSetForeground if the curFore was not changed....
1522 
1523 					SET_PEN(curFore, block->pChunk ? block->pChunk->customFore : QColor());
1524 
1525 					if(curBack != KviControlCodes::Transparent && curBack <= KVI_EXTCOLOR_MAX)
1526 					{
1527 						pa.fillRect(curLeftCoord, curBottomCoord - m_iFontLineSpacing + m_iFontDescent, wdth, m_iFontLineSpacing, getMircColor((unsigned char)curBack));
1528 					}
1529 
1530 					bool bBold = curBold || curLink;
1531 
1532 					newFont = pa.font();
1533 					newFont.setStyle(curItalic ^ (normalFontStyle != QFont::StyleNormal) ? QFont::StyleItalic : QFont::StyleNormal);
1534 					if (m_bUseRealBold)
1535 						newFont.setBold(bBold);
1536 					pa.setFont(newFont);
1537 
1538 					if(curLink)
1539 					{
1540 						SET_PEN(KVI_OPTION_MSGTYPE(KVI_OUT_LINK).fore(), block->pChunk ? block->pChunk->customFore : QColor());
1541 						pa.drawLine(curLeftCoord, curBottomCoord + 2, curLeftCoord + wdth, curBottomCoord + 2);
1542 					}
1543 
1544 					pa.drawText(curLeftCoord, curBottomCoord, pCurTextLine->szText.mid(block->block_start, block->block_len));
1545 
1546 					if (bBold && !m_bUseRealBold)
1547 					{
1548 						// Draw doubled font (simulate bold)
1549 						pa.drawText(curLeftCoord + 1, curBottomCoord, pCurTextLine->szText.mid(block->block_start, block->block_len));
1550 					}
1551 					if(curUnderline)
1552 					{
1553 						// Draw a line under the text block....
1554 						pa.drawLine(curLeftCoord, curBottomCoord + 2, curLeftCoord + wdth, curBottomCoord + 2);
1555 					}
1556 					curLeftCoord += block->block_width;
1557 				}
1558 			}
1559 		}
1560 
1561 		curBottomCoord -= (lineWrapsHeight + m_iFontLineSpacing);
1562 
1563 		// paint the "last read line marker"
1564 		if(pCurTextLine->uIndex == m_uLineMarkLineIndex)
1565 		{
1566 			if((curBottomCoord >= KVI_IRCVIEW_VERTICAL_BORDER) && !bLineMarkPainted)
1567 			{
1568 				// visible!
1569 				bLineMarkPainted = true;
1570 				//pa.setRasterOp(NotROP);
1571 
1572 				// Pen setup for marker line
1573 				QPen pen(KVI_OPTION_COLOR(KviOption_colorIrcViewMarkLine), KVI_OPTION_UINT(KviOption_uintIrcViewMarkerSize));
1574 
1575 				switch(KVI_OPTION_UINT(KviOption_uintIrcViewMarkerStyle))
1576 				{
1577 					case 1:
1578 						pen.setStyle(Qt::DashLine);
1579 						break;
1580 					case 2:
1581 						pen.setStyle(Qt::SolidLine);
1582 						break;
1583 					case 3:
1584 						pen.setStyle(Qt::DashDotLine);
1585 						break;
1586 					case 4:
1587 						pen.setStyle(Qt::DashDotDotLine);
1588 						break;
1589 					default:
1590 						pen.setStyle(Qt::DotLine);
1591 				}
1592 
1593 				pa.setPen(pen);
1594 				pa.drawLine(0, curBottomCoord, widgetWidth, curBottomCoord);
1595 				//pa.setRasterOp(CopyROP);
1596 			}       // else was partially visible only
1597 		}
1598 
1599 		pCurTextLine = pCurTextLine->pPrev;
1600 		iLinesPerPage++;
1601 	}
1602 
1603 	/* REMINDER
1604 	 * Try to get the current number of KviIrcViewLines from the paintEvent and set the m_pScrollBar's
1605 	 * pageStep accordingly; the calculated value is valid only:
1606 	 * if there are enough lines to fill up at least a page (pCurTextLine)
1607 	 * if the value is safe (iLinesPerPage > 0)
1608 	 * if it's different from the actual one (iLinesPerPage != m_pScrollBar->pageStep())
1609 	 * only on full repaints: rectHeight == rect().height()
1610 	 */
1611 	if(pCurTextLine && iLinesPerPage > 0 && iLinesPerPage != m_pScrollBar->pageStep() && rectHeight == rect().height())
1612 		m_pScrollBar->setPageStep(iLinesPerPage);
1613 
1614 	if(!bLineMarkPainted && pCurTextLine && (rectTop <= (KVI_IRCVIEW_VERTICAL_BORDER + 5)))
1615 	{
1616 		// the line mark hasn't been painted yet
1617 		// need to find out if the mark is above the display
1618 		// the mark might be somewhere before the current text line
1619 		// find the first line that can't be painted in the view at all
1620 		while((curBottomCoord >= KVI_IRCVIEW_VERTICAL_BORDER) && pCurTextLine)
1621 		{
1622 			// the line wraps for the visible lines MUST have been already calculated
1623 			// for this view width
1624 			lineWrapsHeight = (pCurTextLine->uLineWraps) * m_iFontLineSpacing;
1625 			curBottomCoord -= lineWrapsHeight + m_iFontLineSpacing + m_iFontDescent;
1626 			pCurTextLine = pCurTextLine->pPrev;
1627 		}
1628 
1629 		if(pCurTextLine)
1630 		{
1631 			// this is the first NOT visible
1632 			// so pCurTextLine->pNext is the last visible one
1633 			if(pCurTextLine->pNext)
1634 			{
1635 				if(pCurTextLine->pNext->uIndex >= m_uLineMarkLineIndex)
1636 					bLineMarkPainted = true; // yes, its somewhere before or on this line
1637 			}
1638 			else
1639 			{
1640 				// no next line ? hm... compare to the not visible one.. but this should never happen
1641 				if(pCurTextLine->uIndex >= m_uLineMarkLineIndex)
1642 					bLineMarkPainted = true; // yes, its somewhere before or on this line
1643 			}
1644 			if(bLineMarkPainted)
1645 			{
1646 				// need to mark it!
1647 				//pa.setRasterOp(NotROP);
1648 				pa.setPen(QPen(KVI_OPTION_COLOR(KviOption_colorIrcViewMarkLine), 1, Qt::DotLine));
1649 
1650 				// Marker icon
1651 				// 16(width) + 5(border) = 21
1652 				int x = widgetWidth - 21;
1653 				int y = KVI_IRCVIEW_VERTICAL_BORDER;
1654 				/*
1655 				* Old icon... what a lame code :D
1656 				* pa.drawLine(x,y,x,y);
1657 				* y++; pa.drawLine(x-1,y,x+1,y);
1658 				* y++; pa.drawLine(x-2,y,x+2,y);
1659 				* y++; pa.drawLine(x-3,y,x+3,y);
1660 				* y++; pa.drawLine(x-4,y,x+4,y);
1661 				*/
1662 				QPixmap * pIcon = g_pIconManager->getSmallIcon(KviIconManager::UnreadText);
1663 				m_lineMarkArea = QRect(x, y, 16, 16);
1664 				pa.drawPixmap(x, y, 16, 16, *pIcon);
1665 				//pa.setRasterOp(CopyROP);
1666 			}
1667 			else
1668 			{
1669 				m_lineMarkArea = QRect();
1670 			}
1671 		}
1672 	}
1673 
1674 	// Need to draw the sunken rect around the view now...
1675 	pa.setPen(palette().dark().color());
1676 	pa.drawLine(0, 0, widgetWidth, 0);
1677 	pa.drawLine(0, 0, 0, widgetHeight);
1678 	pa.setPen(palette().light().color());
1679 	widgetWidth--;
1680 	pa.drawLine(1, widgetHeight - 1, widgetWidth, widgetHeight - 1);
1681 	pa.drawLine(widgetWidth, 1, widgetWidth, widgetHeight);
1682 }
1683 
1684 //
1685 // The IrcView : calculate line wraps
1686 //
1687 
1688 #define IRCVIEW_WCHARWIDTH(c) (((c).unicode() < 0xff) ? m_iFontCharacterWidth[(c).unicode()] : m_pFm->width(c))
1689 
calculateLineWraps(KviIrcViewLine * ptr,int maxWidth)1690 void KviIrcView::calculateLineWraps(KviIrcViewLine * ptr, int maxWidth)
1691 {
1692 	// Another monster
1693 	if(maxWidth <= m_iIconWidth)
1694 		return;
1695 
1696 	if(ptr->iBlockCount != 0)
1697 		KviMemory::free(ptr->pBlocks); // free any previous wrap blocks
1698 
1699 	ptr->pBlocks = (KviIrcViewWrappedBlock *)KviMemory::allocate(sizeof(KviIrcViewWrappedBlock)); // alloc one block
1700 	ptr->iMaxLineWidth = maxWidth;                                                                // calculus for this width
1701 	ptr->iBlockCount = 0;                                                                         // it will be ++
1702 	ptr->uLineWraps = 0;                                                                          // no line wraps yet
1703 
1704 	unsigned int curAttrBlock = 0; // Current attribute block
1705 	int curLineWidth = 0;
1706 
1707 	// init the first block
1708 	ptr->pBlocks->block_start = 0;
1709 	ptr->pBlocks->block_len = 0;
1710 	ptr->pBlocks->block_width = 0;
1711 	ptr->pBlocks->pChunk = &(ptr->pChunks[0]); // always an attribute block
1712 
1713 	int maxBlockLen = ptr->pChunks->iTextLen; // ptr->pChunks[0].iTextLen
1714 
1715 	const QChar * unicode = ptr->szText.unicode();
1716 
1717 	for(;;)
1718 	{
1719 		// Calculate the block_width
1720 		const QChar * p = unicode + ptr->pBlocks[ptr->iBlockCount].block_start;
1721 
1722 		int curBlockLen = 0;
1723 		int curBlockWidth = 0;
1724 
1725 		if(ptr->pChunks[curAttrBlock].type == KviControlCodes::Icon)
1726 		{
1727 			curBlockWidth = m_iIconWidth;
1728 		}
1729 		else
1730 		{
1731 			while(curBlockLen < maxBlockLen)
1732 			{
1733 				// FIXME: this is ugly :/
1734 				curBlockWidth += IRCVIEW_WCHARWIDTH(*p);
1735 				curBlockLen++;
1736 				p++;
1737 			}
1738 		}
1739 
1740 		// Check the length
1741 		curLineWidth += curBlockWidth;
1742 
1743 		if(curLineWidth < maxWidth)
1744 		{
1745 			// Ok....proceed to next block
1746 			ptr->pBlocks[ptr->iBlockCount].block_len = curBlockLen;
1747 			ptr->pBlocks[ptr->iBlockCount].block_width = curBlockWidth;
1748 			curAttrBlock++;
1749 			ptr->iBlockCount++;
1750 
1751 			// if we have no more blocks, return (with is ok)
1752 			if(curAttrBlock >= ptr->uChunkCount)
1753 				return;
1754 
1755 			// Process the next block of data in the next loop
1756 			ptr->pBlocks = (KviIrcViewWrappedBlock *)KviMemory::reallocate(ptr->pBlocks, (ptr->iBlockCount + 1) * sizeof(KviIrcViewWrappedBlock));
1757 			ptr->pBlocks[ptr->iBlockCount].block_start = ptr->pChunks[curAttrBlock].iTextStart;
1758 			ptr->pBlocks[ptr->iBlockCount].block_len = 0;
1759 			ptr->pBlocks[ptr->iBlockCount].block_width = 0;
1760 			ptr->pBlocks[ptr->iBlockCount].pChunk = &(ptr->pChunks[curAttrBlock]);
1761 			maxBlockLen = ptr->pBlocks[ptr->iBlockCount].pChunk->iTextLen;
1762 
1763 			continue;
1764 		}
1765 
1766 		// Need word wrap
1767 		// First go back to an admissible width
1768 		while((curLineWidth >= maxWidth) && (curBlockLen > 0))
1769 		{
1770 			p--;
1771 			curBlockLen--;
1772 			curLineWidth -= IRCVIEW_WCHARWIDTH(*p);
1773 		}
1774 
1775 		// Now look for a space (or a tabulation)
1776 		while((p->unicode() != ' ') && (p->unicode() != '\t') && (curBlockLen > 0))
1777 		{
1778 			p--;
1779 			curBlockLen--;
1780 			curLineWidth -= IRCVIEW_WCHARWIDTH(*p);
1781 		}
1782 
1783 		if(curBlockLen == 0)
1784 		{
1785 			// ran up to the beginning of the block....
1786 			if(ptr->pChunks[curAttrBlock].type == KviControlCodes::Icon)
1787 			{
1788 				// FIXME what if the icon curBlockWidth is > maxWidth ? => endless loop
1789 				// This is an icon block: needs to be wrapped differently:
1790 				// The wrap block goes BEFORE the icon itself
1791 				ptr->pBlocks[ptr->iBlockCount].pChunk = nullptr;
1792 				ptr->pBlocks[ptr->iBlockCount].block_width = 0;
1793 				ptr->iBlockCount++;
1794 				ptr->pBlocks = (KviIrcViewWrappedBlock *)KviMemory::reallocate(ptr->pBlocks, (ptr->iBlockCount + 1) * sizeof(KviIrcViewWrappedBlock));
1795 				ptr->pBlocks[ptr->iBlockCount].block_start = p - unicode;
1796 				ptr->pBlocks[ptr->iBlockCount].block_len = 0;
1797 				ptr->pBlocks[ptr->iBlockCount].block_width = 0;
1798 				ptr->pBlocks[ptr->iBlockCount].pChunk = &(ptr->pChunks[curAttrBlock]);
1799 				goto wrap_line;
1800 			}
1801 			// Don't like it....forced wrap here...
1802 			// Go ahead up to the biggest possible string
1803 			if(maxBlockLen > 0)
1804 			{
1805 				// avoid a loop when IRCVIEW_WCHARWIDTH(*p) > maxWidth
1806 				uint uLoopedChars = 0;
1807 				do
1808 				{
1809 					curBlockLen++;
1810 					p++;
1811 					curLineWidth += IRCVIEW_WCHARWIDTH(*p);
1812 					uLoopedChars++;
1813 				} while((curLineWidth < maxWidth) && (curBlockLen < maxBlockLen));
1814 				// Now overrun, go back 1 char (if we ran over at least 2 chars)
1815 				if(uLoopedChars > 1)
1816 				{
1817 					p--;
1818 					curBlockLen--;
1819 				}
1820 			}
1821 			//K...wrap
1822 		}
1823 		else
1824 		{
1825 			p++;           // found a space...
1826 			curBlockLen++; // include it in the first block
1827 		}
1828 
1829 		ptr->pBlocks[ptr->iBlockCount].block_len = curBlockLen;
1830 		ptr->pBlocks[ptr->iBlockCount].block_width = -1; // word wrap --> negative block_width
1831 		maxBlockLen -= curBlockLen;
1832 		ptr->iBlockCount++;
1833 		ptr->pBlocks = (KviIrcViewWrappedBlock *)KviMemory::reallocate(ptr->pBlocks, (ptr->iBlockCount + 1) * sizeof(KviIrcViewWrappedBlock));
1834 		ptr->pBlocks[ptr->iBlockCount].block_start = p - unicode;
1835 		ptr->pBlocks[ptr->iBlockCount].block_len = 0;
1836 		ptr->pBlocks[ptr->iBlockCount].block_width = 0;
1837 		ptr->pBlocks[ptr->iBlockCount].pChunk = nullptr;
1838 
1839 	wrap_line:
1840 		curLineWidth = 0;
1841 		ptr->uLineWraps++;
1842 
1843 		if(ptr->uLineWraps == 1)
1844 		{
1845 			if(KVI_OPTION_BOOL(KviOption_boolIrcViewWrapMargin))
1846 				maxWidth -= m_iWrapMargin;
1847 			if(maxWidth <= m_iIconWidth)
1848 				return;
1849 		}
1850 		else if(ptr->uLineWraps > 128)
1851 		{	// oops.. this is looping endlessly: it may happen in certain insane window width / font size configurations...
1852 			return;
1853 		}
1854 	}
1855 
1856 	ptr->iBlockCount++;
1857 }
1858 
1859 //
1860 // checkSelectionBlock
1861 //
1862 
checkSelectionBlock(KviIrcViewLine * line,int bufIndex)1863 bool KviIrcView::checkSelectionBlock(KviIrcViewLine * line, int bufIndex)
1864 {
1865 	// Checks if the specified chunk in the specified ircviewline is part of the current selection
1866 	const QChar * unicode = line->szText.unicode();
1867 	const QChar * p = unicode + line->pBlocks[bufIndex].block_start;
1868 
1869 	if(!m_pSelectionInitLine || !m_pSelectionEndLine)
1870 		return false;
1871 
1872 	// check if selection is bottom to top or vice-versa
1873 	KviIrcViewLine *init, *end;
1874 	if(m_pSelectionInitLine->uIndex <= m_pSelectionEndLine->uIndex)
1875 	{
1876 		init = m_pSelectionInitLine;
1877 		end = m_pSelectionEndLine;
1878 	}
1879 	else
1880 	{
1881 		end = m_pSelectionInitLine;
1882 		init = m_pSelectionEndLine;
1883 	}
1884 
1885 	// line is between the first selected line and the last selected one
1886 	if(line->uIndex > init->uIndex && line->uIndex < end->uIndex)
1887 	{
1888 		if(line->pBlocks[bufIndex].pChunk && line->pBlocks[bufIndex].pChunk->type == KviControlCodes::Icon)
1889 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_ICON;
1890 		else
1891 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_TOTAL;
1892 		return true;
1893 	}
1894 
1895 	if(line->uIndex == init->uIndex && line->uIndex == end->uIndex)
1896 	{
1897 		// Selection begins and ends in this line
1898 		int initChar, endChar;
1899 
1900 		// check if the selection is rtol or ltor
1901 		if(m_iSelectionInitCharIndex <= m_iSelectionEndCharIndex)
1902 		{
1903 			initChar = m_iSelectionInitCharIndex;
1904 			endChar = m_iSelectionEndCharIndex;
1905 		}
1906 		else
1907 		{
1908 			endChar = m_iSelectionInitCharIndex;
1909 			initChar = m_iSelectionEndCharIndex;
1910 		}
1911 
1912 		// quick check if we're outside the selection bounds
1913 		if(line->pBlocks[bufIndex].block_start > endChar)
1914 			return false;
1915 		if(line->pBlocks[bufIndex].block_start + line->pBlocks[bufIndex].block_len < initChar)
1916 			return false;
1917 
1918 		// checks if this is an icon block
1919 		if(line->pBlocks[bufIndex].pChunk && line->pBlocks[bufIndex].pChunk->type == KviControlCodes::Icon)
1920 		{
1921 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_ICON;
1922 			return true;
1923 		}
1924 		if(line->pBlocks[bufIndex].block_start >= initChar && (line->pBlocks[bufIndex].block_start + line->pBlocks[bufIndex].block_len) <= endChar)
1925 		{
1926 			// Whole chunk selected
1927 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_TOTAL;
1928 			return true;
1929 		}
1930 		if(line->pBlocks[bufIndex].block_start <= initChar && (line->pBlocks[bufIndex].block_start + line->pBlocks[bufIndex].block_len) >= endChar)
1931 		{
1932 			// Selection ends and begins in THIS BLOCK!
1933 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_CENTRAL;
1934 			m_pWrappedBlockSelectionInfo->part_1_length = initChar - line->pBlocks[bufIndex].block_start;
1935 			m_pWrappedBlockSelectionInfo->part_1_width = 0;
1936 			m_pWrappedBlockSelectionInfo->part_2_length = endChar - initChar;
1937 			m_pWrappedBlockSelectionInfo->part_3_length = line->pBlocks[bufIndex].block_start + line->pBlocks[bufIndex].block_len - endChar;
1938 			m_pWrappedBlockSelectionInfo->part_2_width = 0;
1939 			for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++)
1940 			{
1941 				int www = IRCVIEW_WCHARWIDTH(*p);
1942 				m_pWrappedBlockSelectionInfo->part_1_width += www;
1943 				p++;
1944 			}
1945 			for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_2_length; i++)
1946 			{
1947 				int www = IRCVIEW_WCHARWIDTH(*p);
1948 				m_pWrappedBlockSelectionInfo->part_2_width += www;
1949 				p++;
1950 			}
1951 			m_pWrappedBlockSelectionInfo->part_3_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width - m_pWrappedBlockSelectionInfo->part_2_width;
1952 			return true;
1953 		}
1954 
1955 		if(line->pBlocks[bufIndex].block_start > initChar && (line->pBlocks[bufIndex].block_start + line->pBlocks[bufIndex].block_len) > endChar)
1956 		{
1957 			// Selection ends in THIS BLOCK!
1958 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_LEFT;
1959 			m_pWrappedBlockSelectionInfo->part_1_length = endChar - line->pBlocks[bufIndex].block_start;
1960 			m_pWrappedBlockSelectionInfo->part_1_width = 0;
1961 			for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++)
1962 			{
1963 				int www = IRCVIEW_WCHARWIDTH(*p);
1964 				m_pWrappedBlockSelectionInfo->part_1_width += www;
1965 				p++;
1966 			}
1967 			m_pWrappedBlockSelectionInfo->part_2_length = line->pBlocks[bufIndex].block_len - m_pWrappedBlockSelectionInfo->part_1_length;
1968 			m_pWrappedBlockSelectionInfo->part_2_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width;
1969 			return true;
1970 		}
1971 
1972 		if(line->pBlocks[bufIndex].block_start < initChar && (line->pBlocks[bufIndex].block_start + line->pBlocks[bufIndex].block_len) < endChar)
1973 		{
1974 			// Selection begins in THIS BLOCK!
1975 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_RIGHT;
1976 			m_pWrappedBlockSelectionInfo->part_1_length = initChar - line->pBlocks[bufIndex].block_start;
1977 			m_pWrappedBlockSelectionInfo->part_1_width = 0;
1978 			for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++)
1979 			{
1980 				int www = IRCVIEW_WCHARWIDTH(*p);
1981 				m_pWrappedBlockSelectionInfo->part_1_width += www;
1982 				p++;
1983 			}
1984 			m_pWrappedBlockSelectionInfo->part_2_length = line->pBlocks[bufIndex].block_len - m_pWrappedBlockSelectionInfo->part_1_length;
1985 			m_pWrappedBlockSelectionInfo->part_2_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width;
1986 			return true;
1987 		}
1988 		return false;
1989 	}
1990 
1991 	if(line->uIndex == init->uIndex)
1992 	{
1993 		// Selection begins in this line
1994 		int initChar;
1995 
1996 		// check if the selection is uptobottom or bottomtoup
1997 		if(m_pSelectionInitLine->uIndex <= m_pSelectionEndLine->uIndex)
1998 		{
1999 			initChar = m_iSelectionInitCharIndex;
2000 		}
2001 		else
2002 		{
2003 			initChar = m_iSelectionEndCharIndex;
2004 		}
2005 		// icon chunk
2006 		if(line->pBlocks[bufIndex].pChunk && line->pBlocks[bufIndex].pChunk->type == KviControlCodes::Icon)
2007 		{
2008 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_ICON;
2009 			return true;
2010 		}
2011 		if(line->pBlocks[bufIndex].block_start >= initChar)
2012 		{	// Whole chunk selected
2013 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_TOTAL;
2014 			return true;
2015 		}
2016 
2017 		if(line->pBlocks[bufIndex].block_start < initChar && (line->pBlocks[bufIndex].block_start + line->pBlocks[bufIndex].block_len) > initChar)
2018 		{	// Selection begins in THIS BLOCK!
2019 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_RIGHT;
2020 			m_pWrappedBlockSelectionInfo->part_1_length = initChar - line->pBlocks[bufIndex].block_start;
2021 			m_pWrappedBlockSelectionInfo->part_1_width = 0;
2022 			for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++)
2023 			{
2024 				int www = IRCVIEW_WCHARWIDTH(*p);
2025 				m_pWrappedBlockSelectionInfo->part_1_width += www;
2026 				p++;
2027 			}
2028 			m_pWrappedBlockSelectionInfo->part_2_length = line->pBlocks[bufIndex].block_len - m_pWrappedBlockSelectionInfo->part_1_length;
2029 			m_pWrappedBlockSelectionInfo->part_2_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width;
2030 			return true;
2031 		}
2032 		return false;
2033 	}
2034 
2035 	if(line->uIndex == end->uIndex)
2036 	{
2037 		// Selection ends in this line
2038 		int endChar;
2039 
2040 		// check if the selection is uptobottom or bottomtoup
2041 		if(m_pSelectionInitLine->uIndex <= m_pSelectionEndLine->uIndex)
2042 		{
2043 			endChar = m_iSelectionEndCharIndex;
2044 		}
2045 		else
2046 		{
2047 			endChar = m_iSelectionInitCharIndex;
2048 		}
2049 
2050 		// icon chunk
2051 		if(line->pBlocks[bufIndex].pChunk && line->pBlocks[bufIndex].pChunk->type == KviControlCodes::Icon)
2052 		{
2053 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_ICON;
2054 			return true;
2055 		}
2056 		if((line->pBlocks[bufIndex].block_start + line->pBlocks[bufIndex].block_len) <= endChar)
2057 		{	// Whole chunk selected
2058 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_TOTAL;
2059 			return true;
2060 		}
2061 
2062 		if(line->pBlocks[bufIndex].block_start < endChar && (line->pBlocks[bufIndex].block_start + line->pBlocks[bufIndex].block_len) > endChar)
2063 		{	// Selection ends in THIS BLOCK!
2064 			m_pWrappedBlockSelectionInfo->selection_type = KVI_IRCVIEW_BLOCK_SELECTION_LEFT;
2065 			m_pWrappedBlockSelectionInfo->part_1_length = endChar - line->pBlocks[bufIndex].block_start;
2066 			m_pWrappedBlockSelectionInfo->part_1_width = 0;
2067 			for(int i = 0; i < m_pWrappedBlockSelectionInfo->part_1_length; i++)
2068 			{
2069 				int www = IRCVIEW_WCHARWIDTH(*p);
2070 				m_pWrappedBlockSelectionInfo->part_1_width += www;
2071 				p++;
2072 			}
2073 			m_pWrappedBlockSelectionInfo->part_2_length = line->pBlocks[bufIndex].block_len - m_pWrappedBlockSelectionInfo->part_1_length;
2074 			m_pWrappedBlockSelectionInfo->part_2_width = line->pBlocks[bufIndex].block_width - m_pWrappedBlockSelectionInfo->part_1_width;
2075 			return true;
2076 		}
2077 		return false;
2078 	}
2079 	return false;
2080 }
2081 
2082 //
2083 // recalcFontVariables
2084 //
2085 
recalcFontVariables(const QFont & font,const QFontInfo & fi)2086 void KviIrcView::recalcFontVariables(const QFont & font, const QFontInfo & fi)
2087 {
2088 	if(m_pFm)
2089 		delete m_pFm;
2090 
2091 	m_pFm = new QFontMetrics(font);
2092 
2093 	m_iFontLineSpacing = m_pFm->lineSpacing();
2094 
2095 	if((m_iFontLineSpacing < KVI_IRCVIEW_PIXMAP_SIZE) && KVI_OPTION_BOOL(KviOption_boolIrcViewShowImages))
2096 		m_iFontLineSpacing = KVI_IRCVIEW_PIXMAP_SIZE;
2097 
2098 	m_iFontDescent = m_pFm->descent();
2099 	m_iFontLineWidth = m_pFm->lineWidth();
2100 
2101 	// cache the first 256 characters
2102 	for(unsigned short i = 0; i < 256; i++)
2103 		m_iFontCharacterWidth[i] = m_pFm->width(QChar(i));
2104 
2105 	// Currently KviIrcView requires that the bold font variant has the same metrics as the non-bold one.
2106 	// To ensure this, we check if the width of the bold and non-bold variants of the first 256 characters match.
2107 	// If it does, use real bold; otherwise, use faux-bold by drawing the text again at (x,y+1).
2108 	{
2109 		m_bUseRealBold = true;
2110 		QFont fontBold = QFont(font);
2111 		fontBold.setBold(true);
2112 		QFontMetrics fmBold = QFontMetrics(fontBold);
2113 		for(unsigned short i = 32; i < 256; i++)
2114 			if (m_iFontCharacterWidth[i] && fmBold.width(QChar(i)) != m_iFontCharacterWidth[i])
2115 			{
2116 				//printf("Char %d: %d != %d\n", i, fmBold.width(QChar(i)), m_iFontCharacterWidth[i]);
2117 				m_bUseRealBold = false;
2118 				break;
2119 			}
2120 		//printf("m_bUseRealBold = %d\n", m_bUseRealBold);
2121 	}
2122 
2123 	// fix for #489 (horizontal tabulations)
2124 	m_iFontCharacterWidth[9] = m_pFm->width("\t");
2125 
2126 	if(m_iFontLineWidth < 1)
2127 		m_iFontLineWidth = 1;
2128 
2129 	if(KVI_OPTION_BOOL(KviOption_boolIrcViewTimestamp))
2130 	{
2131 		QString szTimestamp;
2132 		QDateTime datetime = KVI_OPTION_BOOL(KviOption_boolIrcViewTimestampUTC) ? QDateTime::currentDateTime().toUTC() : QDateTime::currentDateTime();
2133 		szTimestamp = datetime.toString(KVI_OPTION_STRING(KviOption_stringIrcViewTimestampFormat));
2134 		szTimestamp.append(' ');
2135 		m_iWrapMargin = m_pFm->width(szTimestamp);
2136 	}
2137 	else
2138 	{
2139 		m_iWrapMargin = m_pFm->width("wwww");
2140 	}
2141 
2142 	m_iMinimumPaintWidth = (((int)(m_pFm->width('w'))) << 1);
2143 	if(KVI_OPTION_BOOL(KviOption_boolIrcViewWrapMargin))
2144 		m_iMinimumPaintWidth += m_iWrapMargin;
2145 
2146 	m_iRelativePixmapY = (int)(m_iFontLineSpacing + KVI_IRCVIEW_PIXMAP_SIZE) >> 1;
2147 
2148 	m_iIconWidth = (int)m_pFm->width("w");
2149 
2150 	if(fi.fixedPitch() && (m_iIconWidth > 0))
2151 	{
2152 		while(m_iIconWidth < 18)
2153 			m_iIconWidth += m_iIconWidth;
2154 		m_iIconSideSpacing = (m_iIconWidth - 16) >> 1;
2155 	}
2156 	else
2157 	{
2158 		m_iIconWidth = 18;
2159 		m_iIconSideSpacing = 1;
2160 	}
2161 }
2162 
2163 //
2164 // resizeEvent
2165 //
2166 
resizeEvent(QResizeEvent *)2167 void KviIrcView::resizeEvent(QResizeEvent *)
2168 {
2169 	int iScr = m_pScrollBar->sizeHint().width();
2170 	int iLeft = width() - iScr;
2171 	m_pToolsButton->setGeometry(iLeft, 0, iScr, iScr);
2172 	m_pScrollBar->setGeometry(iLeft, iScr, iScr, height() - iScr);
2173 
2174 	if(m_pToolWidget && m_pToolWidget->isVisible())
2175 	{
2176 		int h = m_pToolWidget->sizeHint().height();
2177 		m_pToolWidget->setGeometry(0, height() - h, width() - iScr, h);
2178 	}
2179 }
2180 
sizeHint() const2181 QSize KviIrcView::sizeHint() const
2182 {
2183 	QSize ret(KVI_IRCVIEW_SIZEHINT_WIDTH, KVI_IRCVIEW_SIZEHINT_HEIGHT);
2184 	return ret;
2185 }
2186 
showToolsPopup()2187 void KviIrcView::showToolsPopup()
2188 {
2189 	if(!m_pToolsPopup)
2190 	{
2191 		m_pToolsPopup = new QMenu(this);
2192 
2193 		m_pToolsPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Search)), __tr2qs("Toggle Search"), this, SLOT(toggleToolWidget()));
2194 
2195 		m_pToolsPopup->addSeparator();
2196 
2197 		m_pToolsPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Plus)), __tr2qs("Zoom In"), this, SLOT(increaseFontSize())); // We let fly "in" as a capitalized preposition.
2198 		m_pToolsPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Minus)), __tr2qs("Zoom Out"), this, SLOT(decreaseFontSize()));
2199 
2200 		m_pToolsPopup->addSeparator();
2201 
2202 		m_pToolsPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Topic)), __tr2qs("Choose Temporary Font..."), this, SLOT(chooseFont()));
2203 		m_pToolsPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Canvas)), __tr2qs("Choose Temporary Background..."), this, SLOT(chooseBackground()));
2204 
2205 		m_pToolsPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Discard)), __tr2qs("Reset Font"), this, SLOT(resetDefaultFont()));
2206 		m_pToolsPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Discard)), __tr2qs("Reset Background"), this, SLOT(resetBackground()));
2207 
2208 		m_pToolsPopup->addSeparator();
2209 
2210 		m_pToolsPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::Remove)), __tr2qs("Clear Buffer"), this, SLOT(clearBuffer()));
2211 	}
2212 	QSize s = m_pToolsPopup->sizeHint();
2213 
2214 	m_pToolsPopup->popup(m_pToolsButton->mapToGlobal(QPoint(m_pToolsButton->width() - s.width(), m_pToolsButton->height())));
2215 }
2216 
increaseFontSize()2217 void KviIrcView::increaseFontSize()
2218 {
2219 	QFont f = font();
2220 	f.setPointSize(f.pointSize() + 1);
2221 	setFont(f);
2222 }
2223 
decreaseFontSize()2224 void KviIrcView::decreaseFontSize()
2225 {
2226 	QFont f = font();
2227 	int p = f.pointSize();
2228 	if(p > 2)
2229 		p--;
2230 	f.setPointSize(p);
2231 	setFont(f);
2232 }
2233 
resetDefaultFont()2234 void KviIrcView::resetDefaultFont()
2235 {
2236 	setFont(KVI_OPTION_FONT(KviOption_fontIrcView));
2237 }
2238 
chooseFont()2239 void KviIrcView::chooseFont()
2240 {
2241 	bool bOk;
2242 #ifdef COMPILE_ON_MAC
2243 	// The native font dialog makes Qt 4.6 go into a strange modal infinite loop (the font dialog is never properly closed).
2244 	// FIXME: Re-check it with future releases of Qt.
2245 	QFont f = QFontDialog::getFont(&bOk, font(), this, __tr("Choose Font"), QFontDialog::DontUseNativeDialog);
2246 #else  //!COMPILE_ON_MAC
2247 	QFont f = QFontDialog::getFont(&bOk, font(), this, __tr("Choose Font"));
2248 #endif //!COMPILE_ON_MAC
2249 	if(!bOk)
2250 		return;
2251 	setFont(f);
2252 }
2253 
chooseBackground()2254 void KviIrcView::chooseBackground()
2255 {
2256 	QString f;
2257 	if(
2258 	    !KviFileDialog::askForOpenFileName(
2259 	        f,
2260 	        __tr2qs("Choose the background image..."),
2261 	        QString(),
2262 	        QString(),
2263 	        false,
2264 	        true,
2265 	        this))
2266 		return;
2267 
2268 	if(f.isEmpty())
2269 		return;
2270 
2271 	QPixmap p(f);
2272 	if(p.isNull())
2273 	{
2274 		QMessageBox::information(this, __tr2qs("Invalid Image - KVIrc"), __tr2qs("Failed to load the selected image!"), __tr2qs("OK"));
2275 		return;
2276 	}
2277 
2278 	setPrivateBackgroundPixmap(p);
2279 }
2280 
resetBackground()2281 void KviIrcView::resetBackground()
2282 {
2283 	QPixmap dummy;
2284 	setPrivateBackgroundPixmap(dummy);
2285 }
2286 
toggleToolWidget()2287 void KviIrcView::toggleToolWidget()
2288 {
2289 	if(m_pToolWidget && m_pToolWidget->isVisible())
2290 	{
2291 		m_pToolWidget->setVisible(false);
2292 		m_pCursorLine = nullptr;
2293 
2294 		// When the tool widget is hidden, ensure the input is focussed (otherwise text is still entered into the 'string to find' widget...)
2295 		if(m_pKviWindow && m_pKviWindow->input())
2296 			m_pKviWindow->input()->setFocus();
2297 	}
2298 	else
2299 	{
2300 		if(!m_pToolWidget)
2301 			m_pToolWidget = new KviIrcViewToolWidget(this);
2302 
2303 		int h = m_pToolWidget->sizeHint().height();
2304 		int iScr = m_pScrollBar->sizeHint().width();
2305 		m_pToolWidget->setGeometry(0, height() - h, width() - iScr, h);
2306 		m_pToolWidget->setVisible(true);
2307 
2308 		// Ensure the 'string to find' widget is always in focus when the search functionality is requested
2309 		m_pToolWidget->focusStringToFind();
2310 
2311 		m_pToolWidget->m_pStringToFind->selectAll();
2312 	}
2313 
2314 	repaint();
2315 }
2316 
2317 //
2318 // The IrcView : find
2319 //
2320 
ensureLineVisible(KviIrcViewLine * pLineToShow)2321 void KviIrcView::ensureLineVisible(KviIrcViewLine * pLineToShow)
2322 {
2323 	if(!pLineToShow)
2324 		return;
2325 
2326 	if(pLineToShow == m_pCurLine)
2327 	{
2328 		// nothing to do, just repaint for safety sake
2329 		repaint();
2330 		return;
2331 	}
2332 
2333 	// need to scroll
2334 	int sc = m_pScrollBar->value();
2335 
2336 	if(pLineToShow->uIndex > m_pCurLine->uIndex)
2337 	{
2338 		// The cursor line is below the current line
2339 		// Go down counting scroll steps (and verify if the line is really there)
2340 		KviIrcViewLine * pLine = m_pCurLine;
2341 
2342 		while(pLine && (pLine != pLineToShow))
2343 		{
2344 			pLine = pLine->pNext;
2345 			sc++;
2346 		}
2347 
2348 		if(!pLine)
2349 			return; // oops.. line not found ?
2350 
2351 		if(sc != m_pScrollBar->value())
2352 		{
2353 			m_pCurLine = pLine;
2354 			m_iLastScrollBarValue = sc;
2355 			m_pScrollBar->setValue(sc);
2356 		}
2357 		else
2358 		{
2359 			repaint();
2360 		}
2361 		return;
2362 	}
2363 
2364 	// The cursor line is over the current line
2365 	// Here we're in trouble :D
2366 	int toolWidgetHeight = (m_pToolWidget && m_pToolWidget->isVisible()) ? m_pToolWidget->sizeHint().height() : 0;
2367 	int scrollbarWidth = m_pScrollBar->width();
2368 	int curBottomCoord = height() - toolWidgetHeight - KVI_IRCVIEW_VERTICAL_BORDER;
2369 	int maxLineWidth = width() - scrollbarWidth - KVI_IRCVIEW_DOUBLEBORDER_WIDTH;
2370 
2371 	if(KVI_OPTION_BOOL(KviOption_boolIrcViewShowImages))
2372 		maxLineWidth -= KVI_IRCVIEW_PIXMAP_AND_SEPARATOR;
2373 	// Make sure that we have enough space to paint something...
2374 	if(maxLineWidth < m_iMinimumPaintWidth)
2375 		return; // ugh
2376 	// And loop through lines until we not run over the upper bound of the view
2377 	KviIrcViewLine * pLine = m_pCurLine;
2378 	KviIrcViewLine * pCurLine = m_pCurLine;
2379 	while(pLine)
2380 	{
2381 		if(maxLineWidth != pLine->iMaxLineWidth)
2382 			calculateLineWraps(pLine, maxLineWidth);
2383 		curBottomCoord -= (pLine->uLineWraps + 1) * m_iFontLineSpacing;
2384 		while(pCurLine && (curBottomCoord < KVI_IRCVIEW_VERTICAL_BORDER))
2385 		{
2386 			if(pCurLine->iMaxLineWidth != maxLineWidth)
2387 				calculateLineWraps(pCurLine, maxLineWidth);
2388 			curBottomCoord += ((pCurLine->uLineWraps + 1) * m_iFontLineSpacing) + m_iFontDescent;
2389 			pCurLine = pCurLine->pPrev;
2390 			sc--;
2391 		}
2392 		if(pLine == pLineToShow)
2393 			break;
2394 		curBottomCoord -= m_iFontDescent;
2395 		pLine = pLine->pPrev;
2396 	}
2397 
2398 	if(!pCurLine)
2399 		return; // ooops.. line not found :D
2400 
2401 	if(sc != m_pScrollBar->value())
2402 	{
2403 		m_pCurLine = pCurLine;
2404 		m_iLastScrollBarValue = sc;
2405 		m_pScrollBar->setValue(sc);
2406 	}
2407 	else
2408 	{
2409 		repaint();
2410 	}
2411 }
2412 
setCursorLine(KviIrcViewLine * l)2413 void KviIrcView::setCursorLine(KviIrcViewLine * l)
2414 {
2415 	m_pCursorLine = l;
2416 	ensureLineVisible(l);
2417 }
2418 
findNext(const QString & szText,bool bCaseS,bool bRegExp,bool bExtended)2419 void KviIrcView::findNext(const QString & szText, bool bCaseS, bool bRegExp, bool bExtended)
2420 {
2421 	KviIrcViewLine * l = m_pCursorLine;
2422 	if(!l)
2423 		l = m_pCurLine;
2424 	if(l)
2425 	{
2426 		l = l->pNext;
2427 		if(!l)
2428 			l = m_pFirstLine;
2429 		KviIrcViewLine * start = l;
2430 
2431 		int idx = -1;
2432 
2433 		do
2434 		{
2435 			if(m_pToolWidget)
2436 			{
2437 				if(!(m_pToolWidget->messageEnabled(l->iMsgType)))
2438 					goto do_pNext;
2439 			}
2440 
2441 			if(bRegExp)
2442 			{
2443 				QRegExp re(szText, bCaseS ? Qt::CaseSensitive : Qt::CaseInsensitive, bExtended ? QRegExp::RegExp : QRegExp::Wildcard);
2444 				idx = re.indexIn(l->szText, 0);
2445 			}
2446 			else
2447 			{
2448 				QString tmp = l->szText;
2449 				idx = tmp.indexOf(szText, 0, bCaseS ? Qt::CaseSensitive : Qt::CaseInsensitive);
2450 			}
2451 
2452 			if(idx != -1)
2453 			{
2454 				setCursorLine(l);
2455 				if(m_pToolWidget)
2456 				{
2457 					QString szTmp = QString(__tr2qs("Pos %1")).arg(idx);
2458 					m_pToolWidget->setFindResult(szTmp);
2459 				}
2460 				return;
2461 			}
2462 
2463 		do_pNext:
2464 
2465 			l = l->pNext;
2466 			if(!l)
2467 				l = m_pFirstLine;
2468 
2469 		} while(l != start);
2470 	}
2471 	m_pCursorLine = nullptr;
2472 	repaint();
2473 	if(m_pToolWidget)
2474 		m_pToolWidget->setFindResult(__tr2qs("Not found"));
2475 }
2476 
findPrev(const QString & szText,bool bCaseS,bool bRegExp,bool bExtended)2477 void KviIrcView::findPrev(const QString & szText, bool bCaseS, bool bRegExp, bool bExtended)
2478 {
2479 	KviIrcViewLine * l = m_pCursorLine;
2480 	if(!l)
2481 		l = m_pCurLine;
2482 	if(l)
2483 	{
2484 		l = l->pPrev;
2485 		if(!l)
2486 			l = m_pLastLine;
2487 		KviIrcViewLine * start = l;
2488 
2489 		int idx = -1;
2490 
2491 		do
2492 		{
2493 
2494 			if(m_pToolWidget)
2495 			{
2496 				if(!(m_pToolWidget->messageEnabled(l->iMsgType)))
2497 					goto do_pPrev;
2498 			}
2499 
2500 			if(bRegExp)
2501 			{
2502 				QRegExp re(szText, bCaseS ? Qt::CaseSensitive : Qt::CaseInsensitive, bExtended ? QRegExp::RegExp : QRegExp::Wildcard);
2503 				idx = re.indexIn(l->szText, 0);
2504 			}
2505 			else
2506 			{
2507 				QString tmp = l->szText;
2508 				idx = tmp.indexOf(szText, 0, bCaseS ? Qt::CaseSensitive : Qt::CaseInsensitive);
2509 			}
2510 
2511 			if(idx != -1)
2512 			{
2513 				setCursorLine(l);
2514 				if(m_pToolWidget)
2515 				{
2516 					QString szTmp = QString(__tr2qs("Pos %1")).arg(idx);
2517 					m_pToolWidget->setFindResult(szTmp);
2518 				}
2519 				return;
2520 			}
2521 
2522 		do_pPrev:
2523 
2524 			l = l->pPrev;
2525 			if(!l)
2526 				l = m_pLastLine;
2527 
2528 		} while(l != start);
2529 	}
2530 	m_pCursorLine = nullptr;
2531 
2532 	repaint();
2533 	if(m_pToolWidget)
2534 		m_pToolWidget->setFindResult(__tr2qs("Not found"));
2535 }
2536 
getVisibleLineAt(int yPos)2537 KviIrcViewLine * KviIrcView::getVisibleLineAt(int yPos)
2538 {
2539 	KviIrcViewLine * l = m_pCurLine;
2540 	int toolWidgetHeight = (m_pToolWidget && m_pToolWidget->isVisible()) ? m_pToolWidget->sizeHint().height() : 0;
2541 	int iTop = height() + m_iFontDescent - KVI_IRCVIEW_VERTICAL_BORDER - toolWidgetHeight;
2542 
2543 	while(iTop > yPos)
2544 	{
2545 		if(l)
2546 		{
2547 			iTop -= ((l->uLineWraps + 1) * m_iFontLineSpacing) + m_iFontDescent;
2548 			if(iTop <= yPos)
2549 				return l;
2550 			l = l->pPrev;
2551 		}
2552 		else
2553 			return nullptr;
2554 	}
2555 	return nullptr;
2556 }
2557 
getVisibleCharIndexAt(KviIrcViewLine *,int xPos,int yPos)2558 int KviIrcView::getVisibleCharIndexAt(KviIrcViewLine *, int xPos, int yPos)
2559 {
2560 	/*
2561 	 * Profane description: this functions sums up most of the complications involved in the ircview. We got a mouse position and have
2562 	 * to identify if there's a link inside the KviIrcViewLine at that position.
2563 	 * l contains the current KviIrcViewLine we're checking, iTop is the y coordinate of the
2564 	 * that line. We go from the bottom to the top: l is the last line and iTop is the y coordinate of the end of that line (imagine it
2565 	 * as the beginning of the "next" line that have to come.
2566 	 */
2567 
2568 	KviIrcViewLine * l = m_pCurLine;
2569 	int toolWidgetHeight = (m_pToolWidget && m_pToolWidget->isVisible()) ? m_pToolWidget->sizeHint().height() : 0;
2570 	int iTop = height() + m_iFontDescent - KVI_IRCVIEW_VERTICAL_BORDER - toolWidgetHeight;
2571 
2572 	// our current line begins after the mouse position... go on
2573 	while(iTop > yPos)
2574 	{
2575 		// no lines, go away
2576 		if(!l)
2577 			return -1;
2578 
2579 		// subtract from iTop the height of the current line (aka go to the end of the previous / start of the current point)
2580 		iTop -= ((l->uLineWraps + 1) * m_iFontLineSpacing) + m_iFontDescent;
2581 
2582 		// we're still below the mouse position.. go on
2583 		if(iTop > yPos)
2584 		{
2585 			// next round, try with the previous line
2586 			l = l->pPrev;
2587 			continue;
2588 		}
2589 
2590 		/*
2591 		 * Profane description: if we are here we have found the right line where our mouse is over; l is the KviIrcViewLine *,
2592 		 * iTop is the line start y coordinate. Now we have to go through this line's text and find the exact text under the mouse.
2593 		 * The line start x posistion is iLeft; we save iTop to firstRowTop (many rows can be part of this lingle line of text)
2594 		 */
2595 		int iLeft = KVI_IRCVIEW_HORIZONTAL_BORDER;
2596 
2597 		if(KVI_OPTION_BOOL(KviOption_boolIrcViewShowImages))
2598 			iLeft += KVI_IRCVIEW_PIXMAP_AND_SEPARATOR;
2599 
2600 		int firstRowTop = iTop;
2601 		int i = 0;
2602 
2603 		for(;;)
2604 		{
2605 			// if the mouse position is > start_of_this_row + row_height, move on to the next row of this line
2606 			if(yPos > iTop + m_iFontLineSpacing)
2607 			{
2608 				// run until a word wrap block (aka a new line); move at least one block forward
2609 				i++;
2610 				while(i < l->iBlockCount)
2611 				{
2612 					if(l->pBlocks[i].pChunk == nullptr)
2613 						break; // word wrap found
2614 					else
2615 						i++;
2616 				}
2617 				if(i >= l->iBlockCount)
2618 					return -1; // we reached the last chunk... there's something wrong, return
2619 				else
2620 					iTop += m_iFontLineSpacing; // we found a word wrap, check the next row.
2621 			}
2622 			else
2623 			{
2624 			/*
2625 			 * Profane description: Once we get here, we know the correct line l, the correct row top coordinate iTop and
2626 			 * the index of the first chunk in this line i.
2627 			 * Calculate the left border of this row: if this is not the first one, add any margin.
2628 			 * Note: iLeft will contain the right border position of the current chunk.
2629 			 */
2630 				// this is not the first row of this line and the margin option is enabled?
2631 				if(iTop != firstRowTop)
2632 					if(KVI_OPTION_BOOL(KviOption_boolIrcViewWrapMargin))
2633 						iLeft += m_iWrapMargin;
2634 
2635 				if(xPos < iLeft)
2636 					return 0; // Mouse is out of this row boundaries
2637 
2638 				if(i >= l->iBlockCount)
2639 					return l->szText.size();
2640 
2641 				// run up to the chunk containing the mouse position
2642 				for(; iLeft + l->pBlocks[i].block_width < xPos;)
2643 				{
2644 					if(l->pBlocks[i].block_width > 0)
2645 						iLeft += l->pBlocks[i].block_width;
2646 					else if(i < (l->iBlockCount - 1))
2647 					{
2648 						// There is another block, check if it is a wrap (we reached the end of the row)
2649 						if(l->pBlocks[i + 1].pChunk == nullptr)
2650 							break;
2651 						// else simply a zero characters block
2652 					}
2653 					i++;
2654 					if(i >= l->iBlockCount)
2655 						return l->szText.size();
2656 				}
2657 				// now, get the right character inside the block
2658 				int retValue = 0, oldIndex = 0, oldLeft = iLeft;
2659 				QChar curChar;
2660 				// add the width of each single character until we get the right one
2661 				while(iLeft < xPos && retValue < l->pBlocks[i].block_len)
2662 				{
2663 					oldIndex = retValue; oldLeft = iLeft;
2664 
2665 					curChar = l->szText.at(l->pBlocks[i].block_start + retValue);
2666 					if (curChar >= 0xD800 && curChar <= 0xDC00) // Surrogate pair
2667 					{
2668 						iLeft += m_pFm->width(l->szText.mid(retValue), 2);
2669 						retValue+=2;
2670 					}
2671 					else
2672 					{
2673 						iLeft += (curChar < 0xff) ? m_iFontCharacterWidth[curChar.unicode()] : m_pFm->width(curChar);
2674 						retValue++;
2675 					}
2676 				}
2677 
2678 				// Make the horizontal point delimiting which characters are to be selected be in the middle of the character,
2679 				// i.e. if the user starts selecting from the left half of a character, select that character too.
2680 				// This mimics the behavior of most other GUI applications.
2681 				int iMid = (oldLeft + iLeft) / 2;
2682 				if (xPos <= iMid)
2683 					retValue = oldIndex;
2684 
2685 				//printf("%d\n",l->pBlocks[i].block_start+retValue);
2686 				return l->pBlocks[i].block_start + retValue;
2687 			}
2688 		}
2689 	}
2690 	return -1;
2691 }
2692 
getLinkUnderMouse(int xPos,int yPos,QRect * pRect,QString * linkCmd,QString * linkText)2693 KviIrcViewWrappedBlock * KviIrcView::getLinkUnderMouse(int xPos, int yPos, QRect * pRect, QString * linkCmd, QString * linkText)
2694 {	/*
2695 	 * Profane description: this functions sums up most of the complications involved in the ircview. We got a mouse position and have
2696 	 * to identify if there's a link inside the KviIrcViewLine at that position.
2697 	 * l contains the current KviIrcViewLine we're checking, iTop is the y coordinate of the
2698 	 * that line. We go from the bottom to the top: l is the last line and iTop is the y coordinate of the end of that line (imagine it
2699 	 * as the beginning of the "next" line that have to come.
2700 	 */
2701 
2702 	KviIrcViewLine * l = m_pCurLine;
2703 	int toolWidgetHeight = (m_pToolWidget && m_pToolWidget->isVisible()) ? m_pToolWidget->sizeHint().height() : 0;
2704 	int iTop = height() + m_iFontDescent - KVI_IRCVIEW_VERTICAL_BORDER - toolWidgetHeight;
2705 
2706 	// our current line begins after the mouse position... go on
2707 	while(iTop > yPos)
2708 	{
2709 		// no lines, go away
2710 		if(!l)
2711 			return nullptr;
2712 
2713 		// subtract from iTop the height of the current line (aka go to the end of the previous / start of the current point)
2714 		iTop -= ((l->uLineWraps + 1) * m_iFontLineSpacing) + m_iFontDescent;
2715 
2716 		// we're still below the mouse position.. go on
2717 		if(iTop > yPos)
2718 		{
2719 			// next round, try with the previous line
2720 			l = l->pPrev;
2721 			continue;
2722 		}
2723 
2724 		/*
2725 		 * Profane description: if we are here we have found the right line where our mouse is over; l is the KviIrcViewLine *,
2726 		 * iTop is the line start y coordinate. Now we have to go through this line's text and find the exact text under the mouse.
2727 		 * The line start x posistion is iLeft; we save iTop to firstRowTop (many rows can be part of this lingle line of text)
2728 		 */
2729 		int iLeft = KVI_IRCVIEW_HORIZONTAL_BORDER;
2730 		if(KVI_OPTION_BOOL(KviOption_boolIrcViewShowImages))
2731 			iLeft += KVI_IRCVIEW_PIXMAP_AND_SEPARATOR;
2732 		int firstRowTop = iTop;
2733 		int i = 0;
2734 
2735 		int iLastEscapeBlock = -1;
2736 		int iLastEscapeBlockTop = -1;
2737 
2738 		for(;;)
2739 		{
2740 			// if the mouse position is > start_of_this_row + row_height, move on to the next row of this line
2741 			if(yPos > iTop + m_iFontLineSpacing)
2742 			{
2743 				// run until a word wrap block (aka a new line); move at least one block forward
2744 				i++;
2745 				while(i < l->iBlockCount)
2746 				{
2747 					if(l->pBlocks[i].pChunk == nullptr)
2748 					{
2749 						break; // word wrap found
2750 					}
2751 					else
2752 					{
2753 						// still ok to run right, but check if we find a url
2754 						if(i >= l->iBlockCount)
2755 							break;
2756 						// we try to save the position of the last "text escape" tag we find
2757 						if(l->pBlocks[i].pChunk)
2758 							if(l->pBlocks[i].pChunk->type == KviControlCodes::Escape)
2759 							{
2760 								iLastEscapeBlock = i;
2761 								iLastEscapeBlockTop = iTop;
2762 							}
2763 						// we reset the position of the last "text escape" tag if we find a "unescape"
2764 						if(l->pBlocks[i].pChunk)
2765 							if(l->pBlocks[i].pChunk->type == KviControlCodes::UnEscape)
2766 								iLastEscapeBlock = -1;
2767 
2768 						i++;
2769 					}
2770 				}
2771 				if(i >= l->iBlockCount)
2772 					return nullptr; // we reached the last chunk... there's something wrong, return
2773 				else
2774 					iTop += m_iFontLineSpacing; // we found a word wrap, check the next row.
2775 			}
2776 			else
2777 			{
2778 			/*
2779 			 * Profane description: Once we get here, we know the correct line l, the correct row top coordinate iTop and
2780 			 * the index of the first chunk in this line i.
2781 			 * Calculate the left border of this row: if this is not the first one, add any margin.
2782 			 * Note: iLeft will contain the right border position of the current chunk.
2783 			 */
2784 				int iBlockWidth = 0;
2785 
2786 				// this is not the first row of this line and the margin option is enabled?
2787 				if(iTop != firstRowTop)
2788 					if(KVI_OPTION_BOOL(KviOption_boolIrcViewWrapMargin))
2789 						iLeft += m_iWrapMargin;
2790 
2791 				if(xPos < iLeft)
2792 					return nullptr; // Mouse is out of this row boundaries
2793 				for(;;)
2794 				{
2795 					int iLastLeft = iLeft;
2796 					// we've run till the end of the line, go away
2797 					if(i >= l->iBlockCount)
2798 						return nullptr;
2799 					// we try to save the position of the last "text escape" tag we find
2800 					if(l->pBlocks[i].pChunk)
2801 						if(l->pBlocks[i].pChunk->type == KviControlCodes::Escape)
2802 						{
2803 							iLastEscapeBlock = i;
2804 							iLastEscapeBlockTop = iTop;
2805 						}
2806 					// we reset the position of the last "text escape" tag if we find a "unescape"
2807 					if(l->pBlocks[i].pChunk)
2808 						if(l->pBlocks[i].pChunk->type == KviControlCodes::UnEscape)
2809 							iLastEscapeBlock = -1;
2810 					// if the block width is > 0, update iLeft
2811 					if(l->pBlocks[i].block_width > 0)
2812 					{
2813 						iBlockWidth = l->pBlocks[i].block_width;
2814 						iLeft += iBlockWidth;
2815 					}
2816 					else
2817 					{
2818 						if(i < (l->iBlockCount - 1))
2819 						{	// There is another block, check if it is a wrap (we reached the end of the row)
2820 							if(l->pBlocks[i + 1].pChunk == nullptr)
2821 							{
2822 								iBlockWidth = width() - iLastLeft;
2823 								iLeft = width();
2824 							}
2825 							// else simply a zero characters block
2826 						}
2827 					}
2828 					/*
2829 					 * Profane description: mouse was not under the last chunk, try with this one..
2830 					 */
2831 					if(xPos < iLeft)
2832 					{
2833 						// Got it!
2834 						// link ?
2835 						bool bHadWordWraps = false;
2836 						while(l->pBlocks[i].pChunk == nullptr)
2837 						{
2838 							// word wrap ?
2839 							if(i >= 0)
2840 							{
2841 								i--;
2842 								bHadWordWraps = true;
2843 							}
2844 							else
2845 								return nullptr; // all word wraps ?!!!
2846 						}
2847 						if(iLastEscapeBlock != -1)
2848 						{
2849 							int iLeftBorder = iLeft;
2850 							int k;
2851 							for(k = i; k >= iLastEscapeBlock; k--)
2852 								iLeftBorder -= l->pBlocks[k].block_width;
2853 							int iRightBorder = 0;
2854 							unsigned int uLineWraps = 0;
2855 							for(k = iLastEscapeBlock;; k++)
2856 							{
2857 								if(l->pBlocks[k].pChunk)
2858 								{
2859 									if(l->pBlocks[k].pChunk->type != KviControlCodes::UnEscape)
2860 										iRightBorder += l->pBlocks[k].block_width;
2861 									else
2862 										break;
2863 								}
2864 								else
2865 								{
2866 									uLineWraps++;
2867 									bHadWordWraps = true;
2868 								}
2869 							}
2870 							if(pRect)
2871 							{
2872 								*pRect = QRect(iLeftBorder,
2873 								    bHadWordWraps ? iLastEscapeBlockTop : iTop,
2874 								    iRightBorder,
2875 								    ((uLineWraps + 1) * m_iFontLineSpacing) + m_iFontDescent);
2876 							}
2877 							if(linkCmd)
2878 							{
2879 								linkCmd->setUtf16(l->pBlocks[iLastEscapeBlock].pChunk->szPayload, kvi_wstrlen(l->pBlocks[iLastEscapeBlock].pChunk->szPayload));
2880 								*linkCmd = linkCmd->trimmed();
2881 								if((*linkCmd) == "nc")
2882 									(*linkCmd) = "n";
2883 							}
2884 							if(linkText)
2885 							{
2886 								QString szLink;
2887 								int iEndOfLInk = iLastEscapeBlock;
2888 								while(true)
2889 								{
2890 									if(l->pBlocks[iEndOfLInk].pChunk)
2891 									{
2892 										if(l->pBlocks[iEndOfLInk].pChunk->type != KviControlCodes::UnEscape)
2893 										{
2894 											switch(l->pBlocks[iEndOfLInk].pChunk->type)
2895 											{
2896 												case KviControlCodes::Bold:
2897 												case KviControlCodes::Italic:
2898 												case KviControlCodes::Underline:
2899 												case KviControlCodes::Reverse:
2900 												case KviControlCodes::Reset:
2901 													szLink.append(QChar(l->pBlocks[iEndOfLInk].pChunk->type));
2902 													break;
2903 												case KviControlCodes::Color:
2904 													szLink.append(QChar(KviControlCodes::Color));
2905 													if(l->pBlocks[iEndOfLInk].pChunk->colors.fore != KviControlCodes::NoChange)
2906 													{
2907 														szLink.append(QString("%1").arg((int)(l->pBlocks[iEndOfLInk].pChunk->colors.fore)));
2908 													}
2909 													if(l->pBlocks[iEndOfLInk].pChunk->colors.back != KviControlCodes::NoChange)
2910 													{
2911 														szLink.append(QChar(','));
2912 														szLink.append(QString("%1").arg((int)(l->pBlocks[iEndOfLInk].pChunk->colors.back)));
2913 													}
2914 													break;
2915 											}
2916 											szLink.append(l->szText.mid(l->pBlocks[iEndOfLInk].block_start, l->pBlocks[iEndOfLInk].block_len));
2917 										}
2918 										else
2919 										{
2920 											break;
2921 										}
2922 									}
2923 									iEndOfLInk++;
2924 								}
2925 								*linkText = szLink;
2926 								// grab the rest of the link visible string
2927 								// Continue while we do not find a non word wrap block block
2928 								for(int bufIndex = (i + 1); bufIndex < l->iBlockCount; bufIndex++)
2929 								{
2930 									if(l->pBlocks[bufIndex].pChunk)
2931 										break; // finished : not a word wrap
2932 									else
2933 									{
2934 										linkText->append(l->szText.mid(l->pBlocks[bufIndex].block_start, l->pBlocks[bufIndex].block_len));
2935 									}
2936 								}
2937 							}
2938 							return &(l->pBlocks[iLastEscapeBlock]);
2939 						}
2940 						if(l->pBlocks[i].pChunk->type == KviControlCodes::Icon)
2941 						{
2942 							if(pRect)
2943 							{
2944 								*pRect = QRect(iLastLeft,
2945 								    bHadWordWraps ? firstRowTop : iTop,
2946 								    iBlockWidth,
2947 								    ((l->uLineWraps + 1) * m_iFontLineSpacing) + m_iFontDescent);
2948 							}
2949 							if(linkCmd)
2950 							{
2951 								*linkCmd = "[!txt]";
2952 								QString tmp;
2953 								tmp.setUtf16(l->pBlocks[i].pChunk->szPayload, kvi_wstrlen(l->pBlocks[i].pChunk->szPayload));
2954 								linkCmd->append(tmp);
2955 								*linkCmd = linkCmd->trimmed();
2956 							}
2957 							if(linkText)
2958 							{
2959 								*linkText = "";
2960 							}
2961 							return &(l->pBlocks[i]);
2962 						}
2963 						return nullptr;
2964 					}
2965 					i++;
2966 				}
2967 			}
2968 		}
2969 	}
2970 	return nullptr;
2971 }
2972 
console()2973 KviConsoleWindow * KviIrcView::console()
2974 {
2975 	return m_pKviWindow->console();
2976 }
2977 
animatedIconChange()2978 void KviIrcView::animatedIconChange()
2979 {
2980 	update();
2981 }
2982 
scrollToMarker()2983 void KviIrcView::scrollToMarker()
2984 {
2985 	KviIrcViewLine * pLine = m_pCurLine;
2986 
2987 	while(pLine && (pLine->uIndex != m_uLineMarkLineIndex))
2988 		pLine = pLine->pPrev;
2989 
2990 	if(pLine == nullptr)
2991 	{	// The buffer has already cleaned the marker line
2992 		ensureLineVisible(m_pFirstLine);
2993 	}
2994 	else
2995 	{
2996 		ensureLineVisible(pLine);
2997 	}
2998 }
2999