/*
  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.
*/

/*
  Copyright (C) 2002 Dario Abatianni <eisfuchs@tigress.com>
  Copyright (C) 2005-2007 Peter Simonsson <psn@linux.se>
  Copyright (C) 2006-2008 Eike Hein <hein@kde.org>
*/

#include "ircview.h"
#include "channel.h"
#include "dccchat.h"
#include "konversationapplication.h"
#include "konversationmainwindow.h"
#include "viewcontainer.h"
#include "connectionmanager.h"
#include "highlight.h"
#include "server.h"
#include "konversationsound.h"
#include "common.h"
#include "emoticon.h"
#include "notificationhandler.h"

#include <tqrichtext_p.h>
#include <tqstylesheet.h>
#include <tqstringlist.h>
#include <tqregexp.h>
#include <tqtextbrowser.h>
#include <tqclipboard.h>
#include <tqbrush.h>
#include <tqevent.h>
#include <tqdragobject.h>
#include <tqpopupmenu.h>
#include <tqwhatsthis.h>
#include <tqmap.h>
#include <tqcolor.h>
#include <tqscrollbar.h>
#include <tqcursor.h>

#include <dcopref.h>
#include <dcopclient.h>
#include <tdemessagebox.h>
#include <tdelocale.h>
#include <kurl.h>
#include <kurldrag.h>
#include <kbookmark.h>
#include <kbookmarkmanager.h>
#include <tdeversion.h>
#include <kstandarddirs.h>
#include <krun.h>
#include <kprocess.h>
#include <kiconloader.h>
#include <kshell.h>
#include <tdepopupmenu.h>
#include <tdeaction.h>
#include <tdeglobalsettings.h>
#include <kdebug.h>
#include <tdemenubar.h>
#include <tdefiledialog.h>
#include <tdeio/job.h>
#include <tdestdaccel.h>
#include <tdeglobal.h>


IRCView::IRCView(TQWidget* parent, Server* newServer) : KTextBrowser(parent)
{
    m_copyUrlMenu = false;
    m_resetScrollbar = true;
    m_offset = 0;
    m_mousePressed = false;
    m_isOnNick = false;
    m_isOnChannel = false;
    m_chatWin = 0;
    m_findParagraph=0;
    m_findIndex=0;
    m_nickPopup = 0;
    m_channelPopup = 0;

    m_rememberLineParagraph = -1;
    m_rememberLineDirtyBit = false;

    m_disableEnsureCursorVisible = false;
    m_wasPainted = false;

    setAutoFormatting(TQTextEdit::AutoNone);
    setUndoRedoEnabled(0);
    setLinkUnderline(false);
    setVScrollBarMode(AlwaysOn);
    setHScrollBarMode(AlwaysOff);
    setWrapPolicy(TQTextEdit::AtWordOrDocumentBoundary);
    setNotifyClick(true);
    setFocusPolicy(TQWidget::ClickFocus);

    // set basic style sheet for <p> to make paragraph spacing possible
    TQStyleSheet* sheet=new TQStyleSheet(this,"ircview_style_sheet");
    new TQStyleSheetItem(sheet,"p");
    setStyleSheet(sheet);

    m_popup = new TQPopupMenu(this,"ircview_context_menu");
    toggleMenuBarSeparator = m_popup->insertSeparator();
    m_popup->setItemVisible(toggleMenuBarSeparator, false);
    copyUrlMenuSeparator = m_popup->insertSeparator();
    m_popup->setItemVisible(copyUrlMenuSeparator, false);
    m_popup->insertItem(SmallIconSet("edit-copy"),i18n("&Copy"),Copy);
    m_popup->insertItem(i18n("Select All"),SelectAll);
    m_popup->insertItem(SmallIcon("edit-find"),i18n("Find Text..."),Search);

    setServer(newServer);

    setViewBackground(Preferences::color(Preferences::TextViewBackground),TQString());

    if (Preferences::customTextFont())
        setFont(Preferences::textFont());
    else
        setFont(TDEGlobalSettings::generalFont());

    if (Preferences::useParagraphSpacing()) enableParagraphSpacing();

    connect(this, TQ_SIGNAL(highlighted(const TQString&)), this, TQ_SLOT(highlightedSlot(const TQString&)));
}

IRCView::~IRCView()
{
    delete m_popup;
}

void IRCView::enableParagraphSpacing()
{
    // Set style sheet for <p> to define paragraph spacing.
    TQStyleSheet* sheet = styleSheet();

    if (!sheet) return;

    TQStyleSheetItem* style = sheet->item("p");

    if (!style)
    {
        kdDebug() << "IRCView::updateStyleSheet(): style == 0!" << endl;

        return;
    }

    style->setDisplayMode(TQStyleSheetItem::DisplayBlock);
    style->setMargin(TQStyleSheetItem::MarginVertical, Preferences::paragraphSpacing());
    style->setSelfNesting(false);
}

void IRCView::setViewBackground(const TQColor& backgroundColor, const TQString& pixmapName)
{
    TQPixmap backgroundPixmap;
    backgroundPixmap.load(pixmapName);

    if(backgroundPixmap.isNull())
    {
        setPaper(backgroundColor);
    }
    else
    {
        TQBrush backgroundBrush;
        backgroundBrush.setColor(backgroundColor);
        backgroundBrush.setPixmap(backgroundPixmap);
        setPaper(backgroundBrush);
    }
}

void IRCView::setServer(Server* newServer)
{
    m_server = newServer;

    if (newServer)
    {
        TDEAction *action = newServer->getViewContainer()->actionCollection()->action("open_logfile");
        Q_ASSERT(action);
        if(!action) return;
        m_popup->insertSeparator();
        action->plug(m_popup);
    }

}

const TQString& IRCView::getContextNick() const
{
    return m_currentNick;
}

void IRCView::clearContextNick()
{
    m_currentNick = TQString();
}

void IRCView::clear()
{
    m_buffer = TQString();
    KTextBrowser::setText("");
    wipeLineParagraphs();
}

