/***************************************************************************
                          kmessview.cpp  -  description
                             -------------------
    begin                : Thu Jan 9 2003
    copyright            : (C) 2003 by Mike K. Bennett
                           (C) 2005 by Diederik van der Boor
    email                : mkb137b@hotmail.com
                           vdboor --at-- codingdomain.com
 ***************************************************************************/

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

#include "kmessview.h"

#include <qheader.h>
#include <qlabel.h>
#include <qstringlist.h>
#include <qtooltip.h>
#include <qstylesheet.h>
#include <qframe.h>
#include <qlayout.h>

#include <kdebug.h>
#include <kiconloader.h>
#include <klistview.h>
#include <klocale.h>
#include <kurllabel.h>
#include <kaction.h>
#include <kpopupmenu.h>
#include <ksqueezedtextlabel.h>
#include <krun.h>
#include <kurl.h>

#include "contact/contact.h"
#include "contact/contactlist.h"
#include "contact/group.h"
#include "dialogs/contactpropertiesdialog.h"

#include "contactlistviewitem.h"
#include "currentaccount.h"
#include "grouplistviewitem.h"
#include "kmessdebug.h"
#include "specialgroups.h"


#ifdef KMESSDEBUG_KMESSVIEW
#define KMESSDEBUG_KMESSVIEW_GENERAL
#endif


// The constructor
KMessView::KMessView(QWidget *parent, const char *name )
  : KMessViewInterface(parent,name),
    contactList_(0),
    currentAccount_(0),
    initialized_(false)
{
  toolTip_ = new ToolTip(this, 0);

  // I actually want to implement a tooltip like filelight has,
  // but this makes things easier already.
  // It causes a dialog to display tooltips faster.
  QToolTip::setWakeUpDelay(200);
}



// The destructor
KMessView::~KMessView()
{
  delete toolTip_;

#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "DESTROYED KMessView " << endl;
#endif
}



// The program is connected to the server.  Store the user account and
//  update the user information.
void KMessView::connected()
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView - connected" << endl;
#endif

  // Run the update functions with the current user data
  slotUpdateEmailDisplay();
  slotUpdateUserStatus();
  slotUpdateNoEmails();

  // Enable the labels
  onlineStatusLabel_->setEnabled(true);
  emailLabel_->setEnabled(true);
}



// Called when the program has disconnected from the server.
void KMessView::disconnected()
{
  KIconLoader *loader = KGlobal::iconLoader();

  // Disable and reset the labels
  onlineStatusLabel_->setText( i18n("Disconnected") );
  onlineStatusLabel_->setEnabled(false);
  onlineStatusPixmapLabel_->setPixmap( loader->loadIcon( "offline", KIcon::User ) );
  emailLabel_->setText("");
  emailLabel_->setEnabled(false);

  // Clear the contact list
  // (all current items still reffer to non-existing contacts)
  contactListView_->clear();

  // Add the existing groups of the contactlist to this view
  QPtrList<Group>          groupList( contactList_->getGroupList() );
  QPtrListIterator<Group>  it( groupList );
  while(it.current() != 0)
  {
    slotGroupAdded(it.current());
    ++it;
  }
}



// Fix the sort positions, by making them incremental again.
void KMessView::fixGroupSortPositions()
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::fixGroupSortPositions" << endl;
#endif

  Group             *group;
  GroupListViewItem *groupItem  = findFirstGroup();
  int               newPosition = 0;

  while(groupItem != 0)
  {
    group = groupItem->getGroup();

    if(! group->isSpecialGroup())
    {
      // This does not change the list sorting
      // (since we found the items in this sequence),
      // but it ensures the sort-index is unique.
      group->setSortPosition(++newPosition);

#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
        kdDebug() << "KMessView: Group "   << group->getName()
                  << " has sort position " << newPosition      << endl;
#endif
    }

    groupItem = findNextGroup(groupItem);
  }
}



// Return the contact associated with the given item
Contact * KMessView::getContactByItem(QListViewItem *item) const
{
  if(item == 0)
  {
    // Happens when contactListView_->currentItem() is null.
    return 0;
  }

#ifdef KMESSTEST
  ASSERT( dynamic_cast<KMessListViewItem*>(item) != 0 );
#endif

  // Check the type
  int type = static_cast<KMessListViewItem*>(item) -> getType();

  switch(type)
  {
    case KMessListViewItem::LISTTYPE_CONTACT :
    {
      return static_cast<ContactListViewItem*>(item) -> getContact();
    }
  }

  return 0;
}



// Find out whether the contact is added to the group item
ContactListViewItem* KMessView::getContactItemInGroup(const QListViewItem *groupItem, const Contact *contact) const
{
  QListViewItem       *currentItem;
  ContactListViewItem *contactItem;
  int type;

  // Get the first item
  currentItem = groupItem->firstChild();

  while(currentItem != 0)
  {
    // Check the type
    type = static_cast<KMessListViewItem*>( currentItem )->getType();

    switch(type)
    {
      case KMessListViewItem::LISTTYPE_CONTACT :
      {
        // Check if this is the requested contact
        contactItem = static_cast<ContactListViewItem*>( currentItem );
        if(contactItem->getContact() == contact)
        {
          return contactItem;
        }
      }
    }

    // Get the next item
    currentItem = currentItem->nextSibling();
  }

  return 0;
}



