ircview.cpp 74.3 KB
Newer Older
1
// -*- mode: c++; c-file-style: "bsd"; c-basic-offset: 4; tabs-width: 4; indent-tabs-mode: nil -*-
2

Dario Abatianni's avatar
""  
Dario Abatianni committed
3 4 5 6 7 8 9 10
/*
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
*/

/*
Eike Hein's avatar
Eike Hein committed
11
  Copyright (C) 2002 Dario Abatianni <eisfuchs@tigress.com>
12
  Copyright (C) 2005-2007 Peter Simonsson <psn@linux.se>
Eike Hein's avatar
Eike Hein committed
13
  Copyright (C) 2006-2010 Eike Hein <hein@kde.org>
14
  Copyright (C) 2004-2011 Eli Mackenzie <argonel@gmail.com>
Dario Abatianni's avatar
""  
Dario Abatianni committed
15
*/
16

17 18
#include "ircview.h"
#include "channel.h"
19
#include "dcc/chatcontainer.h"
20
#include "application.h"
21
#include "highlight.h"
22
#include "sound.h"
23
#include "emoticons.h"
24
#include "notificationhandler.h"
25

Laurent Montel's avatar
Laurent Montel committed
26
#include <QScrollBar>
27
#include <QTextBlock>
28
#include <QPainter>
29
#include <QTextDocumentFragment>
30

31
#include <KStandardShortcut>
Eli MacKenzie's avatar
Eli MacKenzie committed
32

33 34
using namespace Konversation;

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
class ScrollBarPin
{
        QPointer<QScrollBar> m_bar;
    public:
        ScrollBarPin(QScrollBar *scrollBar) : m_bar(scrollBar)
        {
            if (m_bar)
                m_bar = m_bar->value() == m_bar->maximum()? m_bar : 0;
        }
        ~ScrollBarPin()
        {
            if (m_bar)
                m_bar->setValue(m_bar->maximum());
        }
};

51 52 53 54 55 56 57 58 59 60
// Scribe bug - if the cursor position or anchor points to the last character in the document,
// the cursor becomes glued to the end of the document instead of retaining the actual position.
// This causes the selection to expand when something is appended to the document.
class SelectionPin
{
    int pos, anc;
    QPointer<IRCView> d;
    public:
        SelectionPin(IRCView *doc) : pos(0), anc(0), d(doc)
        {
61 62 63
            if (d->textCursor().hasSelection())
            {
                int end = d->document()->rootFrame()->lastPosition();
64 65 66 67

                //WARNING if selection pins don't work in some build environments, we need to keep the result
                d->document()->lastBlock();

68 69 70 71 72
                pos = d->textCursor().position();
                anc = d->textCursor().anchor();
                if (pos != end && anc != end)
                    anc = pos = 0;
            }
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
        }

        ~SelectionPin()
        {
            if (d && (pos || anc))
            {
                QTextCursor mv(d->textCursor());
                mv.setPosition(anc);
                mv.setPosition(pos, QTextCursor::KeepAnchor);
                d->setTextCursor(mv);
            }
        }
};


88
IRCView::IRCView(QWidget* parent) : KTextBrowser(parent), m_rememberLine(0), m_lastMarkerLine(0), m_rememberLineDirtyBit(false), markerFormatObject(this)
89
{
90
    m_mousePressedOnUrl = false;
Peter Simonsson's avatar
Peter Simonsson committed
91
    m_isOnNick = false;
92
    m_isOnChannel = false;
Peter Simonsson's avatar
Peter Simonsson committed
93
    m_chatWin = 0;
Eike Hein's avatar
Eike Hein committed
94
    m_server = 0;
95

96
    setAcceptDrops(false);
97

Eike Hein's avatar
Eike Hein committed
98
    // Marker lines
Laurent Montel's avatar
Laurent Montel committed
99
    connect(document(), SIGNAL(contentsChange(int,int,int)), SLOT(cullMarkedLine(int,int,int)));
100 101 102 103

    //This assert is here because a bad build environment can cause this to fail. There is a note
    // in the Qt source that indicates an error should be output, but there is no such output.
    QTextObjectInterface *iface = qobject_cast<QTextObjectInterface *>(&markerFormatObject);
Eli MacKenzie's avatar
Eli MacKenzie committed
104 105 106 107
    if (!iface)
    {
        Q_ASSERT(iface);
    }
108 109 110 111 112

    document()->documentLayout()->registerHandler(IRCView::MarkerLine, &markerFormatObject);
    document()->documentLayout()->registerHandler(IRCView::RememberLine, &markerFormatObject);


113
    connect(this, SIGNAL(anchorClicked(QUrl)), this, SLOT(anchorClicked(QUrl)));
Laurent Montel's avatar
Laurent Montel committed
114
    connect( this, SIGNAL(highlighted(QString)), this, SLOT(highlightedSlot(QString)) );
115
    setOpenLinks(false);
Peter Simonsson's avatar
Peter Simonsson committed
116
    setUndoRedoEnabled(0);
117
    document()->setDefaultStyleSheet("a.nick:link {text-decoration: none}");
Eli MacKenzie's avatar
Eli MacKenzie committed
118 119
    setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
    setFocusPolicy(Qt::ClickFocus);
Wouter Becq's avatar
Wouter Becq committed
120 121
    setReadOnly(true);
    viewport()->setCursor(Qt::ArrowCursor);
122 123
    setTextInteractionFlags(Qt::TextBrowserInteraction);
    viewport()->setMouseTracking(true);
Eli MacKenzie's avatar
Eli MacKenzie committed
124

125
    if (Preferences::self()->useParagraphSpacing()) enableParagraphSpacing();
126 127 128 129 130

    //HACK to workaround an issue with the QTextDocument
    //doing a relayout/scrollbar over and over resulting in 100%
    //proc usage. See bug 215256
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
131 132

    setContextMenuOptions(IrcContextMenus::ShowTitle | IrcContextMenus::ShowFindAction, true);
Peter Simonsson's avatar
Peter Simonsson committed
133 134
}