void IRCView::highlightedSlot(const TQString& _link)
{
    TQString link = _link;
    // HACK Replace % with \x03 in the url to keep TQt from doing stupid things
    link = link.replace ('\x03', "%");
    //Hack to handle the fact that we get a decoded url
    link = KURL::fromPathOrURL(link).url();

    // HACK:Use space as a placeholder for \ as TQt tries to be clever and does a replace to / in urls in TQTextEdit
    if(link.startsWith("#"))
    {
        link = link.replace(' ', "\\");
    }

    //we just saw this a second ago.  no need to reemit.
    if (link == m_lastStatusText && !link.isEmpty())
        return;

    // remember current URL to overcome link clicking problems in TQTextBrowser
    m_highlightedURL = link;

    if (link.isEmpty())
    {
        if (!m_lastStatusText.isEmpty()) 
        {
            emit clearStatusBarTempText();
            m_lastStatusText = TQString();
        }
    } else
    {
        m_lastStatusText = link;
    }

    if(!link.startsWith("#"))
    {
        m_isOnNick = false;
        m_isOnChannel = false;

        if (!link.isEmpty()) {
            //link therefore != m_lastStatusText  so emit with this new text
            emit setStatusBarTempText(link);
        }
        if (link.isEmpty() && m_copyUrlMenu)
        {
            m_popup->removeItem(CopyUrl);
            m_popup->removeItem(Bookmark);
            m_popup->removeItem(SaveAs);
            m_popup->setItemVisible(copyUrlMenuSeparator, false);
            m_copyUrlMenu = false;

        }
        else if (!link.isEmpty() && !m_copyUrlMenu)
        {
            m_popup->setItemVisible(copyUrlMenuSeparator, true);
            m_popup->insertItem(SmallIcon("edit-copy"), i18n("Copy URL to Clipboard"), CopyUrl, 1);
            m_popup->insertItem(SmallIcon("bookmark"), i18n("Add to Bookmarks"), Bookmark, 2);
            m_popup->insertItem(SmallIcon("document-save-as"), i18n("Save Link As..."), SaveAs, 3);
            m_copyUrlMenu = true;
            m_urlToCopy = link;
        }
    }
    else if (link.startsWith("#") && !link.startsWith("##"))
    {
        m_currentNick = link.mid(1);
        m_nickPopup->changeTitle(m_nickPopupId,m_currentNick);
        m_isOnNick = true;
        emit setStatusBarTempText(i18n("Open a query with %1").arg(m_currentNick));
    }
    else
    {
        // link.startsWith("##")
        m_currentChannel = link.mid(1);

        TQString prettyId = m_currentChannel;

        if (prettyId.length()>15)
        {
            prettyId.truncate(15);
            prettyId.append("...");
        }

        m_channelPopup->changeTitle(m_channelPopupId,prettyId);
        m_isOnChannel = true;
        emit setStatusBarTempText(i18n("Join the channel %1").arg(m_currentChannel));
    }
}

void IRCView::openLink(const TQString& url, bool newTab)
{
    if (!url.isEmpty() && !url.startsWith("#"))
    {
        if (url.startsWith("irc://"))
        {
            KonversationApplication* konvApp = static_cast<KonversationApplication*>(kapp);
            konvApp->getConnectionManager()->connectTo(Konversation::SilentlyReuseConnection, url);
        }
        else if (!Preferences::useCustomBrowser() || url.startsWith("mailto:"))
        {
            if(newTab && !url.startsWith("mailto:"))
            {
                TQCString foundApp, foundObj;
                TQByteArray data;
                TQDataStream str(data, IO_WriteOnly);
                if( TDEApplication::dcopClient()->findObject("konqueror*", "konqueror-mainwindow*",
                    "windowCanBeUsedForTab()", data, foundApp, foundObj, false, 3000))
                {
                    DCOPRef ref(foundApp, foundObj);
                    ref.call("newTab", url);
                } else
                    new KRun(KURL::fromPathOrURL(url));
            } else
                new KRun(KURL::fromPathOrURL(url));
        }
        else
        {
            TQString cmd = Preferences::webBrowserCmd();
            cmd.replace("%u", url);
            TDEProcess *proc = new TDEProcess;
            TQStringList cmdAndArgs = KShell::splitArgs(cmd);
            *proc << cmdAndArgs;
            //      This code will also work, but starts an extra shell process.
            //      kdDebug() << "IRCView::urlClickSlot(): cmd = " << cmd << endl;
            //      *proc << cmd;
            //      proc->setUseShell(true);
            proc->start(TDEProcess::DontCare);
            delete proc;
        }
    }
    //FIXME: Don't do channel links in DCC Chats to begin with since they don't have a server.
    else if (url.startsWith("##") && m_server && m_server->isConnected())
    {
        TQString channel(url);
        channel.replace("##", "#");
        m_server->sendJoinCommand(channel);
    }
    //FIXME: Don't do user links in DCC Chats to begin with since they don't have a server.
    else if (url.startsWith("#") && m_server && m_server->isConnected()) 
    {
        TQString recipient(url);
        recipient.remove("#");
        NickInfoPtr nickInfo = m_server->obtainNickInfo(recipient);
        m_server->addQuery(nickInfo, true /*we initiated*/);
    }
}

void IRCView::replaceDecoration(TQString& line, char decoration, char replacement)
{
    int pos;
    bool decorated = false;

    while((pos=line.find(decoration))!=-1)
    {
        line.replace(pos,1,(decorated) ? TQString("</%1>").arg(replacement) : TQString("<%1>").arg(replacement));
        decorated = !decorated;
    }
}

