/**********************************************************************

 TopLevel IRC Channel/query Window

 $$Id: toplevel.cpp,v 1.233 2002/11/05 09:00:32 hausmann Exp $$

 This is the main window with which the user interacts.  It handles
 both normal channel conversations and private conversations.

 2 classes are defined, the UserControlMenu and KSircToplevel.  The
 user control menu is used as a list of user defineable menus used by
 KSircToplevel.

 KSircTopLevel:

 Signals:

 outputLine(QString &):
 output_toplevel(QString):

 closing(KSircTopLevel *, QString channel):

 changeChannel(QString old, QString new):

 currentWindow(KSircTopLevel *):

 Slots:



 *********************************************************************/

#include "toplevel.h"
#include "alistbox.h"
#include "chanparser.h"
#include "ksopts.h"
#include "control_message.h"
#include "displayMgr.h"
#include "NewWindowDialog.h"
#include "usercontrolmenu.h"
#include "topic.h"
#include "charSelector.h"
#include "ksview.h"
#include "logfile.h"
#include "servercontroller.h"

#include <stdlib.h>

#include <qaccel.h>
#include <qclipboard.h>
#include <qregexp.h>
#include <qcursor.h>
#include <qtimer.h>
#include <qtextcodec.h>

#include <kapplication.h>
#include <kdebug.h>
#include <kwin.h>
#include <knotifyclient.h>
#include <kpopupmenu.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kfiledialog.h>
#include <ktempfile.h>
#include <kio/netaccess.h>
#include <kstatusbar.h>
#include <kstdaction.h>
#include <kaction.h>
#include <kcharsets.h>

extern DisplayMgr *displayMgr;
//QPopupMenu *KSircTopLevel::user_controls = 0L;
QPtrList<UserControlMenu> *KSircTopLevel::user_menu = 0L;

#define KSB_MAIN_LINEE 10
#define KSB_MAIN_LAG 20

void
KSircTopLevel::initColors()
{
  QColorGroup cg_mainw = kapp->palette().active();
  cg_mainw.setColor(QColorGroup::Base, ksopts->backgroundColor);
  cg_mainw.setColor(QColorGroup::Text, ksopts->textColor);
  mainw->setPalette(QPalette(cg_mainw,cg_mainw, cg_mainw));
  nicks->setPalette(QPalette(cg_mainw,cg_mainw, cg_mainw));
  linee->setPalette(QPalette(cg_mainw,cg_mainw, cg_mainw));
}