135 136
IRCView::~IRCView()
{
Dario Abatianni's avatar
""  
Dario Abatianni committed
137 138
}

Eli MacKenzie's avatar
Eli MacKenzie committed
139
void IRCView::setServer(Server* newServer)
140
{
141 142 143
    if (m_server == newServer)
        return;

Eli MacKenzie's avatar
Eli MacKenzie committed
144 145
    m_server = newServer;
}
146

Eli MacKenzie's avatar
Eli MacKenzie committed
147 148 149 150
void IRCView::setChatWin(ChatWindow* chatWin)
{
    m_chatWin = chatWin;
}
151

152
void IRCView::findText()
Eli MacKenzie's avatar
Eli MacKenzie committed
153 154 155
{
    emit doSearch();
}
Wouter Becq's avatar
Wouter Becq committed
156

157 158 159 160 161 162 163 164 165 166
void IRCView::findNextText()
{
    emit doSearchNext();
}

void IRCView::findPreviousText()
{
    emit doSearchPrevious();
}

Wouter Becq's avatar
Wouter Becq committed
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
bool IRCView::search(const QString& pattern, bool caseSensitive, bool wholeWords, bool forward, bool fromCursor)
{
    if (pattern.isEmpty())
        return true;

    m_pattern       = pattern;
    m_forward       = forward;
    m_searchFlags = 0;
    if (caseSensitive)
        m_searchFlags |= QTextDocument::FindCaseSensitively;
    if (wholeWords)
        m_searchFlags |= QTextDocument::FindWholeWords;
    if (!fromCursor)
        m_forward ? moveCursor(QTextCursor::Start) : moveCursor(QTextCursor::End);

    return searchNext();
}

bool IRCView::searchNext(bool reversed)
{
    bool fwd = (reversed ? !m_forward : m_forward);
    if (fwd) {
        m_searchFlags &= ~QTextDocument::FindBackward;
    }
    else {
        m_searchFlags |= QTextDocument::FindBackward;
    }
    return find(m_pattern, m_searchFlags);
}
196

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
class IrcViewMimeData : public QMimeData
{
public:
    IrcViewMimeData(const QTextDocumentFragment& _fragment): fragment(_fragment) {}
    virtual QStringList formats() const;

protected:
    virtual QVariant retrieveData(const QString &mimeType, QVariant::Type type) const;

private:
    mutable QTextDocumentFragment fragment;
};

QStringList IrcViewMimeData::formats() const
{
    if (!fragment.isEmpty())
        return QStringList() << QString::fromLatin1("text/plain");
    else
        return QMimeData::formats();
}

QVariant IrcViewMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const
{
    if (!fragment.isEmpty())
    {
        IrcViewMimeData *that = const_cast<IrcViewMimeData *>(this);

        //Copy the text, skipping any QChar::ObjectReplacementCharacter
        QRegExp needle(QString("\\xFFFC\\n?"));

        that->setText(fragment.toPlainText().remove(needle));
        fragment = QTextDocumentFragment();
    }
    return QMimeData::retrieveData(mimeType, type);
}

QMimeData *IRCView::createMimeDataFromSelection() const
{
    const QTextDocumentFragment fragment(textCursor());
    return new IrcViewMimeData(fragment);
}

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
void IRCView::dragEnterEvent(QDragEnterEvent* e)
{
    if (e->mimeData()->hasUrls())
        e->acceptProposedAction();
    else
        e->ignore();
}