// Return the group associated with the given item
Group * KMessView::getGroupByItem(QListViewItem *item) const
{
  if(item == 0)
  {
    // Happens when contactListView_->currentItem() is null.
    return 0;
  }

#ifdef KMESSTEST
  ASSERT( dynamic_cast<KMessListViewItem*>(item) != 0 );
#endif

  int            type;
  QListViewItem *realItem;

  // Find the root node
  realItem = item;
  while(realItem->parent() != 0)
  {
    realItem = realItem->parent();
  }

  // Check the type
  type = static_cast<KMessListViewItem*>(realItem) -> getType();

  switch(type)
  {
    case KMessListViewItem::LISTTYPE_GROUP :
    {
      return static_cast<GroupListViewItem*>(realItem) -> getGroup();
    }
  }

  return 0;
}



// Return the special group the contact should be placed in.
QString KMessView::getMetaGroupId(const Contact *contact) const
{
  if( contact->isFriend() )
  {
    // If a contact is in the friends list, show on top
    return SpecialGroups::INDIVIDUALS;
  }
  else if( contact->isAllowed() )
  {
    // If the contact is only allowed, show in "allowed" group
    return SpecialGroups::ALLOWED;
  }
  else if (contact->isBlocked() || contact->isReverse())
  {
    // The contact is either blocked, or *only* at the reverse list
    return SpecialGroups::REMOVED;
  }
  else
  {
    // Contact no longer visible in *any* list, is really removed.
    return QString::null;
  }
}



// Initialize the class
bool KMessView::initialize(const ContactList *contactList)
{
  bool         success;
  KIconLoader *loader = KGlobal::iconLoader();

  if ( initialized_ )
  {
    kdDebug() << "KMessView already initialized." << endl;
    return false;
  }

  success = initContactListView();
  if(! success)
  {
    kdDebug() << "KMessView - Couldn't initialize contact list view." << endl;
    return false;
  }

  success = initContactPopup();
  if(! success)
  {
    kdDebug() << "KMessView - Couldn't initialize contact popup." << endl;
    return false;
  }

  success = initGroupPopup();
  if(! success)
  {
    kdDebug() << "KMessView - Couldn't initialize group popup." << endl;
    return false;
  }

  // Store reference for slotUpdateViewMode()
  contactList_ = contactList;

  // Set an icon for the email label
  emailPixmapLabel_->setPixmap( loader->loadIcon("email", KIcon::User) );

  // Connect signals for the current-account
  currentAccount_ = CurrentAccount::instance();

  connect( currentAccount_, SIGNAL(             changedNoEmails() ),    // Changed e-mail count
           this,            SLOT  (          slotUpdateNoEmails() ) );
  connect( currentAccount_, SIGNAL(               changedStatus() ),    // Changed status
           this,            SLOT  (        slotUpdateUserStatus() ) );
  connect( currentAccount_, SIGNAL(         changedFriendlyName() ),    // Changed name
           this,            SLOT  (        slotUpdateUserStatus() ) );
  connect( currentAccount_, SIGNAL( changedEmailDisplaySettings() ),    // Changed e-mail display settings
           this,            SLOT  (      slotUpdateEmailDisplay() ) );
  connect( currentAccount_, SIGNAL(      changedDisplaySettings() ),    // Changed group-display settings
           this,            SLOT  (   slotUpdateDisplaySettings() ) );
  connect( currentAccount_, SIGNAL(             changedViewMode() ),    // Changed the requested view mode
           this,            SLOT  (          slotUpdateViewMode() ) );

  // Connect signals for the contactlist
  connect( contactList, SIGNAL(             contactAdded(Contact*)       ),   // A contact was added.
           this,          SLOT(         slotContactAdded(Contact*)       ) );
  connect( contactList, SIGNAL(           contactOffline(Contact*,bool)  ),   // A contact went offline
           this,          SLOT( slotContactChangedOnline(Contact*)       ) );
  connect( contactList, SIGNAL(            contactOnline(Contact*,bool)  ),   // A contact went online
           this,          SLOT( slotContactChangedOnline(Contact*)       ) );
  connect( contactList, SIGNAL(             contactMoved(Contact*)       ),   // A contact was moved
           this,          SLOT(         slotContactMoved(Contact*)       ) );
  connect( contactList, SIGNAL(           contactRemoved(const Contact*) ),   // A contact was removed
           this,          SLOT(       slotContactRemoved(const Contact*) ) );
  connect( contactList, SIGNAL(               groupAdded(Group*)         ),   // A group was added
           this,          SLOT(           slotGroupAdded(Group*)         ) );
  connect( contactList, SIGNAL(             groupRemoved(const Group*)   ),   // A group was removed
           this,          SLOT(         slotGroupRemoved(const Group*)   ) );

  // Add the existing groups of the contactlist to this view
  QPtrList<Group>          groupList( contactList->getGroupList() );
  QPtrListIterator<Group>  it( groupList );
  while(it.current() != 0)
  {
    slotGroupAdded(it.current());
    ++it;
  }

  return true;
}



// Find the first group in the first
GroupListViewItem * KMessView::findFirstGroup() const
{
  QListViewItem *currentItem = contactListView_->firstChild();
  int            type;

  while(currentItem != 0)
  {
    // Check for the given type
    type = static_cast<KMessListViewItem*>( currentItem )->getType();

    switch(type)
    {
      case KMessListViewItem::LISTTYPE_GROUP :
      {
        return static_cast<GroupListViewItem*>( currentItem );
      }
    }

    // Get next item
    currentItem = currentItem->nextSibling();
  }

  return 0;
}



