/** Copyright (c) 2020-2022 The Creators of Simphone

    class Contacts (QMainWindow): display contact list in a window (main program window)
    class ContactsTableView (QTableView): display contact list
    class StatusDelegate (QStyledItemDelegate): paint contact status and avatar

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "ui_contacts.h"

#include "qtfix.h"
#include "chatframe.h"
#include "chatwindow.h"
#include "contacts.h"
#include "transfers.h"
#include "contactinfo.h"
#include "settings.h"
#include "about.h"
#include "usermanual.h"

#include <QMouseEvent>
#include <QFileDialog>
#include <QPainter>
#include <QDialogButtonBox>
#include <QLabel>

#include <time.h>

#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__)
#include <objc/message.h>
#include <Carbon/Carbon.h>
#elif !defined(QT_NO_DBUS)
#include <QDBusInterface>
#endif

StatusDelegate::StatusDelegate(QObject * parent)
  : QStyledItemDelegate(parent), m_noAvatar(0), m_offlineIcon(false)
{
  QPixmap pixmaps[Contact::state_Nstates];

  m_avatar = QImage(":/avatar");
  m_call.load(":/call");
  m_file.load(":/received");
  m_msg.load(":/msg");
  m_typing.load(":/typing");

  pixmaps[Contact::state_logged].load(":/on");
  pixmaps[Contact::state_busy].load(":/no");
  pixmaps[Contact::state_away].load(":/away");
  pixmaps[Contact::state_hide].load(":/hiding");
  pixmaps[Contact::state_new].load(":/new");
  pixmaps[Contact::state_blocked].load(":/blocked");
  pixmaps[Contact::state_deleted].load(":/deleted");
  pixmaps[Contact::state_none].load(":/off");
  pixmaps[Contact::state_invisible].load(":/invisible");
  int fontH = QFontMetrics(qApp->font("QStatusBar")).lineSpacing();

  m_callLarge = qtfix::fixPixmapSize(m_call, fontH, false);
  m_fileLarge = qtfix::fixPixmapSize(m_file, fontH, false);
  m_msgLarge = qtfix::fixPixmapSize(m_msg, fontH, false);
  m_typingLarge = qtfix::fixPixmapSize(m_typing, fontH, false);
  m_largePixmaps[Contact::state_logged] = qtfix::fixPixmapSize(pixmaps[Contact::state_logged], fontH, false);
  m_largePixmaps[Contact::state_busy] = qtfix::fixPixmapSize(pixmaps[Contact::state_busy], fontH, false);
  m_largePixmaps[Contact::state_away] = qtfix::fixPixmapSize(pixmaps[Contact::state_away], fontH, false);
  m_largePixmaps[Contact::state_hide] = qtfix::fixPixmapSize(pixmaps[Contact::state_hide], fontH, false);
  m_largePixmaps[Contact::state_new] = qtfix::fixPixmapSize(pixmaps[Contact::state_new], fontH, false);
  m_largePixmaps[Contact::state_blocked] = qtfix::fixPixmapSize(pixmaps[Contact::state_blocked], fontH, false);
  m_largePixmaps[Contact::state_deleted] = qtfix::fixPixmapSize(pixmaps[Contact::state_deleted], fontH, false);
  m_largePixmaps[Contact::state_none] = qtfix::fixPixmapSize(pixmaps[Contact::state_none], fontH, false);
  m_largePixmaps[Contact::state_invisible] = qtfix::fixPixmapSize(pixmaps[Contact::state_invisible], fontH, false);

  m_avatarSize = m_avatar.size();
  fontH = m_avatarSize.height() * 3 / 8;

  m_call = qtfix::fixPixmapSize(m_call, fontH, false);
  m_file = qtfix::fixPixmapSize(m_file, fontH, false);
  m_msg = qtfix::fixPixmapSize(m_msg, fontH, false);
  m_typing = qtfix::fixPixmapSize(m_typing, fontH, false);
  m_smallPixmaps[Contact::state_logged] = qtfix::fixPixmapSize(pixmaps[Contact::state_logged], fontH, false);
  m_smallPixmaps[Contact::state_busy] = qtfix::fixPixmapSize(pixmaps[Contact::state_busy], fontH, false);
  m_smallPixmaps[Contact::state_away] = qtfix::fixPixmapSize(pixmaps[Contact::state_away], fontH, false);
  m_smallPixmaps[Contact::state_hide] = qtfix::fixPixmapSize(pixmaps[Contact::state_hide], fontH, false);
  m_smallPixmaps[Contact::state_new] = qtfix::fixPixmapSize(pixmaps[Contact::state_new], fontH, false);
  m_smallPixmaps[Contact::state_blocked] = qtfix::fixPixmapSize(pixmaps[Contact::state_blocked], fontH, false);
  m_smallPixmaps[Contact::state_deleted] = qtfix::fixPixmapSize(pixmaps[Contact::state_deleted], fontH, false);
  m_smallPixmaps[Contact::state_none] = qtfix::fixPixmapSize(pixmaps[Contact::state_none], fontH, false);
  m_smallPixmaps[Contact::state_invisible] = qtfix::fixPixmapSize(pixmaps[Contact::state_invisible], fontH, false);
}

void StatusDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
  QStyleOptionViewItem fixOption = option;
  Contact * data = ((const ContactsModel *)index.model())->getContact(index);
  if (data) {
    if (((const ContactsModel *)index.model())->getCurContactId() == data->m_id) {
      fixOption.state &= ~QStyle::State_Selected;
    }
  }
  fixOption.state &= ~QStyle::State_HasFocus;
  Parent::paint(painter, fixOption, index);

  if (index.column() == ccol_state && data) {
    //if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, option.palette.highlight());
    QPoint p(option.rect.x(), option.rect.y());
    unsigned state = data->m_state;

    int width = option.rect.width();
    int height = option.rect.height();

    if (!m_noAvatar) {
      int y = m_avatar.height() >= height ? p.y() : p.y() + (height - m_avatar.height()) / 2;
      if (!(state & Contact::flag_testContact)) {
        if (!data->m_avatar) {
          painter->drawImage(p.x(), y, m_avatar);
        } else {
          int w = data->m_avatar->size().width();
          int h = data->m_avatar->size().height();
          painter->drawImage(w >= width ? p.x() : p.x() + (width - w) / 2,
                             h >= height ? p.y() : p.y() + (height - h) / 2, *data->m_avatar);
        }
      }
      if ((state & Contact::flag_typing) && !m_typing.isNull()) {
        painter->drawPixmap(p, m_typing);
      } else if ((state & Contact::flag_hasMissedCall) && !m_call.isNull()) {
        painter->drawPixmap(p, m_call);
      }
      if ((state & Contact::flag_hasIncomingFile) && !m_file.isNull()) {
        painter->drawPixmap(p.x(), option.rect.y() + height - m_msg.height(), m_file);
      }
      if ((state & (Contact::flag_hasUnreadMsg | Contact::flag_hasNewContact)) && !m_msg.isNull()) {
        painter->drawPixmap(option.rect.x() + width - m_msg.width(), p.y(), m_msg);
      }

      // status
      unsigned ndx = data->getContactState();
      if (ndx == Contact::state_unknown && m_offlineIcon) ndx = Contact::state_none;
      if (ndx < Contact::state_invisible && !m_smallPixmaps[ndx].isNull()) {
        painter->drawPixmap(option.rect.x() + width - m_smallPixmaps[ndx].width(),
                            option.rect.y() + height - m_smallPixmaps[ndx].height(), m_smallPixmaps[ndx]);
      }
    } else {
      if ((state & Contact::flag_typing) && !m_typingLarge.isNull()) {
        painter->drawPixmap(p.x(), p.y() + (height - m_typingLarge.height()) / 2, m_typingLarge);
      } else if ((state & Contact::flag_hasMissedCall) && !m_callLarge.isNull()) {
        painter->drawPixmap(p.x(), p.y() + (height - m_callLarge.height()) / 2, m_callLarge);
      }
      if ((state & Contact::flag_hasIncomingFile) && !m_fileLarge.isNull()) {
        if (m_noAvatar == 1) {
          painter->drawPixmap(option.rect.x() + (width - m_fileLarge.width() - m_msgLarge.width()) / 2,
                              p.y() + (height - m_fileLarge.height()) / 2, m_fileLarge);
        } else if (!(state & (Contact::flag_hasMissedCall | Contact::flag_typing))) {
          painter->drawPixmap(p.x(), p.y() + (height - m_fileLarge.height()) / 2, m_fileLarge);
        }
      }
      if ((state & (Contact::flag_hasUnreadMsg | Contact::flag_hasNewContact)) && !m_msgLarge.isNull()) {
        if (m_noAvatar == 1) {
          painter->drawPixmap(option.rect.x() + (width - m_msgLarge.width() + m_fileLarge.width()) / 2,
                              p.y() + (height - m_msgLarge.height()) / 2, m_msgLarge);
        } else if (!(state & (Contact::flag_hasMissedCall | Contact::flag_typing | Contact::flag_hasIncomingFile))) {
          painter->drawPixmap(p.x(), p.y() + (height - m_msgLarge.height()) / 2, m_msgLarge);
        }
      }

      // status
      unsigned ndx = data->getContactState();
      if (ndx == Contact::state_unknown && m_offlineIcon) ndx = Contact::state_none;
      if (ndx < Contact::state_invisible && !m_largePixmaps[ndx].isNull()) {
        painter->drawPixmap(option.rect.x() + width - m_largePixmaps[ndx].width(),
                            option.rect.y() + (height - m_largePixmaps[ndx].height()) / 2, m_largePixmaps[ndx]);
      }
    }
  }
}

QSize StatusDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
{
  if (index.column()) return Parent::sizeHint(option, index);

  if (!m_noAvatar) return m_avatar.size();

  QSize size = m_largePixmaps[Contact::state_logged].size();
  int width = m_noAvatar == 1 ? m_fileLarge.width() + m_msgLarge.width() : 0;
  return QSize(size.width() + qMax(m_callLarge.width(), m_typingLarge.width()) + width, size.height());
}

void ContactsTableView::mousePressEvent(QMouseEvent * event)
{
  m_mouseButtonUsed = event->button();
  if (m_mouseButtonUsed == Qt::LeftButton && !Contacts::get()->isSplitMode()) {
    QModelIndex cell = indexAt(event->pos());
    if (cell.isValid()) {
      ContactsModel * m = (ContactsModel *)model();
      // select row of old current contact (otherwise it is not cleared if not selected)
      // it may not be selected if right clicked another row - so it becomes selected, but not current
      Contact * c = m->getContact(cell);
      if (c && !c->isNewContact()) {
        int oldId = m->getCurContactId();
        //selectRow(m->getRow(m->getCurContactId()));
        //m->setCurContactId(c->m_id);
        if (oldId >= 0) m->emitDataChanged(oldId, ccol_nick);
        if (!Contacts::get()->isClickMode()) m->setCurContactId(c->m_id);
        m->emitDataChanged(c->m_id, ccol_nick);
      }
    }
  }
  Parent::mousePressEvent(event);
}

#ifdef __APPLE__
static objc_object * g_application = 0;
static IMP g_hideApplication = 0, g_unhideApplication = 0, g_becomeActive = 0, g_resignActive = 0;
static SEL g_selHide, g_selUnhide, g_selBecomeActive, g_selResignActive;
static bool g_unhideBlink = false;
static bool g_logout = true;

void onLogout(CFNotificationCenterRef, void *, CFStringRef name, const void *, CFDictionaryRef)
{
  char buffer[32];
  if (CFStringGetCString(name, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
    log_note_("ui", "%s %s\n", __FUNCTION__, buffer);
    if (!strcmp(buffer, "com.apple.logoutContinued")) {
      g_logout = true;
    } else if (!strcmp(buffer, "com.apple.logoutCancelled")) {
      g_logout = false;
    }
  }
}

bool onDockClick(id, SEL, ...)
{
  if (!Contacts::get()->isQuitting()) {
    log_xtra_("ui", "%s\n", __FUNCTION__);
    QTimer::singleShot(abs(SimParam::get("ui.main.sleep")), Contacts::get(), SLOT(showActivateWindow()));
  } else {
    QTimer::singleShot(0, qApp, SLOT(quit()));
  }
  return false;
}

int onDockQuit(id, SEL, ...)
{
  log_xtra_("ui", "%s\n", __FUNCTION__);
#ifndef DONOT_DEFINE
  QListIterator<QWidget *> iterator(QApplication::topLevelWidgets());
  while (iterator.hasNext()) {
    QWidget * widget = iterator.next();
    if (widget->isVisible() && widget->inherits("QDialog")) ((QDialog *)widget)->reject();
  }
#endif
  if (Contacts::get()->isQuitting()) return true;
  if (g_logout) {
    if (SimCore::get()->isLogged()) Contacts::get()->saveSettings(true);
    QTimer::singleShot(0, qApp, SLOT(quit()));
  } else {
    QTimer::singleShot(0, Contacts::get(), SLOT(on_actionQuit_triggered()));
  }
  return false;
}

void hideApplication()
{
  log_xtra_("ui", "%s\n", __FUNCTION__);
  g_unhideBlink = true;
  if (g_hideApplication) ((void (*)(id, SEL, id))g_hideApplication)(g_application, g_selHide, g_application);
}

void unhideApplication()
{
  log_xtra_("ui", "%s\n", __FUNCTION__);
  if (g_unhideApplication) ((void (*)(id, SEL, id))g_unhideApplication)(g_application, g_selUnhide, g_application);
}

void onApplicationHide(id, SEL, ...)
{
  if (g_unhideBlink) {
    log_xtra_("ui", "%s\n", __FUNCTION__);
    g_unhideBlink = false;
    QTimer::singleShot(abs(SimParam::get("ui.main.sleep")), Contacts::get(), SLOT(blinkWindow()));
  }
}

void onApplicationBecomeActive(id self, SEL cmd, void * notification, ...)
{
  Contacts::get()->setApplicationActive(true);
  if (g_becomeActive) ((void (*)(id, SEL, void *))g_becomeActive)(self, cmd, notification);
}

void onApplicationResignActive(id self, SEL cmd, void * notification, ...)
{
  Contacts::get()->setApplicationActive(false);
  if (g_resignActive) ((void (*)(id, SEL, void *))g_resignActive)(self, cmd, notification);
}

OSStatus onHotKeyPressed(EventHandlerCallRef, EventRef event, void *)
{
  EventHotKeyID hotkey;
  if (!GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, 0, sizeof(hotkey), 0, &hotkey)) {
    if (hotkey.id == 1) {
      QTimer::singleShot(0, Contacts::get(), SLOT(on_actionHideAll_triggered()));
    } else if (hotkey.id == 2) {
      QTimer::singleShot(0, Contacts::get(), SLOT(showSpontaneousWindow()));
    }
  }
  return 0;
}

void fixObjectiveC()
{
  CFNotificationCenterRef center = CFNotificationCenterGetDistributedCenter();
  if (center) {
    CFNotificationCenterAddObserver(center, 0, &onLogout, CFSTR("com.apple.logoutContinued"), 0,
                                    CFNotificationSuspensionBehaviorDeliverImmediately);
    CFNotificationCenterAddObserver(center, 0, &onLogout, CFSTR("com.apple.logoutCancelled"), 0,
                                    CFNotificationSuspensionBehaviorDeliverImmediately);
    g_logout = false;
  } else {
    log_error_("ui", "failed to get distributed notification center\n");
  }

  Class applicationClass = (objc_class *)objc_getClass("NSApplication");
  if (applicationClass) {
    g_application = objc_msgSend((objc_object *)applicationClass, sel_registerName("sharedApplication"));
  } else {
    g_application = 0;
  }

  if (!g_application) {
    log_error_("ui", "failed to get shared application instance\n");
    return;
  }

  objc_object * delegate = objc_msgSend(g_application, sel_registerName("delegate"));
  Class delegateClass = (Class)objc_msgSend(delegate, sel_registerName("class"));
  SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");

  if (class_getInstanceMethod(delegateClass, shouldHandle)) { // new qt version
    if (!class_replaceMethod(delegateClass, shouldHandle, (IMP)onDockClick, "B@:")) {
      log_error_("ui", "failed to replace dock-click handler\n");
    }
  } else { // old qt version
    if (!class_addMethod(delegateClass, shouldHandle, (IMP)onDockClick, "B@:")) {
      log_error_("ui", "failed to add dock-click handler\n");
    }
  }

  if (!class_replaceMethod(delegateClass, sel_registerName("applicationShouldTerminate:"), (IMP)onDockQuit, "I@:")) {
    log_error_("ui", "failed to replace dock-quit handler\n");
  }

  g_selBecomeActive = sel_registerName("applicationDidBecomeActive:");
  g_becomeActive = class_replaceMethod(delegateClass, g_selBecomeActive, (IMP)onApplicationBecomeActive, "v@:");
  if (g_becomeActive) {
    g_selResignActive = sel_registerName("applicationDidResignActive:");
    g_resignActive = class_replaceMethod(delegateClass, g_selResignActive, (IMP)onApplicationResignActive, "v@:");
    if (!g_resignActive) log_error_("ui", "failed to replace resign-active handler\n");
  } else {
    log_error_("ui", "failed to replace become-active handler\n");
  }

  if (class_replaceMethod(delegateClass, sel_registerName("applicationDidHide:"), (IMP)onApplicationHide, "v@:")) {
    Method unhide = class_getInstanceMethod(applicationClass, g_selUnhide = sel_registerName("unhide:")); // "unhideWithoutActivation"

    g_unhideApplication = unhide ? method_getImplementation(unhide) : 0;
    if (g_unhideApplication) {
      unhide = class_getInstanceMethod(applicationClass, g_selHide = sel_registerName("hide:"));
      g_hideApplication = unhide ? method_getImplementation(unhide) : 0;
      if (!g_hideApplication) log_error_("ui", "failed to find hide method\n");
    } else {
      log_error_("ui", "failed to find unhide method\n");
    }
  } else {
    log_error_("ui", "failed to replace hide handler\n");
  }

  EventTypeSpec event = { kEventClassKeyboard, kEventHotKeyPressed };
  OSStatus status = InstallEventHandler(GetApplicationEventTarget(), &onHotKeyPressed, 1, &event, Contacts::get(), 0);
  if (status) log_error_("ui", "failed to register hotkey handler (error %d)\n", status);
}
#else
#define fixObjectiveC()
#define unhideApplication()
#endif // __APPLE__

Contacts * Contacts::mc_contacts = 0;

Contacts::Contacts(QWidget * parent, bool autostart)
  : QMainWindow(parent)
  , ui(new Ui::Contacts)
  , m_dockMenu(0)
  , m_trayIcon(0)
  , m_trayIconMenu(0)
  , m_onlineAction(0)
  , m_awayAction(0)
  , m_busyAction(0)
  , m_invisibleAction(0)
  , m_offlineAction(0)
  , m_showAction(0)
  , m_hideAction(0)
  , m_quitAction(0)
  , m_dockedSize(200)
  , m_restoring(-1)
  , m_tray(0)
  , m_trayHtml(0)
  , m_noAvatar(0)
  , m_shutdownPhase(0)
  , m_titleMax(0)
  , m_click(true)
  , m_showOffline(true)
  , m_committing(false)
  , m_committed(false)
  , m_autostart(autostart)
  , m_doQuit(false)
  , m_active(true)
  , m_alert(false)
  , m_trayNotify(0)
  , m_minimized(0)
  , m_timer(this)
  , m_timerNotifyIcon(this)
  , m_extensions(0)
  , m_awayTimeSec(0)
  , m_welcome(0)
  , m_rename(0)
  , m_settingsContact(0)
  , m_trayIconOn(":/trayIconOn")
  , m_trayIconNotify(":/trayIconNotify")
  , m_trayIconOff(":/trayIconOff")
  , m_trayIconBusy(":/trayIconBusy")
  , m_trayIconInvisible(":/trayIconInvisible")
  , m_trayIconAway(":/trayIconAway")
  , m_trayIconUnknown(":/trayIconUnknown")
{
  log_info_("ui", "create main window\n");

  memset(m_trayIcons, 0, sizeof(m_trayIcons));
  m_trayIcons[SIM_STATUS_INVISIBLE - SIM_STATUS_MIN] = &m_trayIconInvisible;
  m_trayIcons[SIM_STATUS_OFF - SIM_STATUS_MIN] = &m_trayIconOff;
  m_trayIcons[SIM_STATUS_ON - SIM_STATUS_MIN] = &m_trayIconOn;
  m_trayIcons[SIM_STATUS_AWAY - SIM_STATUS_MIN] = m_trayIcons[SIM_STATUS_IDLE - SIM_STATUS_MIN] = &m_trayIconAway;
  m_trayIcons[SIM_STATUS_BUSY - SIM_STATUS_MIN] = &m_trayIconBusy;
  m_trayIcons[SIM_STATUS_HIDE - SIM_STATUS_MIN] = &m_trayIconNotify;

  ui->setupUi(this);
#ifndef _DEBUG
  ui->menuMain->removeAction(ui->actionLogout);
#endif
  mc_contacts = this;
  ui->contactView->setModel(&m_model);
  ui->contactView->setItemDelegate(m_statusDelegate = new StatusDelegate);
  connect(&m_model, SIGNAL(layoutChanged()), this, SLOT(recalcDockMenu()));
  connect(SimCore::get(), SIGNAL(signalContactChanged(unsigned)), this, SLOT(onSignalContactChanged(unsigned)));
  connect(SimCore::get(), SIGNAL(signalContactAudioChanged(unsigned, SimAudioState)),
          this, SLOT(onSignalContactAudioChanged(unsigned, SimAudioState)));
  connect(SimCore::get(), SIGNAL(signalInvalidAudioDevice(bool)), this, SLOT(onSignalInvalidAudioDevice(bool)));
  connect(qApp, SIGNAL(focusWindowChanged(QWindow *)), this, SLOT(onFocusWindowChanged(QWindow *)));

  connect(&m_model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
          this, SLOT(onRowsInserted(const QModelIndex &, int, int)));
  connect(&m_timer, SIGNAL(timeout()), this, SLOT(onAwayTimeout()));
  connect(&m_timerNotifyIcon, SIGNAL(timeout()), this, SLOT(onNotifyIconTimeout()));
  ui->statusBar->addPermanentWidget(m_status = new QLabel(this));
  ui->statusBar->installEventFilter(this);
  m_status->installEventFilter(this);
  connect(ui->statusBar, SIGNAL(messageChanged(const QString &)), this, SLOT(onStatusMessageChanged()));
  qtfix::fixStatusBar(ui->statusBar);
  qtfix::fixMenuIndicator(ui->actionSplitMode, ui->menuMain);
  qtfix::fixMenuIndicator(ui->actionShowDeleted, ui->menuContacts);
  qtfix::fixMenuIndicator(ui->actionSortByNick, ui->menuContacts);
#ifndef __APPLE__
  ui->actionPreferences->setVisible(false);
  ui->actionShowMainWindow->setVisible(false);
  ui->actionHideAll->setVisible(SimCore::isHidden() != 0);
#endif
  setWindowIcon(QIcon(":/contactIcon"));

  ui->contactView->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(ui->contactView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenu(const QPoint &)));

  connect(m_status, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenuStatus(const QPoint &)));
  ui->statusBar->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(ui->statusBar, SIGNAL(customContextMenuRequested(QPoint)),
          this, SLOT(onCustomContextMenuStatus(const QPoint &)));

  QPalette p = ui->contactView->palette();
  //if (QApplication::palette((QWidget*)0).highlight().color() == p.color(QPalette::Highlight))
  {
    const QColor hlClr = p.color(QPalette::AlternateBase); // highlight color to set
    const QColor txtClr = p.color(QPalette::Foreground);   // highlighted text color to set

    p.setColor(QPalette::Highlight, hlClr);
    p.setColor(QPalette::HighlightedText, txtClr);
    ui->contactView->setPalette(p);
  }

  recalcTitle(-1);
  fixObjectiveC();
}

Contacts::~Contacts()
{
  m_status->removeEventFilter(this);
  ui->statusBar->removeEventFilter(this);
  if (m_welcome) m_welcome->removeEventFilter(this);

  while (ui->stackedWidget->count()) {
    ui->stackedWidget->removeWidget(ui->stackedWidget->widget(0));
  }

  ui->contactView->clearSelection();
  ui->contactView->setModel(0);
  ui->contactView->setItemDelegate(0);
  layout()->removeWidget(ui->contactView);
  delete ui->contactView;
  delete m_statusDelegate;
  delete m_status;
  delete m_welcome;
  delete ui;
  log_info_("ui", "delete main window\n");
  mc_contacts = 0;
}

#ifndef __APPLE__
bool Contacts::getStayInTaskbar() const
{
  return (!SimCore::isHidden() && !m_trayIcon) || SimParam::get("ui.main.taskbar");
}
#else
bool Contacts::getStayInTaskbar() const
{
  return SimParam::get("ui.main.taskbar") != 0;
}
#endif

const QIcon & Contacts::getStatusIcon(Contact * contact) const
{
  int status;

  if (contact) {
    switch (contact->getState()) {
      case Contact::state_unknown:
        status = SIM_STATUS_INVISIBLE;
        if (contact->isMe() || !m_showOffline) break;
      case Contact::state_none: status = SIM_STATUS_OFF; break;
      case Contact::state_logged: status = SIM_STATUS_ON; break;
      case Contact::state_busy: status = SIM_STATUS_BUSY; break;
      case Contact::state_hide: status = SIM_STATUS_HIDE; break;
      case Contact::state_away: status = SIM_STATUS_AWAY; break;
      default: status = SIM_STATUS_MAX + 1;
    }
  } else {
    status = SimCore::getMyStatus();
  }

  if (SIM_STATUS_MIN > status || status > SIM_STATUS_MAX || !m_trayIcons[status]) return m_trayIconUnknown;
  return *(m_trayIcons[status - SIM_STATUS_MIN]);
}

QString Contacts::getStatusToolTip(int status)
{
  switch (status) {
    case SIM_STATUS_INVISIBLE: return tr("Invisible");
    case SIM_STATUS_OFF: return tr("Offline");
    case SIM_STATUS_ON: return tr("Online");
    case SIM_STATUS_AWAY: return tr("Away From Keyboard");
    case SIM_STATUS_IDLE: return tr("Away From Keyboard (idle)");
    case SIM_STATUS_BUSY: return tr("Do Not Disturb");
    case SIM_STATUS_HIDE: return tr("Hiding");
  }
  return QString();
}

simnumber Contacts::getContactFlags(Contact * contact)
{
  simtype flags = sim_contact_get_(contact->m_simId, 0);
  simnumber n = sim_table_get_number(flags, CONTACT_KEY_FLAGS);
  sim_contact_free_(flags);
  return n;
}

QTextEdit * Contacts::getTextWidget()
{
  if (!m_welcome) {
    QFile file(":/manual");
    m_welcome = new QTextEdit(0);
    m_welcome->setReadOnly(true);
    if (file.open(QFile::ReadOnly | QFile::Text)) m_welcome->setText(file.readAll());
    qtfix::fixPointSize(m_welcome, font().pointSize());
    m_welcome->installEventFilter(this);
  }
  return m_welcome;
}

void Contacts::setApplicationActive(bool active)
{
  m_active = active;
  log_xtra_("ui", "%s %d\n", __FUNCTION__, active);
  if (active) {
    m_alert = false;
    QTimer::singleShot(0, this, SLOT(unblinkTrayIcon()));
  }
}

bool Contacts::isSplitMode() const
{
  return ui->actionSplitMode->isChecked();
}

QWidget * Contacts::hasActiveWindow()
{
  std::vector<Contact *> & contacts = SimCore::get()->m_contacts;

  for (unsigned i = contacts.size(); i--;) {
    if (Contact * c = contacts[i]) {
      if (c->m_chatWindow && c->m_chatWindow->isActiveWindow() && !c->m_chatWindow->isMinimized()) {
        return c->m_chatWindow;
      }
    }
  }

  return 0;
}

void Contacts::saveSettings(bool quit, bool mode) const
{
  if (!mode) mode = isSplitMode();

  simtype params = sim_table_new(101);
  QByteArray savedGeometry = saveGeometry();
  QByteArray savedState = saveState();
  QList<int> sizes = ui->splitter->sizes();

  Contact * lastContact = SimCore::getContact(ChatFrames::get()->getLastChatId());
  QByteArray savedContact;
  if (lastContact || mode) savedContact = mode ? QByteArray("00000000000000000000") : lastContact->getTextId().toUtf8();
  if (!savedContact.isEmpty()) {
    sim_table_add_pointer_length(params, "ui.main.contact", savedContact.data(), savedContact.size());
  }

  sim_table_add_pointer_length(params, "ui.main.geometry", savedGeometry.data(), savedGeometry.size());
  sim_table_add_pointer_length(params, "ui.main.state", savedState.data(), savedState.size());
  sim_table_add_number(params, "ui.main.split", mode);
  sim_table_add_number(params, "ui.main.splitter", sizes[0]);
  sim_table_add_number(params, "ui.main.docked", m_dockedSize);
  sim_table_add_number(params, "ui.main.sort", m_model.getSort());
  sim_table_add_number(params, "ui.main.deleted", ui->actionShowDeleted->isChecked());

  std::vector<Contact *> & contacts = SimCore::get()->m_contacts;

  for (unsigned i = contacts.size(); i--;) {
    Contact * contact = contacts[i];

    if (quit && contact->m_chatWindow) contact->m_chatWindow->saveSettings(params);
    if (contact->m_chatFrame) contact->m_chatFrame->getSettings(params);
  }

  SimParam::set(params);
  sim_table_free(params);
}

void Contacts::rereadSettings()
{
  // all settings which may need reloading (and some action), always check whether there is difference from old value
  int away = SimParam::get("ui.main.away");
  int seconds = away > 0 ? away : 0;
  // if we are idle and now we say - we shall never be idle
  if (m_awayTimeSec && !seconds && SimCore::getMyStatus() == SIM_STATUS_IDLE) {
    m_extensions = onAwayTimeout(1);
    m_awayTimeSec = seconds;
  } else {
    m_extensions = onAwayTimeout(-1);
    m_awayTimeSec = seconds;
    setMyStatus(SimCore::getMyStatus()); // stop idle timer or start it with its new value
  }

  int tray = SimParam::get("ui.main.tray");
  if ((m_tray > 0) != (tray > 0)) {
    m_tray = tray;
    if (tray > 0) {
      createTrayIcon();
    } else {
      removeTrayIcon();
    }
  }

  m_showOffline = !SimParam::get("ui.main.shownostatus");
  m_statusDelegate->setShowOfflineIcon(m_showOffline);
}

void Contacts::readSettings()
{
#ifdef __APPLE__
  bool menu = SimParam::get("ui.main.menu") != 0;
  ui->actionPreferences->setVisible(menu);
  ui->actionShowMainWindow->setVisible(menu);
  ui->menubar->setNativeMenuBar(menu);
#endif

  QByteArray geometry(SimParam::getString("ui.main.geometry"));
  if (!geometry.isEmpty()) restoreGeometry(geometry);
  QByteArray state(SimParam::getString("ui.main.state"));
  if (!state.isEmpty()) restoreState(state);

  ui->statusBar->setSizeGripEnabled(SimParam::get("ui.main.gripper") != 0);
  ui->actionSplitMode->setChecked(SimParam::get("ui.main.split") != 0);

  int splitter = SimParam::get("ui.main.splitter");
  if (splitter > 0) {
    QList<int> sizes;
    sizes.append(splitter);
    ui->splitter->setSizes(sizes);
  }

  int docked = SimParam::get("ui.main.docked");
  if (docked > 0) m_dockedSize = docked;

  m_noAvatar = !SimParam::get("ui.main.avatar");
  if (m_noAvatar) m_noAvatar += !SimParam::get("ui.main.notifications");

  int showInfoLine = SimParam::get("ui.main.infoline");
  m_model.setNoAvatar(m_noAvatar != 0, (showInfoLine & (m_noAvatar ? 4 : 2)) != 0);

  QString desktop = qgetenv("XDG_CURRENT_DESKTOP");
#ifdef __unix__
  if (SimParam::get("ui.main.sleep") < 0 && desktop.indexOf("GNOME") >= 0) SimParam::set("ui.main.sleep", 0, true);
#endif
  m_shutdownPhase = SimParam::get("ui.main.shutdown") - 1;
  if (m_shutdownPhase < 0) m_shutdownPhase = desktop.indexOf("GNOME") >= 0;

  m_titleMax = SimParam::get("ui.main.titlemax");
  m_click = SimParam::get("ui.main.click") != 0;

  rereadSettings();
  m_trayHtml = SimParam::get("ui.main.trayhtml");
#ifdef __unix__
  if (m_autostart && m_tray > 0 && !m_trayIcon) QTimer::singleShot(1000, this, SLOT(onTrayIconTimeout()));
#endif

  if (m_noAvatar != m_statusDelegate->getAvatarPaint()) { // already active
    m_statusDelegate->setAvatarPaint(m_noAvatar);
    for (int i = m_model.rowCount(QModelIndex()); i--;) {
      ui->contactView->resizeRowToContents(i);
    }
  }
  ui->contactView->resizeColumnToContents(0);

  m_model.setSort(ContactsModel::E_SortType(SimParam::get("ui.main.sort")));
  ui->actionSortByNick->setChecked(m_model.getSort() == ContactsModel::sort_nick);
  ui->actionSortByNick->setEnabled(m_model.getSort() != ContactsModel::sort_none);

  ui->actionShowDeleted->setChecked(SimParam::get("ui.main.deleted") != 0);
  m_model.setShowDeleted(ui->actionShowDeleted->isChecked());

  QByteArray last(SimParam::getString("ui.main.contact"));
  m_restoring = !last.isEmpty() ? SimCore::get()->getContactId(last.toULongLong(), true) : -1;
  if (m_restoring == -1) m_restoring = -9;
  on_actionSplitMode_triggered();
  m_restoring = -1;

  LoginDialog::initLanguage(ui->actionEnglish, ui->actionFrench, ui->actionGerman);

  m_infoColor = SimParam::getColor("ui.color.infos");
  m_noteColor = SimParam::getColor("ui.color.notes");
  m_warnColor = SimParam::getColor("ui.color.warns");
  m_errorColor = SimParam::getColor("ui.color.errors");
}

void Contacts::blinkTrayIcon(bool taskbar)
{
  if (taskbar) {
#ifdef __APPLE__
    QWidget * active = hasActiveWindow();
    log_xtra_("ui", "%s (application = %d active = %d minimized = %d window = %d)\n",
              __FUNCTION__, m_active, isActiveWindow(), isMinimized(), active != 0);
    if (isActiveWindow() && !isMinimized()) return; // i do not want to or cannot blink
    int blink = SimParam::get("ui.main.blink");
    if (m_active && (blink > 2 || (blink > 1 && !active))) {
      hideApplication();
    } else if (blink > 0) { // immediate alert should succeed if application is not active
#else
    log_xtra_("ui", "%s (active = %d visible = %d)\n", __FUNCTION__, isActiveWindow(), isVisible());
    if (isActiveWindow()) return; // do not blink tray if contacts will not blink
    if (SimParam::get("ui.main.blink") > 0 && (!m_trayIcon || isVisible())) {
#endif
      QApplication::alert(this);
      if (!m_active) m_alert = true;
    }
  }

  if (m_trayIcon) {
    log_xtra_("ui", "%s\n", __FUNCTION__);
    m_trayNotify = 1;
    m_timerNotifyIcon.stop();
    m_timerNotifyIcon.start(1000);
    m_trayIcon->setIcon(m_trayIconNotify);
  }
}

// u - unblink tray and clear all notifications, c - unblink tray only if no notifications
// s - in single mode, unblink tray. in split mode, unblink tray only if no notifications
void Contacts::unblinkTrayIcon(char action)
{
  bool blinking = false;

  if (action != 's' || isSplitMode()) {
    std::vector<Contact *> & contacts = SimCore::get()->m_contacts;

    for (unsigned i = m_model.m_mapRow2Id.size(); i--;) {
      unsigned id = m_model.m_mapRow2Id[i];
      if (Contact * c = contacts[id]) {
        if (action == 'u') {
          c->clearNotifications(Contact::flag_Notifications);
        } else if (c->m_state & Contact::flag_Notifications && c->m_chatFrame && c->m_chatFrame->isVisible()) {
          blinking = true;
        }
      }
    }
  }
  if (!blinking && (m_active || !m_alert)) unblinkTrayIcon();
}

int Contacts::setMyStatus(int status, int idle)
{
  QPixmap * pixmap = 0;
  int oldStatus = SimCore::getMyStatus();

  int simres = SimCore::setMyStatus(status);

  /*if (simres != SIM_OK)*/ status = SimCore::getMyStatus();

  if (oldStatus == SIM_STATUS_OFF || status == SIM_STATUS_OFF) SimCore::get()->getUserContacts();
  SimCore::get()->getMyContact();
  m_status->setToolTip(getStatusToolTip(status));

  switch (status) {
    case SIM_STATUS_INVISIBLE: // invisible (connected to the DHT but appear offline)
      pixmap = &m_statusDelegate->m_largePixmaps[Contact::state_invisible];
      m_timer.stop();
      break;

    case SIM_STATUS_OFF: // offline (not connected to the DHT)
      pixmap = &m_statusDelegate->m_largePixmaps[Contact::state_none];
      m_timer.stop();
      break;

    case SIM_STATUS_ON: // online (available)
      pixmap = &m_statusDelegate->m_largePixmaps[Contact::state_logged];
      if (!m_awayTimeSec) {
        m_timer.stop();
      } else {
        m_timer.start(m_awayTimeSec * 1000 - idle);
      }
      break;

    case SIM_STATUS_AWAY: // away from keyboard (set by user)
      pixmap = &m_statusDelegate->m_largePixmaps[Contact::state_away];
      m_timer.stop();
      break;

    case SIM_STATUS_IDLE: // auto-away (idle)
      pixmap = &m_statusDelegate->m_largePixmaps[Contact::state_away];
      m_timer.start(1000); // check every second whether user is back
      break;

    case SIM_STATUS_BUSY: // do not disturb (no playing of sounds)
      pixmap = &m_statusDelegate->m_largePixmaps[Contact::state_busy];
      m_timer.stop();
      break;

    case SIM_STATUS_HIDE: // GUI not visible
      pixmap = &m_statusDelegate->m_largePixmaps[Contact::state_hide];
      if (!m_awayTimeSec) {
        m_timer.stop();
      } else {
        m_timer.start(m_awayTimeSec * 1000 - idle);
      }
      break;

    default:
      return simres;
  }

  if (!pixmap) {
    m_status->clear();
  } else {
    m_status->setPixmap(*pixmap);
  }
  m_status->repaint();
  if (m_trayIcon) m_trayIcon->setIcon(getStatusIcon());
  return simres;
}