void IRCView::dragMoveEvent(QDragMoveEvent* e)
{
    if (e->mimeData()->hasUrls())
        e->accept();
    else
        e->ignore();
}

void IRCView::dropEvent(QDropEvent* e)
{
    if (e->mimeData() && e->mimeData()->hasUrls())
        emit urlsDropped(KUrl::List::fromMimeData(e->mimeData(), KUrl::List::PreferLocalUrls));
}

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
// Marker lines

#define _S(x) #x << (x)
#define DebugBanner KDebug::Block myBlock(qPrintable(QString::number((uint)this, 16)))

QDebug operator<<(QDebug dbg, QTextBlockUserData *bd);
QDebug operator<<(QDebug d, QTextFrame* feed);
QDebug operator<<(QDebug d, QTextDocument* document);
QDebug operator<<(QDebug d, QTextBlock b);

// This object gets stuffed into the userData field of a text block.
// Qt does not give us a way to track blocks, so we have to
// rely on the destructor of this object to notify us that a
// block we care about was removed from the document. This does not
// prevent the first block bug from deleting the wrong block's data,
// however that should not result in a crash.
struct Burr: public QTextBlockUserData
{
    Burr(IRCView* o, Burr* prev, QTextBlock b, int objFormat)
        : m_block(b), m_format(objFormat), m_prev(prev), m_next(0),
        m_owner(o)
    {
        if (m_prev)
            m_prev->m_next = this;
    }

    ~Burr()
    {
        DebugBanner;
        kDebug() << "~Burr" << (void*)this << _S(m_format) << _S(m_block.blockNumber()) << "deleted";
        m_owner->blockDeleted(this);
        unlink();
    }

    void unlink()
    {
        kDebug() << "====" << (void*)this << m_block;
        if (m_prev)
        {
            kDebug() << "prev->next" << (void*)m_next;
            m_prev->m_next = m_next;
        }
        else
            kDebug() << "prev was null";
        if (m_next)
        {
            kDebug() << "next";
            m_next->m_prev = m_prev;
        }
        else
            kDebug() << "next was null";
    }

    QTextBlock m_block;
    int m_format;
    Burr* m_prev, *m_next;
    IRCView* m_owner;
};

320 321 322 323 324 325
void IrcViewMarkerLine::drawObject(QPainter *painter, const QRectF &r, QTextDocument *doc, int posInDocument, const QTextFormat &format)
{
    Q_UNUSED(format);

    QTextBlock block=doc->findBlock(posInDocument);
    QPen pen;
326 327 328
    Burr* b = dynamic_cast<Burr*>(block.userData());
    Q_ASSERT(b); // remember kids, only YOU can makes this document support two user data types
    switch (b->m_format)
329 330 331 332 333 334 335
    {
        case IRCView::BlockIsMarker:
            pen.setColor(Preferences::self()->color(Preferences::ActionMessage));
            break;

        case IRCView::BlockIsRemember:
            pen.setColor(Preferences::self()->color(Preferences::CommandMessage));
336
            // pen.setStyle(Qt::DashDotDotLine);
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
            break;

        default:
            //nice color, eh?
            pen.setColor(Qt::cyan);
    }

    pen.setWidth(2); // FIXME this is a hardcoded value...
    painter->setPen(pen);

    qreal y = (r.top() + r.height() / 2);
    QLineF line(r.left(), y, r.right(), y);
    painter->drawLine(line);
}

QSizeF IrcViewMarkerLine::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format)
{
    Q_UNUSED(posInDocument); Q_UNUSED(format);

    QTextFrameFormat f=doc->rootFrame()->frameFormat();
    qreal width = doc->pageSize().width()-(f.leftMargin()+f.rightMargin());
    return QSizeF(width, 6); // FIXME this is a hardcoded value...
}

361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
QTextCharFormat IRCView::getFormat(ObjectFormats x)
{
    QTextCharFormat f;
    f.setObjectType(x);
    return f;
}

void IRCView::blockDeleted(Burr* b) //slot
{
    DebugBanner;
    Q_ASSERT(b); // this method only to be called from a ~Burr();
    kDebug() << "blockDeleted" << (void*)(b) << b->m_block << _S(m_lastMarkerLine) << _S(m_rememberLine);

    //tracking only the tail
    if (b == m_lastMarkerLine)
        m_lastMarkerLine = b->m_prev;

    if (b == m_rememberLine)
        m_rememberLine = 0;
}