// Find the next group in the list
GroupListViewItem * KMessView::findNextGroup(GroupListViewItem *previousGroup) const
{
  QListViewItem *currentItem = previousGroup;
  int            type;

  // Get next item
  currentItem = currentItem->nextSibling();

  while(currentItem != 0)
  {
    // Check for the given type
    type = static_cast<KMessListViewItem*>( currentItem )->getType();

    switch(type)
    {
      case KMessListViewItem::LISTTYPE_GROUP :
      {
        return static_cast<GroupListViewItem*>( currentItem );
      }
    }

    // Get next item
    currentItem = currentItem->nextSibling();
  }

  return 0;
}



// Initialize the contact list view
bool KMessView::initContactListView()
{
  KIconLoader *loader = KGlobal::iconLoader();

  // Hide the column header
  contactListView_->header()->hide();
  contactListView_->setStaticBackground(true);   // Workarround for the smudge
//  contactListView_->setRootIsDecorated(true);
//  contactListView_->setPaletteForegroundColor( QColor( "#000000" ) );


  // Set a background pixmap
  backgroundPixmap_ = loader->loadIcon( "background", KIcon::User, 0, KIcon::DefaultState, 0L, true );

  if(! backgroundPixmap_.isNull())
  {
    contactListView_->setPaletteBackgroundPixmap( backgroundPixmap_ );
  }

  return true;
}



// Initialize the contact popup
bool KMessView::initContactPopup()
{
  // Initialize context menu actions
  addContact_        = new KAction(i18n("&Add Contact"),      "bookmark_add", 0, this, "add");
  allowContact_      = new KAction(i18n("A&llow Contact"),    "bookmark_add", 0, this, "allow");
  blockContact_      = new KAction(i18n("&Block Contact"),    "cancel",       0, this, "block");
  chatWithContact_   = new KAction(i18n("&Chat"),             "launch",       0, this, "chat");
  emailContact_      = new KAction(i18n("&Send email"),       "mail_generic", 0, this, "email");
  contactProperties_ = new KAction(i18n("&Properties"),       0,              0, this, "properties");
  removeContact_     = new KAction(i18n("&Remove Contact"),   "eraser",       0, this, "remove");
  msnProfile_        = new KAction(i18n("&View Profile"),     "kmess",        0, this, "profile");
  unblockContact_    = new KAction(i18n("&Unblock Contact"),  "cancel",       0, this, "unblock");

  // Connect the actions
  connect( addContact_,        SIGNAL(activated()),   this,  SLOT(slotForwardAddContact())     );
  connect( allowContact_,      SIGNAL(activated()),   this,  SLOT(slotForwardAllowContact())   );
  connect( blockContact_,      SIGNAL(activated()),   this,  SLOT(slotForwardBlockContact())   );
  connect( chatWithContact_,   SIGNAL(activated()),   this,  SLOT(slotForwardStartChat())      );
  connect( contactProperties_, SIGNAL(activated()),   this,  SLOT(slotShowContactProperties()) );
  connect( emailContact_,      SIGNAL(activated()),   this,  SLOT(slotEmailContact())          );
  connect( msnProfile_,        SIGNAL(activated()),   this,  SLOT(slotShowContactProfile())    );
  connect( removeContact_,     SIGNAL(activated()),   this,  SLOT(slotForwardRemoveContact())  );
  connect( unblockContact_,    SIGNAL(activated()),   this,  SLOT(slotForwardUnblockContact()) );


  // Initialize sub menu's
  moveContactToGroup_ = new KActionMenu(i18n("&Move to Group"), 0, 0, "moveToGroup");


  // Initialize the popup menu
  contactActionPopup_ = new KPopupMenu(this);
  contactActionPopup_->insertTitle("KMess", 0);

  chatWithContact_   ->plug(contactActionPopup_);
  emailContact_      ->plug(contactActionPopup_);
  msnProfile_        ->plug(contactActionPopup_);
  contactActionPopup_->insertSeparator();

  addContact_        ->plug(contactActionPopup_);
  allowContact_      ->plug(contactActionPopup_);

  blockContact_      ->plug(contactActionPopup_);
  unblockContact_    ->plug(contactActionPopup_);

  moveContactToGroup_->plug(contactActionPopup_);
  removeContact_     ->plug(contactActionPopup_);
  contactActionPopup_->insertSeparator();

  contactProperties_ ->plug(contactActionPopup_);

  return true;
}