TQString IRCView::filter(const TQString& line, const TQString& defaultColor, const TQString& whoSent,
bool doHighlight, bool parseURL, bool self)
{
    TQString filteredLine(line);
    KonversationApplication* konvApp = static_cast<KonversationApplication*>(kapp);

    //Since we can't turn off whitespace simplification withouteliminating text wrapping,
    //  if the line starts with a space turn it into a non-breaking space.
    //    (which magically turns back into a space on copy)

    if (filteredLine[0]==' ')
        filteredLine[0]='\xA0';

    // TODO: Use TQStyleSheet::escape() here
    // Replace all < with &lt;
    filteredLine.replace("<","\x0blt;");
    // Replace all > with &gt;
    filteredLine.replace(">", "\x0bgt;");

    #if 0
    if(!Preferences::disableExpansion())
    {
        TQRegExp boldRe("\\*\\*([a-zA-Z0-9]+)\\*\\*");
        TQRegExp underRe("\\_\\_([a-zA-Z0-9]+)\\_\\_");
        int position = 0;
        TQString replacement;

        while( position >= 0)
        {
            position = boldRe.search(filteredLine, position);
            if( position > -1)
            {
                replacement = boldRe.cap(1);
                replacement = "\x02"+replacement+"\x02";
                filteredLine.replace(position,replacement.length()+2,replacement);
            }
            position += boldRe.matchedLength();
        }

        position = 0;
        while( position >= 0)
        {
            position = underRe.search(filteredLine, position);
            if( position > -1)
            {
                replacement = underRe.cap(1);
                replacement = "\x1f"+replacement+"\x1f";
                filteredLine.replace(position,replacement.length()+2,replacement);
            }
            position += underRe.matchedLength();
        }
    }
    #endif

    if(filteredLine.find("\x07") != -1)
    {
        if(Preferences::beep())
        {
            kapp->beep();
        }
    }

    // replace \003 and \017 codes with rich text color codes
    // captures          1    2                   23 4                   4 3     1
    TQRegExp colorRegExp("(\003([0-9]|0[0-9]|1[0-5]|)(,([0-9]|0[0-9]|1[0-5])|,|)|\017)");

    int pos;
    bool allowColors = Preferences::allowColorCodes();
    bool firstColor = true;
    TQString colorString;

    while((pos=colorRegExp.search(filteredLine))!=-1)
    {
        if(!allowColors)
        {
            colorString = TQString();
        }
        else
        {
            colorString = (firstColor) ? TQString() : TQString("</font>");

            // reset colors on \017 to default value
            if(colorRegExp.cap(1) == "\017")
                colorString += "<font color=\""+defaultColor+"\">";
            else
            {
                if(!colorRegExp.cap(2).isEmpty())
                {
                    int foregroundColor = colorRegExp.cap(2).toInt();
                    colorString += "<font color=\"" + Preferences::ircColorCode(foregroundColor).name() + "\">";
                }
                else
                {
                    colorString += "<font color=\""+defaultColor+"\">";
                }
            }

            firstColor = false;
        }

        filteredLine.replace(pos, colorRegExp.cap(0).length(), colorString);
    }

    if(!firstColor)
        filteredLine+="</font>";

    // Replace all text decorations
    // TODO: \017 should reset all textt decorations to plain text
    replaceDecoration(filteredLine,'\x02','b');
    replaceDecoration(filteredLine,'\x09','i');
    replaceDecoration(filteredLine,'\x13','s');
    replaceDecoration(filteredLine,'\x15','u');
    replaceDecoration(filteredLine,'\x16','b');   // should be inverse
    replaceDecoration(filteredLine,'\x1f','u');

    if(parseURL)
    {
        filteredLine = Konversation::tagURLs(filteredLine, whoSent);
    }
    else
    {
        // Change & to &amp; to prevent html entities to do strange things to the text
        filteredLine.replace('&', "&amp;");
        filteredLine.replace("\x0b", "&");
    }

    filteredLine = Konversation::EmotIcon::filter(filteredLine, fontMetrics());

    // Highlight
    TQString ownNick;

    if (m_server)
    {
        ownNick = m_server->getNickname();
    }
    else if (m_chatWin->getType() == ChatWindow::DccChat)
    {
        ownNick = static_cast<DccChat*>(m_chatWin)->getOwnNick();
    }

    if(doHighlight && (whoSent != ownNick) && !self)
    {
        TQString highlightColor;

        if(Preferences::highlightNick() &&
            filteredLine.lower().find(TQRegExp("(^|[^\\d\\w])" +
            TQRegExp::escape(ownNick.lower()) +
            "([^\\d\\w]|$)")) != -1)
        {
            // highlight current nickname
            highlightColor = Preferences::highlightNickColor().name();
            m_tabNotification = Konversation::tnfNick;
        }
        else
        {
            TQPtrList<Highlight> highlightList = Preferences::highlightList();
            TQPtrListIterator<Highlight> it(highlightList);
            Highlight* highlight = it.current();
            bool patternFound = false;
            int index = 0;

            TQStringList captures;
            while(highlight)
            {
                if(highlight->getRegExp())
                {
                    TQRegExp needleReg=highlight->getPattern();
                    needleReg.setCaseSensitive(false);
                                                  // highlight regexp in text
                    patternFound = ((filteredLine.find(needleReg) != -1) ||
                                                  // highlight regexp in nickname
                        (whoSent.find(needleReg) != -1));

                    // remember captured patterns for later
                    captures=needleReg.capturedTexts();

                }
                else
                {
                    TQString needle=highlight->getPattern();
                                                  // highlight patterns in text
                    patternFound = ((filteredLine.find(needle, 0, false) != -1) ||
                                                  // highlight patterns in nickname
                        (whoSent.find(needle, 0, false) != -1));
                }

                if(!patternFound)
                {
                    ++it;
                    highlight = it.current();
                    ++index;
                }
                else
                {
                    break;
                }
            }

            if(patternFound)
            {
                highlightColor = highlight->getColor().name();
                m_highlightColor = highlightColor;
                m_tabNotification = Konversation::tnfHighlight;

                if(Preferences::highlightSoundsEnabled() && m_chatWin->notificationsEnabled())
                {
                    konvApp->sound()->play(highlight->getSoundURL());
                }

                konvApp->notificationHandler()->highlight(m_chatWin, whoSent, line);
                m_autoTextToSend = highlight->getAutoText();

                // replace %0 - %9 in regex groups
                for(unsigned int capture=0;capture<captures.count();capture++)
                {
                  m_autoTextToSend.replace(TQString("%%1").arg(capture),captures[capture]);
                }
                m_autoTextToSend.replace(TQRegExp("%[0-9]"),TQString());
            }
        }

        // apply found highlight color to line
        if(!highlightColor.isEmpty())
        {
            filteredLine = "<font color=\"" + highlightColor + "\">" + filteredLine + "</font>";
        }
    }
    else if(doHighlight && (whoSent == ownNick) && Preferences::highlightOwnLines())
    {
        // highlight own lines
        filteredLine = "<font color=\"" + Preferences::highlightOwnLinesColor().name() +
            "\">" + filteredLine + "</font>";
    }

    // Replace pairs of spaces with "<space>&nbsp;" to preserve some semblance of text wrapping
    filteredLine.replace("  "," \xA0");
    return filteredLine;
}

TQString IRCView::createNickLine(const TQString& nick, bool encapsulateNick, bool privMsg)
{
    TQString nickLine = "%2";

    if(Preferences::useClickableNicks())
    {
        // HACK:Use space as a placeholder for \ as TQt tries to be clever and does a replace to / in urls in TQTextEdit
        nickLine = "<a href=\"#" + TQString(nick).replace('\\', " ") + "\">%2</a>";
    }

    if(privMsg)
    {
        nickLine.prepend ("-&gt; ");
    }

    if(encapsulateNick)
        nickLine = "&lt;" + nickLine + "&gt;";

    if(Preferences::useColoredNicks() && m_server)
    {
        TQString nickColor;

        if (nick != m_server->getNickname())
            nickColor = Preferences::nickColor(m_server->obtainNickInfo(nick)->getNickColor()).name();
        else
            nickColor =  Preferences::nickColor(8).name();

        if(nickColor == "#000000")
        {
            nickColor = "#000001";                    // HACK Working around TQTextBrowser's auto link coloring
        }

        nickLine = "<font color=\"" + nickColor + "\">"+nickLine+"</font>";
    }
    //FIXME: Another last-minute hack to get DCC Chat colored nicknames
    // working. We can't use NickInfo::getNickColor() because we don't
    // have a server.
    else if (Preferences::useColoredNicks() && m_chatWin->getType() == ChatWindow::DccChat)
    {
        TQString ownNick = static_cast<DccChat*>(m_chatWin)->getOwnNick();
        TQString nickColor;

        if (nick != ownNick)
        {
            int nickvalue = 0;

            for (uint index = 0; index < nick.length(); index++)
            {
                nickvalue += nick[index].unicode();
            }

            nickColor = Preferences::nickColor((nickvalue % 8)).name();
        }
        else
            nickColor =  Preferences::nickColor(8).name();

        if(nickColor == "#000000")
        {
            nickColor = "#000001";                    // HACK Working around TQTextBrowser's auto link coloring
        }

        nickLine = "<font color=\"" + nickColor + "\">"+nickLine+"</font>";
    }

    if(Preferences::useBoldNicks())
        nickLine = "<b>" + nickLine + "</b>";

    return nickLine;
}