382 383
void IRCView::cullMarkedLine(int where, int rem, int add) //slot
{
384 385 386 387 388 389 390 391 392 393 394 395
    bool showDebug = false;
    QString output;
    QDebug d = QDebug(&output);//KDebug(QtDebugMsg, __FILE__, __LINE__, Q_FUNC_INFO)();

    bool merged = (add!=0 && rem !=0); // i have never seen this happen, adds and removes are always separate
    int blockCount = document()->blockCount();
    void *view = this;
    QTextBlock prime = document()->firstBlock();

    d << "================= cullMarkedLine" << _S(view) << _S(where) << _S(rem) << _S(add) << _S(blockCount) << _S(prime.length()) << _S(merged);

    if (prime.length() == 1)
396
    {
397
        if (document()->blockCount() == 1) //the entire document was wiped. was a signal such a burden? apparently..
398
        {
399 400
            showDebug = true;
            d << "- wipeLineParagraphs()" << (void*)m_rememberLine << (void*)m_lastMarkerLine;
401 402
            wipeLineParagraphs();
        }
403 404 405 406 407 408 409 410
        else if (document()->characterAt(0).unicode() == 0x2029)
        {
            showDebug = true;
            d << "- only QChar::ParagraphSeparator";
            // this should never happen, it should be 0xfffc2029
            if (dynamic_cast<Burr*>(prime.userData()))
                d << "Burr!" << prime.userData();
        }
411 412
        else
        {
413 414 415
            showDebug = true;
            QString fc = "0x" + QString::number(document()->characterAt(0).unicode(), 16).rightJustified(4, '0');
            d << "- block of length 1 but not 2029" << qPrintable(fc);
416 417
        }
    }
418 419 420 421 422 423 424 425 426 427 428 429 430
    else if (prime.length() == 2)
    {
        //probably a Burr going to be culled next..
        showDebug = true;
        QString fc = "0x" + QString::number(document()->characterAt(0).unicode(), 16).rightJustified(4, '0');
        QString sc = "0x" + QString::number(document()->characterAt(1).unicode(), 16).rightJustified(4, '0');
        d << "- prime(2)" << fc << sc;
    }
    if (showDebug)
    {
        DebugBanner;
        kDebug() << output;
    }
431 432 433 434
}

void IRCView::insertMarkerLine() //slot
{
435 436
    DebugBanner;

437
    //if the last line is already a marker of any kind, skip out
438
    if (lastBlockIsLine(BlockIsMarker))
439 440
        return;

441 442
    kDebug() << "ok to insert..";

443 444 445 446 447 448 449
    //the code used to preserve the dirty bit status, but that was never affected by appendLine...
    //maybe i missed something
    appendLine(IRCView::MarkerLine);
}

void IRCView::insertRememberLine() //slot
{
450 451
    DebugBanner;

452 453 454
    m_rememberLineDirtyBit = true; // means we're going to append a remember line if some text gets inserted

    if (!Preferences::self()->automaticRememberLineOnlyOnTextChange())
455 456
    {
        kDebug();
457
        appendRememberLine();
458
    }
459 460 461 462 463 464 465 466 467
}

void IRCView::cancelRememberLine() //slot
{
    m_rememberLineDirtyBit = false;
}

bool IRCView::lastBlockIsLine(int select)
{
468 469 470 471 472 473 474 475
    Burr *b = dynamic_cast<Burr*>(document()->lastBlock().userData());

    int state = -1;

    if (b)
        state = b->m_format;

    kDebug() << _S(state) << _S(select);
476 477 478 479 480 481 482 483 484

    if (select == -1)
        return (state == BlockIsRemember || state == BlockIsMarker);

    return state == select;
}

void IRCView::appendRememberLine()
{
485 486
    DebugBanner;

487 488 489 490 491 492 493
    //clear this now, so that doAppend doesn't double insert
    m_rememberLineDirtyBit = false;

    //if the last line is already the remember line, do nothing
    if (lastBlockIsLine(BlockIsRemember))
        return;

494
    if (m_rememberLine)
495
    {
496
        QTextBlock rem = m_rememberLine->m_block;
497
        voidLineBlock(rem);
498
        Q_ASSERT(m_rememberLine == 0);
499 500
    }

501
    m_rememberLine = appendLine(IRCView::RememberLine);
502

503
    kDebug() << (void*)m_rememberLine;
504 505 506 507 508
}

void IRCView::voidLineBlock(QTextBlock rem)
{
    QTextCursor c(rem);
509

510 511 512 513 514 515
    c.select(QTextCursor::BlockUnderCursor);
    c.removeSelectedText();
}

void IRCView::clearLines()
{
516 517
    DebugBanner;
    kDebug() << document();
518

519
    while (hasLines())
520
    {
521 522 523
        //IRCView::blockDeleted takes care of the pointers
        voidLineBlock(m_lastMarkerLine->m_block);
    };
524 525 526 527
}

void IRCView::wipeLineParagraphs()
{
528
    m_rememberLine = m_lastMarkerLine = 0;
529 530 531 532
}

bool IRCView::hasLines()
{
533
    DebugBanner;
534

535 536 537
    kDebug() << (void*)m_lastMarkerLine << (void*)m_rememberLine;

    return m_lastMarkerLine != 0;
538 539
}