KSircTopLevel::KSircTopLevel(KSircProcess *_proc, const QString &cname, const char * name)
    : KMainWindow(0, name, 0/*no WDestructiveClose !*/),
      UnicodeMessageReceiver(_proc),
      channel_name(cname),
      lastBeep( QTime::currentTime() )
{
    // prevent us from being quitted when closing a channel-window. Only
    // closing the servercontroller shall quit.
    // KMainWindow will deref() us in closeEvent
    kapp->ref();

  /*
   * Make sure we tell others when we are destroyed
   */
  connect(this, SIGNAL(destroyed()),
          this, SLOT(iamDestroyed()));

  proc = _proc;

  QString kstl_name = QString(QObject::name()) + "_" + "toplevel";
  setName(kstl_name);

  if(!channel_name.isEmpty()) {
    setCaption(channel_name);
    caption = channel_name;
  }
  else
  {
    caption = QString::null;
  }

  Buffer = FALSE;

  have_focus = 0;
  tab_pressed = 0; // Tab (nick completion not pressed yet)
  tab_start = -1;
  tab_end = -1;

  KickWinOpen = false;
  current_size = size();

  selector = new charSelector();
  connect(selector, SIGNAL(clicked()), this, SLOT(insertText()));

  file = new QPopupMenu(this, QString(QObject::name()) + "_popup_file");
  file->setCheckable(true);

  KAction *act = KStdAction::openNew( this, SLOT( newWindow() ), actionCollection() );
  act->plug( file );
  file->insertSeparator();
  file->insertItem(i18n("&Save to Logfile..."), this, SLOT(saveCurrLog()), CTRL + Key_S);

  tsitem = file->insertItem(i18n("Time St&amp"), this, SLOT(toggleTimestamp()), CTRL + Key_A);
  file->setItemChecked(tsitem, ksopts->timeStamp);
  file->insertItem(i18n("ASCII &Table"), selector, SLOT(show()));
  beepitem = file->insertItem(i18n("N&otify on Change"), this, SLOT(toggleBeep()), CTRL + Key_P);
  file->setItemChecked(beepitem, ksopts->beepOnMsg);

  encodingAction = new KSelectAction( i18n( "&Encoding" ), 0, this );
  connect( encodingAction, SIGNAL( activated() ), this, SLOT( setEncoding() ) );
  QStringList encodings = KGlobal::charsets()->descriptiveEncodingNames();

  // remove utf16/ucs2 as it just doesn't work for IRC
  QStringList::Iterator encodingIt = encodings.begin();
  while ( encodingIt != encodings.end() ) {
    if ( ( *encodingIt ).find( "utf16" ) != -1 ||
         ( *encodingIt ).find( "iso-10646" ) != -1 )
      encodingIt = encodings.remove( encodingIt );
    else
      ++encodingIt;
  }
  encodings.prepend( i18n( "Default" ) );

  encodingAction->setItems( encodings );
  encodingAction->plug( file );
  encodingAction->setCurrentItem( 0 );

  file->insertSeparator();
  act = KStdAction::close( this, SLOT( terminate() ), actionCollection() );
  act->plug( file );

  kmenu = menuBar();
  kmenu->insertItem(i18n("&Channel"), file, 2, -1);
  kmenu->setAccel(Key_F, 2);

 ksb_main = new KStatusBar(this, QString(QObject::name()) + "_" + "KStatusBar");

  /*
   * Ok, let's look at the basic widget "layout"
   * Everything belongs to q QFrame F, this is use so we
   * can give the KApplication a single main client widget, which is needs.
   *
   * A QVbox and a QHbox is used to ctronl the 3 sub widget
   * The Modified QListBox is then added side by side with the User list box.
   * The SLE is then fit bello.
   */

  // kstInside does not setup fonts, etc, it simply handles sizing

  QVBox *top = new QVBox( this );
  QHBox *box = new QHBox(top);

  ksTopic = new KSircTopic( box );

  channelButtons = new chanButtons(box);
  connect(channelButtons, SIGNAL(mode(QString, int, QString)),
           this, SLOT(setMode(QString, int, QString)));

  connect( ksTopic, SIGNAL( topicChange( const QString & ) ),
           this, SLOT( setTopicIntern( const QString & ) ) );

  f = new kstInside(top, QString(QObject::name()) + "_" + "kstIFrame");
  top->setStretchFactor(f, 1);

  setCentralWidget(top);  // Tell the KApplication what the main widget is.

  logFile = 0;
  if ( ksopts->logging && (channel_name != "!no_channel" ))
  {
      logFile = new LogFile( cname, _proc->serverName() );
      logFile->open();
  }

  // get basic variable

  mainw = f->mainw;
  nicks = f->nicks;
  pan = f->pan;

  edit = new QPopupMenu(this);
  act = KStdAction::copy( mainw, SLOT( copy() ), actionCollection() );
  act->plug( edit );
  act = KStdAction::paste( this, SLOT( pasteToWindow() ), actionCollection() );
  act->plug( edit );
  edit->insertItem(i18n("C&lear Window"), this, SLOT(clearWindow()), CTRL + Key_L);
  kmenu->insertItem(i18n("&Edit"), edit, -1, -1);

  clearWindow();

  linee = new aHistLineEdit(ksb_main, "");

  initColors();

  ksb_main->addWidget(linee, mainw->width());
  ksb_main->insertItem(i18n("Lag: Wait"), KSB_MAIN_LAG, true);

  // don't show the nick lists in a private chat or the default window
  if (isPrivateChat() || channel_name.startsWith("!no_channel"))
  {
      nicks->hide();
      ksTopic->hide();
      channelButtons->hide();
  }
  else
  {
      nicks->show();
      ksTopic->show();
      channelButtons->show();
  }

  connect(mainw, SIGNAL(pasteReq( const QString& )),
          this, SLOT( slotTextDropped( const QString& )));

  nicks->setFont(ksopts->defaultFont);

  // setup line editor

  linee->setFocusPolicy(QWidget::StrongFocus);
  linee->setFont(ksopts->defaultFont);
  connect(linee, SIGNAL(gotFocus()),
          this, SLOT(gotFocus()));
  connect(linee, SIGNAL(lostFocus()),
          this, SLOT(lostFocus()));
  connect(linee, SIGNAL(pasteText(const QString&)),
          this, SLOT(slotTextDropped(const QString&)));
  connect(linee, SIGNAL(notTab()),
          this, SLOT(lineeNotTab()));

  connect( linee, SIGNAL( returnPressed() ),
           this, SLOT( returnPressed() ) );

  linee->setFocus();  // Give SLE focus

  lines = 0;          // Set internal line counter to 0

  /*
   * Set generic run time variables
   *
   */

  opami = FALSE;
  continued_line = FALSE;
//  on_root = FALSE;

  /*
   * Load basic pics and images
   * This should use on of the KDE finder commands
   */

  pix_info = QString::fromLatin1( "user|info" );
  pix_star = QString::fromLatin1( "user|star" );
  pix_bball = QString::fromLatin1( "user|blueball" );
  pix_greenp = QString::fromLatin1( "user|greenpin" );
  pix_bluep = QString::fromLatin1( "user|bluepin" );
  pix_madsmile = QString::fromLatin1( "user|madsmiley" );

  KWin::setIcons(winId(), kapp->icon(), kapp->miniIcon());

  /*
   * Create our basic parser object
   */

  ChanParser = new ChannelParser(this);


  /*
   * Create the user Controls popup menu, and connect it with the
   * nicks list on the lefthand side (highlighted()).
   *
   */

  if(user_menu == 0)
    user_menu = UserControlMenu::parseKConfig();

  user_controls = new QPopupMenu(this);
  kmenu->insertItem(i18n("&Users"), user_controls);
  kmenu->insertItem(i18n("&Help"), helpMenu( QString::null, false ));

  connect(user_controls, SIGNAL(activated(int)),
          this, SLOT(UserParseMenu(int)));

  connect(nicks, SIGNAL(contextMenuRequested(int)), this,
          SLOT(UserSelected(int)));
  connect(nicks, SIGNAL(selectedNick(const QString &)),
          this, SLOT(openQueryFromNick(const QString &)));



  UserUpdateMenu();  // Must call to update Popup.

  accel = new QAccel(this, "accel");

  accel->connectItem(accel->insertItem(SHIFT + Key_PageUp),
                     this,
                     SLOT(AccelScrollUpPage()));
  accel->connectItem(accel->insertItem(SHIFT + Key_PageDown),
                     this,
                     SLOT(AccelScrollDownPage()));

  /*
   * Pageup/dn
   * Added for stupid wheel mice
   */

  accel->connectItem(accel->insertItem(Key_PageUp),
                     this,
                     SLOT(AccelScrollUpPage()));
  accel->connectItem(accel->insertItem(Key_PageDown),
                     this,
                     SLOT(AccelScrollDownPage()));

  accel->connectItem(accel->insertItem(CTRL + Key_Enter),
                     this,
                     SLOT(AccelPriorMsgNick()));

  accel->connectItem(accel->insertItem(CTRL + SHIFT + Key_Enter),
                     this,
                     SLOT(AccelNextMsgNick()));

  accel->connectItem(accel->insertItem(CTRL + Key_Return),
                     this,
                     SLOT(AccelPriorMsgNick()));

  accel->connectItem(accel->insertItem(CTRL + SHIFT + Key_Return),
                     this,
                     SLOT(AccelNextMsgNick()));

  accel->connectItem(accel->insertItem(Key_Tab), // adds TAB accelerator
                     this,                         // connected to the main
                     SLOT(TabNickCompletion()));  // TabNickCompletion() slot
  accel->connectItem(accel->insertItem(CTRL + Key_N),
                     this, SLOT(newWindow()));
//  accel->connectItem(accel->insertItem(CTRL + Key_S),
//                     this, SLOT(toggleTimestamp()));

  // Drag & Drop
  connect( mainw, SIGNAL( textDropped(const QString&) ),
           SLOT( slotTextDropped(const QString&) ));
  connect( mainw, SIGNAL( urlsDropped(const QStringList&) ),
           SLOT( slotDropURLs(const QStringList&) ));
  connect( nicks, SIGNAL( urlsDropped( const QStringList&, const QString& )),
           SLOT( slotDccURLs( const QStringList&, const QString& )));
  connect( this, SIGNAL( changed(bool) ), this, SLOT( beep(bool) ));

  f->mainw->setAcceptFiles( isPrivateChat() );
  resize(600, 360);
}

