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