540
Burr* IRCView::appendLine(IRCView::ObjectFormats type)
541
{
542 543
    DebugBanner;

544 545
    ScrollBarPin barpin(verticalScrollBar());
    SelectionPin selpin(this);
546

Eli MacKenzie's avatar
Eli MacKenzie committed
547 548
    QTextCursor cursor(document());
    cursor.movePosition(QTextCursor::End);
549 550 551
    cursor.insertBlock();
    cursor.insertText(QString(QChar::ObjectReplacementCharacter), getFormat(type));

552 553 554 555 556
    QTextBlock block = cursor.block();
    Burr *b = new Burr(this, m_lastMarkerLine, block, type == MarkerLine? BlockIsMarker : BlockIsRemember);
    block.setUserData(b);

    m_lastMarkerLine = b;
557

558 559 560 561 562 563 564
    //TODO figure out what this is for
    cursor.setPosition(block.position());

    kDebug() << block;

    return b;
}
565

Eike Hein's avatar
Eike Hein committed
566
// Other stuff
Wouter Becq's avatar
Wouter Becq committed
567

Eli MacKenzie's avatar
Eli MacKenzie committed
568
void IRCView::enableParagraphSpacing() {}
569 570

void IRCView::updateAppearance()
571
{
572 573 574 575 576 577 578 579
    if (Preferences::self()->customTextFont())
        setFont(Preferences::self()->textFont());
    else
        setFont(KGlobalSettings::generalFont());

    setVerticalScrollBarPolicy(Preferences::self()->showIRCViewScrollBar() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);

    if (Preferences::self()->showBackgroundImage())
Wouter Becq's avatar
Wouter Becq committed
580
    {
581 582
        KUrl url = Preferences::self()->backgroundImage();

583
        if (url.hasPath())
584
        {
585
            viewport()->setStyleSheet("QWidget { background-image: url("+url.path()+"); background-attachment:fixed; }");
586

587
            return;
588
        }
589
    }
590 591 592 593 594 595

    if (!viewport()->styleSheet().isEmpty())
        viewport()->setStyleSheet("");

    QPalette p;
    p.setColor(QPalette::Base, Preferences::self()->color(Preferences::TextViewBackground));
596
    viewport()->setPalette(p);
597
}
598

599

Eli MacKenzie's avatar
Eli MacKenzie committed
600 601 602
// Data insertion

void IRCView::append(const QString& nick, const QString& message)
603
{
604
    QString channelColor = Preferences::self()->color(Preferences::ChannelMessage).name();
605

Eli MacKenzie's avatar
Eli MacKenzie committed
606 607
    m_tabNotification = Konversation::tnfNormal;

608
    QString nickLine = createNickLine(nick, channelColor);
Eli MacKenzie's avatar
Eli MacKenzie committed
609

610 611
    QChar::Direction dir;
    QString text(filter(message, channelColor, nick, true, true, false, &dir));
Eli MacKenzie's avatar
Eli MacKenzie committed
612
    QString line;
613
    bool rtl = (dir == QChar::DirR);
614 615 616 617 618 619 620 621 622

    if(rtl)
    {
        line = RLE;
        line += LRE;
        line += "<font color=\"" + channelColor + "\">" + nickLine +" %1" + PDF + RLM + " %3</font>";
    }
    else
    {
623 624 625 626
        if (!QApplication::isLeftToRight())
            line += LRE;

        line += "<font color=\"" + channelColor + "\">%1" + nickLine + " %3</font>";
627 628
    }

629
    line = line.arg(timeStamp(), nick, text);
Eli MacKenzie's avatar
Eli MacKenzie committed
630

631
    emit textToLog(QString("<%1>\t%2").arg(nick, message));
Eli MacKenzie's avatar
Eli MacKenzie committed
632

633
    doAppend(line, rtl);
Eli MacKenzie's avatar
Eli MacKenzie committed
634 635 636 637
}

void IRCView::appendRaw(const QString& message, bool suppressTimestamps, bool self)
{
638
    QColor channelColor=Preferences::self()->color(Preferences::ChannelMessage);
Eli MacKenzie's avatar
Eli MacKenzie committed
639 640 641 642
    m_tabNotification = Konversation::tnfNone;

    QString line;
    if (suppressTimestamps)
643
        line = QString("<font color=\"" + channelColor.name() + "\">" + message + "</font>");
644
    else
645
        line = QString(timeStamp() + " <font color=\"" + channelColor.name() + "\">" + message + "</font>");
Eli MacKenzie's avatar
Eli MacKenzie committed
646

647
    doAppend(line, false, self);
648 649
}