// Initialize the group popup
bool KMessView::initGroupPopup()
{
  // Initialize context menu actions
  moveGroupDown_ = new KAction(i18n("Move Group &Down"), "1downarrow",   0, 0, "movedown" );
  moveGroupUp_   = new KAction(i18n("Move Group &Up"),   "1uparrow",     0, 0, "moveup"   );
  removeGroup_   = new KAction(i18n("Re&move Group"),    "editdelete",   0, 0, "remove"   );
  renameGroup_   = new KAction(i18n("Re&name Group"),    "edit",         0, 0, "rename"   );

  // Connect the actions
  connect( moveGroupDown_, SIGNAL(activated()),   this,  SLOT(slotMoveGroupDown())      );
  connect( moveGroupUp_,   SIGNAL(activated()),   this,  SLOT(slotMoveGroupUp())        );
  connect( removeGroup_,   SIGNAL(activated()),   this,  SLOT(slotForwardRemoveGroup()) );
  connect( renameGroup_,   SIGNAL(activated()),   this,  SLOT(slotForwardRenameGroup()) );

  // Initialize the popup menu
  groupActionPopup_ = new KPopupMenu(this);
  groupActionPopup_->insertTitle("KMess", 0);

  moveGroupUp_     ->plug(groupActionPopup_);
  moveGroupDown_   ->plug(groupActionPopup_);
  groupActionPopup_->insertSeparator();

  renameGroup_     ->plug(groupActionPopup_);
  removeGroup_     ->plug(groupActionPopup_);

  return true;
}



// Rebuild the contents of the move-contact menu.
void KMessView::rebuildMoveMenu(const Contact *contact)
{
  bool               inGroup;
  KAction           *moveAction = 0;
  QStringList        groupIds   = contact->getGroupIds();
  GroupListViewItem *groupItem  = findFirstGroup();

  while(groupItem != 0)
  {
    // First remove the item, because there is no way to tell whether
    // it was already added (besides, this fixes the sorting)
    moveContactToGroup_->remove(groupItem->getMoveAction());

    // add the item
    moveAction = groupItem->getMoveAction();  // is null for special groups

    if(moveAction != 0)
    {
      inGroup = groupIds.contains(groupItem->getGroup()->getId());
      if(! inGroup)
      {
        moveContactToGroup_->insert(moveAction);
      }
    }

    groupItem = findNextGroup(groupItem);
  }
}



// The email label was clicked so open the user's preferred email url
void KMessView::slotEmailLabelClicked()
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::slotEmailLabelClicked" << endl;
#endif

  currentAccount_->openHotmailAtInbox();
}



// Called when a contact was added.
void KMessView::slotContactAdded(Contact *contact)
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::slotContactAdded" << endl;
#endif

  ContactListViewItem *item = 0;
  GroupListViewItem   *groupItem;

  Group       *group;
  QString      metaGroupId;
  QStringList  groupIds     = contact->getGroupIds();
  bool         addToOnline  = contact->isOnline();
  bool         addToOffline = contact->isOffline();
  bool         foundGroup   = false;
  bool         isIndividual = false;


  // Use meta-group id for non-grouped contacts
  if(groupIds.isEmpty())
  {
    metaGroupId = getMetaGroupId(contact);

    if(! metaGroupId.isNull())
    {
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
      kdDebug() << "KMessView::slotContactAdded: using meta group " << metaGroupId << endl;
#endif

      // Use meta group ID
      groupIds.append(metaGroupId);
      isIndividual = (metaGroupId == SpecialGroups::INDIVIDUALS);

      // Only add to online/offline if the contact
      // is visible in any other normal list.
      addToOnline  = (addToOnline  && isIndividual);
      addToOffline = (addToOffline && isIndividual);
    }
  }


  // Add a contact node to every related group
  groupItem = findFirstGroup();

  while(groupItem != 0)
  {
    group = groupItem->getGroup();

    // If the contact is a member of the group, add it
    if( groupIds.contains(group->getId()) )
    {
      groupItem->addContactItem(contact);
      foundGroup = true;
    }
    else if( (addToOnline  && group->getId() == SpecialGroups::ONLINE) ||
             (addToOffline && group->getId() == SpecialGroups::OFFLINE)  )
    {
      // Also make sure the contact is always added to the online/offline group.
      groupItem->addContactItem(contact);
    }

    groupItem = findNextGroup(groupItem);
  }


  // Also add item to root if not found
  if(! foundGroup)
  {
    // Add it to the list directly.
    item = new ContactListViewItem(contactListView_, contact);
  }
}



// Called when a contact went online or offline
void KMessView::slotContactChangedOnline(Contact *contact)
{
  // Move the contact to the other group.
  slotContactMoved(contact);
}