void Contacts::setMyStatusIcon(int status, const QString & statusName)
{
  int simres = setMyStatus(status);
  if (simres != SIM_OK) {
    QString error = tr("Setting status to %1 not successful (%2)").arg(QLocale().quoteString(statusName)).arg(simres);
    SimCore::execMessageBox(false, error, SimCore::getError(simres));
  }
}

void Contacts::showStatus(const QString & text) const
{
  QString now = "";
  char buffer[64];
  if (sim_convert_time_to_string(time(0), buffer)) now.sprintf("%.*s ", 5, buffer + SIM_SIZE_TIME - 9);
  ui->statusBar->showMessage(now.append(text));
}

void Contacts::showStatus(const QString & text, char color) const
{
  QPalette palette;
  palette.setColor(QPalette::WindowText, color == 'e' ? m_errorColor : color == 'w' ? m_warnColor : m_noteColor);
  showStatus(text);
  ui->statusBar->setPalette(palette);
}

void Contacts::showDialog(Contact * contact, const QPoint & pos, int msgNdx)
{
  int posX;
  int posY;
  int dialog = msgNdx > 0 ? 2 : msgNdx < 0 ? 0 : 1;
  QDialog * window = contact->getDialog(dialog, &posX, &posY);
  if (window) {
    qtfix::showActivateWindow(window, false);
    if (msgNdx > 0) ((Transfers *)window)->setFilter(msgNdx);
  } else {
    if (!pos.isNull() && posX == -1 && posY == -1) contact->setDialog(dialog, (QDialog *)this, pos.x(), pos.y());
    window = msgNdx < 0 ? (QDialog *)new ContactInfo(0, contact) : (QDialog *)new Transfers(0, contact, msgNdx);
    window->show();
  }
}