650 651 652 653 654
void IRCView::appendLog(const QString & message)
{
    QColor channelColor = Preferences::self()->color(Preferences::ChannelMessage);
    m_tabNotification = Konversation::tnfNone;

655
    QString line("<font color=\"" + channelColor.name() + "\">" + message + "</font>");
656

657
    doRawAppend(line, !QApplication::isLeftToRight());
658 659
}

Eli MacKenzie's avatar
Eli MacKenzie committed
660
void IRCView::appendQuery(const QString& nick, const QString& message, bool inChannel)
661
{
662
    QString queryColor=Preferences::self()->color(Preferences::QueryMessage).name();
663

Eli MacKenzie's avatar
Eli MacKenzie committed
664 665
    m_tabNotification = Konversation::tnfPrivate;

666
    QString nickLine = createNickLine(nick, queryColor, true, inChannel);
Eli MacKenzie's avatar
Eli MacKenzie committed
667 668

    QString line;
669 670 671
    QChar::Direction dir;
    QString text(filter(message, queryColor, nick, true, true, false, &dir));
    bool rtl = (dir == QChar::DirR);
672 673 674 675 676 677 678 679 680

    if(rtl)
    {
        line = RLE;
        line += LRE;
        line += "<font color=\"" + queryColor + "\">" + nickLine + " %1" + PDF + RLM + " %3</font>";
    }
    else
    {
681 682 683 684
        if (!QApplication::isLeftToRight())
            line += LRE;

        line += "<font color=\"" + queryColor + "\">%1 " + nickLine + " %3</font>";
685 686
    }

687
    line = line.arg(timeStamp(), nick, text);
Eli MacKenzie's avatar
Eli MacKenzie committed
688

689
    emit textToLog(QString("<%1>\t%2").arg(nick, message));
690

691
    doAppend(line, rtl);
692 693
}

Eli MacKenzie's avatar
Eli MacKenzie committed
694
void IRCView::appendChannelAction(const QString& nick, const QString& message)
695
{
Eli MacKenzie's avatar
Eli MacKenzie committed
696 697
    m_tabNotification = Konversation::tnfNormal;
    appendAction(nick, message);
İsmail Dönmez's avatar
 
İsmail Dönmez committed
698 699
}

Eli MacKenzie's avatar
Eli MacKenzie committed
700
void IRCView::appendQueryAction(const QString& nick, const QString& message)
701
{
Eli MacKenzie's avatar
Eli MacKenzie committed
702 703
    m_tabNotification = Konversation::tnfPrivate;
    appendAction(nick, message);
İsmail Dönmez's avatar
 
İsmail Dönmez committed
704 705
}

Eli MacKenzie's avatar
Eli MacKenzie committed
706
void IRCView::appendAction(const QString& nick, const QString& message)
707
{
708
    QString actionColor = Preferences::self()->color(Preferences::ActionMessage).name();
Eli MacKenzie's avatar
Eli MacKenzie committed
709 710

    QString line;
711

712 713 714
    QString nickLine = createNickLine(nick, actionColor, false);

    if (message.isEmpty())
715
    {
716 717 718 719 720 721 722 723 724 725
        if (!QApplication::isLeftToRight())
            line += LRE;

        line += "<font color=\"" + actionColor + "\">%1 * " + nickLine + "</font>";

        line = line.arg(timeStamp(), nick);

        emit textToLog(QString("\t * %1").arg(nick));

        doAppend(line, false);
726 727 728
    }
    else
    {
729 730 731
        QChar::Direction dir;
        QString text(filter(message, actionColor, nick, true,true, false, &dir));
        bool rtl = (dir == QChar::DirR);
732 733 734 735

        if (rtl)
        {
            line = RLE;
736
            line += LRE;
737 738 739 740 741 742
            line += "<font color=\"" + actionColor + "\">" + nickLine + " * %1" + PDF + " %3</font>";
        }
        else
        {
            if (!QApplication::isLeftToRight())
                line += LRE;
743

744 745
            line += "<font color=\"" + actionColor + "\">%1 * " + nickLine + " %3</font>";
        }
746

747
        line = line.arg(timeStamp(), nick, text);
Eli MacKenzie's avatar
Eli MacKenzie committed
748

749
        emit textToLog(QString("\t * %1 %2").arg(nick, message));
Eli MacKenzie's avatar
Eli MacKenzie committed
750

751 752
        doAppend(line, rtl);
    }
Dario Abatianni's avatar
""  
Dario Abatianni committed
753 754
}