// Called when a contact is moved to a different position
void KMessView::slotContactMoved(Contact *contact)
{
  // Create the iterator
  QListViewItem       *currentItem;
  ContactListViewItem *contactItem;

  GroupListViewItem   *metaGroupItem = 0;
  GroupListViewItem   *groupItem     = 0;
  QString              groupId;
  int                  type;

  QString     metaGroupId;
  QStringList groupIds       = contact->getGroupIds();
  bool        addToOnline    = contact->isOnline();
  bool        addToOffline   = contact->isOffline();
  bool        contactInGroup = false;
  bool        contactFound   = false;
  bool        isIndividual   = false;


  // Use meta-group id for non-grouped contacts
  if(groupIds.isEmpty())
  {
    metaGroupId = getMetaGroupId(contact);

    if(! metaGroupId.isNull())
    {
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
      kdDebug() << "KMessView::slotContactMoved: using meta group " << metaGroupId << endl;
#endif

      // Use meta group
      groupIds.append(metaGroupId);
      isIndividual = (metaGroupId == SpecialGroups::INDIVIDUALS);

      // Only add to online/offline if the contact
      // is visible in any other normal list.
      addToOnline  = (addToOnline  && isIndividual);
      addToOffline = (addToOffline && isIndividual);
    }
  }


#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::slotContactMoved "
            << contact->getHandle() << " moved to " << groupIds.join(",") << endl;
#endif


  currentItem = contactListView_->firstChild();

  while(currentItem != 0)
  {
    // Check for the given type
    type = static_cast<KMessListViewItem*>( currentItem )->getType();

    switch(type)
    {
      case KMessListViewItem::LISTTYPE_GROUP :
      {
        // Read group properties
        groupItem      = static_cast<GroupListViewItem*>( currentItem );
        groupId        = groupItem->getGroup()->getId();

        // Does the contact exist here / should it exist here?
        contactItem    = getContactItemInGroup(currentItem, contact);
        contactInGroup = groupIds.contains(groupId);

        // Contact not found in this groupItem?
        if(contactItem == 0)
        {
          if((contactInGroup) ||
            (addToOnline  && groupId == SpecialGroups::ONLINE) ||
            (addToOffline && groupId == SpecialGroups::OFFLINE))
          {
            // Add to current group
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
            kdDebug() << "KMessView::slotContactMoved: Adding contact to " << groupId << endl;
#endif
            groupItem->addContactItem(contact);

            // Only register contact if it's a normal group
            if(contactInGroup)
            {
              contactFound = true;
            }
          }
        }
        else // item != 0
        {
          if(contactInGroup)
          {
            // Found in the list
            contactFound = true;
          }
          else if(! (addToOnline  && groupId == SpecialGroups::ONLINE) &&  // unless "keep here, in online group"
                  ! (addToOffline && groupId == SpecialGroups::OFFLINE))   // and    "keep here, in offline group"
          {
            // Remove from current group.
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
            kdDebug() << "KMessView::slotContactMoved: contact removed from " << groupId << endl;
#endif
            groupItem->detachContactItem(contactItem);
            delete contactItem;
            groupItem->recountContacts();
          }
        }

        // If we found the meta-group, store it
        if(groupId == metaGroupId)
        {
          metaGroupItem = groupItem;
        }

        break;
      }
      case KMessListViewItem::LISTTYPE_CONTACT :
      {
        // Found a contact in the root.
        contactItem = static_cast<ContactListViewItem*>( currentItem );
        if(contactItem->getContact() == contact)
        {
          if(isIndividual)
          {
            // Found in the list
            contactFound = true;
          }
          else
          {
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
            kdDebug() << "KMessView::slotContactMoved: contact removed from root" << endl;
#endif
            // Jump to nextSibling before we delete the object
            currentItem = currentItem->nextSibling();

            // Delete the object
            delete contactItem;
            continue;
          }
        }

        break;
      }
    }

    currentItem = currentItem->nextSibling();
  }


  // If the item was not found, add it to a special group
  if(! contactFound)
  {
    if(isIndividual)
    {
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
      kdDebug() << "KMessView::slotContactMoved: Adding contact to root" << endl;
#endif
      // Add it to the list directly.
      contactItem = new ContactListViewItem(contactListView_, contact);
    }
    else if(metaGroupItem != 0)
    {
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
      kdDebug() << "KMessView::slotContactMoved: Adding contact to meta " << metaGroupId << endl;
#endif
      // Add the contact to the meta-group
      metaGroupItem->addContactItem(contact);
    }
    else
    {
      // the contact is removed from all lists,
      // only exists in kmess memory, no not display.
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
      kdDebug() << "KMessView::slotContactMoved: Contact " << contact->getHandle() << " no longer displayed." << endl;
#endif
    }
  }
}



// Called when a contact is removed
void KMessView::slotContactRemoved(const Contact *contact)
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::slotContactRemoved" << endl;
#endif

  QListViewItemIterator iterator(contactListView_);
  GroupListViewItem    *currentGroup = 0;
  ContactListViewItem  *contactItem;
  int type;

  while(iterator.current() != 0)
  {
    // Check for the given type
    type = static_cast<KMessListViewItem*>( iterator.current() )->getType();

    switch(type)
    {
      case KMessListViewItem::LISTTYPE_GROUP :
      {
        currentGroup = static_cast<GroupListViewItem*>( iterator.current() );
        break;
      }
      case KMessListViewItem::LISTTYPE_CONTACT :
      {
        // Check whether the object references a contact
        contactItem = static_cast<ContactListViewItem*>( iterator.current() );
        if(contactItem->getContact() == contact)
        {
          // Detach from current group
          if(currentGroup != 0)
          {
            currentGroup->detachContactItem(contactItem);
          }

          // remove it
          delete contactItem;  // is iterator.current()

          // Update group contact counter
          if(currentGroup != 0)
          {
            currentGroup->recountContacts();
          }
        }
        break;
      }
    }
    ++iterator;
  }
}