void IRCView::append(const TQString& nick,const TQString& message)
{
    TQString channelColor = Preferences::color(Preferences::ChannelMessage).name();

    if(channelColor  == "#000000")
    {
        channelColor = "#000001";              // HACK Working around TQTextBrowser's auto link coloring
    }

    TQString line;
    m_tabNotification = Konversation::tnfNormal;

    TQString nickLine = createNickLine(nick);

    if(basicDirection(message) == TQChar::DirR)
    {
        line = RLE;
        line += LRE;
        line += "<p><font color=\"" + channelColor + "\">" + nickLine + " %1" + PDF + RLM + " %3</font></p>\n";
    }
    else
    {
        line = "<p><font color=\"" + channelColor + "\">%1" + nickLine + " %3</font></p>\n";
    }

    line = line.arg(timeStamp(), nick, filter(message, channelColor, nick, true));

    emit textToLog(TQString("<%1>\t%2").arg(nick).arg(message));

    doAppend(line);
}

void IRCView::insertRememberLine()
{
    m_rememberLineDirtyBit = true;

    if (!Preferences::automaticRememberLineOnlyOnTextChange())
        appendRememberLine();
}

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

void IRCView::appendRememberLine()
{
    m_rememberLineDirtyBit = false;

    if (m_rememberLineParagraph == paragraphs() - 1)
        return;

    if (m_rememberLineParagraph > -1)
    {
        removeParagraph(m_rememberLineParagraph);

        TQValueList<int> newList;
        TQValueList<int>::ConstIterator it;

        for (it = m_markerLineParagraphs.begin(); it != m_markerLineParagraphs.end(); ++it)
        {
            if ((*it) < m_rememberLineParagraph)
                newList << (*it);
            else if ((*it) > m_rememberLineParagraph)
                newList << (*it) - 1;
        }

        m_markerLineParagraphs = newList;
    }

    repaintChanged();

    appendLine(Preferences::color(Preferences::CommandMessage).name());

    m_rememberLineParagraph = paragraphs() - 1;
}

void IRCView::insertMarkerLine()
{
    qHeapSort(m_markerLineParagraphs);

    if (m_markerLineParagraphs.last() == paragraphs() - 1)
        return;

    bool rememberLineDirtyBit = m_rememberLineDirtyBit;
    m_rememberLineDirtyBit = false;

    appendLine(Preferences::color(Preferences::ActionMessage).name());

    m_rememberLineDirtyBit = rememberLineDirtyBit;

    m_markerLineParagraphs.append(paragraphs() - 1);
}

void IRCView::appendLine(const TQString& color)
{
    TQColor channelColor = Preferences::color(Preferences::ChannelMessage);

    TQString line = "<p><font style=\"font-size:1pt;\"><br><br><hr color=\""+color+"\" noshade></font></p>\n";

    doAppend(line, true);
}

void IRCView::clearLines()
{
    if (m_rememberLineParagraph > -1)
        m_markerLineParagraphs.append(m_rememberLineParagraph);

    if (m_markerLineParagraphs.count() > 0)
    {
        qHeapSort(m_markerLineParagraphs);

        TQValueList<int>::ConstIterator it;
        int removeCounter = 0;

        for (it = m_markerLineParagraphs.begin(); it != m_markerLineParagraphs.end(); ++it)
        {
            removeParagraph((*it) - removeCounter);
            removeCounter++;
        }

        wipeLineParagraphs();

        repaintChanged();
    }
}

bool IRCView::hasLines()
{
    if (m_rememberLineParagraph > -1 || m_markerLineParagraphs.count() > 0)
        return true;

    return false;
}

void IRCView::updateLineParagraphs(int numRemoved)
{
    if (m_rememberLineParagraph - numRemoved < 0)
        m_rememberLineParagraph = -1;
    else
        m_rememberLineParagraph -= numRemoved;

    if (!m_markerLineParagraphs.isEmpty())
    {
        TQValueList<int> newMarkerLineParagraphs;

        TQValueList<int>::const_iterator it;

        for (it = m_markerLineParagraphs.begin(); it != m_markerLineParagraphs.end(); ++it)
        {
            if ((*it) - numRemoved >= 0)
                newMarkerLineParagraphs << ((*it) - numRemoved);
        }

        m_markerLineParagraphs = newMarkerLineParagraphs;
    }
}

void IRCView::wipeLineParagraphs()
{
    m_markerLineParagraphs.clear();
    m_rememberLineParagraph = -1;
}

void IRCView::appendRaw(const TQString& message, bool suppressTimestamps, bool self)
{
    TQColor channelColor=Preferences::color(Preferences::ChannelMessage);
    TQString line;
    m_tabNotification = Konversation::tnfNone;

    if(suppressTimestamps)
    {
        line = TQString("<p><font color=\"" + channelColor.name() + "\">" + message + "</font></p>\n");
    }
    else
    {
        line = TQString("<p>" + timeStamp() + " <font color=\"" + channelColor.name() + "\">" + message + "</font></p>\n");
    }

    doAppend(line, self);
}

void IRCView::appendQuery(const TQString& nick, const TQString& message, bool inChannel)
{
    TQString queryColor=Preferences::color(Preferences::QueryMessage).name();

    if(queryColor  == "#000000")
    {
        queryColor = "#000001";                // HACK Working around TQTextBrowser's auto link coloring
    }

    TQString line;
    m_tabNotification = Konversation::tnfPrivate;

    TQString nickLine = createNickLine(nick, true, inChannel);

    if(basicDirection(message) == TQChar::DirR)
    {
        line = RLE;
        line += LRE;
        line += "<p><font color=\"" + queryColor + "\">" + nickLine + " %1" + PDF + " %3</font></p>\n";
    }
    else
    {
        line = "<p><font color=\"" + queryColor + "\">%1 " + nickLine + " %3</font></p>\n";
    }

    line = line.arg(timeStamp(), nick, filter(message, queryColor, nick, true));

    emit textToLog(TQString("<%1>\t%2").arg(nick).arg(message));

    doAppend(line);
}

void IRCView::appendChannelAction(const TQString& nick,const TQString& message)
{
    m_tabNotification = Konversation::tnfNormal;

    appendAction(nick, message);
}


void IRCView::appendQueryAction(const TQString& nick,const TQString& message)
{
    m_tabNotification = Konversation::tnfPrivate;

    appendAction(nick, message);
}

void IRCView::appendAction(const TQString& nick,const TQString& message)
{
    TQString actionColor=Preferences::color(Preferences::ActionMessage).name();

    // HACK Working around TQTextBrowser's auto link coloring
    if (actionColor  == "#000000") actionColor = "#000001";

    TQString line;
    TQString nickLine = createNickLine(nick, false);

    if (basicDirection(message) == TQChar::DirR)
    {
        line = RLE;
        line += LRE;
        line += "<p><font color=\"" + actionColor + "\">" + nickLine + " * %1" + PDF + " %3</font></p>\n";
    }
    else
    {
        line = "<p><font color=\"" + actionColor + "\">%1 * " + nickLine + " %3</font></p>\n";
    }

    line = line.arg(timeStamp(), nick, filter(message, actionColor, nick, true));

    emit textToLog(TQString("\t * %1 %2").arg(nick).arg(message));

    doAppend(line);
}