void Contacts::acceptContact(const QString & nick)
{
  if (!SimParam::get("ui.main.accept")) {
    QString qs = tr("<br/>You have successfully added <b>%1</b> to your list of contacts.<br/><br/>Next,"
                    " push the <b>Verify</b> button in chat, please.<br/><br/>Communication with unverified contacts"
                    " is NOT guaranteed to be private, <b>even if there's no doubt to their identity</b>.<br/><br/>");
    qs = qs.arg(nick.toHtmlEscaped());
    if (SimCore::execMessageBox(QMessageBox::Information, tr("Added new contact").toUtf8().data(), qs,
                                tr("I am aware that I need to verify all my contacts.\nDo not remind me again."))) {
      SimParam::set("ui.main.accept", 1, false);
    }
  }
}

void Contacts::renameContact(Contact * contact)
{
  QString nick;
  simtype table = sim_contact_get_(contact->m_simId, CONTACT_BIT_DEFAULT);
  if (sim_get_pointer(table)) nick = qtfix::getString(sim_table_get_pointer(table, CONTACT_KEY_OWN_NICK));
  sim_contact_free_(table);

#ifdef DONOT_DEFINE
  bool ok = true;
  QString qs = tr("Please, enter a new nickname of <b>%1</b> or an empty one to use own nick:");
  QString nm = QInputDialog::getText(this, tr("Rename contact"), qs.arg(contact->m_nick.toHtmlEscaped()),
                                     QLineEdit::Normal, contact->m_nick, &ok);
#else
  int ok;
  QString nm;
  QInputDialog input(this);
  input.setWindowTitle(tr("Rename contact"));
  input.setLabelText(tr("Please, enter a new nickname of <b>%1</b>:").arg(contact->m_nick.toHtmlEscaped()));
  input.setTextEchoMode(QLineEdit::Normal);
  input.setTextValue(contact->m_nick);
  input.setModal(true);
  input.show();

  QPushButton * button = 0;
  QDialogButtonBox * box = (QDialogButtonBox *)qtfix::findChild(&input, "QDialogButtonBox");

  if (box && ((contact->m_rights & CONTACT_FLAG_RENAMED) != 0 || (!nick.isEmpty() && contact->m_nick != nick))) {
    box->addButton(button = new QPushButton(tr("Restore own nick")), QDialogButtonBox::ResetRole);
    connect(button, SIGNAL(clicked()), this, SLOT(onRenameButtonClicked()));
  }

  m_rename = &input;
  ok = input.exec();
  m_rename = 0;
  delete button;
  if (ok == QDialog::Accepted) nm = input.textValue();
#endif

  nm = nm.trimmed();
  if (ok && nm != contact->m_nick) {
    int simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_NICK, sim_pointer_new(nm.toUtf8().data()));
    QString simerr = SimCore::getError(simres);
    SimCore::get()->getUserContacts();
    if (simres != SIM_OK) {
      SimCore::execMessageBox(true, tr("Renaming %1 not successful (%2)").arg(contact->m_nick).arg(simres), simerr);
    } else if (contact->isMe()) {
      QMessageBox::information(this, tr("Set nickname"), tr("\nYou have changed your own nickname."));
    } else if (!SimParam::get("ui.main.rename")) {
      QString title = tr("Restored own nickname");
      QString checkBox = tr("I know how this works. Do not remind me again.");
      QString qs;
      if (!nm.isEmpty()) {
        title = tr("Permanently renamed contact");
        qs = tr("You will no longer use <b>%1</b>'s own nickname, unless you would"
                " rename again and push the <b>Restore own nick</b> button.");
      } else if (contact->isDeleted()) {
        qs = tr("You will use <b>%1</b>'s own nickname after you also undelete <b>%1</b>.");
      } else if (contact->isBlocked()) {
        qs = tr("You will use <b>%1</b>'s own nickname after you also unblock <b>%1</b>.");
      } else if (contact->m_nick != nick) {
        qs = tr("<b>%1</b> has been automatically renamed to <b>%2</b> because"
                " the former is already used by another one of your contacts.");
        qs = qs.arg(nick.toHtmlEscaped());
      } else {
        qs = tr("You will use <b>%1</b>'s own nickname from now on.");
      }
      qs = "<br/>" + qs.arg(contact->m_nick.toHtmlEscaped()) + "<br/><br/>";
      if (SimCore::execMessageBox(QMessageBox::Information, title.toUtf8().data(), qs, checkBox)) {
        SimParam::set("ui.main.rename", 1, false);
      }
    }
  }
}