KSircTopLevel::~KSircTopLevel()
{
  // Cleanup and shutdown
  //  if(this == proc->getWindowList()["default"])
  //    write(sirc_stdin, "/quit\n", 7); // tell dsirc to close
  //
  //  if(proc->getWindowList()[channel_name])
  //    proc->getWindowList().remove(channel_name);

  //  if((channel_name[0] == '#') || (channel_name[0] == '&')){
  //    QString str = QString("/part ") + channel_name + "\n";
  //    emit outputLine(str);
  //  }

    kdDebug() << "**** ~KSircTopLevel!!!" << endl;

  if ( isPublicChat() ) {
      kdDebug() << "*** parting channel: " << channel_name << endl;
      QString str = QString("/part ") + channel_name + "\n";
      emit outputUnicodeLine(str);
  }

  delete user_controls;
  delete ChanParser;
  delete selector;
  delete channelButtons;
  delete logFile;
}

void KSircTopLevel::setMode(QString mode, int mode_type, QString currentNick)
{
  QString command;
  if (mode_type == 0)
    command = QString::fromLatin1("/mode %1 %2\n").arg(channel_name).arg(mode);
  else
    command = QString::fromLatin1("/mode %1 %2\n").arg(currentNick).arg(mode);
  sirc_write(command);
  linee->setFocus();
}

void KSircTopLevel::setNick( const QString& nick )
{
    m_currentNick = nick;
    while (!m_currentNick.isEmpty() &&
          (m_currentNick[0].latin1() == '@' || m_currentNick[0].latin1() == '*'))
      m_currentNick.remove(0, 1);
}

void KSircTopLevel::setEncoding()
{
  int index = encodingAction->currentItem();
  if ( index == 0 ) // default (locale) encoding
    UnicodeMessageReceiver::setEncoding( QString::null );
  else
    UnicodeMessageReceiver::setEncoding( KGlobal::charsets()->encodingForName( encodingAction->currentText() ) );
}

void KSircTopLevel::insertText()
{
  linee->setText(linee->text() + selector->currentText());
}

void KSircTopLevel::show()
{
  KMainWindow::show();
  scrollToBottom();
}