void IRCView::appendServerMessage(const TQString& type, const TQString& message, bool parseURL)
{
    TQString serverColor = Preferences::color(Preferences::ServerMessage).name();
    m_tabNotification = Konversation::tnfControl;

    // Fixed width font option for MOTD
    TQString fixed;
    if(Preferences::fixedMOTD() && !m_fontDataBase.isFixedPitch(font().family()))
    {
        if(type == i18n("MOTD"))
            fixed=" face=\"" + TDEGlobalSettings::fixedFont().family() + "\"";
    }

    TQString line;

    if(basicDirection(message) == TQChar::DirR)
    {
        line = RLE;
        line += LRE;
        line += "<p><font color=\"" + serverColor + "\"" + fixed + "><b>[</b>%2<b>]</b> %1" + PDF + " %3</font></p>\n";
    }
    else
    {
        line = "<p><font color=\"" + serverColor + "\"" + fixed + ">%1 <b>[</b>%2<b>]</b> %3</font></p>\n";
    }

    if(type != i18n("Notify"))
        line = line.arg(timeStamp(), type, filter(message, serverColor, 0 , true, parseURL));
    else
        line = "<font color=\"" + serverColor + "\">"+line.arg(timeStamp(), type, message)+"</font>";

    emit textToLog(TQString("%1\t%2").arg(type).arg(message));

    doAppend(line);
}

void IRCView::appendCommandMessage(const TQString& type,const TQString& message, bool important, bool parseURL, bool self)
{
    if (Preferences::hideUnimportantEvents() && !important)
        return;

    TQString commandColor = Preferences::color(Preferences::CommandMessage).name();
    TQString line;
    TQString prefix="***";
    m_tabNotification = Konversation::tnfControl;

    if(type == i18n("Join"))
    {
        prefix="-->";
        parseURL=false;
    }
    else if(type == i18n("Part") || type == i18n("Quit"))
    {
        prefix="<--";
    }

    prefix=TQStyleSheet::escape(prefix);

    if(basicDirection(message) == TQChar::DirR)
    {
        line = RLE;
        line += LRE;
        line += "<p><font color=\"" + commandColor + "\">%2 %1" + PDF + " %3</font></p>\n";
    }
    else
    {
        line = "<p><font color=\"" + commandColor + "\">%1 %2 %3</font></p>\n";
    }

    line = line.arg(timeStamp(), prefix, filter(message, commandColor, 0, true, parseURL, self));

    emit textToLog(TQString("%1\t%2").arg(type).arg(message));

    doAppend(line, self);
}

void IRCView::appendBacklogMessage(const TQString& firstColumn,const TQString& rawMessage)
{
    TQString time;
    TQString message = rawMessage;
    TQString nick = firstColumn;
    TQString backlogColor = Preferences::color(Preferences::BacklogMessage).name();
    m_tabNotification = Konversation::tnfNone;

    time = nick.section(' ', 0, 4);
    nick = nick.section(' ', 5);

    if(!nick.isEmpty() && !nick.startsWith("<") && !nick.startsWith("*"))
    {
        nick = '|' + nick + '|';
    }

    // Nicks are in "<nick>" format so replace the "<>"
    nick.replace("<","&lt;");
    nick.replace(">","&gt;");

    TQString line;

    if(basicDirection(message) == TQChar::DirR)
    {
        line = "<p><font color=\"" + backlogColor + "\">%2 %1 %3</font></p>\n";
    }
    else
    {
        line = "<p><font color=\"" + backlogColor + "\">%1 %2 %3</font></p>\n";
    }

    line = line.arg(time, nick, filter(message, backlogColor, NULL, false, false));

    doAppend(line);
}

//without any display update stuff that freaks out the scrollview
void IRCView::removeSelectedText( int selNum )
{
    TQTextDocument* doc=document();

    for ( int i = 0; i < (int)doc->numSelections(); ++i )
    {
        if ( i == selNum )
            continue;
        doc->removeSelection( i );
    }
    // ...snip...

    doc->removeSelectedText( selNum, TQTextEdit::textCursor() );

    // ...snip...
}

void IRCView::scrollToBottom()
{
    // TQTextEdit::scrollToBottom does sync() too, but we don't want it because its slow
    setContentsPos( contentsX(), contentsHeight() - visibleHeight() );
}

void IRCView::ensureCursorVisible()
{
    if (!m_disableEnsureCursorVisible)
        TQTextEdit::ensureCursorVisible();
}

void IRCView::doAppend(const TQString& newLine, bool self)
{
    if (m_rememberLineDirtyBit) appendRememberLine();

    // Add line to buffer
    TQString line(newLine);

    if (!self && m_chatWin) m_chatWin->activateTabNotification(m_tabNotification);

    // scroll view only if the scroll bar is already at the bottom
    bool doScroll = ( KTextBrowser::verticalScrollBar()->value() == KTextBrowser::verticalScrollBar()->maxValue());

    line.remove('\n');                        // TODO why have newlines? we get <p>, so the \n are unnecessary...

    bool up = KTextBrowser::viewport()->isUpdatesEnabled();

    KTextBrowser::viewport()->setUpdatesEnabled(false);
    int paraFrom, indexFrom, paraTo, indexTo;
    bool textselected = hasSelectedText();

    // Remember the selection so we don't loose it when adding the new line
    if(textselected)
        getSelection(&paraFrom, &indexFrom, &paraTo, &indexTo);

    document()->lastParagraph()->format();

    KTextBrowser::append(line);

    document()->lastParagraph()->format();

    // get maximum number of lines we want to have in the scollback buffer
    int sbm = Preferences::scrollbackMax();

    // Explanation: the scrolling mechanism cannot handle the buffer changing when the scrollbar is not
    //              at an end, so the scrollbar wets its pants and forgets who it is for ten minutes
    // Also make sure not to delete any lines if maximum lines of scrollback is set to 0 (unlimited)
    if (sbm && doScroll)
    {
        int numRemoved = paragraphs() - sbm;

        if (numRemoved > 0)
        {
            for (int index = numRemoved; index > 0; --index)
            {
                removeParagraph(0);
            }

            updateLineParagraphs(numRemoved);

            if (textselected)
            {
                paraFrom -= numRemoved;
                paraTo -= numRemoved;
            }
        }
    }

    resizeContents(contentsWidth(), document()->height());
    KTextBrowser::viewport()->setUpdatesEnabled(up);

    // Restore selection
    if(textselected && paraFrom >= 0 && paraTo >= 0) {
        // HACK: do not change scrollback position: setSelection() calls
	// ensureCursorVisible()
        m_disableEnsureCursorVisible = true;
        setSelection(paraFrom, indexFrom, paraTo, indexTo);
        m_disableEnsureCursorVisible = false;
    }

    if (doScroll) updateScrollBarPos();

    //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
        TQString sendText = m_server->parseWildcards(m_autoTextToSend,m_server->getNickname(),
            TQString(), TQString(), TQString(), TQString());
        // avoid recursion due to signalling
        m_autoTextToSend = TQString();
        // send signal only now
        emit autoText(sendText);
    }
    else
    {
        m_autoTextToSend = TQString();
    }

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