void Contacts::changeInfoContact(Contact * contact)
{
  bool ok;
  QString nfo = QInputDialog::getText(this, tr("Edit info line"),
                                      tr("<p><b><br/><span style=\" color:#ff0000;\">"
                                         "Any text you enter here will be shown to all your contacts.</span></b>"
                                         "<br/><br/>Please, enter your motto (or leave empty for none)</p>"),
                                      QLineEdit::Normal, contact->m_info, &ok);
  nfo = nfo.trimmed();
  if (ok && nfo != contact->m_info) {
    int simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_LINE, sim_pointer_new(nfo.toUtf8().data()));
    QString simerr = SimCore::getError(simres);
    SimCore::get()->getUserContacts();
    if (simres != SIM_OK) {
      SimCore::execMessageBox(true, tr("Editing info line not successful (%1)").arg(simres), simerr);
    }
  }
}

void Contacts::forgetContact(Contact * contact)
{
  QString qs = tr("<br/>");
  if (SimParam::get("contact.strangers")) qs.append(tr("Forgotten users can send you contact requests.")).append(" ");
  qs.append(tr("Are you sure you want to forget <b>%1</b>?"));
  if (QMessageBox::question(this, tr("Forget contact"), qs.arg(contact->m_nick.toHtmlEscaped()),
                            QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
    int simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_AUTH, sim_number_new(CONTACT_AUTH_FORGET));
    QString simerr = SimCore::getError(simres);
    SimCore::get()->getUserContacts();
    if (contact->isForgotten()) {
      SimParam::unset(QString("ui.").append(contact->getTextId()).append(".utf").toUtf8().data());
    }
    if (simres != SIM_OK) {
      SimCore::execMessageBox(true, tr("Forgetting %1 not successful (%2)").arg(contact->m_nick).arg(simres), simerr);
    } else if (!SimParam::get("ui.main.forget")) {
      QString msg = tr("<b>%1</b> is blocked and will be completely forgotten when you restart Simphone.");
      qs = tr("I know that forgotten contacts remain blocked only temporarily.\nDo not remind me again.");
      msg = "<br/>" + msg.arg(contact->m_nick.toHtmlEscaped()) + "<br/><br/>";
      if (SimCore::execMessageBox(QMessageBox::Information, tr("Forgotten contact").toUtf8().data(), msg, qs)) {
        SimParam::set("ui.main.forget", 1, false);
      }
    }
  }
}

void Contacts::blockContact(Contact * contact)
{
  QCheckBox cbox(tr("I do not plan to undelete this user.\nBlock permanently and delete from my list of contacts."));
  QString msg = "<br/>";
  QString title = tr("Block contact");
  int nok = 0;
  if (!contact->isTest()) Transfers::countTransfers(contact->m_simId, SIM_XFER_GET_SENT, &nok);
  if (contact->isBlocked() || contact->isNewContact() || contact->isTest() || contact->isMe()) {
    if (nok) {
      QString nick = contact->m_nick.toHtmlEscaped();
      if (nok < 0) {
        msg.append(tr("Please, restart Simphone before deleting <b>%1</b>.").arg(nick));
        QMessageBox::warning(this, tr("Simphone warning"), msg);
      } else {
        QString error = tr("You have %n outgoing file transfer(s) with %1 that are currently active.", 0, nok);
        msg = tr("Please, cancel the transfer(s) before deleting <b>%1</b>.", 0, nok).arg(nick);
        SimCore::execMessageBox(false, error.arg(nick), msg);
      }
      return;
    }
    if (contact->isVerified() && !contact->isSystem() && !contact->isTest() && !contact->isMe()) {
      msg.append(tr("If you proceed, this contact will become \"NOT VERIFIED\".")).append(" ");
    }
    msg.append(tr("Are you sure you want to delete <b>%1</b>?").arg(contact->m_nick.toHtmlEscaped()));
    cbox.setChecked(true);
    cbox.setEnabled(false);
    title = tr("Delete contact");
  } else {
    if (nok) cbox.setEnabled(false);
    msg.append(tr("Are you sure you want to block <b>%1</b>?").arg(contact->m_nick.toHtmlEscaped()));
  }

  QCheckBox * checkBox = contact->isMe() || contact->isTest() ? 0 : &cbox;
  if (!SimCore::execMessageBox(checkBox, title, msg.append("<br/><br/>"), true)) return;
  int auth = cbox.isChecked() ? CONTACT_AUTH_DELETED : CONTACT_AUTH_BLOCKED;
  int simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_AUTH, sim_number_new(auth));
  QString simerr = SimCore::getError(simres);
  SimCore::get()->getUserContacts();
  if (auth == CONTACT_AUTH_DELETED) {
    if (contact->isDeleted() && !contact->isTest() && !contact->isSystem() && !contact->isMe()) {
      QString ui_number = contact->getTextId();
      QByteArray ui_key = QString("ui.").append(ui_number).append(".utf").toUtf8();
      simtype params = sim_table_new(3);
      if (contact->m_chatFrame) contact->m_chatFrame->getSettings(params);
      if (!sim_get_length(sim_table_get_string(params, ui_key.data()))) SimParam::unset(ui_key.data());
      sim_table_free(params);
      SimParam::unset(QString("ui.").append(ui_number).append(".top").toUtf8().data());
      SimParam::unset(QString("ui.").append(ui_number).append(".bottom").toUtf8().data());
      SimParam::unset(QString("ui.").append(ui_number).append(".geometry").toUtf8().data());
    }
  }
  if (simres != SIM_OK) {
    QString error = tr("Blocking %1 not successful (%2)").arg(contact->m_nick).arg(simres);
    if (auth == CONTACT_AUTH_DELETED) {
      error = tr("Deleting %1 not successful (%2)").arg(contact->m_nick).arg(simres);
    }
    SimCore::execMessageBox(true, error, simerr);
  } else if (auth == CONTACT_AUTH_DELETED && !contact->isMe() && !contact->isTest()) {
    if (!SimParam::get("ui.main.delete")) {
      QString qs = tr("I know that deleted contacts keep their chat history and files.\nDo not remind me again.");
      msg = tr("Please do not forget to remove chat history and transferred files with <b>%1</b> if you so require.");
      msg = "<br/>" + msg.arg(contact->m_nick.toHtmlEscaped()) + "<br/><br/>";
      if (SimCore::execMessageBox(QMessageBox::Information, tr("Deleted contact").toUtf8().data(), msg, qs)) {
        SimParam::set("ui.main.delete", 1, false);
      }
    }
  }
}

void Contacts::unblockContact(Contact * contact)
{
  if (contact->isDeleted()) {
    QString qs = tr("Are you sure you want to renew contact with <b>%1</b>?").arg(contact->m_nick.toHtmlEscaped());
    if (QMessageBox::question(this, tr("Undelete contact"), "<br/>" + qs,
                              QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes) return;
  }

  bool deleted = contact->isDeleted() && !contact->isVerified();
  int simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_AUTH, sim_number_new(CONTACT_AUTH_ACCEPTED));
  QString simerr = SimCore::getError(simres);
  SimCore::get()->getUserContacts();
  if (simres == SIM_OK) {
    if (deleted) acceptContact(contact->m_nick);
  } else {
    SimCore::execMessageBox(true, tr("Unblocking of %1 not successful (%2)").arg(contact->m_nick).arg(simres), simerr);
  }
}

void Contacts::callContact(Contact * contact)
{
  if (contact->m_rights & CONTACT_FLAG_AUDIO || contact->isTest()) {
    if (contact->isCallState(Contact::call_incoming)) { // the other party is calling, so switch to talking state
      contact->clearNotifications(Contact::flag_hasMissedCall);
    }
    int simres = sim_audio_call_(contact->m_simId);
    if (simres != SIM_OK) {
      QString error = tr("Calling %1 not successful (%2)").arg(contact->m_nick).arg(simres);
      SimCore::execMessageBox(false, error, SimCore::getError(simres));
    }
  }
}