// A list item was right clicked.
void KMessView::slotContextMenu(QListViewItem *item, const QPoint &point, int /*col*/)
{
  Contact *contact = getContactByItem(item);
  Group   *group   = getGroupByItem(item);

  // A contact was pressed
  if(contact != 0)
  {
    bool isAllowed = contact->isAllowed();
    bool isBlocked = contact->isBlocked();
    bool isReverse = contact->isReverse();
    bool isFriend  = contact->isFriend();
    bool isOnline  = contact->isOnline();

    // Only chat if contact is friends list, and not blocked.
    chatWithContact_    -> setEnabled( isFriend && ! isBlocked && isOnline );

    moveContactToGroup_ -> setEnabled(   isFriend );

    // Only if not added yet
    addContact_         -> setEnabled( ! isFriend );

    // Only allow if contact is on the "removed" group.
    allowContact_       -> setEnabled( ! isFriend && ! isAllowed && ! isBlocked );

    // Only one of these is enabled:
    blockContact_       -> setEnabled( ! isBlocked );
    unblockContact_     -> setEnabled(   isBlocked );

    // Only if the contact is on the friends list, or no longer
    // added to reverse list (otherwise an "contact has added you" message appears)
    removeContact_      -> setEnabled(   isFriend || ! isReverse );


    // Rebuild the move-to-group list
    rebuildMoveMenu(contact);

    // Show the popup
    contactActionPopup_->changeTitle(0, contact->getHandle()); // Not name, because most msn names are too weird.
    contactActionPopup_->popup(point);
  }
  else if(group != 0)
  {
    if(! group->isSpecialGroup())
    {
      // TODO: setEnabled() checks

      // Show the popup
      groupActionPopup_->changeTitle(0, group->getName());
      groupActionPopup_->popup(point);
    }
  }
}



// E-mail the current contact
void KMessView::slotEmailContact()
{
  Contact *contact = getContactByItem( contactListView_->currentItem() );
  if(contact != 0)
  {
    currentAccount_->openHotmailAtCompose( contact->getHandle() );
  }
}


// A item was selected
void KMessView::slotItemClicked(QListViewItem *item)
{
  if(item != 0 && item->isExpandable() && item->childCount() > 0)
  {
    // Open group if it's collapsed, close if expanded.
    Group *group = getGroupByItem(item);
    group->setExpanded(! group->isExpanded());
  }
}



// A list item was executed
void KMessView::slotItemExecuted(QListViewItem *item)
{
  Contact *contact = getContactByItem(item);
  if(contact != 0)
  {
    // If the contact is online
    if(contact->isOnline())
    {
      // Tell the "KMess" class we like to start a chat.
      emit startChat( contact->getHandle() );
    }
  }
}



// Forward the "add contact" menu action
void KMessView::slotForwardAddContact()
{
  Contact *contact = getContactByItem( contactListView_->currentItem() );
  if(contact != 0)
  {
    emit addContact(contact->getHandle());
  }
}



// Forward the "allow contact" menu action
void KMessView::slotForwardAllowContact()
{
  Contact *contact = getContactByItem( contactListView_->currentItem() );
  if(contact != 0)
  {
    emit allowContact(contact->getHandle());
  }
}



// Forward the "block contact" menu action
void KMessView::slotForwardBlockContact()
{
  Contact *contact = getContactByItem( contactListView_->currentItem() );
  if(contact != 0)
  {
    emit blockContact(contact->getHandle());
  }
}



// Called when a contact should be moved
void KMessView::slotForwardMoveContact(Group *newGroup)
{
  Contact *contact    = getContactByItem( contactListView_->currentItem() );
  Group   *oldGroup   = getGroupByItem( contactListView_->currentItem() );
  QString  oldGroupId = (oldGroup != 0 ? oldGroup->getId() : "0");

  if(contact != 0)
  {
    emit moveContact(contact->getHandle(), oldGroupId, newGroup->getId());
  }
}



// Forward the "remove contact" menu action
void KMessView::slotForwardRemoveContact()
{
  Contact *contact = getContactByItem( contactListView_->currentItem() );
  if(contact != 0)
  {
    // TODO: Only remove the contact from the current group
    emit removeContact(contact->getHandle());
  }
}



// Forward the "remove group" menu action
void KMessView::slotForwardRemoveGroup()
{
  Group *group = getGroupByItem( contactListView_->currentItem() );
  if(group != 0)
  {
    emit removeGroup(group->getId());
  }
}



// Forward the "rename group" menu action
void KMessView::slotForwardRenameGroup()
{
  Group *group = getGroupByItem( contactListView_->currentItem() );
  if(group != 0)
  {
    emit renameGroup(group->getId());
  }
}



// Forward the "start chat" menu action
void KMessView::slotForwardStartChat()
{
  slotItemExecuted( contactListView_->currentItem() );
}



// Forward the "unblock contact" menu action
void KMessView::slotForwardUnblockContact()
{
  Contact *contact = getContactByItem( contactListView_->currentItem() );
  if(contact != 0)
  {
    emit unblockContact(contact->getHandle());
  }
}



// Called when a group was added
void KMessView::slotGroupAdded(Group *group)
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::slotGroupAdded" << endl;
#endif

  GroupListViewItem *item;
  item = new GroupListViewItem(contactListView_, group);

  if(! group->isSpecialGroup() || group->getId() == SpecialGroups::INDIVIDUALS)
  {
    // Connect item move signal
    connect(item, SIGNAL(moveToGroup(Group*)), this, SLOT(slotForwardMoveContact(Group*)));
    // Hide normal group if user likes to sort contacts by online/offline state
    item->setVisible( currentAccount_->getShowContactsByGroup() );
  }
  else
  {
    // TODO: use ints + switch
    // For special groups, determine whether to show the item or not.
    if(group->getId() == SpecialGroups::ALLOWED)
    {
      item->setVisible( currentAccount_->getShowAllowedContacts() );
    }
    else if(group->getId() == SpecialGroups::REMOVED)
    {
      item->setVisible( currentAccount_->getShowRemovedContacts() );
    }
    else if(group->getId() == SpecialGroups::ONLINE)
    {
      item->setVisible( ! currentAccount_->getShowContactsByGroup() );
    }
    else if(group->getId() == SpecialGroups::OFFLINE)
    {
      item->setVisible( ! currentAccount_->getShowContactsByGroup() &&
                          currentAccount_->getShowOfflineContacts() );
    }
  }
}