// remember if scrollbar was positioned at the end of the text or not
void IRCView::hideEvent(TQHideEvent* /* event */) {
    m_resetScrollbar = ((contentsHeight()-visibleHeight()) == contentsY());
}

// Workaround to scroll to the end of the TextView when it's shown
void IRCView::showEvent(TQShowEvent* event) {
    Q_UNUSED(event);
    // did the user scroll the view to the end of the text before hiding?
    if (m_resetScrollbar)
    {
        // NOTE: when the view has not been painted yet, contentsHeight() might
        // not return accurate information and hence this code won't reset
        // scrollback properly in such a case. See paintEvent() hack below.
        moveCursor(MoveEnd,false);
        ensureVisible(0,contentsHeight());
    }
}

void IRCView::paintEvent(TQPaintEvent* event)
{
    // HACK: if the widget is being painted for the first time, call
    // showEvent() which will reset scrollback position as needed. It seems
    // that at the time this event is triggered, TQTextEdit provides more
    // accurate information than when showEvent() is triggered.
    if (!m_wasPainted)
    {
        m_wasPainted = true;
        showEvent(0);
    }
    KTextBrowser::paintEvent(event);
}

void IRCView::contentsMouseReleaseEvent(TQMouseEvent *ev)
{
    if (ev->button() == TQt::MidButton)
    {
        if(m_copyUrlMenu)
        {
            openLink(m_urlToCopy,true);
            return;
        }
        else
        {
            emit textPasted(true);
            return;
        }
    }

    if (ev->button() == TQt::LeftButton)
    {
        if (m_mousePressed)
        {
            if (ev->state() == (TQt::LeftButton|TQt::ShiftButton))
                saveLinkAs(m_highlightedURL);
            else
                openLink(m_highlightedURL);

            m_mousePressed = false;
            return;
        }
    }

    KTextBrowser::contentsMouseReleaseEvent(ev);
}

void IRCView::contentsMousePressEvent(TQMouseEvent* ev)
{
    if (ev->button() == TQt::LeftButton)
    {
        m_urlToDrag = m_highlightedURL;

        if (!m_urlToDrag.isNull())
        {
            m_mousePressed = true;
            m_pressPosition = ev->pos();
            return;
        }
    }

    KTextBrowser::contentsMousePressEvent(ev);
}

void IRCView::contentsMouseMoveEvent(TQMouseEvent* ev)
{
    if (m_mousePressed && (m_pressPosition - ev->pos()).manhattanLength() > TQApplication::startDragDistance())
    {
        m_mousePressed = false;
        removeSelection();
        KURL ux = KURL::fromPathOrURL(m_urlToDrag);

        if (m_server && m_urlToDrag.startsWith("##"))
        {
            //FIXME consistent IRC URL serialization
            ux = TQString("irc://%1:%2/%3").arg(m_server->getServerName()).arg(m_server->getPort()).arg(m_urlToDrag.mid(2));
        }
        else if (m_urlToDrag.startsWith("#"))
        {
            ux = m_urlToDrag.mid(1);
        }

        KURLDrag* u = new KURLDrag(ux, viewport());
        u->drag();
        return;
    }

    KTextBrowser::contentsMouseMoveEvent(ev);
}

void IRCView::contentsContextMenuEvent(TQContextMenuEvent* ev)
{
    bool block = contextMenu(ev);

    // HACK Replace % with \x03 in the url to keep TQt from doing stupid things
    m_highlightedURL = anchorAt(viewportToContents(mapFromGlobal(TQCursor::pos())));
    m_highlightedURL = m_highlightedURL.replace('\x03', "%");
    // Hack to counter the fact that we're given an decoded url
    m_highlightedURL = KURL::fromPathOrURL(m_highlightedURL).url();

    if (m_highlightedURL.isEmpty()) viewport()->setCursor(TQt::ArrowCursor);

    if(m_highlightedURL.startsWith("#"))
    {
        // HACK:Use space as a placeholder for \ as TQt tries to be clever and does a replace to / in urls in TQTextEdit
        m_highlightedURL = m_highlightedURL.replace(' ', "\\");
    }

    if (!block)
        KTextBrowser::contentsContextMenuEvent(ev);
}

bool IRCView::contextMenu(TQContextMenuEvent* ce)
{
    if (m_server && m_isOnNick && m_nickPopup->isEnabled())
    {
        updateNickMenuEntries(m_nickPopup, getContextNick());

        if(m_nickPopup->exec(ce->globalPos()) == -1)
            clearContextNick();

        m_isOnNick = false;
    }
    else if (m_server && m_isOnChannel && m_channelPopup->isEnabled())
    {
        m_channelPopup->exec(ce->globalPos());
        m_isOnChannel = false;
    }
    else
    {
        TDEActionCollection* actionCollection = KonversationApplication::instance()->getMainWindow()->actionCollection();
        TDEToggleAction* toggleMenuBarAction = static_cast<TDEToggleAction*>(actionCollection->action("options_show_menubar"));

        if (toggleMenuBarAction && !toggleMenuBarAction->isChecked())
        {
            toggleMenuBarAction->plug(m_popup, 0);
            m_popup->setItemVisible(toggleMenuBarSeparator, true);
        }

        m_popup->setItemEnabled(Copy,(hasSelectedText()));

        TDEAction* channelSettingsAction = 0;

        if (m_chatWin->getType() == ChatWindow::Channel)
        {
            channelSettingsAction = KonversationApplication::instance()->getMainWindow()->actionCollection()->action("channel_settings");
            if (channelSettingsAction) channelSettingsAction->plug(m_popup);
        }

        if (m_chatWin->getType() == ChatWindow::Query)
        {
            updateNickMenuEntries(m_popup, m_chatWin->getName());
            clearContextNick();
        }

        int r = m_popup->exec(ce->globalPos());

        switch (r)
        {
            case -1:
                // dummy. -1 means, no entry selected. we don't want -1to go in default, so
                // we catch it here
                break;
            case Copy:
                copy();
                break;
            case CopyUrl:
            {
                TQClipboard *cb = TDEApplication::kApplication()->clipboard();
                cb->setText(m_urlToCopy,TQClipboard::Selection);
                cb->setText(m_urlToCopy,TQClipboard::Clipboard);
                break;
            }
            case SelectAll:
                selectAll();
                break;
            case Search:
                search();
                break;
            case SendFile:
                emit sendFile();
                break;
            case Bookmark:
            {
                KBookmarkManager* bm = KBookmarkManager::userBookmarksManager();
                KBookmarkGroup bg = bm->addBookmarkDialog(m_urlToCopy, TQString());
                bm->save();
                bm->emitChanged(bg);
                break;
            }
            case SaveAs:
                saveLinkAs(m_urlToCopy);
                break;
            default:
                emit extendedPopup(r);
        }

        if (toggleMenuBarAction)
        {
            toggleMenuBarAction->unplug(m_popup);
            m_popup->setItemVisible(toggleMenuBarSeparator, false);
        }

        if (channelSettingsAction) channelSettingsAction->unplug(m_popup);
    }
    return true;
}