void Contacts::hangupContact(Contact * contact)
{
  int simres = sim_audio_hangup_(contact->m_simId); // call rejected
  if (simres != SIM_OK) {
    QString error = tr("Hanging up to %1 not successful (%2)").arg(contact->m_nick).arg(simres);
    SimCore::execMessageBox(false, error, SimCore::getError(simres));
  }
}

void Contacts::changeAvatar(Contact * contact)
{
  QString title = tr("Choose avatar file");
  QString filter = tr("Image Files (*.png)");
#ifdef _WIN32
  WCHAR name[2048];
  OPENFILENAME ofn;
  filter.append(" *.png");
  name[0] = 0;
  memset(&ofn, 0, sizeof(ofn));
  ofn.lStructSize = sizeof(ofn);
  ofn.lpstrFilter = (WCHAR *)filter.utf16();
  ofn.nFilterIndex = 1;
  ofn.lpstrFile = name;
  ofn.nMaxFile = SIM_ARRAY_SIZE(name);
  ofn.lpstrTitle = (WCHAR *)title.utf16();
  ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_LONGNAMES |
              OFN_NOTESTFILECREATE | OFN_HIDEREADONLY | OFN_DONTADDTORECENT | OFN_NOCHANGEDIR;
  QString fileName = GetOpenFileNameW(&ofn) ? QString::fromWCharArray(name) : QString();
#else
  QString fileName = QFileDialog::getOpenFileName(this, title, "", filter, 0, QFileDialog::ReadOnly);
#endif
  if (!fileName.isNull() && !fileName.isEmpty()) {
    if (fileName == "*") {
      delete contact->m_avatar;
      contact->m_avatar = 0;
    }

    QImage * img = new QImage(fileName);
    if (!img->width() || !img->height()) {
      delete img;
      return;
    }

    QSize sz = m_statusDelegate->getDefaultAvatarSize();
    if (img->width() > sz.width() || img->height() > sz.height()) {
      QImage simg = img->scaled(sz, Qt::KeepAspectRatio, Qt::FastTransformation);
      img->swap(simg);
    }
    contact->m_avatar = img;
  }
}

static const char * g_itemSet[] = {
  QT_TRANSLATE_NOOP("Contacts", "Echo cancellation"), QT_TRANSLATE_NOOP("Contacts", "No echo cancellation"),
  QT_TRANSLATE_NOOP("Contacts", "Typing notification"), QT_TRANSLATE_NOOP("Contacts", "No typing notification"),
  QT_TRANSLATE_NOOP("Contacts", "Chat history"), QT_TRANSLATE_NOOP("Contacts", "No chat history"),
  QT_TRANSLATE_NOOP("Contacts", "Edit messages"), QT_TRANSLATE_NOOP("Contacts", "No edit messages"),
  QT_TRANSLATE_NOOP("Contacts", "Logon sound"), QT_TRANSLATE_NOOP("Contacts", "No logon sound"),
  QT_TRANSLATE_NOOP("Contacts", "Logoff sound"), QT_TRANSLATE_NOOP("Contacts", "No logoff sound")
};

static const simnumber g_flagSet[] = {
  CONTACT_FLAG_ECHO_Y, CONTACT_FLAG_ECHO_N, CONTACT_FLAG_TYPING_Y, CONTACT_FLAG_TYPING_N,
  CONTACT_FLAG_HISTORY_Y, CONTACT_FLAG_HISTORY_N, CONTACT_FLAG_EDIT_Y, CONTACT_FLAG_EDIT_N,
  CONTACT_FLAG_SOUND_ON_Y, CONTACT_FLAG_SOUND_ON_N, CONTACT_FLAG_SOUND_OFF_Y, CONTACT_FLAG_SOUND_OFF_N
};

void Contacts::doSettingsAction(Contact * contact, QAction * actions[], int i)
{
  int simres;
  int history = g_flagSet[i] == CONTACT_FLAG_HISTORY_Y ? 1 : g_flagSet[i] == CONTACT_FLAG_HISTORY_N ? 0 : -1;
  if (history >= 0 && (actions[i]->isChecked() || actions[i ^ 1]->isChecked())) {
    QString qs;
    if (actions[i]->isChecked() ^ history) {
      qs = tr("You are about to override your settings and <span style=\" color:#ff0000;\">never</span>"
              " keep a chat history with <b>%1</b>.");
    } else {
      qs = tr("You are about to override your settings and <span style=\" color:#ff0000;\">always</span>"
              " keep a chat history with <b>%1</b>.");
    }
    qs = "<br/>" + qs.arg(contact->m_nick.toHtmlEscaped()) + "<br/><br/>";
    if (QMessageBox::question(this, tr("Set chat history"), qs.append(tr("Are you sure you want to do this?")),
                              QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes) return;
  }

  if (actions[i]->isChecked()) {
    simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_FLAGS, sim_number_new(g_flagSet[i]));
  } else if (actions[i ^ 1]->isChecked()) {
    simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_FLAGS, sim_number_new(g_flagSet[i ^ 1]));
  } else {
    simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_FLAGS, sim_number_new(g_flagSet[i] | g_flagSet[i ^ 1]));
  }

  if (simres != SIM_OK) {
    QString error = tr("Setting permissions of %1 not successful (%2)").arg(contact->m_nick).arg(simres);
    SimCore::execMessageBox(true, error, SimCore::getError(simres));
  }
}

void Contacts::addSettingsActions(QMenu * menu, Contact * contact, QAction * actions[])
{
  simnumber n = getContactFlags(contact);

  for (unsigned i = 0; i < SIM_ARRAY_SIZE(g_flagSet); i++) {
    actions[i]->setText(tr(g_itemSet[i]));
    actions[i]->setCheckable(true);
    if (g_flagSet[i] & n) {
      actions[i]->setChecked(true);
    } else {
      actions[i]->setChecked(false);
    }
    menu->addAction(actions[i]);
    if (contact->isMe() && i != 4 && i != 5 && i < 8) actions[i]->setEnabled(false);
    qtfix::fixMenuIndicator(actions[i], menu);
    if (i == 1 || i == 7 || i == 11) menu->addSeparator();
  }
}

bool Contacts::isContactShown(Contact * contact) const
{
  return !contact->isDeleted() || ui->actionShowDeleted->isChecked();
}

// -2 - activate by mode switch, -1 - by context menu, 0 - by left click, 1/2/3 - ui.main.popup, 4 - by dock menu
void Contacts::activateContactChat(int id, int popupNotify)
{
  Contact * contact = SimCore::getContact(id);
  if (!contact) return;

  if (contact->isNewContact() && popupNotify >= 0 && popupNotify < 4) {
    simtype table = sim_contact_get_(contact->m_simId, CONTACT_BIT_DEFAULT);
    simtype addr = sim_get_pointer(table) ? sim_table_get_string(table, CONTACT_KEY_ADDRESS) : sim_nil();

    QString qs = "<p><b><br/>";
    QString field = tr("Address:").replace(" ", "&nbsp;").append("&nbsp;</td><td>");
    qs.append(tr("A user has requested to be added to your list of contacts.")).append("<br/>");
    qs.append("<table><tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>").append(field);
    if (sim_get_pointer(addr)) qs.append(sim_get_pointer(addr));
    qs.append("</td></tr><tr></tr><tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>");
    field = tr("Nickname:").replace(" ", "&nbsp;").append("&nbsp;</td><td>");
    qs.append("<td>").append(field).append(contact->m_nick.toHtmlEscaped()).append("</td></tr><tr></tr>");
    if (sim_get_pointer(table)) {
      simunsigned n = sim_table_get_number(table, CONTACT_KEY_SEEN);
      simtype ip = sim_table_get_string(table, CONTACT_KEY_LOCATION);
      if (sim_get_pointer(ip)) {
        field = tr("Location:").replace(" ", "&nbsp;").append("&nbsp;</td><td>");
        qs.append("<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>").append(field);
        field = contact->convertLocationToCountry(sim_get_pointer(ip), 0);
        qs.append(qApp->translate("SimCountry", field.toUtf8().data())).append("</td></tr><tr></tr>");
        field = tr("IP address:").replace(" ", "&nbsp;").append("&nbsp;</td><td>");
        qs.append("<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>").append(field);
        qs.append(sim_get_pointer(ip)).append("</td></tr><tr></tr>");
      }
      if (n) {
        char buff[64];
        field = tr("Last seen:").replace(" ", "&nbsp;").append("&nbsp;</td><td>");
        qs.append("<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>").append(field);
        sim_convert_time_to_string(n, buff);
        qs.append(QString(buff).replace(" ", "&nbsp;")).append("</td></tr><tr></tr>");
      }
      sim_contact_free_(table);
    }
    qs.append("</table><br/>").append(tr("Do you wish to communicate with this user?")).append("<br/></b></p>");
    const QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel;
    QMessageBox mbox(QMessageBox::Question, tr("New contact"), qs, buttons, this);
    mbox.setTextInteractionFlags(Qt::TextSelectableByMouse);
    mbox.setDefaultButton(QMessageBox::Cancel);
    int simres;
    contact->clearNotifications(Contact::flag_hasNewContact);
    switch (mbox.exec()) {
      case QMessageBox::Yes:
        simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_AUTH, sim_number_new(CONTACT_AUTH_ACCEPTED));
        qs = SimCore::getError(simres);
        SimCore::get()->getUserContacts();
        if (simres == SIM_OK) {
          if (!contact->isVerified()) acceptContact(contact->m_nick);
        } else {
          SimCore::execMessageBox(true, tr("Adding %1 not successful (%2)").arg(contact->m_nick).arg(simres), qs);
        }
        break;

      case QMessageBox::No:
        simres = sim_contact_set_(contact->m_simId, CONTACT_KEY_AUTH, sim_number_new(CONTACT_AUTH_DELETED));
        qs = SimCore::getError(simres);
        SimCore::get()->getUserContacts();
        if (simres != SIM_OK) {
          SimCore::execMessageBox(true, tr("Deleting %1 not successful (%2)").arg(contact->m_nick).arg(simres), qs);
        }
        break;
    }
  } else if (isSplitMode()) {
    ChatFrames::get()->activateChat(contact, popupNotify);
  } else {
    if (popupNotify == 1) {
      if (!isVisible()) {
        qtfix::showMinimizedWindow(this, SimParam::get("ui.main.sleep"));
      } else {
        show();
      }
    } else if (popupNotify == 3) {
      if (!(windowState() & Qt::WindowMinimized)) show();
    } else {
      if (popupNotify < 1 || contact->isTest()) {
        // switch chat contact when normal select happens
        if (m_model.getCurContactId() >= 0) m_model.emitDataChanged(m_model.getCurContactId(), ccol_nick);
        m_model.setCurContactId(id);
        m_model.emitDataChanged(id, ccol_nick);
        ChatFrames::getFrame(contact);
        int ndx = ui->stackedWidget->indexOf(contact->m_chatFrame);
        if (ndx < 0) {
          contact->m_chatFrame->setParent(0);
          ui->stackedWidget->insertWidget(0, contact->m_chatFrame);
          contact->m_chatFrame->show();
          ndx = 0;
        }
        ui->stackedWidget->setCurrentIndex(ndx);
        ui->stackedWidget->widget(ndx)->setFocus();
        ui->contactView->clearSelection();
      }
      if (popupNotify >= -1) qtfix::showActivateWindow(this, popupNotify > 0);
    }
  }
}

void Contacts::doHide()
{
  if (SimCore::isHidden()) {
    if (m_trayIcon) m_trayIcon->hide();
#ifdef __APPLE__
    if (SimCore::isHidden() != 'h') removeTrayIcon();
#endif
    SimCore::hide();
    if (SimCore::getMyStatus() == SIM_STATUS_ON && SimCore::isHiding()) setMyStatus(SIM_STATUS_HIDE);
  }
}

void Contacts::doQuit()
{
  log_debug_("ui", "%s\n", __FUNCTION__);

  m_doQuit = true;
  ui->contactView->clearSelection();
  ui->contactView->setModel(0);
  ui->contactView->setItemDelegate(0);
  qApp->quit();
}

void Contacts::doLogout()
{
  emit signalLogout();
  recalcTitle(-1);
  ui->statusBar->clearMessage();
  if (SimCore::get()->isLogged()) saveSettings(true);
  ChatFrames::get()->closeAll();

  while (ui->stackedWidget->count()) {
    ui->stackedWidget->removeWidget(ui->stackedWidget->widget(0));
  }
  ui->contactView->clearSelection();
}

void Contacts::recalcTitle(int id)
{
  QString title = SIM_UI_NAME;

  if (id >= 0) {
    if (Contact * contact = SimCore::getContact(id)) {
      QString qs = contact->m_info.left(m_titleMax);

      title.append(" - ").append(contact->m_nick);
      if (!qs.isEmpty()) title.append(" - ").append(qs.left(qs.indexOf('\n')));
    }
  }

  setWindowTitle(title);
}