void KSircTopLevel::TabNickCompletion()
{
  /*
   * Gets current text from lined find the last item and try and perform
   * a nick completion, then return and reset the line.
   */

  int start, end;
  QString s;

  if(tab_pressed > 0){
    s = tab_saved;
    start = tab_start;
    end = tab_end;
  }
  else{
    s = linee->text();
    tab_saved = s;
    end = linee->cursorPosition() - 1;
    start = s.findRev(" ", end, FALSE);
    tab_start = start;
    tab_end = end;

  }

  if(s.length() == 0){
    QString line = tab_nick + ": "; // tab_nick holds the last night since we haven't overritten it yet.
    linee->setText(line);
    linee->setCursorPosition(line.length());
    return;
  }

  if (start == -1) {
    tab_nick = findNick(s.mid(0, end+1), tab_pressed);
    if(tab_nick.isNull() == TRUE){
      tab_pressed = 0;
      tab_nick = findNick(s.mid(0, end+1), tab_pressed);
    }
    s.replace(0, end + 1, tab_nick);
  }
  else {
    tab_nick = findNick(s.mid(start + 1, end - start), tab_pressed);
    if(tab_nick.isNull() == TRUE){
      tab_pressed = 0;
      tab_nick = findNick(s.mid(start + 1, end - start), tab_pressed);
    }
    s.replace(start + 1, end - start, tab_nick);
  }

  int tab = tab_pressed + 1;

  linee->setText(s);

  linee->setCursorPosition(start + tab_nick.length() + 1);

  tab_pressed = tab; // setText causes lineeTextChanged to get called and erase tab_pressed

  connect(linee, SIGNAL(notTab()),
          this, SLOT(lineeNotTab()));

}

void KSircTopLevel::sirc_receive(QString str, bool broadcast)
{

  /*
   * read and parse output from dsirc.
   * call reader, split the read input into lines, parse the lines
   * then print line by line into the main text area.
   *
   * PROBLEMS: if a line terminates in mid line, it will get borken oddly
   *
   */

  /*
   * If we have too many lines, nuke the top 100, leave us with 100
   */
  if(!Buffer){
    if( !str.isEmpty() ){
        LineBuffer.append( BufferedLine( str, broadcast ) );
    }

    bool atBottom = mainw->verticalScrollBar()->maxValue() - mainw->verticalScrollBar()->value() < 20;
    bool addressed = false;
    BufferedLine line;

    // be careful not to use a QValueList iterator here, as it is possible
    // that we enter a local event loop (think of the ssfeprompt dialog!)
    // which might trigger a socketnotifier activation which in turn
    // might result in the execution of code that modifies the LineBuffer,
    // which would invalidate iterators (Simon)
    while ( LineBuffer.begin() != LineBuffer.end() )
    {
      line = *LineBuffer.begin();
      LineBuffer.remove( LineBuffer.begin() );

      // Get the need list box item, with colour, etc all set
      if (parse_input(line.message))
      {
        // If we should add anything, add it.
        // Don't announce server messages as they are
        // spread through all channels anyway
        bool addressedLine = line.message.contains( m_currentNick, false /*case sensitive*/ ) &&
          line.message.find( ">" ) < line.message.find( m_currentNick, 0, false /*case sensitive*/ );

        // detect /msg's -- needed when auto-create-window is off
        if ( line.message.find( QRegExp( "^\\[~b.+~b\\].+$" ) ) == 0 )
            addressedLine = true;

        if ( addressedLine )
          addressed = true;
        if ( !line.wasBroadcast ) {
          // This line is addressed to me if it contains my nick, or is in a private chat
          emit changed( addressedLine || isPrivateChat() );
        }
      }
    }
    LineBuffer.clear(); // Clear it since it's been added

    if (atBottom || addressed)
      scrollToBottom();
  }
  else{
    LineBuffer.append( BufferedLine( str, broadcast ) );
  }
}

void KSircTopLevel::sirc_line_return(const QString &text)
{

  /* Take line from SLE, and output if to dsirc */

  QString s = text;

  if(s.length() == 0)
    return;

  tab_pressed = 0; // new line, zero the counter.
  s += '\n'; // Append a need carriage return :)

  if((uint) nick_ring.at() < (nick_ring.count() - 1))
    nick_ring.next();
  else
    nick_ring.last();

  sirc_write(s);

  linee->setText("");

}