void IRCView::setupNickPopupMenu()
{
    m_nickPopup = new TDEPopupMenu(this,"nicklist_context_menu");
    m_modes = new TDEPopupMenu(this,"nicklist_modes_context_submenu");
    m_kickban = new TDEPopupMenu(this,"nicklist_kick_ban_context_submenu");
    m_nickPopupId= m_nickPopup->insertTitle(m_currentNick);

    m_nickPopup->insertItem(i18n("&Whois"),Konversation::Whois);
    m_nickPopup->insertItem(i18n("&Version"),Konversation::Version);
    m_nickPopup->insertItem(i18n("&Ping"),Konversation::Ping);

    m_nickPopup->insertSeparator();

    m_modes->insertItem(i18n("Give Op"),Konversation::GiveOp);
    m_modes->insertItem(i18n("Take Op"),Konversation::TakeOp);
    m_modes->insertItem(i18n("Give Voice"),Konversation::GiveVoice);
    m_modes->insertItem(i18n("Take Voice"),Konversation::TakeVoice);
    m_nickPopup->insertItem(i18n("Modes"),m_modes,Konversation::ModesSub);

    m_kickban->insertItem(i18n("Kick"),Konversation::Kick);
    m_kickban->insertItem(i18n("Kickban"),Konversation::KickBan);
    m_kickban->insertItem(i18n("Ban Nickname"),Konversation::BanNick);
    m_kickban->insertSeparator();
    m_kickban->insertItem(i18n("Ban *!*@*.host"),Konversation::BanHost);
    m_kickban->insertItem(i18n("Ban *!*@domain"),Konversation::BanDomain);
    m_kickban->insertItem(i18n("Ban *!user@*.host"),Konversation::BanUserHost);
    m_kickban->insertItem(i18n("Ban *!user@domain"),Konversation::BanUserDomain);
    m_kickban->insertSeparator();
    m_kickban->insertItem(i18n("Kickban *!*@*.host"),Konversation::KickBanHost);
    m_kickban->insertItem(i18n("Kickban *!*@domain"),Konversation::KickBanDomain);
    m_kickban->insertItem(i18n("Kickban *!user@*.host"),Konversation::KickBanUserHost);
    m_kickban->insertItem(i18n("Kickban *!user@domain"),Konversation::KickBanUserDomain);
    m_nickPopup->insertItem(i18n("Kick / Ban"),m_kickban,Konversation::KickBanSub);

    m_nickPopup->insertItem(i18n("Ignore"), Konversation::IgnoreNick);
    m_nickPopup->insertItem(i18n("Unignore"), Konversation::UnignoreNick);
    m_nickPopup->setItemVisible(Konversation::IgnoreNick, false);
    m_nickPopup->setItemVisible(Konversation::UnignoreNick, false);

    m_nickPopup->insertSeparator();

    m_nickPopup->insertItem(i18n("Open Query"),Konversation::OpenQuery);
    if (kapp->authorize("allow_downloading"))
    {
        m_nickPopup->insertItem(SmallIcon("2rightarrow"),i18n("Send &File..."),Konversation::DccSend);
    }
    m_nickPopup->insertSeparator();

    m_nickPopup->insertItem(i18n("Add to Watched Nicks"), Konversation::AddNotify);

    connect(m_nickPopup, TQ_SIGNAL(activated(int)), this, TQ_SIGNAL(popupCommand(int)));
    connect(m_modes, TQ_SIGNAL(activated(int)), this, TQ_SIGNAL(popupCommand(int)));
    connect(m_kickban, TQ_SIGNAL(activated(int)), this, TQ_SIGNAL(popupCommand(int)));
}

void IRCView::updateNickMenuEntries(TQPopupMenu* popup, const TQString& nickname)
{
    if (popup)
    {
        if (Preferences::isIgnored(nickname))
        {
            popup->setItemVisible(Konversation::UnignoreNick, true);
            popup->setItemVisible(Konversation::IgnoreNick, false);
        }
        else
        {
            popup->setItemVisible(Konversation::IgnoreNick, true);
            popup->setItemVisible(Konversation::UnignoreNick, false);
        }

        if (!m_server || !m_server->getServerGroup())
            popup->setItemEnabled(Konversation::AddNotify, false);
        else if (!m_server->isConnected())
            popup->setItemEnabled(Konversation::AddNotify, false);
        else if (!Preferences::hasNotifyList(m_server->getServerGroup()->id()))
            popup->setItemEnabled(Konversation::AddNotify, false);
        else if (Preferences::isNotify(m_server->getServerGroup()->id(), nickname))
            popup->setItemEnabled(Konversation::AddNotify, false);
        else
            popup->setItemEnabled(Konversation::AddNotify, true);
    }
}

void IRCView::setupQueryPopupMenu()
{
    m_nickPopup = new TDEPopupMenu(this,"query_context_menu");
    m_nickPopupId = m_nickPopup->insertTitle(m_currentNick);
    m_nickPopup->insertItem(i18n("&Whois"),Konversation::Whois);
    m_nickPopup->insertItem(i18n("&Version"),Konversation::Version);
    m_nickPopup->insertItem(i18n("&Ping"),Konversation::Ping);
    m_nickPopup->insertSeparator();

    m_nickPopup->insertItem(i18n("Ignore"), Konversation::IgnoreNick);
    m_nickPopup->insertItem(i18n("Unignore"), Konversation::UnignoreNick);
    m_nickPopup->setItemVisible(Konversation::IgnoreNick, false);
    m_nickPopup->setItemVisible(Konversation::UnignoreNick, false);

    if (kapp->authorize("allow_downloading"))
        m_nickPopup->insertItem(SmallIcon("2rightarrow"),i18n("Send &File..."),Konversation::DccSend);

    m_nickPopup->insertItem(i18n("Add to Watched Nicks"), Konversation::AddNotify);

    connect(m_nickPopup, TQ_SIGNAL(activated(int)), this, TQ_SIGNAL(popupCommand(int)));
}

void IRCView::setupChannelPopupMenu()
{
    m_channelPopup = new TDEPopupMenu(this,"channel_context_menu");
    m_channelPopupId = m_channelPopup->insertTitle(m_currentChannel);
    m_channelPopup->insertItem(i18n("&Join"),Konversation::Join);
    m_channelPopup->insertItem(i18n("Get &user list"),Konversation::Names);
    m_channelPopup->insertItem(i18n("Get &topic"),Konversation::Topic);

    connect(m_channelPopup, TQ_SIGNAL(activated(int)), this, TQ_SIGNAL(popupCommand(int)));
}

void IRCView::setNickAndChannelContextMenusEnabled(bool enable)
{
    if (m_nickPopup) m_nickPopup->setEnabled(enable);
    if (m_channelPopup) m_channelPopup->setEnabled(enable);
}

void IRCView::search()
{
    emit doSearch();
}