bool Contacts::eventFilter(QObject * obj, QEvent * event)
{
  if (event->type() == QEvent::MouseButtonPress) {
    QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);

    if (mouseEvent->button() == Qt::LeftButton) {
      if (obj == m_status) {
        onCustomContextMenuStatus(mouseEvent->globalPos(), true);
        return true;
      }
      if (obj == ui->statusBar) {
        ui->statusBar->clearMessage();
        return true;
      }
    }
  } else if (event->type() == QEvent::KeyPress) {
    QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);

    if (keyEvent->key() == Qt::Key_Escape) {
      setWindowState(Qt::WindowMinimized);
      return true;
    }
  }
  return Parent::eventFilter(obj, event);
}

void Contacts::closeEvent(QCloseEvent * event)
{
  log_note_("ui", "%s\n", __FUNCTION__);
  if (m_doQuit) {
    Parent::closeEvent(event);
  } else {
    saveSettings(false);
    if (getStayInTaskbar()) {
      log_info_("ui", "%s: minimize instead of close\n", __FUNCTION__);
      setWindowState(Qt::WindowMinimized);
    } else {
      log_info_("ui", "%s: hide in tray instead of close\n", __FUNCTION__);
      unblinkTrayIcon('s');
      hide();
      if (!isSplitMode()) doHide();
    }
    event->ignore();
  }
}

void Contacts::changeEvent(QEvent * event)
{
  if (event->type() == QEvent::ActivationChange || event->type() == QEvent::WindowStateChange) {
    m_minimized = m_minimized < 0 ? 1 : isMinimized();
    log_xtra_("ui", "%s active %d state %d minimized %d\n", __FUNCTION__,
              isActiveWindow(), int(windowState()), m_minimized);
    if (isActiveWindow() && !isMinimized() && !isSplitMode() && ui->stackedWidget->currentWidget()) {
      ui->stackedWidget->currentWidget()->setFocus();
    }
  } else if (event->type() == QEvent::LanguageChange) {
    ui->retranslateUi(this);
    m_status->setToolTip(getStatusToolTip(SimCore::getMyStatus()));
    if (m_trayIcon) reCreateTrayIcon();
    if (m_welcome) {
      QFile file(":/manual");
      if (file.open(QFile::ReadOnly | QFile::Text)) m_welcome->setText(file.readAll());
    }
    ui->statusBar->clearMessage();
  }
  Parent::changeEvent(event);
}

void Contacts::showEvent(QShowEvent * event)
{
  log_xtra_("ui", "%s\n", __FUNCTION__);
  Parent::showEvent(event);
#ifdef __unix__
  bool hidden = isHidden();
#else
  const bool hidden = false;
#endif
  if (!hidden && SimCore::isHidden()) {
    if (SimCore::getMyStatus() == SIM_STATUS_HIDE) setMyStatus(SIM_STATUS_ON);
#ifdef __APPLE__
    if (!m_trayIcon && m_tray > 0 && SimCore::isHidden() == 'h') createTrayIcon();
#endif
    if (m_trayIcon) m_trayIcon->show();
    SimCore::unhide();
  }

  if (!isSplitMode() && !ui->stackedWidget->currentWidget()) ui->stackedWidget->insertWidget(0, getTextWidget());

#ifndef _WIN32
  m_doQuit = false;
  if (m_committed) {
    m_committed = false;
    if (m_trayIcon) reCreateTrayIcon();
    log_debug_("ui", "%s\n", __FUNCTION__);
  }
#endif
}

#ifdef _WIN32
bool Contacts::nativeEvent(const QByteArray & /*eventType*/, void * message, long * /*result*/)
{
  /*if (eventType == "windows_generic_MSG")*/ {
    MSG * msg = (MSG *)message;
    if (msg->message == WM_HOTKEY) {
      if (msg->wParam == 1) {
        on_actionHideAll_triggered();
      } else if (msg->wParam == 2) {
        showSpontaneousWindow();
      }
    } else if (msg->message == WM_QUERYENDSESSION) {
      log_debug_("ui", "%s WM_QUERYENDSESSION %lX\n", __FUNCTION__, msg->lParam);
    } else if (msg->message == WM_ENDSESSION) {
      log_debug_("ui", "%s WM_ENDSESSION %d %lX\n", __FUNCTION__, msg->wParam, msg->lParam);
    }
  }
  return false;
}
#endif

// special activation of a console in separate window (regardless of split view setting)
void Contacts::activateConsole()
{
  int id = SimCore::get()->getTestContactId();
  if (id >= 0) {
    Contact * contact = SimCore::getContact(id);
    if (contact) ChatFrames::get()->activateChat(contact, 0);
  }
}

void Contacts::showActivateWindow()
{
  QWidget * active = hasActiveWindow();

  log_xtra_("ui", "%s active %d\n", __FUNCTION__, active != 0);
  qtfix::showActivateWindow(this, false);
  if (active) active->activateWindow();
}

void Contacts::showSpontaneousWindow()
{
  qtfix::showActivateWindow(this, true);
}

void Contacts::blinkWindow()
{
  unhideApplication();
  QApplication::alert(this);
  if (!m_active) m_alert = true;
}

void Contacts::unblinkTrayIcon()
{
  if (m_trayIcon) {
    log_xtra_("ui", "%s\n", __FUNCTION__);
    m_trayNotify = 0;
    m_trayIcon->setIcon(getStatusIcon());
  }
}

#ifdef __APPLE__
void Contacts::recalcDockMenu(QWidget * chatWindow)
{
  QMenu * menu = new QMenu;
  std::vector<Contact *> contacts;

  contacts.reserve(SimCore::get()->m_contacts.size());
  for (unsigned i = SimCore::get()->m_contacts.size(); i--;) {
    Contact * c = SimCore::get()->m_contacts[i];
    if (c->m_chatWindow && (c->m_chatWindow == chatWindow || c->m_chatWindow->isVisible())) {
      contacts.push_back(c);
    }
  }

  for (unsigned i = 1; i < contacts.size(); i++) {
    for (int j = i; j > 0 && m_model.compareContacts(contacts[j]->m_id, contacts[j - 1]->m_id); j--) {
      Contact * c = contacts[j - 1];
      contacts[j - 1] = contacts[j];
      contacts[j] = c;
    }
  }

  for (unsigned i = 0; i < contacts.size(); i++) {
    Contact * c = contacts[i];
    QString text = c->m_nick;
    QString qs = Contact::tr("off");
    bool showoff = c->getState() == Contact::state_unknown && !c->isMe() && m_showOffline;
    if (!showoff) c->convertStatusToState(c->m_status, &qs);
    QAction * action = new QAction(text.append(" (").append(qs).append(")"), menu);

    action->setProperty("dock-contact-id", c->m_id);
    menu->addAction(action);
    connect(action, SIGNAL(triggered()), this, SLOT(onDockMenuTriggered()));
  }

  menu->setAsDockMenu();
  delete m_dockMenu;
  m_dockMenu = menu;
}

void Contacts::onDockMenuTriggered()
{
  QVariant senderId = QObject::sender()->property("dock-contact-id");

  if (senderId.isValid()) activateContactChat(senderId.toInt(), 4);
}
#else
void Contacts::recalcDockMenu(QWidget *)
{}
void Contacts::onDockMenuTriggered() {}
#endif

void Contacts::onRenameButtonClicked()
{
  if (m_rename) m_rename->done(2);
}

void Contacts::on_contactView_pressed(QModelIndex ndx)
{
  if (Contact * contact = m_model.getContact(ndx)) {
    if (!m_click && ui->contactView->m_mouseButtonUsed == Qt::LeftButton && !isSplitMode()) {
      activateContactChat(contact->m_id, 0);
    }
  }
}

void Contacts::on_contactView_clicked(QModelIndex ndx)
{
  if (Contact * contact = m_model.getContact(ndx)) {
    if (m_click && !isSplitMode()) {
      activateContactChat(contact->m_id, 0);
    }
  }
}

void Contacts::on_contactView_doubleClicked(QModelIndex ndx)
{
  if (Contact * contact = m_model.getContact(ndx)) {
    if (ui->contactView->m_mouseButtonUsed == Qt::LeftButton) {
      int doubleClick = SimParam::get("ui.main.doubleclick");
      activateContactChat(contact->m_id, 0);
      if (doubleClick > 1 || (doubleClick > 0 && !isSplitMode())) callContact(contact);
    }
  }
}

void Contacts::on_actionHideAll_triggered()
{
#ifdef __APPLE__
  if (SimCore::isHidden()) {
    saveSettings(false);
    hide();
    doHide();
    return;
  }
#endif
  saveSettings(true);
  qtfix::hideMinimizedWindow(this);
  doHide();
  ChatFrames::get()->detachAll();
}

void Contacts::on_actionQuit_triggered()
{
  if (m_doQuit) {
    qApp->quit();
    return;
  }

  m_doQuit = true;
  doLogout();
  hide();
  ui->contactView->setModel(0);
  ui->contactView->setItemDelegate(0);
  if (m_trayIcon) {
    m_trayIcon->hide();
    m_trayIcon->setIcon(QIcon());
  }
  m_trayIcon = 0; // do not remove it (if quit action is activated by tray it will crash)
  m_tray = 0;
  SimCore::get()->logoutStart(true);
}

void Contacts::on_actionShowMainWindow_triggered()
{
  qtfix::showActivateWindow(this, false);
}

void Contacts::on_actionOnline_triggered()
{
  setMyStatusIcon(SIM_STATUS_ON, tr("online"));
}

void Contacts::on_actionAway_triggered()
{
  setMyStatusIcon(SIM_STATUS_AWAY, tr("away"));
}

void Contacts::on_actionBusy_triggered()
{
  setMyStatusIcon(SIM_STATUS_BUSY, tr("busy"));
}

void Contacts::on_actionInvisible_triggered()
{
  setMyStatusIcon(SIM_STATUS_INVISIBLE, tr("invisible"));
}

void Contacts::on_actionOffline_triggered()
{
  setMyStatusIcon(SIM_STATUS_OFF, tr("offline"));
}

void Contacts::on_actionEnglish_triggered()
{
  LoginDialog::on_actionEnglish_triggered(ui->actionEnglish, ui->actionFrench, ui->actionGerman);
  if (ui->menubar->isNativeMenuBar()) QApplication::postEvent(ui->menubar, new QEvent(QEvent::ParentChange));
}

void Contacts::on_actionFrench_triggered()
{
  LoginDialog::on_actionFrench_triggered(ui->actionEnglish, ui->actionFrench, ui->actionGerman);
  if (ui->menubar->isNativeMenuBar()) QApplication::postEvent(ui->menubar, new QEvent(QEvent::ParentChange));
}

void Contacts::on_actionGerman_triggered()
{
  LoginDialog::on_actionGerman_triggered(ui->actionEnglish, ui->actionFrench, ui->actionGerman);
  if (ui->menubar->isNativeMenuBar()) QApplication::postEvent(ui->menubar, new QEvent(QEvent::ParentChange));
}

void Contacts::on_actionUserManual_triggered()
{
  UserManual * um = UserManual::isActive();

  if (um) {
    qtfix::showActivateWindow(um, false);
  } else {
    um = new UserManual(0);
    qtfix::showMaximizedWindow(um);
    um->show();
  }
}

void Contacts::on_actionAbout_triggered()
{
  if (m_doQuit) return;

  About * a = new About(0);
  a->exec();
}