Eli MacKenzie's avatar
Eli MacKenzie committed
755
void IRCView::appendServerMessage(const QString& type, const QString& message, bool parseURL)
756
{
757
    QString serverColor = Preferences::self()->color(Preferences::ServerMessage).name();
Eli MacKenzie's avatar
Eli MacKenzie committed
758 759 760 761
    m_tabNotification = Konversation::tnfControl;

    // Fixed width font option for MOTD
    QString fixed;
762
    if(Preferences::self()->fixedMOTD() && !m_fontDataBase.isFixedPitch(font().family()))
763
    {
Eli MacKenzie's avatar
Eli MacKenzie committed
764 765
        if(type == i18n("MOTD"))
            fixed=" face=\"" + KGlobalSettings::fixedFont().family() + "\"";
766 767
    }

Eli MacKenzie's avatar
Eli MacKenzie committed
768
    QString line;
769 770 771
    QChar::Direction dir;
    QString text(filter(message, serverColor, 0 , true, parseURL, false, &dir));
    bool rtl = (dir == QChar::DirR);
772 773 774 775 776 777 778 779 780

    if(rtl)
    {
        line = RLE;
        line += LRE;
        line += "<font color=\"" + serverColor + "\"" + fixed + "><b>[</b>%2<b>]</b> %1" + PDF + " %3</font>";
    }
    else
    {
781 782 783 784
        if (!QApplication::isLeftToRight())
            line += LRE;

        line += "<font color=\"" + serverColor + "\"" + fixed + ">%1 <b>[</b>%2<b>]</b> %3</font>";
785 786
    }

Eike Hein's avatar
Eike Hein committed
787
    line = line.arg(timeStamp(), type, text);
Eli MacKenzie's avatar
Eli MacKenzie committed
788

789
    emit textToLog(QString("%1\t%2").arg(type, message));
Eli MacKenzie's avatar
Eli MacKenzie committed
790

791
    doAppend(line, rtl);
Eli MacKenzie's avatar
Eli MacKenzie committed
792 793
}

794
void IRCView::appendCommandMessage(const QString& type,const QString& message, bool parseURL, bool self)
Eli MacKenzie's avatar
Eli MacKenzie committed
795
{
796
    QString commandColor = Preferences::self()->color(Preferences::CommandMessage).name();
Eli MacKenzie's avatar
Eli MacKenzie committed
797 798
    QString prefix="***";
    m_tabNotification = Konversation::tnfControl;
799

Eli MacKenzie's avatar
Eli MacKenzie committed
800
    if(type == i18n("Join"))
801
    {
Eli MacKenzie's avatar
Eli MacKenzie committed
802 803 804 805
        prefix="-->";
        parseURL=false;
    }
    else if(type == i18n("Part") || type == i18n("Quit"))
806
    {
Eli MacKenzie's avatar
Eli MacKenzie committed
807
        prefix="<--";
808 809
    }

Eli MacKenzie's avatar
Eli MacKenzie committed
810
    prefix=Qt::escape(prefix);
811

Eli MacKenzie's avatar
Eli MacKenzie committed
812
    QString line;
813 814 815
    QChar::Direction dir;
    QString text(filter(message, commandColor, 0, true, parseURL, self, &dir));
    bool rtl = (dir == QChar::DirR);
816 817 818 819 820 821 822 823 824

    if(rtl)
    {
        line = RLE;
        line += LRE;
        line += "<font color=\"" + commandColor + "\">%2 %1" + PDF + " %3</font>";
    }
    else
    {
825 826 827 828
        if (!QApplication::isLeftToRight())
            line += LRE;

        line += "<font color=\"" + commandColor + "\">%1 %2 %3</font>";
829
    }
830

831
    line = line.arg(timeStamp(), prefix, text);
Eli MacKenzie's avatar
Eli MacKenzie committed
832

833
    emit textToLog(QString("%1\t%2").arg(type, message));
Eli MacKenzie's avatar
Eli MacKenzie committed
834

835
    doAppend(line, rtl, self);
Eli MacKenzie's avatar
Eli MacKenzie committed
836 837 838 839 840 841 842
}