void KSircTopLevel::sirc_write(const QString &str)
{
  /*
   * Parse line forcommand we handle
   */
  QString command = str, plain = str.lower().simplifyWhiteSpace();
  if(plain.startsWith("/join ") ||
     plain.startsWith("/j ") ||
     plain.startsWith("/query ")) {
    int pos1 = plain.find(' ') + 1;
    if(!pos1)
      return;
    int pos2 = plain.find(' ', pos1);
    if(pos2 == -1)
      pos2 = plain.length();
    if(pos1 > 2){
      QString name = plain.mid(pos1, pos2 - pos1);
      // In case of a channel key, first join and then open
      // the toplevel, to avoid a "Could not join, wrong key"
      // when the new toplevel emits a /join on activation
      if(name[0] != '#'){
        emit open_toplevel(name);
        linee->setText(QString::null);
        return;
      }
      else {
        emit outputUnicodeLine(plain + "\n");
        emit open_toplevel(name);
      }
      // Finish sending /join
    }
    return;
  }
  else if(plain.startsWith("/server ")) {
    command = "/eval &print(\"*E* Use The Server Controller\\n\");\n";
    sirc_write(command);
    linee->setText(QString::null);
    return;
  }
  else if(plain.startsWith("/part") ||
          plain.startsWith("/leave") ||
          plain.startsWith("/hop")) {
    QApplication::postEvent(this, new QCloseEvent()); // WE'RE DEAD
    linee->setText(QString::null);
    return;
  }
  else if( plain.startsWith( "/bye" ) ||
           plain.startsWith( "/exit" ) ||
           plain.startsWith( "/quit" )) {
    linee->setText( QString::null );
    emit requestQuit( command.ascii() );
    return;
  }
  else if ( plain.startsWith( "/away" ) ) {
    QString awayEvalCommand = QString::fromLatin1( "/eval $publicAway = %1\n" ).arg( ksopts->publicAway ? '1' : '0' );
    emit outputUnicodeLine( awayEvalCommand );
  }

  //
  // Look at the command, if we're assigned a channel name, default
  // messages, etc to the right place.  This include /me, etc
  //

  if(!isSpecialWindow()) { // channel or private chat
    if(plain[0].latin1() != '/'){
      command.prepend(QString::fromLatin1("/msg %1 ").arg(channel_name));
    }
    else if(plain.startsWith("/me ")) {
      command.remove(0, 3);
      command.prepend(QString("/de ") + channel_name);
    }
  }

  // Write out line

  scrollToBottom();
  emit outputUnicodeLine(command);
}

bool KSircTopLevel::parse_input(const QString &string)
{
  /*
   * Parsing routines are broken into 3 majour sections
   *
   * 1. Search and replace evil characters. The string is searched for
   * each character in turn (evil[i]), and the character string in
   * evil_rep[i] is replaced.
   *
   * 2. Parse control messages, add pixmaps, and colourize required
   * lines.  Caption is also set as required.
   *
   * 3. Create and return the ircListItem.
   *
   */

  /*
   * No-output get's set to 1 if we should ignore the line
   */

  /*
   * This is the second generation parsing code.
   * Each line type is determined by the first 3 characters on it.
   * Each line type is then sent to a parsing function.
   */
  parseResult *pResult = ChanParser->parse(string);

  parseSucc *item = dynamic_cast<parseSucc *>(pResult);
  parseError *err = dynamic_cast<parseError *>(pResult);

  QString logString;

  if(item)
  {
    if (!item->string.isEmpty()) {
        logString = mainw->addLine( item->pm, item->colour, item->string );
    } else {
      delete pResult;
      return false;
    }
  }
  else if (err)
  {
    if(err->err.isEmpty() == FALSE)
    {
      kdWarning() << err->err << ": " << string << endl;
      delete pResult;
      return false;
    }
    if (!err->str.isEmpty())
        logString = mainw->addLine( pix_madsmile, ksopts->errorColor, err->str );
  }
  else
  {
        // If it contains our nick, move the speaker to the top
        // of the nick completion list
        if ( string[0].latin1() == '<' &&
             string.find( ksopts->nick ) >= 0 )
        {
            int end = string.find( '>' );
            if ( end >= 0 )
            {
                QString nick = string.mid( 1, end - 1 );
                // strip nick colouring
                while ( nick[0].latin1() == '~' )
                    nick.remove( 0, 2 );
                while ( nick.length() >= 2 && nick[nick.length() - 2].latin1() == '~' )
                    nick.remove( nick.length() - 2, 2 );
                if ( nick != ksopts->nick )
                    addCompleteNick( nick );
            }
        }
        logString = mainw->addLine( QString::null, ksopts->textColor, string );
  }
  delete pResult;

  if ( !logString.isEmpty() && logFile )
      logFile->log( logString );

  return true;
}

void KSircTopLevel::returnPressed()
{
  QString s = linee->text();
  if ( s.startsWith( "/j " ) || s.startsWith( "/join " ) ) {
    s.remove( 0, s.find( "#" ) );
    QStringList fragments = QStringList::split( ",", s );
    QStringList::ConstIterator it = fragments.begin();
    QStringList::ConstIterator end = fragments.end();
    for ( ; it != end; ++it )
      sirc_line_return( "/join " + *it );
  } else
    sirc_line_return( s );
}

void KSircTopLevel::UserSelected(int index)
{
  if(index >= 0)
    user_controls->popup(this->cursor().pos());
}

void KSircTopLevel::UserParseMenu(int id)
{
  if(nicks->currentItem() < 0)
      return;

  QString s;
  s = QString("/eval $dest_nick='%1';\n").arg(nicks->text(nicks->currentItem()));
  sirc_write(s);
  // set $$dest_chan variable
  s = QString("/eval $dest_chan='%1';\n").arg(channel_name);
  sirc_write(s);
  QString action = user_menu->at(id)->action;
  if (action.length() && action[0] == '/')
      action.remove(0, 1);
  s = QString("/eval &docommand(eval{\"%1\"});\n").arg(action);
  s.replace(QRegExp("\\$\\$"), "$");
  sirc_write(s);
}