void Contacts::on_actionAddNewContact_triggered()
{
  bool ok;
  QString addr = QInputDialog::getText(this, tr("Add contact"),
                                       tr("<p>Please, enter the <span style=\" color:#ff0000;\">"
                                          "Simphone address</span> of the user you want to add:</p>"),
                                       QLineEdit::Normal, m_lastAddAddress, &ok);
  if (ok) {
    addr = addr.trimmed();
    if (addr.startsWith(tr("Address: "))) addr.remove(0, tr("Address: ").size());
    simnumber simId = sim_contact_convert_to_id_(addr.toUtf8().data());
    int id = SimCore::get()->getContactId(simId ? simId : addr.toULongLong(), true);
    int simres;
    QString simerr;

    m_lastAddAddress = QString();
    if (id >= 0) {
      Contact * c = SimCore::getContact(id);
      if (!c) return; // this shall never happen
      QString nick = c->m_nick.toHtmlEscaped();

      if (c->isMe()) {
        if (!c->isDeleted() && !c->isBlocked() && !c->isForgotten()) {
          QMessageBox::information(this, tr("New contact"), tr("\nIt is not necessary to add yourself."));
          return;
        }
      } else if (c->isForgotten()) {
        QString qs = "<br/>";
        qs.append(tr("You have already forgotten <b>%1</b>. Are you sure you want to renew contact with this user?"));
        if (QMessageBox::question(this, tr("New contact"), qs.arg(nick),
                                  QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes) return;
      } else if (c->isDeleted()) {
        QString qs = tr("<br/>You have already deleted <b>%1</b>. Are you sure you want to undelete this user?");
        if (QMessageBox::question(this, tr("New contact"), qs.arg(nick),
                                  QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes) return;
      } else if (c->isBlocked()) {
        QString qs = tr("<br/>You have already blocked <b>%1</b>. Are you sure you want to unblock this user?");
        if (QMessageBox::question(this, tr("New contact"), qs.arg(nick),
                                  QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes) return;
      } else {
        QMessageBox::information(this, tr("New contact"), tr("<br/>You have already added <b>%1</b>.").arg(nick));
        return;
      }
      if (!simId) simId = addr.toULongLong();
      simres = sim_contact_set_(simId, CONTACT_KEY_AUTH, sim_number_new(CONTACT_AUTH_ACCEPTED));
      simerr = SimCore::getError(simres);
      SimCore::get()->getUserContacts();
      if (simres == SIM_OK && !c->isVerified()) acceptContact(c->m_nick);
    } else {
      simres = sim_contact_add_(addr.toUtf8().data(), &simId);
      simerr = SimCore::getError(simres);
      SimCore::get()->getUserContacts();
      if (simres == SIM_OK) {
        Contact * c = SimCore::getContact(SimCore::get()->getContactId(simId, true));
        if (c) acceptContact(c->m_nick);
      }
    }

    if (simres != SIM_OK) {
      m_lastAddAddress = addr;
      SimCore::execMessageBox(true, tr("Adding %1 not successful (%2)").arg(addr).arg(simres), simerr);
    }
  }
}

void Contacts::on_actionShowDeleted_triggered()
{
  m_model.setShowDeleted(ui->actionShowDeleted->isChecked());
}

void Contacts::on_actionSortByNick_triggered()
{
  m_model.setSortBy(ui->actionSortByNick->isChecked() ? ContactsModel::sort_nick : ContactsModel::sort_level);
}

void Contacts::on_actionTransfers_triggered()
{
  showDialog(SimCore::getContact(SimCore::get()->getTestContactId()), QPoint(), 0);
}

void Contacts::on_actionSplitMode_triggered()
{
  qtfix::fixSplitterHandle(ui->splitter, font(), isSplitMode() ? 'h' : 'e');
  unblinkTrayIcon('u');
  if (!isSplitMode()) {
    int lastid = m_restoring;
    if (lastid == -1) {
      lastid = ChatFrames::get()->getLastChatId();
      saveSettings(true, true);
      ChatFrames::get()->detachAll();
    }
    if (m_restoring == -1) {
      QList<int> sizes;
      sizes.append(ui->contactView->width());
      qtfix::resizeMainWindow(this, width() + m_dockedSize, height());
      ui->splitter->setSizes(sizes);
    }
    QSizePolicy sp = ui->contactView->sizePolicy();
    sp.setHorizontalStretch(0);
    ui->contactView->setSizePolicy(sp);
    sp = ui->stackedWidget->sizePolicy();
    sp.setHorizontalStretch(1);
    ui->stackedWidget->setSizePolicy(sp);
    if (!ui->stackedWidget->currentWidget()) ui->stackedWidget->insertWidget(0, getTextWidget());

    if (lastid >= 0) {
      ui->contactView->selectRow(m_model.getRow(lastid));
      activateContactChat(lastid, /*m_restoring == -1 ? 0 :*/ -2);
    }
  } else {
    m_model.setCurContactId(-1); // no current contact in split mode
    ChatFrame * frame = 0;
    if (ui->stackedWidget->currentWidget() != m_welcome) frame = (ChatFrame *)ui->stackedWidget->currentWidget();
    while (ui->stackedWidget->count()) {
      ui->stackedWidget->removeWidget(ui->stackedWidget->widget(0));
    }
    if (m_restoring == -1) {
      m_dockedSize = width();
      int w = ui->contactView->width();
      QList<int> sizes = ui->splitter->sizes();
      sizes[0] += sizes[1];
      sizes[1] = 0;
      ui->splitter->setSizes(sizes);
      qtfix::resizeMainWindow(this, w, height());
      m_dockedSize = int(m_dockedSize) < width() ? 200 : m_dockedSize - width();
      if (frame) activateContactChat(frame->getContact()->m_id, 0);
    }
    QSizePolicy sp = ui->contactView->sizePolicy();
    sp.setHorizontalStretch(1);
    ui->contactView->setSizePolicy(sp);
    sp = ui->stackedWidget->sizePolicy();
    sp.setHorizontalStretch(0);
    ui->stackedWidget->setSizePolicy(sp);
    if (m_restoring == -1) saveSettings(false);
  }
}

void Contacts::on_actionSettings_triggered()
{
  if (m_doQuit || Settings::isActive()) return;

  Settings * s = new Settings(0, sim_audio_check_talking_() || SimCore::isCalling() ? 't' : 'c');
  s->prepareAudio(true);
  s->exec();
  emitSettingsChanged();
}

void Contacts::on_actionPreferences_triggered()
{
  on_actionSettings_triggered();
}

void Contacts::on_actionLogout_triggered()
{
  doLogout();
  qtfix::hideMinimizedWindow(this);
  removeTrayIcon();
  m_tray = 0;
  SimCore::get()->logoutStart(false);
}

void Contacts::onStatusMessageChanged()
{
  QPalette palette;
  palette.setColor(QPalette::WindowText, m_infoColor);
  ui->statusBar->setPalette(palette);
}

void Contacts::onFocusWindowChanged(QWindow * focusWindow)
{
  bool mine = windowHandle() == focusWindow;

  log_xtra_("ui", "%s %s\n", __FUNCTION__, mine ? "contacts" : focusWindow ? "other" : "NONE");
#ifndef __APPLE__
  if (mine) unblinkTrayIcon();
#endif
}

void Contacts::onRowsInserted(const QModelIndex &, int first, int last)
{
  if (last < 0) last = first;
  if (first >= 0) {
    for (int i = last, j = 1000; i >= first && j >= 0; j--) {
      ui->contactView->resizeRowToContents(i--);
    }
  }
}

void Contacts::onAboutToQuit()
{
#ifdef _WIN32
  log_note_("ui", "%s\n", __FUNCTION__);
  if (m_committed && sim_exit_() == SIM_OK) {
    SimThread * thread = SimCore::get()->getThread();
    if (thread) thread->wait();
  }
#endif
}

void Contacts::onCommitDataRequest(QSessionManager & manager)
{
  log_note_("ui", "%s\n", __FUNCTION__);
#ifdef _WIN32
  if (m_trayIcon) {
    m_committing = true;
    hide(); // hide in tray so that we can close afterwords
    ChatFrames::get()->closeAll();
    unblinkTrayIcon();
  }
  ui->contactView->clearSelection();

  SimThread * thread = SimCore::get()->getThread();

  if (SimCore::get()->isLogged()) SimParam::set("client.logoff", 0, true);
  if (!thread || thread->m_action == SimThread::thread_login) {
    if (thread) thread->wait();
    m_committed = true;
    on_actionQuit_triggered(); // You should not exit the application within this signal
    thread = SimCore::get()->getThread();
    if (thread) thread->wait();
  } else if (sim_exit_() == SIM_OK) {
    thread->wait();
  }
#else
  m_committed = true;
  if (!m_shutdownPhase || manager.isPhase2()) {
    if (!m_doQuit) {
      m_doQuit = true; // allow exiting
      if (SimCore::get()->isLogged()) saveSettings(true);
      ChatFrames::get()->detachAll();
    }
    if (!getStayInTaskbar()) {
      log_note_("ui", "%s: hide in tray instead of close\n", __FUNCTION__);
      hide();
    } else {
      log_note_("ui", "%s: minimize instead of close\n", __FUNCTION__);
      setWindowState(Qt::WindowMinimized);
    }
  } else {
    log_note_("ui", "%s: requestPhase2\n", __FUNCTION__);
    manager.requestPhase2();
  }
#endif
  manager.setRestartHint(QSessionManager::RestartNever);
}

void Contacts::onSaveStateRequest(QSessionManager & manager)
{
  log_note_("ui", "%s minimized %d\n", __FUNCTION__, isMinimized());
  if (isMinimized()) m_minimized = -1;
  manager.setRestartHint(QSessionManager::RestartNever);
}

#ifdef HAVE_LIBXSS
#include <X11/Xlib.h>
#include <X11/extensions/scrnsaver.h>
#endif

int Contacts::onAwayTimeout(int force)
{
  if (!m_awayTimeSec && !force) return 0;

  unsigned long idle = 0;
  int ok = 0;
#ifdef _WIN32
  LASTINPUTINFO lastInputInfo;
  lastInputInfo.cbSize = sizeof(lastInputInfo);

  ok = GetLastInputInfo(&lastInputInfo) != 0;
  if (ok) idle = GetTickCount() - lastInputInfo.dwTime;
    //log_note_("ui", "idle = %lu\n", idle);
#elif defined(__APPLE__)
  io_iterator_t iter = 0;
  if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOHIDSystem"), &iter) == KERN_SUCCESS) {
    io_registry_entry_t entry = IOIteratorNext(iter);
    if (entry) {
      CFMutableDictionaryRef dict = NULL;
      if (IORegistryEntryCreateCFProperties(entry, &dict, kCFAllocatorDefault, 0) == KERN_SUCCESS) {
        CFNumberRef obj = (CFNumberRef)CFDictionaryGetValue(dict, CFSTR("HIDIdleTime"));
        int64_t nanoseconds = 0;
        if (obj && CFNumberGetValue(obj, kCFNumberSInt64Type, &nanoseconds)) {
          idle = nanoseconds / 1000000;
          ok = 1;
        }
        CFRelease(dict);
      }
      IOObjectRelease(entry);
    }
    IOObjectRelease(iter);
  }
#elif defined(HAVE_LIBXSS)
  XScreenSaverInfo ssi;
  Display * dpy = XOpenDisplay(0);
  int event_basep;
  int error_basep;
  if (dpy) {
    if (!XScreenSaverQueryExtension(dpy, &event_basep, &error_basep)) {
#ifndef QT_NO_DBUS
      QDBusConnection bus = QDBusConnection::sessionBus();
      if (bus.isConnected()) {
        QDBusInterface face("org.gnome.Mutter.IdleMonitor", "/org/gnome/Mutter/IdleMonitor/Core",
                            "org.gnome.Mutter.IdleMonitor", bus);
        QList<QVariant> list(face.call("GetIdletime").arguments());
        if (!list.isEmpty() && list.at(0).type() == QVariant::ULongLong) {
          idle = long(list.at(0).toULongLong());
          ok = -1;
        }
      }
#endif
    } else if (XScreenSaverQueryInfo(dpy, DefaultRootWindow(dpy), &ssi)) {
      idle = ssi.idle;
      ok = 1;
    }
    XCloseDisplay(dpy);
  }
#endif
  if ((ok || force) && m_awayTimeSec && force >= 0) {
    switch (SimCore::getMyStatus()) {
      case SIM_STATUS_ON:
      case SIM_STATUS_HIDE:
        if (force || idle >= m_awayTimeSec * 1000) {
          setMyStatus(SIM_STATUS_IDLE);
        } else {
          m_timer.start(m_awayTimeSec * 1000 - unsigned(idle));
        }
        break;

      case SIM_STATUS_IDLE:
        if (force) idle = 0;
        if (idle < m_awayTimeSec * 1000) setMyStatus(SimCore::isHiding() ? SIM_STATUS_HIDE : SIM_STATUS_ON, int(idle));
        break;
    }
  }

  return ok;
}

void Contacts::onNotifyIconTimeout()
{
  if (!m_trayNotify || !m_trayIcon) {
    m_timerNotifyIcon.stop();
  } else {
    m_trayNotify = -m_trayNotify;
  }

  if (m_trayIcon) {
    if (m_trayNotify > 0) {
      m_trayIcon->setIcon(m_trayIconNotify);
    } else {
      m_trayIcon->setIcon(getStatusIcon());
    }
  }
}

void Contacts::onTrayIconTimeout()
{
  log_xtra_("ui", "%s%d (minimized = %d/%d autostart = %d)\n", __FUNCTION__, m_tray,
            isMinimized(), m_minimized, m_autostart);
  reCreateTrayIcon();
  if (m_trayIcon) {
    if (m_autostart && !SimParam::get("ui.main.taskbar") && m_minimized) qtfix::hideMinimizedWindow(this);
  } else if (m_tray > 1) {
    m_tray--;
    QTimer::singleShot(1000, this, SLOT(onTrayIconTimeout()));
  }
}

void Contacts::onSignalInvalidAudioDevice(bool valid)
{
  if (Settings::isActive()) return;

  if (sim_audio_check_talking_() || SimCore::isCalling()) {
    QMessageBox::warning(this, tr("Choose audio devices"),
                         tr("<p><br/><span style=\" color:#ff0000;\">Some of your chosen audio devices have"
                            " become invalid.</span><br/><br/>Please, plug the device back in or hang up your"
                            " audio call and choose a valid device from the \"Main\" menu \"Settings\".<br/></p>"));
    return;
  }

  Settings * s = new Settings(0, 'i');
  s->prepareAudio(valid);
  s->exec();
}

void Contacts::onSignalContactAudioChanged(unsigned id, SimAudioState)
{
  onSignalContactChanged(id);
}

void Contacts::onSignalContactChanged(unsigned contactId)
{
  log_xtra_("ui", "%s\n", __FUNCTION__);
  std::vector<Contact *> & contacts = SimCore::get()->m_contacts;
  int blink = -1; // do not do anything at for loop end

  for (unsigned i = m_model.m_mapRow2Id.size(); i--;) {
    unsigned id = m_model.m_mapRow2Id[i];
    if (Contact * c = contacts[id]) {
      if (id == contactId && c->m_chatWindow) c->m_chatWindow->recalcTitle();
      if (id == contactId && c->isMe()) {
        recalcTitle(id);
        updateTrayIcon();
      }
      if (c->m_state & Contact::flag_notify) {
        bool justPoppedUp = false;
        if (c->m_state & Contact::flag_Notifications && id == contactId) {
          int popupNotify = SimParam::get(SimCore::isHidden() == 'h' ? "ui.main.popuphide" : "ui.main.popup");
          if (popupNotify) {
            if (c->isNewContact()) {
              if (!isVisible()) {
                qtfix::showMinimizedWindow(this, SimParam::get("ui.main.sleep"));
              } else {
                show();
              }
            } else if (isSplitMode() || !isVisible() || !isActiveWindow() || isMinimized()) {
              activateContactChat(id, popupNotify);
              justPoppedUp = isSplitMode();
            }
          }
        }
#ifndef __APPLE__
        if (c->m_chatWindow && (justPoppedUp || c->m_chatWindow->isVisible())) {
          c->m_chatWindow->blinkWindow();
          if (blink < 0) blink = 0; // blink tray
        } else
#endif
        {
          blink = 1; // blink tray and taskbar/dock
        }
        c->m_state &= ~Contact::flag_notify;
      }
    }
  }

  if (blink >= 0) blinkTrayIcon(blink);
}

void Contacts::onCustomContextMenu(const QPoint & pos)
{
  QMenu menu(this);

  QModelIndex cell = ui->contactView->indexAt(pos);

  // Make sure the right click occurred on a cell!
  Contact * c = !cell.isValid() ? 0 : m_model.getContact(cell);

  if (c) {
    ui->contactView->selectRow(cell.row());

    if (c->isNewContact() && SimParam::get("contact.strangers") < 3) {
      activateContactChat(c->m_id, 0);
      return;
    }

    QAction * i = menu.addAction(tr("&Info"));
    QAction * t = menu.addAction(tr("&Transfers"));
    if (!c->isTest() && !c->isMe()) {
      m_actions[0] = ui->action0;
      m_actions[1] = ui->action1;
      m_actions[2] = ui->action2;
      m_actions[3] = ui->action3;
      m_actions[4] = ui->action4;
      m_actions[5] = ui->action5;
      m_actions[6] = ui->action6;
      m_actions[7] = ui->action7;
      m_actions[8] = ui->action8;
      m_actions[9] = ui->action9;
      m_actions[10] = ui->action10;
      m_actions[11] = ui->action11;
      addSettingsActions(menu.addMenu(tr("Settings", "contact")), m_settingsContact = c, m_actions);
    }
    menu.addSeparator();
    QAction * o = c->isTest() ? menu.addAction(tr("&Console")) : menu.addAction(tr("&Chat"));
    QAction * s = menu.addAction(tr("&Send Files"));
    QAction * p = 0;
    QAction * j = 0;
    if (!(c->m_rights & CONTACT_FLAG_XFER)) s->setEnabled(false);
    if (c->isCallState(Contact::call_incoming)) { // the other party is calling
      p = menu.addAction(tr("&Answer"));
      j = menu.addAction(tr("Decline"));
    } else {
      simnumber talkid = sim_audio_check_talking_();
      p = menu.addAction(tr("Call"));
      if (!(c->m_rights & CONTACT_FLAG_AUDIO) && !c->isTest()) p->setEnabled(false);
      if (talkid) {
        p->setEnabled(false);
        if (c->m_simId == talkid) j = menu.addAction(tr("&Hang Up"));
      } else if (c->isCallState(Contact::call_outgoing)) { // we are calling
        p->setEnabled(false);
        j = menu.addAction(tr("End Call"));
      } else if (SimCore::isCalling()) {
        p->setEnabled(false);
      }
    }
    menu.addSeparator();
    QAction * r = menu.addAction(tr("&Rename"));
    QAction * n = c->isMe() ? menu.addAction(tr("Set Info")) : 0;
    QAction * v = m_noAvatar ? 0 : menu.addAction(tr("Avatar"));
    menu.addSeparator();
    QString del = c->isBlocked() || c->isNewContact() || c->isTest() || c->isMe() ? tr("&Delete") : tr("&Block");
    QAction * b = menu.addAction(del);
    QAction * f = menu.addAction(tr("&Forget"));
    QAction * u = menu.addAction(c->isDeleted() ? tr("&Undelete") : tr("&Unblock"));

    if (c->isTest()) {
      r->setEnabled(false);
      if (v) v->setEnabled(false);
    }
    if (!c->isDeleted() || c->isTest() || c->isSystem() || c->isMe()) f->setEnabled(false);
    if (c->isDeleted()) {
      b->setEnabled(false);
    } else if (!c->isBlocked()) {
      u->setEnabled(false);
    }

    QPoint point = ui->contactView->viewport()->mapToGlobal(pos);
    if (QAction * a = menu.exec(point)) {
      if (a == r) {
        renameContact(c);
      } else if (a == n) {
        changeInfoContact(c);
      } else if (a == f) {
        forgetContact(c);
      } else if (a == b) {
        blockContact(c);
      } else if (a == u) {
        unblockContact(c);
      } else if (a == i || a == t) {
        showDialog(c, point, a == i ? -1 : 0);
      } else if (a == s) {
        Transfers::sendFiles(c);
      } else if (a == o || a == p) {
        activateContactChat(c->m_id, -1);
        if (a == p) callContact(c);
      } else if (a == j) {
        hangupContact(c);
      } else if (a == v) {
        changeAvatar(c);
      }
    }
  }
  ui->contactView->clearSelection();
}

void Contacts::onCustomContextMenuStatus(const QPoint & pos, bool global)
{
  QMenu menu(this);

  QPixmap * pixmaps = m_statusDelegate->m_largePixmaps;
  QAction * onlineAction = qtfix::addMenuItem(&menu, tr("Online"), pixmaps[Contact::state_logged]);
  QAction * awayAction = qtfix::addMenuItem(&menu, tr("Away"), pixmaps[Contact::state_away]);
  QAction * busyAction = qtfix::addMenuItem(&menu, tr("Do Not Disturb"), pixmaps[Contact::state_busy]);
  QAction * invisibleAction = qtfix::addMenuItem(&menu, tr("Invisible"), pixmaps[Contact::state_invisible]);
  QAction * offlineAction = qtfix::addMenuItem(&menu, tr("Offline"), pixmaps[Contact::state_none]);

  switch (SimCore::getMyStatus()) {
    case SIM_STATUS_ON: menu.setActiveAction(onlineAction); break;
    case SIM_STATUS_AWAY:
    case SIM_STATUS_IDLE: menu.setActiveAction(awayAction); break;
    case SIM_STATUS_BUSY: menu.setActiveAction(busyAction); break;
    case SIM_STATUS_INVISIBLE: menu.setActiveAction(invisibleAction); break;
    case SIM_STATUS_OFF: menu.setActiveAction(offlineAction); break;
  }
  qtfix::fixIconSize(&menu, menu.font());

  QAction * action = menu.exec(global ? pos : ui->statusBar->mapToGlobal(pos));
  if (action == onlineAction) {
    setMyStatusIcon(SIM_STATUS_ON, tr("online"));
  } else if (action == awayAction) {
    setMyStatusIcon(SIM_STATUS_AWAY, tr("away"));
  } else if (action == busyAction) {
    setMyStatusIcon(SIM_STATUS_BUSY, tr("busy"));
  } else if (action == invisibleAction) {
    setMyStatusIcon(SIM_STATUS_INVISIBLE, tr("invisible"));
  } else if (action == offlineAction) {
    setMyStatusIcon(SIM_STATUS_OFF, tr("offline"));
  }
}

void Contacts::onTrayIconActivated(QSystemTrayIcon::ActivationReason reason)
{
  switch (reason) {
    case QSystemTrayIcon::Trigger:
    case QSystemTrayIcon::DoubleClick:
      log_xtra_("ui", "%s%d (visible = %d minimized = %d)\n", __FUNCTION__, reason, isVisible(), isMinimized());
      if (m_active && isVisible() && !isMinimized()) {
        saveSettings(false);
        unblinkTrayIcon('s');
        hide();
      } else {
        showSpontaneousWindow();
      }
      break;

    case QSystemTrayIcon::Context:
      switch (SimCore::getMyStatus()) {
        case SIM_STATUS_ON: m_trayIconMenu->setActiveAction(m_onlineAction); break;
        case SIM_STATUS_AWAY:
        case SIM_STATUS_IDLE: m_trayIconMenu->setActiveAction(m_awayAction); break;
        case SIM_STATUS_BUSY: m_trayIconMenu->setActiveAction(m_busyAction); break;
        case SIM_STATUS_INVISIBLE: m_trayIconMenu->setActiveAction(m_invisibleAction); break;
        case SIM_STATUS_OFF: m_trayIconMenu->setActiveAction(m_offlineAction); break;
      }
      break;

    default:
    case QSystemTrayIcon::MiddleClick:
    case QSystemTrayIcon::Unknown:
      break;
  }
}

void Contacts::onTrayMenuAboutToShow()
{
  log_xtra_("ui", "%s\n", __FUNCTION__);
  if (isVisible()) {
    if (!isMinimized()) {
      activateWindow();
      raise();
#ifdef __APPLE__
    }
#else
      m_showAction->setEnabled(false);
    } else {
      m_showAction->setEnabled(true);
    }
    m_hideAction->setEnabled(true);
  } else {
    m_hideAction->setEnabled(SimCore::isHidden() == 's');
    m_showAction->setEnabled(true);
#endif
  }
}

void Contacts::createTrayIcon()
{
  log_xtra_("ui", "%s\n", __FUNCTION__);
  if (QSystemTrayIcon::isSystemTrayAvailable()) {
    m_trayIcon = new QSystemTrayIcon(this);
    m_trayIcon->setIcon(getStatusIcon());
    if (SimCore::isHidden() != 'h') m_trayIcon->show();
    connect(m_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
            this, SLOT(onTrayIconActivated(QSystemTrayIcon::ActivationReason)));

    m_trayIconMenu = new QMenu(this);
    QPixmap * pixmaps = m_statusDelegate->m_largePixmaps;
    m_onlineAction = qtfix::addMenuItem(m_trayIconMenu, tr("Online"), pixmaps[Contact::state_logged]);
    m_awayAction = qtfix::addMenuItem(m_trayIconMenu, tr("Away"), pixmaps[Contact::state_away]);
    m_busyAction = qtfix::addMenuItem(m_trayIconMenu, tr("Do Not Disturb"), pixmaps[Contact::state_busy]);
    m_invisibleAction = qtfix::addMenuItem(m_trayIconMenu, tr("Invisible"), pixmaps[Contact::state_invisible]);
    m_offlineAction = qtfix::addMenuItem(m_trayIconMenu, tr("Offline"), pixmaps[Contact::state_none]);

    connect(m_onlineAction, SIGNAL(triggered()), this, SLOT(on_actionOnline_triggered()));
    connect(m_awayAction, SIGNAL(triggered()), this, SLOT(on_actionAway_triggered()));
    connect(m_busyAction, SIGNAL(triggered()), this, SLOT(on_actionBusy_triggered()));
    connect(m_invisibleAction, SIGNAL(triggered()), this, SLOT(on_actionInvisible_triggered()));
    connect(m_offlineAction, SIGNAL(triggered()), this, SLOT(on_actionOffline_triggered()));

    m_trayIconMenu->addSeparator();
#ifndef __APPLE__
    m_showAction = qtfix::addMenuItem(m_trayIconMenu, tr("Show"));
    m_hideAction = qtfix::addMenuItem(m_trayIconMenu, tr("Hide"));
    connect(m_showAction, SIGNAL(triggered()), this, SLOT(on_actionShowMainWindow_triggered()));
    connect(m_hideAction, SIGNAL(triggered()), this, SLOT(on_actionHideAll_triggered()));
#endif
    m_quitAction = qtfix::addMenuItem(m_trayIconMenu, tr("&Quit"));
    connect(m_quitAction, SIGNAL(triggered()), this, SLOT(on_actionQuit_triggered()));

    qtfix::fixIconSize(m_trayIconMenu, m_trayIconMenu->font());
    m_trayIcon->setContextMenu(m_trayIconMenu);
    connect(m_trayIconMenu, SIGNAL(aboutToShow()), this, SLOT(onTrayMenuAboutToShow()));
    updateTrayIcon();
  }
}

void Contacts::updateTrayIcon()
{
  if (m_trayIcon) {
    QString qs = SIM_UI_NAME;
    QString nick = SimCore::get()->getMyNick();
    if (m_trayHtml > 1) qs.prepend("<p style='white-space:pre'>");
    if (m_trayHtml >= 0) qs.append(" - ").append(m_trayHtml ? nick.toHtmlEscaped() : nick);
    m_trayIcon->setToolTip(qs);
  }
}

void Contacts::removeTrayIcon()
{
  log_xtra_("ui", "%s\n", __FUNCTION__);
  if (m_trayIcon) {
    m_timerNotifyIcon.stop();
    m_trayIcon->hide();
    delete m_trayIcon;
    delete m_trayIconMenu;
    m_trayIconMenu = 0;
    m_trayIcon = 0;
  }
}