// Called when a group was removed
void KMessView::slotGroupRemoved(const Group *group)
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::slotGroupRemoved" << endl;
#endif

  GroupListViewItem *groupItem = findFirstGroup();

  while(groupItem != 0)
  {
    // If the group is found
    if(groupItem->getGroup() == group)
    {
#ifdef KMESSTEST
      ASSERT( groupItem->childCount() == 0);
#endif
      // remove it
      delete groupItem;
      return; // don't expect to find another group.
    }

    groupItem = findNextGroup(groupItem);
  }
}



// Called when a group should move down
void KMessView::slotMoveGroupDown()
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::slotMoveGroupDown()" << endl;
#endif

  Group *nextGroup;
  int    sortPosition;
  Group *group = getGroupByItem( contactListView_->currentItem() );

  if(group != 0)
  {
    fixGroupSortPositions();

    // Swap both sort positions
    sortPosition = group->getSortPosition();
    nextGroup    = contactList_->getGroupBySortPosition(sortPosition + 1);

    if(nextGroup != 0)
    {
      group     ->setSortPosition( nextGroup->getSortPosition() );
      nextGroup ->setSortPosition( sortPosition                 );
      contactListView_->sort();
    }
    else
    {
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
      kdDebug() << "KMessView::slotMoveGroupDown: Next group not found" << endl;
#endif
    }
  }
}



// Called when a group should move up
void KMessView::slotMoveGroupUp()
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::slotMoveGroupUp()" << endl;
#endif

  Group *previousGroup;
  int    sortPosition;
  Group *group = getGroupByItem( contactListView_->currentItem() );

  if(group != 0)
  {
    fixGroupSortPositions();

    // Swap both sort positions
    sortPosition  = group->getSortPosition();
    if(sortPosition <= 1) return;  // Already on top.
    previousGroup = contactList_->getGroupBySortPosition(sortPosition - 1);

    if(previousGroup != 0)
    {
      group         ->setSortPosition( previousGroup->getSortPosition() );
      previousGroup ->setSortPosition( sortPosition                     );
      contactListView_->sort();
    }
    else
    {
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
      kdDebug() << "KMessView::slotMoveGroupUp: Previous group not found" << endl;
#endif
    }
  }
}




// Display the profile of the current contact
void KMessView::slotShowContactProfile()
{
  QString   urlPath;
  KRun      *run;

  Contact *contact = getContactByItem( contactListView_->currentItem() );
  if(contact != 0)
  {
    // Create a URL to the msn profile page.  (Unfortunately, this isn't internationalized)
    run = new KRun( KURL( "http://members.msn.com/default.msnw?mem=" + contact->getHandle() ));
  }
}



// Display the properties of the current contact
void KMessView::slotShowContactProperties()
{
  ContactPropertiesDialog *dialog;

  Contact *contact = getContactByItem( contactListView_->currentItem() );
  if(contact != 0)
  {
    dialog = new ContactPropertiesDialog( this, "contactProperties" );
    dialog->launch( contact );
    delete dialog;
  }
}



// Change whether the allowed,removed groups are displayed.
void KMessView::slotUpdateDisplaySettings()
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView::slotUpdateDisplaySettings - changing visibility of groups" << endl;
#endif

  // Read the settings
  bool showAllowed = currentAccount_->getShowAllowedContacts();
  bool showRemoved = currentAccount_->getShowRemovedContacts();
  bool showByGroup = currentAccount_->getShowContactsByGroup();
  bool showOffline = currentAccount_->getShowOfflineContacts();

  QString            groupId;
  GroupListViewItem *groupItem = findFirstGroup();

  while(groupItem != 0)
  {
    // TODO: use switch + ints
    groupId = groupItem->getGroup()->getId();

    // Hide or show the special groups
    if(groupId == SpecialGroups::ALLOWED)
    {
      groupItem->setVisible( showAllowed );
    }
    else if(groupId == SpecialGroups::REMOVED)
    {
      groupItem->setVisible( showRemoved );
    }
    else if(groupId == SpecialGroups::ONLINE)
    {
      groupItem->setVisible( ! showByGroup );
    }
    else if(groupId == SpecialGroups::OFFLINE)
    {
      groupItem->setVisible( ! showByGroup && showOffline );
    }
    else
    {
      // For convenience, hide normal groups if display mode has changed.
      // This feature is used from slotUpdateViewMode()
      if(! groupItem->getGroup()->isSpecialGroup())
      {
        groupItem->setVisible( showByGroup );
      }
    }

    groupItem = findNextGroup(groupItem);
  }
}



// Change whether or not the email label is displayed based on account settings.
void KMessView::slotUpdateEmailDisplay()
{
  if ( currentAccount_->getShowEmail() && currentAccount_->getEmailSupported() )
  {
    emailFrame_->setHidden(false);
    //emailFrame_->setMargin(statusLayout_->margin());
    emailFrame_->setMaximumHeight(40);
  }
  else
  {
    emailFrame_->setHidden(true);
    //emailFrame_->setMargin(0);
    emailFrame_->setMaximumHeight(0);
  }
}