void KSircTopLevel::UserUpdateMenu()
{
  int i = 0;
  UserControlMenu *ucm;

  user_controls->clear();
  for(ucm = user_menu->first(); ucm != 0; ucm = user_menu->next(), i++){
    if(ucm->type == UserControlMenu::Seperator){
      user_controls->insertSeparator();
    }
    else{
      user_controls->insertItem(ucm->title, i);
      if(ucm->accel)
        user_controls->setAccel(i, ucm->accel);
      if((ucm->op_only == TRUE) && (opami == FALSE))
        user_controls->setItemEnabled(i, FALSE);
    }
  }
}

void KSircTopLevel::AccelScrollDownPage()
{
   mainw->verticalScrollBar()->addPage();
}

void KSircTopLevel::AccelScrollUpPage()
{
   mainw->verticalScrollBar()->subtractPage();
}

void KSircTopLevel::AccelPriorMsgNick()
{
  linee->setText(QString("/msg ") + nick_ring.current() + " ");

  if(nick_ring.at() > 0)
    nick_ring.prev();

}

void KSircTopLevel::AccelNextMsgNick()
{
  if(nick_ring.at() < ((int) nick_ring.count() - 1) )
    linee->setText(QString("/msg ") + nick_ring.next() + " ");
}

void KSircTopLevel::newWindow()
{
  NewWindowDialog w;
  connect(&w, SIGNAL(openTopLevel(QString)), SIGNAL(open_toplevel(QString)));
  w.exec();
}

void KSircTopLevel::closeEvent(QCloseEvent *e)
{
  KMainWindow::closeEvent( e );
  e->accept();

  // Let's not part the channel till we are acutally delete.
  // We should always get a close event, *BUT* we will always be deleted.
  //   if( isPublicChat() ) {
  //       QString str = QString("/part ") + channel_name + "\n";
  //       emit outputLine(str);
  //   }

  // Hide ourselves until we finally die
  hide();
  qApp->flushX();
  // Let's say we're closing
  emit closing(this, channel_name);
}

void KSircTopLevel::gotFocus()
{
  if(isVisible() == TRUE){
    if(have_focus == 0){
      if(channel_name[0] == '#'){
        QString str = QString("/join %1\n").arg(channel_name);
        emit outputUnicodeLine(str);
        emit outputLine("/eval $query=''\n");
      }
      else if (channel_name[0] != '!')
      {
          emit outputUnicodeLine(QString("/eval $query='%1'\n").arg(channel_name));
      }
      have_focus = 1;
      emit currentWindow(this);
    }
  }
}

void KSircTopLevel::lostFocus()
{
  if(have_focus == 1){
    have_focus = 0;
  }
}

void KSircTopLevel::control_message(int command, QString str)
{
  switch(command){
  case CHANGE_CHANNEL: // 001 is defined as changeChannel
    {
      QString server, chan;
      int bang;
      bang = str.find("!!!");
      if(bang < 0){
          chan = str;
          QString mname = name();
          int end = mname.find('_');
          if(end < 0){
              kdWarning() << "Change channel message was invalid: " << str << endl;
              break;
          }
          server = mname.mid(0, end);
      }
      else{
          server = str.mid(0, bang);
          chan = str.mid(bang + 3, str.length() - (bang + 3));
      }
      emit changeChannel(channel_name, chan);
      channel_name = chan;
      bool isPrivate = isPrivateChat();
      if ( !logFile && ksopts->logging )
      {
          logFile = new LogFile( channel_name, ksircProcess()->serverName() );
          logFile->open();
      }
      setName(server + "_" + channel_name + "_" + "toplevel");
      f->setName(QString(QString(QObject::name()) + "_" + "kstIFrame"));
      kmenu->setName(QString(QObject::name()) + "_ktoolframe");
      linee->setName(QString(QObject::name()) + "_" + "LineEnter");
      kmenu->show();
      have_focus = 0;
      setCaption(channel_name);
      scrollToBottom();
      emit currentWindow(this);

      f->mainw->setAcceptFiles( isPrivate );
      if ( isPrivate )
      {
          f->nicks->hide();
          ksTopic->hide();
          channelButtons->hide();
      }
      else
      {
          f->nicks->show();
          ksTopic->show();
          channelButtons->show();
      }
      scrollToBottom();
      break;
    }
  case STOP_UPDATES:
    Buffer = TRUE;
    break;
  case RESUME_UPDATES:
    Buffer = FALSE;
    if(LineBuffer.isEmpty() == FALSE)
      sirc_receive(QString::null);
    break;
  case REREAD_CONFIG:
    emit freezeUpdates(TRUE); // Stop the list boxes update
    mainw->setFont( ksopts->defaultFont );
    nicks->setFont( ksopts->defaultFont );
    linee->setFont( ksopts->defaultFont );
    UserUpdateMenu();  // Must call to update Popup.
    emit freezeUpdates(FALSE); // Stop the list boxes update
    initColors();
    scrollToBottom();
    update();
    break;
  case SET_LAG:
    if(str.isNull() == FALSE){
      bool ok = TRUE;

      str.truncate(6);
      double lag = str.toDouble(&ok);
      if(ok == TRUE){
        lag -= (lag*100.0 - int(lag*100.0))/100.0;
        QString s_lag;
        s_lag.sprintf("Lag: %.2f", lag);
        ksb_main->changeItem(s_lag, KSB_MAIN_LAG);
      }
      else{
        ksb_main->changeItem(str, KSB_MAIN_LAG);
      }
    }
    break;
  default:
    kdDebug() << "Unkown control message: " << str << endl;
  }
}