void IRCView::searchAgain()
{
    if (m_pattern.isEmpty())
    {
        emit doSearch();
    }
    else
    {
        // next search must begin one index before / after the last search
        // depending on the search direction.
        if(m_forward)
        {
            ++m_findIndex;
            if(m_findIndex == paragraphLength(m_findParagraph))
            {
                m_findIndex = 0;
                ++m_findParagraph;
            }
        }
        else
        {
            if(m_findIndex)
            {
                --m_findIndex;
            }
            else
            {
                --m_findParagraph;
                m_findIndex = paragraphLength(m_findParagraph);
            }
        }

        if(!find(m_pattern, m_caseSensitive, m_wholeWords, m_forward, &m_findParagraph, &m_findIndex))
        {
            KMessageBox::information(this,i18n("No matches found for \"%1\".").arg(m_pattern),i18n("Information"));
        }

    }
}

bool IRCView::search(const TQString& pattern, bool caseSensitive,
bool wholeWords, bool forward, bool fromCursor)
{
    m_pattern       = pattern;
    m_caseSensitive = caseSensitive;
    m_wholeWords    = wholeWords;
    m_forward       = forward;
    m_fromCursor    = fromCursor;

    if (m_pattern.isEmpty())
        return true;

    if (!m_fromCursor)
    {
        if(m_forward)
        {
            m_findParagraph = 1;
            m_findIndex = 1;
        }
        else
        {
            m_findParagraph = paragraphs();
            m_findIndex = paragraphLength(paragraphs());
        }
    }

    return searchNext();
}

bool IRCView::searchNext(bool reversed)
{
    if (m_pattern.isEmpty())
        return true;

    bool fwd = (reversed ? !m_forward : m_forward);
    // next search must begin one index before / after the last search
    // depending on the search direction.
    if (fwd)
    {
        ++m_findIndex;
        if(m_findIndex == paragraphLength(m_findParagraph))
        {
            m_findIndex = 0;
            ++m_findParagraph;
        }
    }
    else
    {
        if (m_findIndex)
        {
            --m_findIndex;
        }
        else
        {
            --m_findParagraph;
            m_findIndex = paragraphLength(m_findParagraph);
        }
    }

    return find(m_pattern, m_caseSensitive, m_wholeWords, fwd,
        &m_findParagraph, &m_findIndex);
}

// other windows can link own menu entries here
TQPopupMenu* IRCView::getPopup() const
{
    return m_popup;
}

// for more information about these RTFM
//    http://www.unicode.org/reports/tr9/
//    http://www.w3.org/TR/unicode-xml/
TQChar IRCView::LRM = (ushort)0x200e; // Right-to-Left Mark
TQChar IRCView::RLM = (ushort)0x200f; // Left-to-Right Mark
TQChar IRCView::LRE = (ushort)0x202a; // Left-to-Right Embedding
TQChar IRCView::RLE = (ushort)0x202b; // Right-to-Left Embedding
TQChar IRCView::RLO = (ushort)0x202e; // Right-to-Left Override
TQChar IRCView::LRO = (ushort)0x202d; // Left-to-Right Override
TQChar IRCView::PDF = (ushort)0x202c; // Previously Defined Format

TQChar::Direction IRCView::basicDirection(const TQString &string)
{
    // The following code decides between LTR or RTL direction for
    // a line based on the amount of each type of characters pre-
    // sent. It does so by counting, but stops when one of the two
    // counters becomes higher than half of the string length to
    // avoid unnecessary work.

    unsigned int pos = 0;
    unsigned int rtl_chars = 0;
    unsigned int ltr_chars = 0;
    unsigned int str_len = string.length();
    unsigned int str_half_len = str_len/2;

    for(pos=0; pos < str_len; pos++)
    {
        if (!(string[pos].isNumber() || string[pos].isSymbol() ||
            string[pos].isSpace()  || string[pos].isPunct()  ||
            string[pos].isMark()))
        {
            switch(string[pos].direction())
            {
                case TQChar::DirL:
                case TQChar::DirLRO:
                case TQChar::DirLRE:
                    ltr_chars++;
                    break;
                case TQChar::DirR:
                case TQChar::DirAL:
                case TQChar::DirRLO:
                case TQChar::DirRLE:
                    rtl_chars++;
                    break;
                default:
                    break;
            }
        }

        if (ltr_chars > str_half_len)
            return TQChar::DirL;
        else if (rtl_chars > str_half_len)
            return TQChar::DirR;
    }

    if (rtl_chars > ltr_chars)
        return TQChar::DirR;
    else
        return TQChar::DirL;
}

void IRCView::contentsDragMoveEvent(TQDragMoveEvent *e)
{
    if(acceptDrops() && TQUriDrag::canDecode(e))
        e->accept();
}

void IRCView::contentsDropEvent(TQDropEvent *e)
{
    TQStrList s;
    if(TQUriDrag::decode(e,s))
        emit filesDropped(s);
}

TQString IRCView::timeStamp()
{
    if(Preferences::timestamping())
    {
        TQTime time = TQTime::currentTime();
        TQString timeColor = Preferences::color(Preferences::Time).name();
        TQString timeFormat = Preferences::timestampFormat();
        TQString timeString;

        if(!Preferences::showDate())
        {
            timeString = TQString("<font color=\"" + timeColor + "\">[%1]</font> ").arg(time.toString(timeFormat));
        }
        else
        {
            TQDate date = TQDate::currentDate();
            timeString = TQString("<font color=\"" +
                timeColor + "\">[%1 %2]</font> ")
                    .arg(TDEGlobal::locale()->formatDate(date, true /*short format*/),
                         time.toString(timeFormat));
        }

        return timeString;
    }

    return TQString();
}

void IRCView::setChatWin(ChatWindow* chatWin)
{
    m_chatWin = chatWin;

    if(m_chatWin->getType()==ChatWindow::Channel)
        setupNickPopupMenu();
    else
        setupQueryPopupMenu();

    setupChannelPopupMenu();
}

void IRCView::keyPressEvent(TQKeyEvent* e)
{
    KKey key(e);

    if (TDEStdAccel::copy().contains(key))
    {
        copy();
        e->accept();
        return;
    }
    else if (TDEStdAccel::paste().contains(key))
    {
        emit textPasted(false);
        e->accept();
        return;
    }

    KTextBrowser::keyPressEvent(e);
}

void IRCView::resizeEvent(TQResizeEvent* e)
{
    bool doScroll = ( KTextBrowser::verticalScrollBar()->value() == KTextBrowser::verticalScrollBar()->maxValue());
    KTextBrowser::resizeEvent(e);

    if(doScroll)
    {
        TQTimer::singleShot(0, this, TQ_SLOT(updateScrollBarPos()));
    }
}

void IRCView::updateScrollBarPos()
{
    ensureVisible(contentsX(), contentsHeight());
    repaintContents(false);
}

void IRCView::saveLinkAs(const TQString& url)
{
    KURL source(url);
    KFileDialog dialog(":SaveLinkAs", TQString (), this, "savelinkdia", true);
    dialog.setCaption(i18n("Save Link As"));
    dialog.setSelection(source.fileName());

    if(dialog.exec() == TQDialog::Rejected)
        return;

    KURL destination = dialog.selectedURL();
    TDEIO::copyAs(source, destination);
}

#include "ircview.moc"