void IRCView::appendBacklogMessage(const QString& firstColumn,const QString& rawMessage)
{
    QString time;
    QString message = rawMessage;
    QString nick = firstColumn;
843
    QString backlogColor = Preferences::self()->color(Preferences::BacklogMessage).name();
Eli MacKenzie's avatar
Eli MacKenzie committed
844 845
    m_tabNotification = Konversation::tnfNone;

846
    //The format in Chatwindow::logText is not configurable, so as long as nobody allows square brackets in a date/time format....
847
    int eot = nick.lastIndexOf(' ');
848 849
    time = nick.left(eot);
    nick = nick.mid(eot+1);
Eli MacKenzie's avatar
Eli MacKenzie committed
850

851
    if(!nick.isEmpty() && !nick.startsWith('<') && !nick.startsWith('*'))
852
    {
Eli MacKenzie's avatar
Eli MacKenzie committed
853
        nick = '|' + nick + '|';
854
    }
855

Eli MacKenzie's avatar
Eli MacKenzie committed
856
    // Nicks are in "<nick>" format so replace the "<>"
857 858
    nick.replace('<',"&lt;");
    nick.replace('>',"&gt;");
859

Eli MacKenzie's avatar
Eli MacKenzie committed
860
    QString line;
861 862 863
    QChar::Direction dir;
    QString text(filter(message, backlogColor, NULL, false, false, false, &dir));
    bool rtl = (dir == QChar::DirR);
864 865 866 867 868 869 870 871 872

    if(rtl)
    {
        line = RLE;
        line += LRE;
        line += "<font color=\"" + backlogColor + "\">%2 %1" + PDF + " %3</font>";
    }
    else
    {
873 874 875 876
        if (!QApplication::isLeftToRight())
            line += LRE;

        line += "<font color=\"" + backlogColor + "\">%1 %2 %3</font>";
877
    }
Eli MacKenzie's avatar
Eli MacKenzie committed
878

879
    line = line.arg(time, nick, text);
Eli MacKenzie's avatar
Eli MacKenzie committed
880

881
    doAppend(line, rtl);
Eli MacKenzie's avatar
Eli MacKenzie committed
882 883
}

884
void IRCView::doAppend(const QString& newLine, bool rtl, bool self)
Eli MacKenzie's avatar
Eli MacKenzie committed
885
{
886 887 888
    if (m_rememberLineDirtyBit)
        appendRememberLine();

Eli MacKenzie's avatar
Eli MacKenzie committed
889 890
    if (!self && m_chatWin)
        m_chatWin->activateTabNotification(m_tabNotification);
891

892
    int scrollMax = Preferences::self()->scrollbackMax();
893 894
    if (scrollMax != 0)
    {
895 896
        //don't remove lines if the user has scrolled up to read old lines
        bool atBottom = (verticalScrollBar()->value() == verticalScrollBar()->maximum());
897
        document()->setMaximumBlockCount(atBottom ? scrollMax : document()->maximumBlockCount() + 1);
898
    }
Eli MacKenzie's avatar
Eli MacKenzie committed
899

900
    doRawAppend(newLine, rtl);
901

Eli MacKenzie's avatar
Eli MacKenzie committed
902 903 904 905 906 907 908
    //FIXME: Disable auto-text for DCC Chats since we don't have a server to parse wildcards.
    if (!m_autoTextToSend.isEmpty() && m_server)
    {
        // replace placeholders in autoText
        QString sendText = m_server->parseWildcards(m_autoTextToSend,m_server->getNickname(),
            QString(), QString(), QString(), QString());
        // avoid recursion due to signalling
Wouter Becq's avatar
Wouter Becq committed
909
        m_autoTextToSend.clear();
Eli MacKenzie's avatar
Eli MacKenzie committed
910 911 912 913 914
        // send signal only now
        emit autoText(sendText);
    }
    else
    {
Wouter Becq's avatar
Wouter Becq committed
915
        m_autoTextToSend.clear();
İsmail Dönmez's avatar
 
İsmail Dönmez committed
916
    }
Eli MacKenzie's avatar
Eli MacKenzie committed
917 918 919

    if (!m_lastStatusText.isEmpty())
        emit clearStatusBarTempText();
920 921
}

922
void IRCView::doRawAppend(const QString& newLine, bool rtl)
923
{
924
    SelectionPin selpin(this); // HACK stop selection at end from growing
925 926
    QString line(newLine);

927
    line.remove('\n');
928 929

    KTextBrowser::append(line);
930

931 932
    QTextCursor formatCursor(document()->lastBlock());
    QTextBlockFormat format = formatCursor.blockFormat();
933

934
    if (!QApplication::isLeftToRight())
935 936
        rtl = !rtl;

937 938
    format.setAlignment(rtl ? Qt::AlignRight : Qt::AlignLeft);
    formatCursor.setBlockFormat(format);
939 940
}

Eli MacKenzie's avatar
Eli MacKenzie committed
941
QString IRCView::timeStamp()
942
{
943
    if(Preferences::self()->timestamping())
944
    {
Eli MacKenzie's avatar
Eli MacKenzie committed
945
        QTime time = QTime::currentTime();
946 947
        QString timeColor = Preferences::self()->color(Preferences::Time).name();
        QString timeFormat = Preferences::self()->timestampFormat();
Eli MacKenzie's avatar
Eli MacKenzie committed
948 949
        QString timeString;

950
        if(!Preferences::self()->showDate())
951
        {
952
            timeString = QString(QLatin1String("<font color=\"") + timeColor + QLatin1String("\">[%1]</font> ")).arg(time.toString(timeFormat));
953 954 955
        }
        else
        {
Eli MacKenzie's avatar
Eli MacKenzie committed
956 957 958 959 960
            QDate date = QDate::currentDate