void KSircTopLevel::setTopic( const QString &topic )
{
    m_topic = topic;
    ksTopic->setText( topic );
}

void KSircTopLevel::toggleTimestamp()
{
    ksopts->timeStamp = !ksopts->timeStamp;
    ksopts->save(KSOptions::General);

    QDictIterator<KSircProcess> processIt( servercontroller::self()->processes() );
    for (; processIt.current(); ++processIt )
    {
      QPtrList<KSircMessageReceiver> receivers = processIt.current()->messageReceivers();
      QPtrListIterator<KSircMessageReceiver> messageReceiverIt( receivers );
      for ( ; messageReceiverIt.current(); ++messageReceiverIt )
      {
        KSircTopLevel *tl = dynamic_cast<KSircTopLevel *>( messageReceiverIt.current() );
        if ( tl )
          tl->updateTimeStampStatus( ksopts->timeStamp );
      }
    }
}

void KSircTopLevel::updateTimeStampStatus( bool enabled )
{
    file->setItemChecked( tsitem, enabled );
    mainw->enableTimeStamps( enabled );
}

QString KSircTopLevel::findNick(QString part, uint which)
{
  QStrList matches;
  for (QStringList::ConstIterator it = completeNicks.begin();
       it != completeNicks.end(); ++it)
    if ((*it).left(part.length()).lower() == part.lower() &&
        nicks->findNick(*it) >= 0)
      matches.append(*it);

  for(uint i=0; i < nicks->count(); i++){
    if (matches.contains(nicks->text(i)))
      continue;
    if(qstrlen(nicks->text(i)) >= part.length()){
      if(qstrnicmp(part, nicks->text(i), part.length()) == 0){
        QString qsNick = ksopts->nick;
        if(qstrcmp(nicks->text(i), qsNick) != 0){ // Don't match your own nick
          matches.append(nicks->text(i));
        }
      }
    }
  }
  if(matches.count() > 0){
    if(which < matches.count())
      return matches.at(which);
    else
      return QString::null;
  }
  return part;

}

void KSircTopLevel::addCompleteNick(const QString &nick)
{
  QStringList::Iterator it = completeNicks.find(nick);
  if (it != completeNicks.end())
    completeNicks.remove(it);

  completeNicks.prepend(nick);
}

void KSircTopLevel::changeCompleteNick(const QString &oldNick, const QString &newNick)
{
  QStringList::Iterator it = completeNicks.find(oldNick);
  if (it != completeNicks.end())
    *it = newNick;
}

void KSircTopLevel::openQueryFromNick(const QString &nick)
{
    emit open_toplevel(nick.lower());
}

void KSircTopLevel::pasteToWindow()
{
  // Ctrl-V
  KApplication::clipboard()->setSelectionMode( false );
  slotTextDropped(KApplication::clipboard()->text());
}

enum KSircTopLevelCommands{ Ask, Parse, Escape };
void KSircTopLevel::slotTextDropped( const QString& _text )
{
  if (_text.isEmpty())
    return;
  QString text = linee->text();
  int curPos = linee->cursorPosition();

  text = text.mid( 0, curPos ) + _text + text.mid( curPos );

  if (text[text.length()-1] != '\n')
     text += "\n";
  int lines = text.contains("\n");
  int approx_lines = text.length() / 75;
  if ( lines > approx_lines )
     approx_lines = lines;
  if (lines > 4) {
      int result =  KMessageBox::warningContinueCancel(this,
                i18n("You are about to send %1 lines of text.\nDo you really want to send that much?").arg(approx_lines),
                QString::null, i18n("Send"));
      if (result != KMessageBox::Continue)
      {
//        linee->setText("");
        return;
      }
  }

  tab_pressed = 0;
  if(lines > 1){
    linee->setUpdatesEnabled(FALSE);

    QStringList lines = QStringList::split( '\n', text, true );
    QStringList::ConstIterator it = lines.begin();
    QStringList::ConstIterator end = lines.end();
    KSircTopLevelCommands commands = Ask;
    for (; it != end; ++it )
    {
        if ((*it).isEmpty())
            continue;
        QString line = *it;
        if ( line[0].latin1() == '/' )
        {
           if ( commands == Ask )
                switch ( KMessageBox::questionYesNo( this, i18n(
                    "The text you pasted contains lines that start with /.\n"
                    "Should they be interpreted as IRC commands?" ) ) )
                {
                    case KMessageBox::Yes:
                        commands = Parse;
                        break;
                    case KMessageBox::No:
                        commands = Escape;
                        break;
                }
            if ( commands == Escape )
                line.prepend( "/say " );
        }
        linee->setText( line );
        sirc_line_return(line);
    }

    linee->setText("");
    linee->setUpdatesEnabled(TRUE);
    linee->update();
  }
  else{
    text.replace(QRegExp("\n"), "");
    linee->setText(text);
    linee->setCursorPosition( curPos + _text.length() );
  }
}

void KSircTopLevel::clearWindow()
{
  mainw->clear();
}