// Update the email count.
void KMessView::slotUpdateNoEmails()
{
  QString message;

  // Update the label
  if ( CurrentAccount::instance()->getNoEmails() == 1 )
  {
    message = i18n("You have 1 new e-mail in your inbox.");
  }
  else
  {
    message = i18n("You have %1 new emails in your inbox.")
              .arg(CurrentAccount::instance()->getNoEmails());
  }
  emailLabel_->setText( message );
  // Set the user's preferred email website.
  emailLabel_->setURL( CurrentAccount::instance()->getEmailUrl() );
}



// Update the user's status.
void KMessView::slotUpdateUserStatus()
{
  KIconLoader      *loader = KGlobal::iconLoader();
  QString          friendlyName, status;
  QString          iconName, statusIdentifier;

  friendlyName = CurrentAccount::instance()->getFriendlyName();
  status = CurrentAccount::instance()->getStatus();

#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView - update user status to " << status << endl;
#endif

  // Update the onlineStatusLabel and pixmap
  if ( status == "AWY" )
  {
    statusIdentifier = i18n( "(Away)" );
    iconName = "away";
  }
  else if ( status == "BRB" )
  {
    statusIdentifier = i18n( "(Be Right Back)" );
    iconName = "berightback";
  }
  else if ( status == "BSY" )
  {
    statusIdentifier = i18n( "(Busy)" );
    iconName = "busy";
  }
  else if ( status == "FLN" )
  {
    statusIdentifier = "";
    iconName = "offline";
  }
  else if ( status == "HDN" )
  {
    statusIdentifier = i18n("(Invisible)");
    iconName = "invisible";
  }
  else if ( status == "IDL" )
  {
    statusIdentifier = i18n( "(Away - Idle)" );
    iconName = "away";
  }
  else if ( status == "LUN" )
  {
    statusIdentifier = i18n( "(Out to Lunch)" );
    iconName = "lunch";
  }
  else if ( status == "NLN" )
  {
    statusIdentifier = "";
    iconName = "online";
  }
  else if ( status == "PHN" )
  {
    statusIdentifier = i18n( "(On the Phone)" );
    iconName = "onthephone";
  }
  else
  {
    kdDebug() << "KMessView - updateUserStatus() - The account had the invalid status " << status << "." << endl;
    return;
  }
  onlineStatusLabel_->setText( friendlyName + "  " + statusIdentifier );
  onlineStatusPixmapLabel_->setPixmap( loader->loadIcon( iconName, KIcon::User ) );
}



// Update the way contact groups are displayed
void KMessView::slotUpdateViewMode()
{
  // Make the groups visible, contacts are already added to the
  // online/offline groups to make this viewmode update
  // easier and faster. (no need to move the contacts)
  slotUpdateDisplaySettings();
}



KMessView::ToolTip::ToolTip(KMessView *kmessView, QToolTipGroup *group)
: QToolTip(kmessView->contactListView_->viewport(), group),
  kmessView_(kmessView)
{
#ifdef KMESSDEBUG_KMESSVIEW_GENERAL
  kdDebug() << "KMessView: Created tooltip" << endl;
#endif
}

KMessView::ToolTip::~ToolTip()
{

}


void KMessView::ToolTip::maybeTip(const QPoint &cursorPosition)
{
  QListViewItem *currentItem = kmessView_->contactListView_->itemAt(cursorPosition);
  Contact       *contact     = kmessView_->getContactByItem(currentItem);
  QString        tipText;
  QString        name;
  QString        handle;
  QString        status, statusname;
  QString        iconPath;

  if(contact != 0)
  {
    name   = QStyleSheet::escape(contact->getTrueFriendlyName());
    handle = QStyleSheet::escape(contact->getHandle());
    status  = QStyleSheet::escape(contact->getStatus());
    iconPath = contact->getContactPicturePath();

    // convert AWY to Away etc.
    if ( status == "AWY" )
    {
      statusname = i18n( "(Away)" );
    }
    else if ( status == "BRB" )
    {
      statusname = i18n( "(Be Right Back)" );
    }
    else if ( status == "BSY" )
    {
     statusname = i18n( "(Busy)" );
    }
    else if ( status == "FLN" )
    {
      statusname = "";
    }
    else if ( status == "HDN" )
    {
      statusname = i18n("(Invisible)");
    }
    else if ( status == "IDL" )
    {
      statusname = i18n( "(Away - Idle)" );
    }
    else if ( status == "LUN" )
    {
      statusname = i18n( "(Out to Lunch)" );
    }
    else if ( status == "NLN" )
    {
      statusname = i18n( "(Online)" );
    }
    else if ( status == "PHN" )
    {
      statusname = i18n( "(On the Phone)" );
    }
    else
    {
      statusname = "";
    }

    tipText  = "<qt>";
    if( ! iconPath.isEmpty() )
    {
      tipText += "<img src=\"" + iconPath + "\" align=left>";
    }
    tipText += "&nbsp;<b>" + name + "</b>"  + " " + statusname + "<br>";
    tipText += handle;
    tipText += "</qt>";


    tip( kmessView_->contactListView_->itemRect(currentItem), tipText );
  }
}

#include "kmessview.moc"