void KSircTopLevel::lineeNotTab()
{
  tab_pressed = 0;
  disconnect(linee, SIGNAL(notTab()),
             this, SLOT(lineeNotTab()));

}

void KSircTopLevel::toggleRootWindow()
{
}

bool KSircTopLevel::event( QEvent *e)
{
  if (e->type() == QEvent::ApplicationPaletteChange)
  {
    initColors();
  }
  return KMainWindow::event(e);
}

void KSircTopLevel::saveCurrLog()
{
    KURL url = KFileDialog::getSaveFileName(QString::null,
                                            "*.log", 0L,
                                            i18n("Save Chat/Query Logfile"));

    KTempFile temp;
    QTextStream *str = temp.textStream();

    *str << mainw->plainText();

    temp.close();
    KIO::NetAccess::upload(temp.name(), url);
}

void KSircTopLevel::iamDestroyed()
{
  emit objDestroyed(this);
}



void KSircTopLevel::slotDropURLs( const QStringList& urls )
{
    if ( !isPrivateChat() )
        return;

    slotDccURLs( urls, channel_name );
}

// sends the list of urls to $dest_nick
void KSircTopLevel::slotDccURLs( const QStringList& urls, const QString& nick )
{
    if ( urls.isEmpty() || nick.isEmpty() )
        return;

    QStringList::ConstIterator it = urls.begin();
    // QString s("/eval &docommand(eval{\"dcc send " + nick + " %1\"});\n");
    QString s("/dcc send " + nick + " %1\n");
    for ( ; it != urls.end(); ++it ) {
        QString file( *it );
        kdDebug() << "........ " << file << endl;
        if ( !file.isEmpty() )
            sirc_write(s.arg( file ));
    }
}

bool KSircTopLevel::isPrivateChat() const
{
    return ((channel_name[0] != '!') && (channel_name[0] != '&') &&
            (channel_name[0] != '#'));
}

bool KSircTopLevel::isPublicChat() const
{
    return ((channel_name[0] == '#') || (channel_name[0] == '&'));
}

bool KSircTopLevel::isSpecialWindow() const
{
    return (channel_name[0] == '!');
}

void KSircTopLevel::scrollToBottom()
{
    mainw->setContentsPos( 0, mainw->contentsHeight() );
//    mainw->ensureVisible( 0, mainw->contentsHeight() );
}

void KSircTopLevel::outputUnicodeLine( const QString &message )
{
  emit outputLine( encoder()->fromUnicode( message ) );
}

void KSircTopLevel::setTopicIntern( const QString &topic )
{
    QString command = QString::fromLatin1( "/topic %1 %2\n" ).arg( channel_name ).arg( topic );
    sirc_write( command );
    linee->setFocus();
}

void KSircTopLevel::beep(bool pers)
{
    QTime ctime = QTime::currentTime();
    if (ksopts->beepOnMsg) {
        // beep only every 2 seconds otherwise it'll get very noisy in the room ;)
        if ( QABS( lastBeep.secsTo( ctime ) ) > 1 ) {
          if ( pers ) {
              KNotifyClient::event(QString::fromLatin1("ChannelPersonal"),
                  i18n("Your nick appeared on channel %1").arg(channel_name));
          } else {
              KNotifyClient::event(QString::fromLatin1("ChannelChanged"),
                  i18n("Channel %1 changed").arg(channel_name));
          }
          lastBeep = ctime;
        }
    }
}

void KSircTopLevel::toggleBeep()
{
    ksopts->beepOnMsg = !ksopts->beepOnMsg;
    ksopts->save(KSOptions::General);
    file->setItemChecked(beepitem, ksopts->beepOnMsg);
}

// ####################################################################



kstInside::kstInside ( QWidget * parent, const char * name, WFlags f )
    : QHBox(parent, name, f)
{
  pan = new QSplitter(QSplitter::Horizontal, this, "");
  pan->setOpaqueResize( true );

//  mainw = new KSircListBox(pan, "");
  mainw = new KSircView( pan, "user" );
  mainw->setFocusPolicy(QWidget::NoFocus);
  // mainw->setSmoothScrolling(TRUE);

  nicks = new aListBox(pan, "");
  nicks->setFocusPolicy(QWidget::NoFocus);
  nicks->hide(); // default = only the main widget

  QValueList<int> sizes;
  sizes << 85 << 15;
  pan->setSizes(sizes);
  pan->setResizeMode( mainw, QSplitter::Stretch );
  pan->setResizeMode( nicks, QSplitter::Stretch );

  setName(name);
}

kstInside::~kstInside()
{
  delete mainw;
  delete nicks;
  delete pan;
}


void kstInside::setName(const char *name)
{
  QObject::setName(name);
  my_name = name;
  panner_name = my_name + "_" + "Panner";
  mainw_name = my_name + "_" + "MainIrc";
  nicks_name = my_name + "_" + "NickListBox";
  linee_name = my_name + "_" + "LineEnter";

  pan->setName(panner_name);
  mainw->setName(mainw_name);
  nicks->setName(nicks_name);
//  linee->setName(linee_name);
}
#include "toplevel.moc"

// vim: ts=2 sw=2 et
