/*
 *   This file is part of Dianara
 *   Copyright 2012-2019  JanKusanagi JRR <jancoding@gmx.com>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .
 */

#include "mainwindow.h"


MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    this->setWindowTitle(QStringLiteral("Dianara"));
    this->setWindowIcon(QIcon::fromTheme("dianara",
                                         QIcon(":/icon/64x64/dianara.png")));
    this->setMinimumSize(400, 400);

    // Ensure closing a dialog while the main window is hidden won't end the program
    qApp->setQuitOnLastWindowClosed(false);

    QSettings settings;

    firstRun = true;
    prepareDataDirectory(); // This sets this->dataDirectory

    reallyQuitProgram = false;
    trayIconAvailable = false;
    trayCurrentNewCount = 0;
    trayCurrentHLCount = 0;

    qDebug() << "System is:" << QSysInfo::prettyProductName()
                             << QSysInfo::productType();

    qDebug() << "Qt widget style in use:" << qApp->style()->objectName();

    QString currentIconset = QIcon::themeName();
    qDebug() << "System iconset:" << currentIconset;
    qDebug() << "Icon theme search paths:" << QIcon::themeSearchPaths();
    if (currentIconset.isEmpty() || currentIconset == "hicolor")
    {
        qDebug() << ">> No system iconset (or hicolor) configured; "
                    "trying to use Oxygen";
        QIcon::setThemeName("oxygen"); // TMP; FIXME
    }
#if 0 // 1 to test the fallback icons
    QIcon::setThemeSearchPaths(QStringList() << "./");
    QIcon::setThemeName("dianara-nonexistent-theme");
#endif

#if 0 // 1 to test with a specific iconset
    QIcon::setThemeName("breeze");
#endif

    // Network control
    pumpController = new PumpController(this);

    // Global object to connect different classes directly
    globalObject = new GlobalObject(this);
    globalObject->setDataDirectory(this->dataDirectory);


    // Filter checker
    m_filterChecker = new FilterChecker(this);


    ////// GUI

    // User's profile editor, in its own window
    m_profileEditor = new ProfileEditor(pumpController, this);




    //////////////////////////////// Movable side panel, initially on left side
    avatarIconButton = new QPushButton(this);
    avatarIconButton->setIcon(QIcon(QPixmap(":/images/no-avatar.png")
                                    .scaled(64, 64,
                                            Qt::KeepAspectRatio,
                                            Qt::SmoothTransformation)));
    avatarIconButton->setSizePolicy(QSizePolicy::Maximum,
                                    QSizePolicy::Maximum);
    avatarIconButton->setIconSize(QSize(64, 64));
    avatarIconButton->setStyleSheet("QPushButton { border: 4px;   "
                                    "              padding: 4px } "
                                    "QPushButton:hover { border: 4px ridge   "
                                    "                      palette(highlight);"
                                    "                    border-radius: 8px };");
    connect(avatarIconButton, &QAbstractButton::clicked,
            m_profileEditor, &QWidget::show);


    m_fullNameLabel = new QLabel("[ ----- ----- ]", this);
    m_fullNameLabel->setWordWrap(true);
    m_fullNameLabel->setSizePolicy(QSizePolicy::Ignored,
                                   QSizePolicy::Minimum);

    QFont userDetailsFont;
    userDetailsFont.setBold(true);
    userDetailsFont.setItalic(true);
    userDetailsFont.setPointSize(userDetailsFont.pointSize() - 2);

    m_userIdLabel = new QLabel(this);
    m_userIdLabel->setWordWrap(true);
    m_userIdLabel->setSizePolicy(QSizePolicy::Ignored,
                                 QSizePolicy::Minimum);
    m_userIdLabel->setFont(userDetailsFont);

    userDetailsFont.setBold(false);
    userDetailsFont.setItalic(false);

    m_userHometownLabel = new QLabel(this);
    m_userHometownLabel->setWordWrap(true);
    m_userHometownLabel->setSizePolicy(QSizePolicy::Ignored,
                                       QSizePolicy::Minimum);
    m_userHometownLabel->setFont(userDetailsFont);


    userInfoLayout = new QVBoxLayout();
    userInfoLayout->addSpacing(2);
    userInfoLayout->addWidget(m_fullNameLabel);
    userInfoLayout->addWidget(m_userIdLabel);
    userInfoLayout->addWidget(m_userHometownLabel);

    leftTopLayout = new QHBoxLayout();
    leftTopLayout->setContentsMargins(0, 0, 0, 0);
    leftTopLayout->addWidget(avatarIconButton, 0, Qt::AlignLeft);
    leftTopLayout->addSpacing(2);
    leftTopLayout->addLayout(userInfoLayout,   1);


    leftPanel = new QToolBox(this); // Will hold the minor feeds
    leftPanel->setContentsMargins(0, 0, 0, 0);

    //////// Meanwhile feed: inbox/minor
    meanwhileFeed = new MinorFeed(PumpController::MinorFeedMainRequest,
                                  pumpController,
                                  globalObject,
                                  m_filterChecker,
                                  this);
    connect(meanwhileFeed, SIGNAL(newItemsCountChanged(int,int)),
            this, SLOT(setMinorFeedTitle(int,int)));
    connect(meanwhileFeed, SIGNAL(newItemsReceived(PumpController::requestTypes,int,int,int,int)),
            this, SLOT(notifyMinorFeedUpdate(PumpController::requestTypes,int,int,int,int)));
    leftPanel->addItem(meanwhileFeed,
                       QIcon::fromTheme("clock",
                                        QIcon(":/images/feed-clock.png")),
                       QString());
    this->setMinorFeedTitle(0, 0); // Set initial title (0 new, 0 HL)
    leftPanel->setItemToolTip(0,
                              "<b></b>"
                              + tr("Minor activities done by everyone, such "
                                   "as replying to posts"));


    //////// Mentions feed: inbox/direct/minor
    mentionsFeed = new MinorFeed(PumpController::MinorFeedDirectRequest,
                                 pumpController,
                                 globalObject,
                                 m_filterChecker,
                                 this);
    connect(mentionsFeed, SIGNAL(newItemsCountChanged(int,int)),
            this, SLOT(setMentionsFeedTitle(int,int)));
    connect(mentionsFeed, SIGNAL(newItemsReceived(PumpController::requestTypes,int,int,int,int)),
            this, SLOT(notifyMinorFeedUpdate(PumpController::requestTypes,int,int,int,int)));
    leftPanel->addItem(mentionsFeed,
                       QIcon::fromTheme("mail-folder-inbox",
                                        QIcon(":/images/feed-inbox.png")),
                       QString());
    this->setMentionsFeedTitle(0, 0); // Set initial title, with 0 new
    leftPanel->setItemToolTip(1, "<b></b>"
                                 + tr("Minor activities addressed to you"));


    //////// Actions feed: feed/minor
    actionsFeed = new MinorFeed(PumpController::MinorFeedActivityRequest,
                                pumpController,
                                globalObject,
                                m_filterChecker,
                                this);
    connect(actionsFeed, SIGNAL(newItemsReceived(PumpController::requestTypes,int,int,int,int)),
            this, SLOT(notifyMinorFeedUpdate(PumpController::requestTypes,int,int,int,int)));
    leftPanel->addItem(actionsFeed,
                       QIcon::fromTheme("mail-folder-outbox",
                                        QIcon(":/images/feed-outbox.png")),
                       PumpController::getFeedNameAndPath(PumpController::MinorFeedActivityRequest)
                                       .first());
    leftPanel->setItemToolTip(2, "<b></b>"
                                 + tr("Minor activities done by you"));



    leftLayout = new QVBoxLayout();
    leftLayout->setSpacing(0); // Minimal spacing
    leftLayout->setContentsMargins(0, 0, 0, 0);
    leftLayout->addLayout(leftTopLayout);    // Avatar + user info
    leftLayout->addSpacing(2);               // Some styles look bad without a minimum space here
    leftLayout->addWidget(leftPanel);        // Meanwhile and other minor feeds

    // Shortcuts to change between the different minor feeds
    this->showMeanwhileFeed = new QAction(this);
    showMeanwhileFeed->setShortcut(QKeySequence("Ctrl+1"));
    connect(showMeanwhileFeed, SIGNAL(triggered()),
            this, SLOT(toggleMeanwhileFeed()));
    this->addAction(showMeanwhileFeed);

    this->showMentionsFeed = new QAction(this);
    showMentionsFeed->setShortcut(QKeySequence("Ctrl+2"));
    connect(showMentionsFeed, SIGNAL(triggered()),
            this, SLOT(toggleMentionsFeed()));
    this->addAction(showMentionsFeed);

    this->showActionsFeed = new QAction(this);
    showActionsFeed->setShortcut(QKeySequence("Ctrl+3"));
    connect(showActionsFeed, SIGNAL(triggered()),
            this, SLOT(toggleActionsFeed()));
    this->addAction(showActionsFeed);


////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////// TMP GROUP MANAGER STUFF
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
#ifdef GROUPSUPPORT
    GroupsManager *TMPGROUPSMANAGER = new GroupsManager(this->pumpController,
                                                        this);

    QPushButton *TMPEDITGROUPSBUTTON = new QPushButton(QIcon::fromTheme("user-group-properties"),
                                                       "*MANAGE GROUPS*",
                                                       this);
    connect(TMPEDITGROUPSBUTTON, &QAbstractButton::clicked,
            TMPGROUPSMANAGER, &QWidget::show);
    this->leftLayout->addWidget(TMPEDITGROUPSBUTTON);
#endif
////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////// TMP GROUP MANAGER STUFF
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

    this->leftSideWidget = new QWidget(this);
    leftSideWidget->setLayout(leftLayout);

    this->sideDockWidget = new QDockWidget(this);
    sideDockWidget->setObjectName("sidePanelDock"); // Needed for saveState()
    sideDockWidget->setWidget(leftSideWidget);
    // Until window state is restored (including docks), ensure a decent default width
    sideDockWidget->setMinimumWidth(leftSideWidget->sizeHint().width() * 1.5);
    this->addDockWidget(Qt::LeftDockWidgetArea, sideDockWidget);


    // Empty widget to remove titlebar, when locking the interface
    this->sideDockTitleWidget = new QWidget(this);

    //////////////////////////////////////////////////////////// Right side
    publisher = new Publisher(pumpController,
                              globalObject,
                              this);

    // Passive notifications widget
    m_bannerNotification = new BannerNotification(this);
    m_bannerNotification->hide();
    connect(m_bannerNotification, &BannerNotification::updateRequested,
            this, &MainWindow::onUpdateRequestViaBanner);
    connect(m_bannerNotification, &BannerNotification::bannerCancelled,
            this, &MainWindow::onUpdateDelayedViaBanner);


    /// START SETTING UP TIMELINES


    // Main timeline //

    mainTimeline = new TimeLine(PumpController::MainTimelineRequest,
                                pumpController,
                                globalObject,
                                m_filterChecker,
                                this);
    mainTimelineScrollArea = new QScrollArea(this);
    mainTimelineScrollArea->setContentsMargins(1, 1, 1, 1);
    mainTimelineScrollArea->setFrameStyle(QFrame::NoFrame);
    mainTimelineScrollArea->setWidget(mainTimeline);  // Make timeline scrollable
    mainTimelineScrollArea->setWidgetResizable(true);
    mainTimelineScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    connect(mainTimeline, SIGNAL(scrollTo(QAbstractSlider::SliderAction)),
            this, SLOT(scrollMainTimelineTo(QAbstractSlider::SliderAction)));
    connect(mainTimeline, SIGNAL(unreadPostsCountChanged(PumpController::requestTypes,int,int,int)),
            this, SLOT(setTimelineTabTitle(PumpController::requestTypes,int,int,int)));

    connect(mainTimeline, SIGNAL(timelineRendered(PumpController::requestTypes,int,int,int,int,int,int,int,QString)),
            this, SLOT(notifyTimelineUpdate(PumpController::requestTypes,int,int,int,int,int,int,int,QString)));

    // To ensure comment composer is visible, by scrolling to it
    connect(mainTimeline, &TimeLine::commentingOnPost,
            this, &MainWindow::scrollMainTimelineToWidget);


    // Direct timeline //

    directTimeline = new TimeLine(PumpController::DirectTimelineRequest,
                                  pumpController,
                                  globalObject,
                                  m_filterChecker,
                                  this);
    directTimelineScrollArea = new QScrollArea(this);
    directTimelineScrollArea->setContentsMargins(1, 1, 1, 1);
    directTimelineScrollArea->setFrameStyle(QFrame::NoFrame);
    directTimelineScrollArea->setWidget(directTimeline);
    directTimelineScrollArea->setWidgetResizable(true);
    directTimelineScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    connect(directTimeline, SIGNAL(scrollTo(QAbstractSlider::SliderAction)),
            this, SLOT(scrollDirectTimelineTo(QAbstractSlider::SliderAction)));
    connect(directTimeline, SIGNAL(unreadPostsCountChanged(PumpController::requestTypes,int,int,int)),
            this, SLOT(setTimelineTabTitle(PumpController::requestTypes,int,int,int)));

    connect(directTimeline, SIGNAL(timelineRendered(PumpController::requestTypes,int,int,int,int,int,int,int,QString)),
            this, SLOT(notifyTimelineUpdate(PumpController::requestTypes,int,int,int,int,int,int,int,QString)));

    // To ensure comment composer is visible
    connect(directTimeline, SIGNAL(commentingOnPost(QWidget*)),
            this, SLOT(scrollDirectTimelineToWidget(QWidget*)));


    // Activity timeline //

    activityTimeline = new TimeLine(PumpController::ActivityTimelineRequest,
                                    pumpController,
                                    globalObject,
                                    m_filterChecker,
                                    this);
    activityTimelineScrollArea = new QScrollArea(this);
    activityTimelineScrollArea->setContentsMargins(1, 1, 1, 1);
    activityTimelineScrollArea->setFrameStyle(QFrame::NoFrame);
    activityTimelineScrollArea->setWidget(activityTimeline);  // Make it scrollable
    activityTimelineScrollArea->setWidgetResizable(true);
    activityTimelineScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    connect(activityTimeline, SIGNAL(scrollTo(QAbstractSlider::SliderAction)),
            this, SLOT(scrollActivityTimelineTo(QAbstractSlider::SliderAction)));
    connect(activityTimeline, SIGNAL(unreadPostsCountChanged(PumpController::requestTypes,int,int,int)),
            this, SLOT(setTimelineTabTitle(PumpController::requestTypes,int,int,int)));

    connect(activityTimeline, SIGNAL(timelineRendered(PumpController::requestTypes,int,int,int,int,int,int,int,QString)),
            this, SLOT(notifyTimelineUpdate(PumpController::requestTypes,int,int,int,int,int,int,int,QString)));

    // To ensure comment composer is visible
    connect(activityTimeline, SIGNAL(commentingOnPost(QWidget*)),
            this, SLOT(scrollActivityTimelineToWidget(QWidget*)));


    // Favorites timeline //

    favoritesTimeline = new TimeLine(PumpController::FavoritesTimelineRequest,
                                     pumpController,
                                     globalObject,
                                     m_filterChecker,
                                     this);
    favoritesTimelineScrollArea = new QScrollArea(this);
    favoritesTimelineScrollArea->setContentsMargins(1, 1, 1, 1);
    favoritesTimelineScrollArea->setFrameStyle(QFrame::NoFrame);
    favoritesTimelineScrollArea->setWidget(favoritesTimeline);
    favoritesTimelineScrollArea->setWidgetResizable(true);
    favoritesTimelineScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    connect(favoritesTimeline, SIGNAL(scrollTo(QAbstractSlider::SliderAction)),
            this, SLOT(scrollFavoritesTimelineTo(QAbstractSlider::SliderAction)));
    connect(favoritesTimeline, SIGNAL(unreadPostsCountChanged(PumpController::requestTypes,int,int,int)),
            this, SLOT(setTimelineTabTitle(PumpController::requestTypes,int,int,int)));

    connect(favoritesTimeline, SIGNAL(timelineRendered(PumpController::requestTypes,int,int,int,int,int,int,int,QString)),
            this, SLOT(notifyTimelineUpdate(PumpController::requestTypes,int,int,int,int,int,int,int,QString)));

    // To ensure comment composer is visible
    connect(favoritesTimeline, SIGNAL(commentingOnPost(QWidget*)),
            this, SLOT(scrollFavoritesTimelineToWidget(QWidget*)));


    /// END SETTING UP TIMELINES


    // The contact list has its own tabs with its own scroll areas
    contactManager = new ContactManager(pumpController,
                                        globalObject,
                                        this);


    tabWidget = new QTabWidget(this);
    /* TODO: Setting this size policy, the publisher could grow much bigger,
     *       and tabWidget could have a bigger stretch factor
     *
     tabWidget->setSizePolicy(QSizePolicy::Preferred,
                             QSizePolicy::Ignored);
     *
     ***/
    tabWidget->addTab(mainTimelineScrollArea,
                      QIcon::fromTheme("view-list-details",
                                       QIcon(":/images/feed-inbox.png")),
                      QString());
    this->setTimelineTabTitle(PumpController::MainTimelineRequest, 0, 0, 0);
    tabWidget->addTab(directTimelineScrollArea,
                      QIcon::fromTheme("mail-message",
                                       QIcon(":/images/feed-inbox.png")),
                      QString());
    this->setTimelineTabTitle(PumpController::DirectTimelineRequest, 0, 0, 0);
    tabWidget->addTab(activityTimelineScrollArea,
                      QIcon::fromTheme("user-home",
                                       QIcon(":/images/feed-outbox.png")),
                      QString());
    this->setTimelineTabTitle(PumpController::ActivityTimelineRequest, 0, 0, 0);
    tabWidget->addTab(favoritesTimelineScrollArea,
                      QIcon::fromTheme("folder-favorites",
                                       QIcon(":/images/button-like.png")),
                      QString());
    this->setTimelineTabTitle(PumpController::FavoritesTimelineRequest, 0, 0, 0);
    tabWidget->addTab(contactManager,
                      QIcon::fromTheme("system-users",
                                       QIcon(":/images/button-users.png")),
                      tr("&Contacts"));
    tabWidget->setTabToolTip(4,
                             "<b></b>"  // HTMLized for wordwrap
                             + tr("The people you follow, the ones who "
                                  "follow you, and your person lists"));
    connect(tabWidget, &QTabWidget::currentChanged,
            this, &MainWindow::setTitleAndTrayInfo);



    // Widget for the main area, on the right by default
    m_rightSideWidget = new QWidget(this);

    rightLayout = new QVBoxLayout();
    rightLayout->setSpacing(0);
    rightLayout->setContentsMargins(0, 1, 1, 1);
    rightLayout->addWidget(publisher, 8); // Big stretch for full mode
    rightLayout->addWidget(tabWidget, 1);
    rightLayout->addWidget(m_bannerNotification);
    m_rightSideWidget->setLayout(rightLayout);

    this->setCentralWidget(m_rightSideWidget);


    // FreeDesktop.org notifications handler
    m_fdNotifications = new FDNotifications(this);
    connect(m_fdNotifications, &FDNotifications::showFallbackNotification,
            this, &MainWindow::showTrayFallbackMessage);


    // D-Bus Interface, for remote control
#ifdef QT_DBUS_LIB
    m_dbusInterface = new DBusInterface(this);

    QDBusConnection bus = QDBusConnection::sessionBus();
    bus.registerObject("/Dianara",
                       m_dbusInterface,
                       QDBusConnection::ExportAllSlots);
    bus.registerService("org.nongnu." + qApp->applicationName().toLower());

    bool connectToNotifyActions = bus.connect("",
                                              "/org/freedesktop/Notifications",
                                              "org.freedesktop.Notifications",
                                              "ActionInvoked",
                                              this,
                                              SLOT(onNotificationAction(uint,QString)));
    qDebug() << "Connection to org.freedesktop.Notifications::ActionInvoked:"
             << connectToNotifyActions;
#endif


    // Timeline updates timer
    updateTimer = new QTimer(this); // Interval is set from loadSettings()
    connect(updateTimer, SIGNAL(timeout()),
            this, SLOT(onTimelineAutoupdate()));
    updateTimer->start();

    // Timestamps refresh timer
    timestampsTimer = new QTimer(this);
    timestampsTimer->setInterval(60000); // 60 sec
    connect(timestampsTimer, SIGNAL(timeout()),
            this, SLOT(refreshAllTimestamps()));
    timestampsTimer->start();

    // Delayed timeline resize timer
    delayedResizeTimer = new QTimer(this);
    delayedResizeTimer->setSingleShot(true);
    connect(delayedResizeTimer, SIGNAL(timeout()),
            this, SLOT(adjustTimelineSizes()));


    favoritesReloadTimer = new QTimer(this);
    favoritesReloadTimer->setSingleShot(true);
    favoritesReloadTimer->setInterval(300000);      // 5 minutes
    connect(favoritesReloadTimer, SIGNAL(timeout()),
            this, SLOT(updateFavoritesTimeline()));

    /*
     * User-did-something timer
     *
     * Every time the user does something minor, such as commenting or
     * following a contact, the timer will be restarted.
     *
     * Some time after the user has stopped doing things, the timer
     * will update the Actions feed.
     *
     */
    userDidSomethingTimer = new QTimer(this);
    userDidSomethingTimer->setSingleShot(true);
    userDidSomethingTimer->setInterval(300000);      // 5 minutes
    connect(userDidSomethingTimer, SIGNAL(timeout()),
            this, SLOT(updateActionsFeed()));
    connect(pumpController, SIGNAL(userDidSomething()),
            userDidSomethingTimer, SLOT(start())); // Restart timer every time


    delayedScrollTimer = new QTimer(this);
    delayedScrollTimer->setSingleShot(true);
    connect(delayedScrollTimer, SIGNAL(timeout()),
            this, SLOT(scrollToNewPosts()));


    ////////////////// Load configuration from disk
    loadSettings();



    //// External widgets which live in their own windows

    // Account wizard
    m_accountDialog = new AccountDialog(pumpController, this);
    connect(m_accountDialog, &AccountDialog::userIdChanged,
            this, &MainWindow::updateUserId);


    // Configuration dialog
    m_configDialog = new ConfigDialog(this->globalObject,
                                      this->dataDirectory,
                                      this->updateInterval,
                                      m_tabsPosition,
                                      m_tabsMovable,
                                      m_fdNotifications,
                                      this);
    connect(m_configDialog, &ConfigDialog::configurationChanged,
            this, &MainWindow::updateConfigSettings);


    // Filter editor
    m_filterEditor = new FilterEditor(m_filterChecker, this);
    connect(m_configDialog, &ConfigDialog::filterEditorRequested,
            m_filterEditor, &QWidget::show);


    // Log viewer; Created without a parent, so it can be a truly independent window
    m_logViewer = new LogViewer(nullptr); // under Plasma 5, for instance
    // NOTE: using nullptr in older compilers requires -std=c++0x, handled via .pro file

    connect(pumpController, &PumpController::logMessage,
            m_logViewer, &LogViewer::addToLog);
    connect(globalObject, &GlobalObject::messageForLog,
            m_logViewer, &LogViewer::addToLog);


    // Help widget; "Getting started", etc.
    m_helpWidget = new HelpWidget(nullptr); // Without a parent, same logic as logViewer


    ///
    ///////////////// Connections between Meanwhile feed and the timelines ///
    ///

    /// Object updated
    connect(meanwhileFeed, SIGNAL(objectUpdated(ASObject*)),
            mainTimeline, SLOT(updatePostsFromMinorFeed(ASObject*)));
    connect(meanwhileFeed, SIGNAL(objectUpdated(ASObject*)),
            directTimeline, SLOT(updatePostsFromMinorFeed(ASObject*)));
    connect(meanwhileFeed, SIGNAL(objectUpdated(ASObject*)),
            activityTimeline, SLOT(updatePostsFromMinorFeed(ASObject*)));

    // Object liked
    connect(meanwhileFeed, SIGNAL(objectLiked(QString,QString,QString,QString,QString)),
            mainTimeline, SLOT(addLikesFromMinorFeed(QString,QString,QString,QString,QString)));
    connect(meanwhileFeed, SIGNAL(objectLiked(QString,QString,QString,QString,QString)),
            directTimeline, SLOT(addLikesFromMinorFeed(QString,QString,QString,QString,QString)));
    connect(meanwhileFeed, SIGNAL(objectLiked(QString,QString,QString,QString,QString)),
            activityTimeline, SLOT(addLikesFromMinorFeed(QString,QString,QString,QString,QString)));

    // Object unliked
    connect(meanwhileFeed, SIGNAL(objectUnliked(QString,QString,QString)),
            mainTimeline, SLOT(removeLikesFromMinorFeed(QString,QString,QString)));
    connect(meanwhileFeed, SIGNAL(objectUnliked(QString,QString,QString)),
            directTimeline, SLOT(removeLikesFromMinorFeed(QString,QString,QString)));
    connect(meanwhileFeed, SIGNAL(objectUnliked(QString,QString,QString)),
            activityTimeline, SLOT(removeLikesFromMinorFeed(QString,QString,QString)));

    // Object got a new reply
    connect(meanwhileFeed, SIGNAL(objectReplyAdded(ASObject*)),
            mainTimeline, SLOT(addReplyFromMinorFeed(ASObject*)));
    connect(meanwhileFeed, SIGNAL(objectReplyAdded(ASObject*)),
            directTimeline, SLOT(addReplyFromMinorFeed(ASObject*)));
    connect(meanwhileFeed, SIGNAL(objectReplyAdded(ASObject*)),
            activityTimeline, SLOT(addReplyFromMinorFeed(ASObject*)));

    /// Object deleted
    connect(meanwhileFeed, SIGNAL(objectDeleted(ASObject*)),
            mainTimeline, SLOT(setPostsDeletedFromMinorFeed(ASObject*)));
    connect(meanwhileFeed, SIGNAL(objectDeleted(ASObject*)),
            directTimeline, SLOT(setPostsDeletedFromMinorFeed(ASObject*)));
    connect(meanwhileFeed, SIGNAL(objectDeleted(ASObject*)),
            activityTimeline, SLOT(setPostsDeletedFromMinorFeed(ASObject*)));

    // TODO: sync other timelines/feeds, too -- FIXME


    ///
    //////////////////////////////// Connections for PumpController //////////
    ///

    connect(pumpController, SIGNAL(profileReceived(QString,QString,QString,QString,QString)),
            this, SLOT(updateProfileData(QString,QString,QString,QString,QString)));

    connect(pumpController, SIGNAL(avatarPictureReceived(QByteArray,QString)),
            this, SLOT(storeAvatar(QByteArray,QString)));
    connect(pumpController, SIGNAL(imageReceived(QByteArray,QString)),
            this, SLOT(storeImage(QByteArray,QString)));

    connect(pumpController, SIGNAL(authorizationStatusChanged(bool)),
            this, SLOT(toggleWidgetsByAuthorization(bool)));
    connect(pumpController, SIGNAL(authorizationFailed(QString,QString)),
            this, SLOT(showAuthError(QString,QString)));

    connect(pumpController, SIGNAL(initializationCompleted()),
            this, SLOT(onInitializationComplete()));


    // After receiving timeline contents, update corresponding timeline
    connect(pumpController, SIGNAL(mainTimelineReceived(QVariantList,QString,QString,int)),
            mainTimeline, SLOT(setTimeLineContents(QVariantList,QString,QString,int)));
    connect(pumpController, SIGNAL(directTimelineReceived(QVariantList,QString,QString,int)),
            directTimeline, SLOT(setTimeLineContents(QVariantList,QString,QString,int)));
    connect(pumpController, SIGNAL(activityTimelineReceived(QVariantList,QString,QString,int)),
            activityTimeline, SLOT(setTimeLineContents(QVariantList,QString,QString,int)));
    connect(pumpController, SIGNAL(favoritesTimelineReceived(QVariantList,QString,QString,int)),
            favoritesTimeline, SLOT(setTimeLineContents(QVariantList,QString,QString,int)));

    // After receiving a main minor feed (Meanwhile, Mentions, Actions), update them
    connect(pumpController, SIGNAL(minorFeedMainReceived(QVariantList,QString,QString,int)),
            meanwhileFeed, SLOT(setFeedContents(QVariantList,QString,QString,int)));
    connect(pumpController, SIGNAL(minorFeedDirectReceived(QVariantList,QString,QString,int)),
            mentionsFeed, SLOT(setFeedContents(QVariantList,QString,QString,int)));
    connect(pumpController, SIGNAL(minorFeedActivityReceived(QVariantList,QString,QString,int)),
            actionsFeed, SLOT(setFeedContents(QVariantList,QString,QString,int)));



    // After sucessful posting, request updated timeline, with your post included
    connect(pumpController, SIGNAL(postPublished()),
            this, SLOT(updateMainActivityMinorTimelines()));


    // After successful liking, update likes count
    connect(pumpController, SIGNAL(likesReceived(QVariantList,QString)),
            mainTimeline, SLOT(setLikesInPost(QVariantList,QString)));
    connect(pumpController, SIGNAL(likesReceived(QVariantList,QString)),
            directTimeline, SLOT(setLikesInPost(QVariantList,QString)));
    connect(pumpController, SIGNAL(likesReceived(QVariantList,QString)),
            activityTimeline, SLOT(setLikesInPost(QVariantList,QString)));
    // We don't update likes count in favorites timeline,
    // since it still doesn't know about this post

    // Instead, schedule to reload favorites timeline
    connect(pumpController, SIGNAL(likeSet()),
            favoritesReloadTimer, SLOT(start()));


    // After commenting successfully, refresh list of comments in that post
    connect(pumpController, SIGNAL(commentsReceived(QVariantList,QString)),
            mainTimeline, SLOT(setCommentsInPost(QVariantList,QString)));
    connect(pumpController, SIGNAL(commentsReceived(QVariantList,QString)),
            directTimeline, SLOT(setCommentsInPost(QVariantList,QString)));
    connect(pumpController, SIGNAL(commentsReceived(QVariantList,QString)),
            activityTimeline, SLOT(setCommentsInPost(QVariantList,QString)));
    connect(pumpController, SIGNAL(commentsReceived(QVariantList,QString)),
            favoritesTimeline, SLOT(setCommentsInPost(QVariantList,QString)));


    // After successful sharing....

    // TODO***


    // Show notifications for events sent from pumpController
    connect(pumpController, SIGNAL(showErrorNotification(QString)),
            this, SLOT(showErrorPopup(QString)));

    // Update statusBar message from pumpController's infos
    connect(pumpController, SIGNAL(currentJobChanged(QString)),
            this, SLOT(setStatusBarMessage(QString)));
    connect(pumpController, SIGNAL(transientStatusBarMessage(QString)),
            this, SLOT(setTransientStatusMessage(QString)));

    // Update statusBar also from globalObject's requests
    connect(globalObject, SIGNAL(messageForStatusBar(QString)),
            this, SLOT(setStatusBarMessage(QString)));

    // Show a user's timeline when requested through GlobalObject
    connect(globalObject, SIGNAL(userTimelineRequested(QString,QString,QIcon,QString)),
            this, SLOT(showUserTimeline(QString,QString,QIcon,QString)));


    // Add menus and toolbar
    createMenus();
    createToolbar();

    loadMainWindowConfig();

    // Info label in the right side of the menu bar
    m_menuInfoLabel = new QLabel(tr("Press F1 for help"), // Just initial text
                                 this);
    m_menuInfoLabel->setFont(userDetailsFont);
    m_menuInfoLabel->setAlignment(Qt::AlignRight);

    m_menuInfoLayout = new QHBoxLayout();
    m_menuInfoLayout->addWidget(m_menuInfoLabel);
    m_menuInfoWidget = new QWidget(this);
    m_menuInfoWidget->setLayout(m_menuInfoLayout);
    this->menuBar()->setCornerWidget(m_menuInfoWidget);
    // If menu is being displayed outside the application, hide this, too
    m_menuInfoWidget->setVisible(!this->menuBar()->isNativeMenuBar());

    // StatusBar stuff
    this->createStatusbarWidgets();
    this->statusBar()->showMessage(tr("Initializing..."));

    // Add the system tray icon
    createTrayIcon();


    settings.beginGroup("MainWindow");

    // Now set the "view side panel" checkable menu to its saved state
    // This will also trigger the action to hide/show it
    viewSidePanel->setChecked(settings.value("viewSidePanel", true).toBool());

    // Check view > toolbar if needed; HIDDEN by default
    viewToolbar->setChecked(settings.value("viewToolbar", false).toBool());

    // Set the "view status bar" checkable menu to its saved state
    viewStatusBar->setChecked(settings.value("viewStatusBar", true).toBool());

    // Lock or unlock the panels, locked by default
    viewLockPanels->setChecked(settings.value("viewLockPanels", true).toBool());

    settings.endGroup();


    initializationComplete = false;

    m_logViewer->addToLog(tr("%1 started.",
                             "1=program name and version")
                          .arg("Dianara v" + qApp->applicationVersion()));
    m_logViewer->addToLog(tr("Running with Qt v%1 on %2.",
                             "1=Qt version, 2=OS name")
                          .arg(qVersion()
                               + QStringLiteral(" (")
                               + qApp->platformName()
                               + QStringLiteral(")"),
                               QSysInfo::prettyProductName()));


#ifdef HAVE_SONNET_SPELLCHECKER
    // Check for Sonnet plugins availability
    Sonnet::Speller speller;
    QStringList sonnetBackends = speller.availableBackends();
    if (sonnetBackends.isEmpty())
    {
        m_logViewer->addToLog(tr("Spell checking has been disabled because "
                                 "there are no Sonnet backends available!"));
    }
    else
    {
        m_logViewer->addToLog(tr("Spell checking available for %1 languages, "
                                 "using %2 by default.",
                                 "%1 is the number of languages, "
                                 "%2 is a language code")
                              .arg(speller.availableDictionaries().count())
                              .arg("'" + speller.defaultLanguage() + "'"));
    }

    // Create the Sonnet config dialog that can be accessed from the Format menu
    m_spellConfigDialog = new Sonnet::ConfigDialog(this);
    m_spellConfigDialog->setWindowTitle(m_spellConfigDialog->windowTitle()
                                        + QStringLiteral(" - Dianara"));
    connect(globalObject, &GlobalObject::spellConfigRequested,
            m_spellConfigDialog, &Sonnet::ConfigDialog::show);
#endif

    m_statusAccountButtonUsed = false;

    // If user ID is defined, set PumpController in motion
    if (!m_userId.isEmpty() && pumpController->currentlyAuthorized())
    {
        pumpController->setUserCredentials(m_userId);
        m_fdNotifications->setCurrentUserId(m_userId);
        // getUserProfile() will be called from setUserCredentials()

        this->initializationProgressBar->show();
    }
    else // Otherwise, just say so in the statusbar and offer a button there
    {
        const QString message = tr("Your account is not configured yet.");
        this->setStatusBarMessage(message);
        m_logViewer->addToLog(message);

        m_statusAccountButton = new QPushButton(QIcon::fromTheme("dialog-password",
                                                                 QIcon(":/images/button-password.png")),
                                                tr("Click here to configure "
                                                   "your account"),
                                                this);
        connect(m_statusAccountButton, &QAbstractButton::clicked,
                settingsAccount, &QAction::trigger);
        this->statusBar()->insertPermanentWidget(0, m_statusAccountButton);
        m_statusAccountButtonUsed = true;
    }

    // Post-init timer
    postInitTimer = new QTimer(this);
    postInitTimer->setSingleShot(true);
    connect(postInitTimer, &QTimer::timeout,
            this, &MainWindow::postInit);
    postInitTimer->start(1000);


    // Handle session manager asking to quit
    connect(qApp, SIGNAL(commitDataRequest(QSessionManager&)),
            this, SLOT(onSessionManagerQuitRequest(QSessionManager&)));


    qDebug() << "MainWindow created";
}



MainWindow::~MainWindow()
{
    qDebug() << "MainWindow destroyed";
}




/*
 * Prepare the data directory. Create if necessary
 *
 */
void MainWindow::prepareDataDirectory()
{
    dataDirectory = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first();
    qDebug() << "Data directory:" << this->dataDirectory;

    QDir dataDir;

    // Base directory
    if (!dataDir.exists(dataDirectory))
    {
        qDebug() << "Creating data directory";
        if (dataDir.mkpath(dataDirectory))
        {
            qDebug() << "Data directory created";
        }
        else
        {
            qDebug() << "Error creating data directory!";
        }
    }

    if (!dataDir.exists(dataDirectory + "/images"))
    {
        qDebug() << "Creating images directory";
        if (dataDir.mkpath(dataDirectory + "/images"))
        {
            qDebug() << "Images directory created";
        }
        else
        {
            qDebug() << "Error creating images directory!";
        }
    }

    if (!dataDir.exists(dataDirectory + "/audios"))
    {
        qDebug() << "Creating audios directory";
        if (dataDir.mkpath(dataDirectory + "/audios"))
        {
            qDebug() << "Audios directory created";
        }
        else
        {
            qDebug() << "Error creating audios directory!";
        }
    }

    if (!dataDir.exists(dataDirectory + "/videos"))
    {
        qDebug() << "Creating videos directory";
        if (dataDir.mkpath(dataDirectory + "/videos"))
        {
            qDebug() << "Videos directory created";
        }
        else
        {
            qDebug() << "Error creating videos directory!";
        }
    }

    if (!dataDir.exists(dataDirectory + "/drafts"))
    {
        qDebug() << "Creating drafts directory";
        if (dataDir.mkpath(dataDirectory + "/drafts"))
        {
            qDebug() << "Drafts directory created";
        }
        else
        {
            qDebug() << "Error creating drafts directory!";
        }
    }

    // Avatars directory
    if (!dataDir.exists(dataDirectory + "/avatars"))
    {
        qDebug() << "Creating avatars directory";
        if (dataDir.mkpath(dataDirectory + "/avatars"))
        {
            qDebug() << "Avatars directory created";
        }
        else
        {
            qDebug() << "Error creating avatars directory!";
        }
    }

}



/*
 * Populate the menus
 *
 */
void MainWindow::createMenus()
{
    // Session
    sessionMenu = new QMenu(tr("&Session"), this);

    QString feedName;
    QString optionString = tr("Update %1");

    // Main timeline
    feedName = PumpController::getFeedNameAndPath(PumpController::MainTimelineRequest).first();
    sessionUpdateMainTimeline = new QAction(QIcon::fromTheme("view-refresh",
                                                             QIcon(":/images/menu-refresh.png")),
                                            optionString.arg(feedName),
                                            this);
    sessionUpdateMainTimeline->setShortcut(QKeySequence(Qt::Key_F5));
    sessionUpdateMainTimeline->setDisabled(true); // Disabled until authorization checked
    connect(sessionUpdateMainTimeline, SIGNAL(triggered()),
            mainTimeline, SLOT(goToFirstPage()));
    sessionMenu->addAction(sessionUpdateMainTimeline);

    // Direct messages
    feedName = PumpController::getFeedNameAndPath(PumpController::DirectTimelineRequest).first();
    sessionUpdateDirectTimeline = new QAction(QIcon::fromTheme("view-refresh",
                                                               QIcon(":/images/menu-refresh.png")),
                                              optionString.arg(feedName),
                                              this);
    sessionUpdateDirectTimeline->setShortcut(QKeySequence(Qt::Key_F6));
    sessionUpdateDirectTimeline->setDisabled(true);
    connect(sessionUpdateDirectTimeline, SIGNAL(triggered()),
            directTimeline, SLOT(goToFirstPage()));
    sessionMenu->addAction(sessionUpdateDirectTimeline);

    // Activity
    feedName = PumpController::getFeedNameAndPath(PumpController::ActivityTimelineRequest).first();
    sessionUpdateActivityTimeline = new QAction(QIcon::fromTheme("view-refresh",
                                                                 QIcon(":/images/menu-refresh.png")),
                                                optionString.arg(feedName),
                                                this);
    sessionUpdateActivityTimeline->setShortcut(QKeySequence(Qt::Key_F7));
    sessionUpdateActivityTimeline->setDisabled(true);
    connect(sessionUpdateActivityTimeline, SIGNAL(triggered()),
            activityTimeline, SLOT(goToFirstPage()));
    sessionMenu->addAction(sessionUpdateActivityTimeline);

    // Favorites
    feedName = PumpController::getFeedNameAndPath(PumpController::FavoritesTimelineRequest).first();
    sessionUpdateFavoritesTimeline = new QAction(QIcon::fromTheme("view-refresh",
                                                                  QIcon(":/images/menu-refresh.png")),
                                                 optionString.arg(feedName),
                                                 this);
    sessionUpdateFavoritesTimeline->setShortcut(QKeySequence(Qt::Key_F8));
    sessionUpdateFavoritesTimeline->setDisabled(true);
    connect(sessionUpdateFavoritesTimeline, SIGNAL(triggered()),
            favoritesTimeline, SLOT(goToFirstPage()));
    sessionMenu->addAction(sessionUpdateFavoritesTimeline);

    sessionMenu->addSeparator(); // ---------- minor feeds

    // Meanwhile
    feedName = PumpController::getFeedNameAndPath(PumpController::MinorFeedMainRequest).first();
    sessionUpdateMinorFeedMain = new QAction(QIcon::fromTheme("view-refresh",
                                                          QIcon(":/images/menu-refresh.png")),
                                             optionString.arg(feedName),
                                             this);
    sessionUpdateMinorFeedMain->setShortcut(QKeySequence(Qt::Key_F2));
    sessionUpdateMinorFeedMain->setDisabled(true);
    connect(sessionUpdateMinorFeedMain, SIGNAL(triggered()),
            meanwhileFeed, SLOT(updateFeed()));
    sessionMenu->addAction(sessionUpdateMinorFeedMain);

    // Mentions
    feedName = PumpController::getFeedNameAndPath(PumpController::MinorFeedDirectRequest).first();
    sessionUpdateMinorFeedDirect = new QAction(QIcon::fromTheme("view-refresh",
                                                                QIcon(":/images/menu-refresh.png")),
                                               optionString.arg(feedName),
                                               this);
    sessionUpdateMinorFeedDirect->setShortcut(QKeySequence(Qt::Key_F3));
    sessionUpdateMinorFeedDirect->setDisabled(true);
    connect(sessionUpdateMinorFeedDirect, SIGNAL(triggered()),
            mentionsFeed, SLOT(updateFeed()));
    sessionMenu->addAction(sessionUpdateMinorFeedDirect);

    // Actions
    feedName = PumpController::getFeedNameAndPath(PumpController::MinorFeedActivityRequest).first();
    sessionUpdateMinorFeedActivity = new QAction(QIcon::fromTheme("view-refresh",
                                                                  QIcon(":/images/menu-refresh.png")),
                                                 optionString.arg(feedName),
                                                 this);
    sessionUpdateMinorFeedActivity->setShortcut(QKeySequence(Qt::Key_F4));
    sessionUpdateMinorFeedActivity->setDisabled(true);
    connect(sessionUpdateMinorFeedActivity, SIGNAL(triggered()),
            this, SLOT(updateActionsFeed())); // Go through this slot to stop the timer
    sessionMenu->addAction(sessionUpdateMinorFeedActivity);


    //////////////////////////////////////////////////////////
    sessionMenu->addSeparator(); // ------


    sessionAutoUpdates = new QAction(QIcon::fromTheme("clock",
                                                      QIcon(":/images/feed-clock.png")),
                                     tr("Auto-update &Timelines"),
                                     this);
    sessionAutoUpdates->setCheckable(true);
    sessionAutoUpdates->setChecked(true);
    sessionAutoUpdates->setDisabled(true); // until initialization is complete
    connect(sessionAutoUpdates, SIGNAL(toggled(bool)),
            this, SLOT(toggleAutoUpdates(bool)));
    sessionMenu->addAction(sessionAutoUpdates);

    sessionMarkAllAsRead = new QAction(QIcon::fromTheme("mail-mark-read"),
                                       tr("Mark All as Read"),
                                       this);
    sessionMarkAllAsRead->setShortcut(QKeySequence("Ctrl+R"));
    connect(sessionMarkAllAsRead, SIGNAL(triggered()),
            this, SLOT(markAllAsRead()));
    sessionMenu->addAction(sessionMarkAllAsRead);    

    sessionMenu->addSeparator();

    sessionPostNote = new QAction(QIcon::fromTheme("document-edit",
                                                   QIcon(":/images/button-edit.png")),
                                  tr("&Post a Note"),
                                  this);
    sessionPostNote->setShortcut(QKeySequence("Ctrl+N"));
    connect(sessionPostNote, SIGNAL(triggered()),
            this, SLOT(startPost())); // Will show window if needed
    sessionMenu->addAction(sessionPostNote);

    sessionMenu->addSeparator();

    sessionQuit = new QAction(QIcon::fromTheme("application-exit",
                                               QIcon(":/images/button-delete.png")),
                              tr("&Quit"),
                              this);
    sessionQuit->setShortcut(QKeySequence("Ctrl+Q"));
    connect(sessionQuit, SIGNAL(triggered()),
            this, SLOT(quitProgram()));
    sessionMenu->addAction(sessionQuit);

    this->menuBar()->addMenu(sessionMenu);


    // View
    viewMenu = new QMenu(tr("&View"), this);

    viewSidePanel = new QAction(QIcon::fromTheme("view-sidetree"),
                                tr("Side Panel"),
                                this);
    connect(viewSidePanel, SIGNAL(toggled(bool)),
            this, SLOT(toggleSidePanel(bool)));
    // Use View > Side Panel menu text as title for the dock when unlocked
    this->sideDockWidget->setWindowTitle(viewSidePanel->text());
    viewSidePanel->setCheckable(true);
    viewSidePanel->setChecked(true);
    viewSidePanel->setShortcut(Qt::Key_F9);

    viewMenu->addAction(viewSidePanel);


    viewToolbar = new QAction(QIcon::fromTheme("configure-toolbars"),
                              tr("&Toolbar"),
                              this);
    connect(viewToolbar, SIGNAL(toggled(bool)),
            this, SLOT(toggleToolbar(bool)));
    viewToolbar->setCheckable(true);

    viewMenu->addAction(viewToolbar);


    viewStatusBar = new QAction(QIcon::fromTheme("configure-toolbars"),
                                tr("Status &Bar"),
                                this);
    connect(viewStatusBar, SIGNAL(toggled(bool)),
            this, SLOT(toggleStatusBar(bool)));
    viewStatusBar->setCheckable(true);
    viewStatusBar->setChecked(true);

    viewMenu->addAction(viewStatusBar);

    viewMenu->addSeparator();


    viewFullscreenAction = new QAction(QIcon::fromTheme("view-fullscreen"),
                                       tr("Full &Screen"),
                                       this);
    connect(viewFullscreenAction, SIGNAL(toggled(bool)),
            this, SLOT(toggleFullscreen(bool)));
    viewFullscreenAction->setCheckable(true);
    viewFullscreenAction->setChecked(false);
    viewFullscreenAction->setShortcut(Qt::Key_F11);

    viewMenu->addAction(viewFullscreenAction);

    viewLogAction = new QAction(QIcon::fromTheme("text-x-log",
                                                 QIcon(":/images/log.png")),
                                tr("&Log"),
                                this);
    connect(viewLogAction, &QAction::triggered,
            m_logViewer, &LogViewer::toggleVisibility);
    viewLogAction->setShortcut(Qt::Key_F12);

    viewMenu->addAction(viewLogAction);


    viewMenu->addSeparator(); // -------------------


    viewLockPanels = new QAction(QIcon::fromTheme("object-locked"),
                                 tr("Locked Panels and Toolbars"),
                                 this);
    connect(viewLockPanels, SIGNAL(toggled(bool)),
            this, SLOT(toggleLockedPanels(bool)));
    viewLockPanels->setCheckable(true);
    // Can't set it checked at it at this point, since that also locks the toolbar

    viewMenu->addAction(viewLockPanels);


    this->menuBar()->addMenu(viewMenu);


    // Settings
    settingsMenu = new QMenu(tr("S&ettings"), this);

    settingsEditProfile = new QAction(QIcon::fromTheme("user-properties",
                                                       QIcon(":/images/no-avatar.png")),
                                      tr("Edit &Profile"),
                                      this);
    settingsEditProfile->setShortcut(QKeySequence("Ctrl+Shift+P"));
    connect(settingsEditProfile, &QAction::triggered,
            m_profileEditor, &QWidget::show);
    settingsMenu->addAction(settingsEditProfile);

    settingsAccount = new QAction(QIcon::fromTheme("dialog-password",
                                                   QIcon(":/images/button-password.png")),
                                  tr("&Account"),
                                  this);
    settingsAccount->setShortcut(QKeySequence("Ctrl+Shift+A"));
    connect(settingsAccount, &QAction::triggered,
            m_accountDialog, &QWidget::show);
    settingsMenu->addAction(settingsAccount);

    settingsMenu->addSeparator();

    settingsFilters = new QAction(QIcon::fromTheme("view-filter",
                                                   QIcon(":/images/button-filter.png")),
                                  tr("&Filters and Highlighting"),
                                  this);
    settingsFilters->setShortcut(QKeySequence("Ctrl+Shift+F"));
    connect(settingsFilters, &QAction::triggered,
            m_filterEditor, &QWidget::show);
    settingsMenu->addAction(settingsFilters);

    settingsConfigure = new QAction(QIcon::fromTheme("configure",
                                                     QIcon(":/images/button-configure.png")),
                                    tr("&Configure Dianara"),
                                    this);
    settingsConfigure->setShortcut(QKeySequence("Ctrl+Shift+S"));
    connect(settingsConfigure, &QAction::triggered,
            m_configDialog, &QWidget::show);
    settingsMenu->addAction(settingsConfigure);
    this->menuBar()->addMenu(settingsMenu);

    this->menuBar()->addSeparator();


    // Help
    helpMenu = new QMenu(tr("&Help"), this);

    helpBasicHelp = new QAction(QIcon::fromTheme("help-browser",
                                                 QIcon(":/icon/64x64/dianara.png")),
                                tr("Basic &Help"),
                                this);
    helpBasicHelp->setShortcut(Qt::Key_F1);
    connect(helpBasicHelp, &QAction::triggered,
            m_helpWidget, &QWidget::show);
    helpMenu->addAction(helpBasicHelp);

    helpShowWizard = new QAction(QIcon::fromTheme("tools-wizard",
                                                  QIcon(":/images/button-online.png")),
                                 tr("Show Welcome Wizard"),
                                 this);
    connect(helpShowWizard, SIGNAL(triggered()),
            this, SLOT(showFirstRunWizard()));
    helpMenu->addAction(helpShowWizard);


    helpMenu->addSeparator(); // ---


    helpVisitWebsite = new QAction(QIcon::fromTheme("internet-web-browser",
                                                    QIcon(":/images/button-download.png")),
                                   tr("Visit &Website"),
                                   this);
    connect(helpVisitWebsite, SIGNAL(triggered()),
            this, SLOT(visitWebSite()));
    helpMenu->addAction(helpVisitWebsite);

    helpVisitBugTracker = new QAction(QIcon::fromTheme("tools-report-bug",
                                                       QIcon(":/images/button-edit.png")),
                                      tr("Report a &Bug"), // "or Suggest a Feature"?
                                      this);
    connect(helpVisitBugTracker, SIGNAL(triggered()),
            this, SLOT(visitBugTracker()));
    helpMenu->addAction(helpVisitBugTracker);


    helpMenu->addSeparator(); // ---


    helpVisitPumpGuide = new QAction(QIcon::fromTheme("help-contents",
                                                      QIcon(":/images/button-download.png")),
                                     tr("Pump.io User &Guide"),
                                     this);
    connect(helpVisitPumpGuide, SIGNAL(triggered()),
            this, SLOT(visitPumpGuide()));
    helpMenu->addAction(helpVisitPumpGuide);

    helpVisitPumpTips = new QAction(QIcon::fromTheme("help-hint",
                                                     QIcon(":/images/button-download.png")),
                                    tr("Some Pump.io &Tips"),
                                    this);
    connect(helpVisitPumpTips, SIGNAL(triggered()),
            this, SLOT(visitTips()));
    helpMenu->addAction(helpVisitPumpTips);

    helpVisitPumpUserList = new QAction(QIcon::fromTheme("system-users",
                                                         QIcon(":/images/button-users.png")),
                                        tr("List of Some Pump.io &Users"),
                                        this);
    connect(helpVisitPumpUserList, SIGNAL(triggered()),
            this, SLOT(visitUserList()));
    helpMenu->addAction(helpVisitPumpUserList);

    helpVisitPumpStatus = new QAction(QIcon::fromTheme("network-server",
                                                       QIcon(":/images/button-online.png")),
                                      tr("Pump.io &Network Status Website"),
                                      this);
    connect(helpVisitPumpStatus, SIGNAL(triggered()),
            this, SLOT(visitPumpStatus()));
    helpMenu->addAction(helpVisitPumpStatus);


    helpMenu->addSeparator(); // ---


    helpAbout = new QAction(QIcon(":/icon/64x64/dianara.png"),
                            tr("About &Dianara"),
                            this);
    connect(helpAbout, SIGNAL(triggered()),
            this, SLOT(aboutDianara()));
    helpMenu->addAction(helpAbout);
    this->menuBar()->addMenu(helpMenu);



    ///// Context menu for the tray icon
    m_trayTitleAction = new QAction(QStringLiteral("Dianara"), this);
    /* Separator actions with names don't work in some environments:
     *
     *  m_trayTitleAction->setSeparator(true);
     *
     * so just use a regular disabled action as 'title' of the menu instead
     */
    m_trayTitleAction->setDisabled(true);


    trayShowWindowAction = new QAction(QIcon(":/icon/64x64/dianara.png"),
                                       QStringLiteral("*show-window*"),
                                       this);
    connect(trayShowWindowAction, SIGNAL(triggered()),
            this, SLOT(toggleMainWindow()));

    trayContextMenu = new QMenu("*Dianara-Tray-Context-Menu*", this);
    trayContextMenu->setSeparatorsCollapsible(false);
    trayContextMenu->addAction(m_trayTitleAction); // Acts as title
    trayContextMenu->addSeparator();
    trayContextMenu->addAction(trayShowWindowAction);
    trayContextMenu->addSeparator();
    trayContextMenu->addAction(sessionUpdateMainTimeline);
    trayContextMenu->addAction(sessionUpdateMinorFeedMain);
    trayContextMenu->addAction(sessionAutoUpdates);
    trayContextMenu->addSeparator();
    trayContextMenu->addAction(sessionMarkAllAsRead);
    trayContextMenu->addAction(sessionPostNote);
    trayContextMenu->addSeparator();
    trayContextMenu->addAction(settingsEditProfile);
    trayContextMenu->addAction(settingsConfigure);
    trayContextMenu->addSeparator();
    trayContextMenu->addAction(helpBasicHelp);
    trayContextMenu->addAction(helpAbout);
    trayContextMenu->addSeparator();
    trayContextMenu->addAction(sessionQuit);

    // FIXME: if mainwindow is hidden, program quits
    // after closing Configure or About window (now partially fixed)


    qDebug() << "Menus created";
}


void MainWindow::createToolbar()
{
    m_mainToolBar = addToolBar(tr("Toolbar"));
    m_mainToolBar->setObjectName("mainToolBar"); // Not really needed in this case
    m_mainToolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);

    m_mainToolBar->addAction(this->sessionUpdateMainTimeline);
    m_mainToolBar->addAction(this->sessionUpdateMinorFeedMain);
    m_mainToolBar->addAction(this->sessionMarkAllAsRead);
    m_mainToolBar->addSeparator();
    this->settingsFilters->setPriority(QAction::LowPriority); // Don't show text in besides-icon mode
    m_mainToolBar->addAction(this->settingsFilters);
    this->settingsConfigure->setPriority(QAction::LowPriority);
    m_mainToolBar->addAction(this->settingsConfigure);

    m_mainToolBar->hide();
}


/*
 * Avoid auto-creation of popup menus in the menu bar, tool bar, etc.
 *
 */
QMenu *MainWindow::createPopupMenu()
{
    return NULL;
}


void MainWindow::createStatusbarWidgets()
{
    this->initializationProgressBar = new QProgressBar(this);
    this->initializationProgressBar->setRange(0, 12);
    this->initializationProgressBar->setMaximumWidth(100);
    this->initializationProgressBar->hide();
    connect(pumpController, SIGNAL(initializationStepChanged(int)),
            initializationProgressBar, SLOT(setValue(int)));


    m_statusStateButton = new QToolButton(this);
    this->setStateIcon(MainWindow::Initializing);
    connect(m_statusStateButton, &QAbstractButton::clicked,
            sessionAutoUpdates, &QAction::toggle);

    m_statusLogButton = new QToolButton(this);
    m_statusLogButton->setIcon(QIcon::fromTheme("text-x-log",
                                                QIcon(":/images/log.png")));
    m_statusLogButton->setToolTip(tr("Open the log viewer"));
    connect(m_statusLogButton, &QAbstractButton::clicked,
            viewLogAction, &QAction::trigger);

    this->statusBar()->addPermanentWidget(initializationProgressBar);
    this->statusBar()->addPermanentWidget(m_statusLogButton);
    this->statusBar()->addPermanentWidget(m_statusStateButton);
    this->statusBar()->setSizeGripEnabled(false);
}


void MainWindow::setStateIcon(MainWindow::StatusType statusType)
{
    if (statusType == Initializing)
    {
        m_statusStateButton->setDisabled(true); // Until initialization is done
        m_statusStateButton->setToolTip(tr("Initializing..."));
        m_statusStateButton->setIcon(QIcon::fromTheme("user-offline",
                                                      QIcon(":/images/button-offline.png")));
    }
    else if (statusType == Autoupdating)
    {
        m_statusStateButton->setEnabled(true);
        m_statusStateButton->setToolTip("<b></b>" + tr("Auto-updating enabled"));
        m_statusStateButton->setIcon(QIcon::fromTheme("user-online",
                                                      QIcon(":/images/button-online.png")));
    }
    else // MainWindow::Stopped
    {
        m_statusStateButton->setEnabled(true);
        m_statusStateButton->setToolTip("<b></b>" + tr("Auto-updating disabled"));
        m_statusStateButton->setIcon(QIcon::fromTheme("user-busy",
                                                      QIcon(":/images/button-busy.png")));
    }
}


/*
 * Create an icon in the system tray, define its contextual menu, etc.
 *
 */
void MainWindow::createTrayIcon()
{
    trayIcon = new QSystemTrayIcon(this);

    if (trayIcon->isSystemTrayAvailable())
    {
        trayIconAvailable = true;

        this->setTrayIconPixmap(); // Set icon for "no unread messages" initially
        this->setTitleAndTrayInfo(this->tabWidget->currentIndex());


        // Catch clicks on icon
        connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
                this, SLOT(trayControl(QSystemTrayIcon::ActivationReason)));

        // clicking in a popup notification (balloon-type) will show the window
        connect(trayIcon, SIGNAL(messageClicked()),
                this, SLOT(show())); // FIXME: this can mess up the first action in the context menu

        // Set contextual menu for the icon
        trayIcon->setContextMenu(this->trayContextMenu);


        trayIcon->show();

        qDebug() << "Tray icon created";
    }
    else
    {
        trayIconAvailable = false;

        qDebug() << "System tray not available";
    }
}


/*
 * Set the tray icon's pixmap, with number of unread messages, or nothing
 *
 */
void MainWindow::setTrayIconPixmap(int count, int highlightedCount)
{
    if (!trayIconAvailable)
    {
        return;
    }

    if (count != -1 && highlightedCount != -1) // Valid counts were passed, update
    {
        this->trayCurrentNewCount = count;
        this->trayCurrentHLCount = highlightedCount;
    }

    QPixmap iconPixmap;

    switch (this->trayIconType)
    {
    case 0: // Default
        iconPixmap = QIcon(":/icon/32x32/dianara.png")
                     .pixmap(32, 32)
                     .scaled(32, 32);
        break;

    case 1: // system iconset, if available
        iconPixmap = QIcon::fromTheme("dianara",
                                       QIcon(":/icon/32x32/dianara.png"))
                                       .pixmap(32, 32)
                                       .scaled(32, 32);
        break;

    case 2: // Use your own avatar
        iconPixmap = this->avatarIconButton->icon()
                     .pixmap(32, 32)
                     .scaled(32, 32);
        break;

    case 3: // Custom icon
        iconPixmap = this->trayCustomPixmap;

        break;

    }


    // Paint the number of unread messages on top of the pixmap, if != 0
    if (trayCurrentNewCount > 0)
    {
        // Draw a pseudo-shadow on the side first
        QPainter painter(&iconPixmap);
        if (trayCurrentHLCount > 0)
        {
            // Paint a shadow that covers the higher part too
            painter.drawPixmap(0, 0,
                               32, 32,
                               QPixmap(":/images/tray-bg-high.png"));
        }
        else
        {
            // Shadow for the lower part only
            painter.drawPixmap(0, 0,
                               32, 32,
                               QPixmap(":/images/tray-bg-low.png"));
        }

        QFont font;
        font.setPixelSize(16);
        font.setWeight(QFont::Black);


        // The number
        QString messagesCountString = QString("%1").arg(trayCurrentNewCount);

        QPen pen;
        pen.setBrush(Qt::white);
        painter.setFont(font);
        painter.setPen(pen);
        // Draw the number of new messages
        painter.drawText(0, 0,
                         32, 34,  // End painting outside, at Y=34, to skip margins
                         Qt::AlignRight | Qt::AlignBottom,
                         messagesCountString);

        if (trayCurrentHLCount > 0)
        {
            // The other number
            messagesCountString = QString("%1").arg(trayCurrentHLCount);

            pen.setBrush(Qt::cyan);
            painter.setPen(pen);

            // Draw the number of highlighted messages
            painter.drawText(0, -2,  // Start painting outside, at Y=-2, to avoid some margins
                             32, 32,
                             Qt::AlignRight | Qt::AlignTop,
                             messagesCountString);
        }
    }

    this->trayIcon->setIcon(iconPixmap);
}



/*
 * Load general program settings and state: size, position...
 *
 */
void MainWindow::loadSettings()
{
    QSettings settings;

    firstRun = settings.value("firstRun", true).toBool();
    if (firstRun)
    {
        qDebug() << "This is the first run";
    }


    m_userId = settings.value("userID", QString()).toString();
    this->setTitleAndTrayInfo(tabWidget->currentIndex());


    // General program configuration
    this->updateConfigSettings();

    // Load map of known post ID's from disk
    loadPostsEverSeen();


    qDebug() << "Settings loaded";
}

/*
 * Restore main window's state
 *
 */
void MainWindow::loadMainWindowConfig()
{
    QSettings settings;
    settings.beginGroup("MainWindow");

    this->resize(settings.value("windowSize", QSize(960, 680)).toSize());
    if (!firstRun) // So we should have a proper position saved
    {
        this->move(settings.value("windowPosition").toPoint());
        this->sideDockWidget->setMinimumWidth(0); // Remove temporary restriction
        this->restoreState(settings.value("windowState").toByteArray());
    }

    settings.endGroup();

    // Since this is called after creating menus and toolbars, we can lock them
    viewLockPanels->setChecked(true); // Lock to initialize; config loaded later
}

/*
 * Save general program settings and state: size, position...
 *
 */
void MainWindow::saveSettings()
{
    QSettings settings;
    if (settings.isWritable())
    {
        settings.setValue("firstRun", false);

        // General main window status
        settings.beginGroup("MainWindow");

        settings.setValue("windowSize",        this->size());
        settings.setValue("windowPosition",    this->pos());
        settings.setValue("windowState",       this->saveState());

        settings.setValue("viewSidePanel",     this->viewSidePanel->isChecked());
        settings.setValue("viewToolbar",       this->viewToolbar->isChecked());
        settings.setValue("viewStatusBar",     this->viewStatusBar->isChecked());
        settings.setValue("viewLockPanels",    this->viewLockPanels->isChecked());

        settings.endGroup();
        settings.sync();

        qDebug() << "MainWindow settings saved:" << settings.fileName();
    }
    else
    {
        qDebug() << "Error saving MainWindow settings to:"
                 << settings.fileName()
                 << "\n(disk full?)";
    }

    this->savePostsEverSeen();
}


/*
 * Set PumpController to ignore all SSL errors
 *
 */
void MainWindow::enableIgnoringSslErrors()
{
    this->pumpController->setIgnoreSslErrors(true);
}

/*
 * Set PumpController's protocol schema to http://
 *
 */
void MainWindow::enableNoHttpsMode()
{
    this->pumpController->setNoHttpsMode();
}


/*
 * Find a post in any of the main timelines;
 *
 * To be used to copy comments over to a cloned post
 *
 */
Post *MainWindow::findPostInTimelines(QString id, bool *ok)
{
    QList<Post *> postsInAllTimelines;
    postsInAllTimelines.append(mainTimeline->getPostsInTimeline());
    postsInAllTimelines.append(directTimeline->getPostsInTimeline());
    postsInAllTimelines.append(activityTimeline->getPostsInTimeline());
    postsInAllTimelines.append(favoritesTimeline->getPostsInTimeline());

    *ok = false;
    foreach (Post *singlePost, postsInAllTimelines)
    {
        if (singlePost->getObjectId() == id)
        {
            *ok = true;
            return singlePost;
        }
    }

    return NULL;
}



void MainWindow::loadPostsEverSeen()
{
    QVariantMap postsEverSeen;

    QFile dataFile(this->dataDirectory + "/postsEverSeen.ids");
    dataFile.open(QIODevice::ReadOnly);

    while (!dataFile.atEnd())
    {
        QString line = dataFile.readLine();
        QStringList splitLine = line.split(" | ");

        QString key = splitLine.first().trimmed();
        QString value = splitLine.last().trimmed();
        if (!key.isEmpty() && !value.isEmpty())
        {
            postsEverSeen.insert(key, value);
        }
    }

    dataFile.close();
    qDebug() << "Loaded" << postsEverSeen.keys().count()
             << "IDs from" << dataFile.fileName();

    this->postIdsToStore = 0;

    pumpController->updatePostsEverSeen(postsEverSeen);
}

/*
 * Called when quitting, and also sometimes from notifyTimelineUpdate()
 *
 */
void MainWindow::savePostsEverSeen()
{
    QVariantMap postsEverSeen = pumpController->getPostsEverSeen();

    QFile dataFile(this->dataDirectory + "/postsEverSeen.ids");
    dataFile.open(QIODevice::WriteOnly);

    foreach (const QString key, postsEverSeen.keys())
    {
        QByteArray line = key.toLocal8Bit();
        line.append(" | ");
        line.append(postsEverSeen.value(key).toByteArray());
        line.append("\n");
        dataFile.write(line);
    }

    dataFile.close();

    this->postIdsToStore = 0;
}



//////////////////////////////////////////////////////////////////////////////
/////////////////////////////////// SLOTS ////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////



/*
 * Update UserID string from signal emitted in AccountDialog
 *
 */
void MainWindow::updateUserId(QString newUserId)
{
    m_userId = newUserId;

    // update window title and tray icon tooltip
    this->setTitleAndTrayInfo(tabWidget->currentIndex());

    // Remove current user's name, id and avatar
    m_fullNameLabel->setText("---");
    m_userIdLabel->setText("__@__");
    m_userHometownLabel->setText("---");

    avatarIconButton->setIcon(QIcon(QPixmap(":/images/no-avatar.png")
                                    .scaled(64, 64,
                                            Qt::KeepAspectRatio,
                                            Qt::SmoothTransformation)));

    // Hide the temporary account button from the status bar, if needed
    if (m_statusAccountButtonUsed)
    {
        m_statusAccountButton->hide();
    }

    // Ensure progress bar is visible, for the first init after account setup
    this->initializationProgressBar->show();

    //////////////////////////////////////////// Restart initialization process
    this->initializationComplete = false;

    this->mainTimeline->clearTimeLineContents();
    this->directTimeline->clearTimeLineContents();
    this->activityTimeline->clearTimeLineContents();
    this->favoritesTimeline->clearTimeLineContents();

    this->meanwhileFeed->clearContents();
    this->mentionsFeed->clearContents();
    this->actionsFeed->clearContents();

    this->pumpController->setUserCredentials(m_userId);
    m_fdNotifications->setCurrentUserId(m_userId);

    qDebug() << "UserID updated from AccountDialog:" << m_userId;
}



/*
 * Update settings changed from ConfigDialog()
 *
 */
void MainWindow::updateConfigSettings()
{
    this->publisher->syncFromConfig();

    QSettings settings;
    settings.beginGroup("Configuration");

    this->updateInterval = settings.value("updateInterval", 5).toInt();
    if (updateInterval < 2 || updateInterval > 99)
    {
        updateInterval = 5; // TMP validation of stored setting - FIXME
        // Should be loaded and validated by GlobalObject
    }
    this->updateTimer->setInterval(updateInterval * 1000 * 60);  // min > msec


    this->pumpController->setPostsPerPageMain(globalObject->getPostsPerPageMain());
    this->pumpController->setPostsPerPageOther(globalObject->getPostsPerPageOther());


    m_tabsPosition = settings.value("tabsPosition", QTabWidget::North).toInt();
    tabWidget->setTabPosition((QTabWidget::TabPosition)m_tabsPosition);

    m_tabsMovable = settings.value("tabsMovable", true).toBool();
    tabWidget->setMovable(m_tabsMovable);


    int selectedProxyType = settings.value("proxyType", 0).toInt(); // 0 means "no proxy"
    QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy;
    if (selectedProxyType == 1)
    {
        proxyType = QNetworkProxy::Socks5Proxy;
    }
    else if (selectedProxyType == 2)
    {
        proxyType = QNetworkProxy::HttpProxy;
    }

    QString proxyHostname = settings.value("proxyHostname").toString();
    int proxyPort = settings.value("proxyPort", 0).toInt();
    bool proxyUseAuth = settings.value("proxyUseAuth", false).toBool();
    QString proxyUser = settings.value("proxyUser").toString();
    QByteArray proxyPassword = QByteArray::fromBase64(settings.value("proxyPassword").toByteArray());

    this->pumpController->setProxyConfig(proxyType,
                                         proxyHostname, proxyPort,
                                         proxyUseAuth,
                                         proxyUser,
                                         QString::fromLocal8Bit(proxyPassword));

    // Sync PumpController's ignore-SSL-for-images option
    this->pumpController->setIgnoreSslInImages(globalObject->getPostIgnoreSslInImages());


    // Sync privacy options in PumpController
    this->pumpController->setSilentFollows(globalObject->getSilentFollows());
    this->pumpController->setSilentLists(globalObject->getSilentLists());
    this->pumpController->setSilentLikes(globalObject->getSilentLikes());


    // System tray icon type and custom icon filename, if any
    this->trayIconType = globalObject->getTrayIconType();
    this->trayCustomPixmap = QPixmap(globalObject->getTrayIconFilename())
                             .scaled(32, 32);
    if (trayCustomPixmap.isNull()) // Custom icon image is gone or something
    {
        trayCustomPixmap = QPixmap(QStringLiteral(":/icon/32x32/dianara.png"));
    }

    this->setTrayIconPixmap(trayCurrentNewCount,
                            trayCurrentHLCount); // Sync icon

    settings.endGroup();


    qDebug() << "updateInterval updated:" << updateInterval << updateInterval*60000;
    qDebug() << "tabsPosition updated:" << m_tabsPosition << tabWidget->tabPosition();
    qDebug() << "tabsMovable updated:" << m_tabsMovable;
    qDebug() << "tray icon type updated:" << trayIconType;
}


/*
 * Enable or disable some widgets, depending on whether the applicacion
 * is authorized or not
 *
 */
void MainWindow::toggleWidgetsByAuthorization(bool authorized)
{
    this->sessionUpdateMainTimeline->setEnabled(authorized);
    this->sessionUpdateDirectTimeline->setEnabled(authorized);
    this->sessionUpdateActivityTimeline->setEnabled(authorized);
    this->sessionUpdateFavoritesTimeline->setEnabled(authorized);

    this->sessionUpdateMinorFeedMain->setEnabled(authorized);
    this->sessionUpdateMinorFeedDirect->setEnabled(authorized);
    this->sessionUpdateMinorFeedActivity->setEnabled(authorized);

    if (authorized)
    {
        // TODO FIXME
    }
    else
    {
        // TODO
    }
}


void MainWindow::onInitializationComplete()
{
    this->initializationComplete = true;
    this->sessionAutoUpdates->setEnabled(true);

    if (this->sessionAutoUpdates->isChecked())
    {
        this->setStateIcon(MainWindow::Autoupdating);
    }
    else
    {
        this->setStateIcon(MainWindow::Stopped);
    }

    this->initializationProgressBar->hide();

    this->adjustTimelineSizes(); // One final adjustment
}


/*
 * Stuff executed 1 second after MainWindow is created
 *
 */
void MainWindow::postInit()
{
    qDebug() << "postInit();";

    this->adjustTimelineSizes();

    // Ask for proxy password if needed
    if (this->pumpController->needsProxyPassword())
    {
        QString proxyPassword = QInputDialog::getText(this, tr("Proxy password required"),
                                                      tr("You have configured a "
                                                         "proxy server with "
                                                         "authentication, but the "
                                                         "password is not set.")
                                                      + "\n\n"
                                                      + tr("Enter the password "
                                                           "for your proxy server:"),
                                                      QLineEdit::Password);
        // FIXME: if this is cancelled, nothing will be loaded until program restart
        this->pumpController->setProxyPassword(proxyPassword);
    }

    QSettings settings;
    if (settings.value("FirstRunWizard/showWizard", true).toBool())
    {
        this->showFirstRunWizard();
    }
}



void MainWindow::showErrorPopup(QString message)
{
    if (m_fdNotifications->getNotifyErrors())
    {
        m_fdNotifications->showMessage(message);
    }
}




/*
 * Control interaction with the system tray icon
 *
 */
void MainWindow::trayControl(QSystemTrayIcon::ActivationReason reason)
{
    qDebug() << "Tray icon activation reason:" << reason;

    if (reason != QSystemTrayIcon::Context) // Simple "main button" click in icon
    {
        /*
        qDebug() << "trayControl()";
        qDebug() << "isHidden?"    << this->isHidden();
        qDebug() << "isVisible?"   << this->isVisible();
        qDebug() << "isMinimized?" << this->isMinimized();
        qDebug() << "hasFocus?"    << this->hasFocus();
        */

        // Hide or show the main window
        if (this->isMinimized())
        {
            // hide and show, because raise() wouldn't work
            this->hide();
            this->showNormal();
            qDebug() << "RAISING from minimized state";
        }
        else
        {
            this->toggleMainWindow();
        }
    }
}



/*
 * If FreeDesktop.org notifications are not available,
 * fall back to Qt's balloon ones
 *
 */
void MainWindow::showTrayFallbackMessage(QString title, QString message, int duration)
{
    this->trayIcon->showMessage(title,
                                message,
                                QSystemTrayIcon::Information,
                                duration);
}



void MainWindow::updateProfileData(QString avatarUrl, QString fullName,
                                   QString hometown, QString bio,
                                   QString eMail)
{
    QString bioTooltip = bio;
    if (!bio.isEmpty())
    {
        bioTooltip.prepend("<b></b>"); // Make it rich text, so it gets wordwrap
        bioTooltip.replace("\n", "<br>"); // HTML newlines
    }
    else
    {
        bioTooltip = "<i>" + tr("Your biography is empty") + "</i>";
    }

    m_fullNameLabel->setText(fullName);
    m_fullNameLabel->setToolTip(bioTooltip);
    m_userIdLabel->setText(m_userId);
    m_userHometownLabel->setText(hometown);
    qDebug() << "Updated profile data from server:" << fullName << " @" << hometown;

    m_avatarUrl = avatarUrl;
    qDebug() << "Own avatar URL:" << m_avatarUrl;


    // Get local file name, which is stored in base64 hash form
    const QString avatarFilename = MiscHelpers::getCachedAvatarFilename(m_avatarUrl);

    if (QFile::exists(avatarFilename))
    {
        // Load avatar if already cached
        this->avatarIconButton->setIcon(QIcon(QPixmap(avatarFilename)
                                              .scaled(64, 64,
                                                      Qt::KeepAspectRatio,
                                                      Qt::SmoothTransformation)));
    }
    else
    {
        pumpController->getAvatar(m_avatarUrl);
    }
    this->avatarIconButton->setToolTip(bioTooltip
                                       + "<br><hr>"
                                         "<b><i>"
                                       + tr("Click to edit your profile")
                                       + "</i></b>");


    // Fill/update this info in the profile editor too
    m_profileEditor->setProfileData(m_avatarUrl, fullName, hometown, bio, eMail);

    // Refresh tray icon without changing unread/highlighted counts
    this->setTrayIconPixmap(-1, -1);
}

/*
 * Enable or disable the timer that auto-updates the timelines
 *
 */
void MainWindow::toggleAutoUpdates(bool checked)
{
    QString message;

    if (checked)
    {
        this->setStateIcon(MainWindow::Autoupdating);
        this->updateTimer->start();
        message = tr("Starting automatic update of timelines, "
                     "once every %1 minutes.").arg(this->updateInterval);
    }
    else
    {
        this->setStateIcon(MainWindow::Stopped);
        this->updateTimer->stop();
        message = tr("Stopping automatic update of timelines.");
    }

    this->setStatusBarMessage(message);
    m_logViewer->addToLog(message);
}


/*
 * Update Main Timeline and Meanwhile feed, if allowed.
 * Called automatically from a timer.
 *
 */
void MainWindow::onTimelineAutoupdate()
{
    bool updatingAllowed = true;

    // Don't allow updates if the scrollbar is not near the top
    if (this->mainTimelineScrollArea->verticalScrollBar()->value() > 10) // Kinda TMP...
    {
        // FIXME: compare with verticalScrollBart()->minimum()
        updatingAllowed = false;
    }

    // Don't allow updates for any scroll position if page > 1
    if (this->mainTimeline->getCurrentPage() > 1)
    {
        updatingAllowed = false;
    }

    // Regardless of that, if Dianara is hidden in the system tray, updates are fine
    if (this->isHidden())
    {
        updatingAllowed = true;
    }


    if (updatingAllowed)
    {
        this->updateMainAndMinorTimelines();

        qDebug() << "Updated some timelines automatically after:"
                 << this->updateInterval << "min";
    }
    else
    {
        m_bannerNotification->show();

        qDebug() << "Not autoupdating timelines because main is not at the top "
                    "of the first page; Showing banner notification instead...";
    }
}


/*
 * Update all timelines
 *
 */
void MainWindow::updateAllTimelines()
{
    mainTimeline->goToFirstPage(); // received timeline will come in a SIGNAL()
    directTimeline->goToFirstPage();
    activityTimeline->goToFirstPage();
    favoritesTimeline->goToFirstPage();

    meanwhileFeed->updateFeed();
    mentionsFeed->updateFeed();
    actionsFeed->updateFeed();

    qDebug() << "Updated all timelines by menu";
}


void MainWindow::onUpdateRequestViaBanner()
{
    // Move to top first
    this->mainTimelineScrollArea->verticalScrollBar()->setValue(0);

    this->updateMainAndMinorTimelines();
}


void MainWindow::onUpdateDelayedViaBanner()
{
    if (this->sessionAutoUpdates->isChecked())
    {
        this->updateTimer->stop();
        this->updateTimer->start();
    }
}


/*
 * Update Main timeline and Meanwhile feed
 *
 * Those, in turn, might update the Messages timeline and the Mentions feed
 *
 */
void MainWindow::updateMainAndMinorTimelines()
{
    this->updateTimer->stop(); // Stop early, to avoid double updates

    meanwhileFeed->updateFeed();
    mainTimeline->goToFirstPage();
}


/*
 * Update relevant feeds that need to reflect user's own activity.
 *
 * Called after posting.
 *
 */
void MainWindow::updateMainActivityMinorTimelines()
{
    this->updateTimer->stop(); // Stop early, avoiding double updates

    mainTimeline->goToFirstPage();
    activityTimeline->goToFirstPage();

    meanwhileFeed->updateFeed();
    userDidSomethingTimer->start(); // Call updateActionsFeed() a little later

    qDebug() << "Updated some timelines after posting";
}


/*
 * Update the Favorites timeline. Called by timer some time after the user
 * liked or unliked a post, unless they keep liking more stuff.
 *
 */
void MainWindow::updateFavoritesTimeline()
{
    favoritesTimeline->goToFirstPage();
}


/*
 * Update the Actions feed. Called by timer some time after the user
 * does something minor, unless they keep doing more things.
 *
 */
void MainWindow::updateActionsFeed()
{
    actionsFeed->updateFeed();

    // Stop it specifically, for cases when this is called from the menu
    this->userDidSomethingTimer->stop();
}



void MainWindow::scrollMainTimelineTo(QAbstractSlider::SliderAction sliderAction)
{
    this->mainTimelineScrollArea->verticalScrollBar()->triggerAction(sliderAction);

//    this->adjustTimelineSizes();
}

void MainWindow::scrollDirectTimelineTo(QAbstractSlider::SliderAction sliderAction)
{
    this->directTimelineScrollArea->verticalScrollBar()->triggerAction(sliderAction);
}

void MainWindow::scrollActivityTimelineTo(QAbstractSlider::SliderAction sliderAction)
{
    this->activityTimelineScrollArea->verticalScrollBar()->triggerAction(sliderAction);
}

void MainWindow::scrollFavoritesTimelineTo(QAbstractSlider::SliderAction sliderAction)
{
    this->favoritesTimelineScrollArea->verticalScrollBar()->triggerAction(sliderAction);
}

/*
 * Scroll timelines to make sure the commenter block of the post
 * currently being commented is shown
 *
 */
void MainWindow::scrollMainTimelineToWidget(QWidget *widget)
{
    this->mainTimelineScrollArea->ensureWidgetVisible(widget, 1, 100);
}

void MainWindow::scrollDirectTimelineToWidget(QWidget *widget)
{
    this->directTimelineScrollArea->ensureWidgetVisible(widget, 1, 100);
}

void MainWindow::scrollActivityTimelineToWidget(QWidget *widget)
{
    this->activityTimelineScrollArea->ensureWidgetVisible(widget, 1, 100);
}

void MainWindow::scrollFavoritesTimelineToWidget(QWidget *widget)
{
    this->favoritesTimelineScrollArea->ensureWidgetVisible(widget, 1, 100);
}

/*
 * Scroll to new stuff separator line widget (delayed, called from a timer)
 *
 */
void MainWindow::scrollToNewPosts()
{
    this->scrollMainTimelineToWidget(mainTimeline->getSeparatorWidget());
}



void MainWindow::notifyTimelineUpdate(PumpController::requestTypes timelineType,
                                      int newPostCount, int highlightCount,
                                      int directPostCount, int hlByFilterCount,
                                      int deletedPostCount, int filteredPostCount,
                                      int pendingPostCount, QString currentPage)
{
    /* Stop auto-updates timer to postpone the auto-updates each time a manual
     * update is triggered (including moving to a different page), but only
     * for the main timeline. Maybe also for the DirectMessages TL?
     *
     */
    if (timelineType == PumpController::MainTimelineRequest)
    {
        this->updateTimer->stop();
    }


    bool loadedOlderPage = false;
    // Both of these set to -1 means it's an older page
    if (highlightCount == -1 && pendingPostCount == -1)
    {
        loadedOlderPage = true;
    }

    // Update postsEverSeen on disk, when there are enough useful IDs pending
    if (newPostCount > 0 && !loadedOlderPage)
    {
        this->postIdsToStore += newPostCount;
        // Some might already be in the file, but still...
        if (postIdsToStore > 10)
        {
            this->savePostsEverSeen(); // This sets postIdsToStore back to 0
        }
    }

    // Restart auto-updates timer ONLY when autoupdates are enabled
    if (timelineType == PumpController::MainTimelineRequest
     && this->sessionAutoUpdates->isChecked())
    {
        this->updateTimer->start();
    }

    // Just in case the banner was visible, depending on how update was requested...
    m_bannerNotification->hide();

    QString timelineName = PumpController::getFeedNameAndPath(timelineType).first();

    // First handle the simpler case, where the loaded posts are from an older page
    if (loadedOlderPage)
    {
        this->setStatusBarMessage(tr("Received %1 older posts in '%2'.",
                                     "%1 is a number, %2 = name of a timeline")
                                  .arg(newPostCount)
                                  .arg(timelineName)
                                  + " "
                                  + currentPage);
        return;
    }

    QString statusBarMessage = tr("'%1' updated.",
                                  "%1 is the name of a feed")
                               .arg(timelineName);

    if (newPostCount > 0)
    {
        QString timelineUpdatedAt = tr("Timeline updated at %1.")
                                    .arg(QTime::currentTime().toString());
        // How many NEW
        QString newPostsString;
        if (newPostCount == 1)
        {
            newPostsString = tr("There is 1 new post.");
        }
        else
        {
            newPostsString = tr("There are %1 new posts.").arg(newPostCount);
        }
        statusBarMessage.append(" " + newPostsString);


        // How many of those NEW are HIGHLIGHTED
        QString hlPostsString;
        if (highlightCount > 0
         && timelineType == PumpController::MainTimelineRequest)
        {
            QString directMessagesString = tr("Direct messages");
            QString byFiltersString = tr("By filters");

            if (highlightCount == 1)
            {
                hlPostsString = tr("1 highlighted",
                                   "singular, refers to a post");
            }
            else
            {
                hlPostsString = tr("%1 highlighted",
                                   "plural, refers to posts").arg(highlightCount);
            }

            hlPostsString.append(" (");
            if (directPostCount > 0 && hlByFilterCount > 0)
            {
                hlPostsString.append(QString("%1: %2").arg(directMessagesString)
                                                      .arg(directPostCount)
                                     + ", "
                                     + QString("%1: %2").arg(byFiltersString)
                                                        .arg(hlByFilterCount));
            }
            else if (directPostCount > 0)
            {
                hlPostsString.append(directMessagesString);
            }
            else if (hlByFilterCount > 0)
            {
                hlPostsString.append(byFiltersString);
            }
            hlPostsString.append(").");

            statusBarMessage.append(" " + hlPostsString);
        }

        // How many more PENDING to receive next time
        QString pendingPostsString;
        if (pendingPostCount > 0)
        {
            if (pendingPostCount == 1)
            {
                pendingPostsString = tr("1 more pending to receive.",
                                        "singular, one post");
            }
            else
            {
                pendingPostsString = tr("%1 more pending to receive.",
                                        "plural, several posts")
                                     .arg(pendingPostCount);
            }

            statusBarMessage.append(" " + pendingPostsString);
        }


        // Also: how many are deleted or filtered out
        if (filteredPostCount > 0 || deletedPostCount > 0)
        {
            statusBarMessage.append(" " + tr("Also:"));

            QString filteredPostsString;
            if (filteredPostCount > 0)
            {
                if (filteredPostCount == 1)
                {
                    filteredPostsString = tr("1 filtered out.",
                                             "singular, refers to a post");
                }
                else
                {
                    filteredPostsString = tr("%1 filtered out.",
                                             "plural, refers to posts")
                                          .arg(filteredPostCount);
                }

                statusBarMessage.append(" " + filteredPostsString);
            }

            QString deletedPostsString;
            if (deletedPostCount > 0)
            {
                if (deletedPostCount == 1)
                {
                    deletedPostsString = tr("1 deleted.",
                                            "singular, refers to a post");
                }
                else
                {
                    deletedPostsString = tr("%1 deleted.",
                                            "plural, refers to posts")
                                         .arg(deletedPostCount);
                }

                statusBarMessage.append(" " + deletedPostsString);
            }
        }


        this->setStatusBarMessage(statusBarMessage);

        // Only for the main timeline
        if (timelineType == PumpController::MainTimelineRequest)
        {
            if (m_fdNotifications->getNotifyHLTimeline() && highlightCount > 0)
            {
                m_fdNotifications->showMessage(timelineUpdatedAt + "\n\n"
                                        + newPostsString + "\n"
                                        + hlPostsString + "\n"
                                        + pendingPostsString);

                // FIXME: needs to depend on notification style
                if (globalObject->getNotifyInTaskbar())
                {
                    qApp->alert(this);
                }
            }
            else if (m_fdNotifications->getNotifyNewTimeline())
            {
                m_fdNotifications->showMessage(timelineUpdatedAt + "\n\n"
                                        + newPostsString + "\n"
                                        + pendingPostsString);

                // FIXME: as the one above
                if (globalObject->getNotifyInTaskbar())
                {
                    qApp->alert(this);
                }
            }

            m_logViewer->addToLog(statusBarMessage);

            // Jump to new stuff separator, if enabled
            if (this->globalObject->getJumpToNewOnUpdate()
             && this->initializationComplete) // Not for the first load!
            {
                /* Timer will jump in one second, after events have been
                 * processed, and widgets have been adjusted
                 */
                this->delayedScrollTimer->start(1000);
            }
        }
    }
    else
    {
        if (timelineType == PumpController::MainTimelineRequest
         || timelineType == PumpController::DirectTimelineRequest)
        {
            // Only add 'no new posts' for timelines that can have unread posts
            statusBarMessage.append(QString::fromUtf8(" \342\210\205 ") // Empty set
                                    + tr("No new posts."));
        }

        this->setStatusBarMessage(statusBarMessage);
    }


    // And adjust timelines sizes
    // NOW DISABLING: makes reloads a lot slower for no good reason
    ///// this->adjustTimelineSizes();


    // If conditions are right, update the Messages tab, too
    if (timelineType == PumpController::MainTimelineRequest)
    {
        // Store statusBarMessage, to restore after the Messages timeline update
        this->previousStatusFeedInfo = statusBarMessage;

        // Some new direct messages (though maybe via Cc), so update that timeline
        if (directPostCount > 0 || pendingPostCount > 0) // Also if more than max new posts
        {
            if (initializationComplete) // But not the very first time
            {
                this->directTimeline->goToFirstPage();
            }
        }


        // Also, update menu info label with current time
        m_menuInfoWidget->hide(); // Hide info widget before updating label
                                  // to ensure it gets the proper size
        m_menuInfoLabel->setText(tr("Last update: %1")
                                 .arg(QTime::currentTime().toString()));
        m_menuInfoLabel->setToolTip(QDate::currentDate().toString());
        // Show again, but only if the menu bar is not native (not handled by environment)
        m_menuInfoWidget->setVisible(!this->menuBar()->isNativeMenuBar());
    }
    else
    {
        /*
         * Updated other timeline, and we have a previous statusbar message,
         * which means this is an auto-update.
         * If this update didn't bring anything new, we'll restore the previous
         * status message, which might have been more interesting.
         *
         */
        if (newPostCount == 0
         && !this->previousStatusFeedInfo.isEmpty())
        {
            this->setStatusBarMessage(this->previousStatusFeedInfo);
        }
        this->previousStatusFeedInfo.clear(); // Empty for next time
    }
}


/*
 * Update timelines titles with number of new posts
 *
 */
void MainWindow::setTimelineTabTitle(PumpController::requestTypes timelineType,
                                     int newPostCount, int highlightCount,
                                     int totalFeedPostCount)
{
    int updatedTab = 0;

    QString unreadPrefix; // Will containg an * if there are new posts
    QString messageCountString;
    if (newPostCount > 0)
    {
        messageCountString = QString(" (%1)").arg(newPostCount);
        unreadPrefix = "* ";

        if (highlightCount > 0
         && timelineType == PumpController::MainTimelineRequest)
        {
            // Showing count of highlighted posts makes sense in the main TL only
            messageCountString.append(QString(" [%1]").arg(highlightCount));
            unreadPrefix.prepend("*");
        }
    }


    QString totalItemsString = "\n\n"
                               + tr("Total posts: %1")
                                 .arg(QLocale::system()
                                      .toString(totalFeedPostCount));

    switch (timelineType)
    {
    case PumpController::MainTimelineRequest:
        this->tabWidget->setTabText(0, unreadPrefix
                                       + tr("&Timeline")
                                       + messageCountString);
        this->tabWidget->setTabToolTip(0, tr("The main timeline")
                                          + totalItemsString);
        updatedTab = 0;
        break;

    case PumpController::DirectTimelineRequest:
        this->tabWidget->setTabText(1, unreadPrefix
                                       + tr("&Messages")
                                       + messageCountString);
        this->tabWidget->setTabToolTip(1, tr("Messages sent explicitly to you")
                                          + totalItemsString);
        updatedTab = 1;
        break;

    case PumpController::ActivityTimelineRequest:
        this->tabWidget->setTabText(2, unreadPrefix
                                       + tr("&Activity")
                                       + messageCountString);
        this->tabWidget->setTabToolTip(2, tr("Your own posts")
                                          + totalItemsString);
        updatedTab = 2;
        break;

    case PumpController::FavoritesTimelineRequest:
        this->tabWidget->setTabText(3, unreadPrefix
                                       + tr("Favor&ites")
                                       + messageCountString);
        this->tabWidget->setTabToolTip(3, tr("Your favorited posts")
                                          + totalItemsString);
        updatedTab = 3;
        break;
    default:
        updatedTab = -1;
        qDebug() << "setTimelineTabTitle() called with wrong feed type!";
    }

    // If the updated tab is the current one, set also window title, etc.
    if (updatedTab == tabWidget->currentIndex())
    {
        this->setTitleAndTrayInfo(updatedTab);
    }


    // If it's the main timeline, set the tray icon pixmap with the newmsg count
    if (updatedTab == 0)
    {
        this->setTrayIconPixmap(newPostCount, highlightCount);
    }
}


/*
 * Set mainWindow's title based on current tab, and user ID
 *
 * Use that same title as tray icon's tooltip
 *
 */
void MainWindow::setTitleAndTrayInfo(int currentTab)
{
    QString currentTabTitle;
    currentTabTitle = this->tabWidget->tabText(currentTab);
    currentTabTitle.remove("&"); // Remove accelators

    QString title = currentTabTitle;
    if (!m_userId.isEmpty())
    {
        title.append(" - " + m_userId);
    }
    title.append(" - Dianara");

    /*  Not sure if I like it

    if (currentTabTitle.endsWith(")")) // kinda TMP
    {
        title.prepend("* ");
    }
    */

    this->setWindowTitle(title);
    if (trayIconAvailable)
    {
        const QString idToShow = m_userId.isEmpty()
                                 ? "[ "
                                   + tr("Your Pump.io account is not configured")
                                   + " ]"
                                 : m_userId;
#ifdef Q_OS_UNIX
        title = "<b>Dianara</b>: "
              + currentTabTitle
              + "<br/>"
              + idToShow;
#else
        // Some OSes don't render HTML in the tray tooltip
        title = "Dianara: "
              + currentTabTitle
              + "\n\n"
              + idToShow
              + "";
#endif


        this->trayIcon->setToolTip(title);

        m_trayTitleAction->setText(QStringLiteral("Dianara - ") + m_userId);
    }
}


/*
 * Set the title for the minor feed (Meanwhile), with new item count
 *
 */
void MainWindow::setMinorFeedTitle(int newItemsCount,
                                   int newHighlightedItemsCount)
{
    QString title = PumpController::getFeedNameAndPath(PumpController::MinorFeedMainRequest)
                                   .first() + "...";
    if (newItemsCount > 0)
    {
        title.append(QString(" (%1)").arg(newItemsCount));
        if (newHighlightedItemsCount > 0)
        {
            title.append(QString(" [%1]").arg(newHighlightedItemsCount));
        }
    }

    this->leftPanel->setItemText(0, title);
}


void MainWindow::setMentionsFeedTitle(int newItemsCount,
                                      int newHighlightedItemsCount)
{
    Q_UNUSED(newHighlightedItemsCount)

    QString title = PumpController::getFeedNameAndPath(PumpController::MinorFeedDirectRequest)
                                    .first();
    if (newItemsCount > 0)
    {
        title.append(QString(" (%1)").arg(newItemsCount));
    }

    this->leftPanel->setItemText(1, title);
}




void MainWindow::notifyMinorFeedUpdate(PumpController::requestTypes feedType,
                                       int newItemsCount,
                                       int highlightedCount,
                                       int filteredCount,
                                       int pendingItemsCount)
{
    QString feedName = PumpController::getFeedNameAndPath(feedType).first();

    // First, handle the simpler case, where items are older
    if (highlightedCount == -1 && pendingItemsCount == -1)
    {
        // When both of these are -1
        this->setStatusBarMessage(tr("Received %1 older activities in '%2'.",
                                     "%1 is a number, %2 = name of feed")
                                  .arg(newItemsCount)
                                  .arg(feedName));
        return;
    }


    QString statusBarMessage = tr("'%1' updated.",
                                  "%1 is the name of a feed").arg(feedName);

    if (newItemsCount > 0)
    {
        QString mwUpdatedAt = tr("Minor feed updated at %1.")
                              .arg(QTime::currentTime().toString());

        // How many NEW items
        QString newItemsString;
        if (newItemsCount == 1)
        {
            newItemsString = tr("There is 1 new activity.");
        }
        else
        {
            newItemsString = tr("There are %1 new activities.")
                             .arg(newItemsCount);
        }
        statusBarMessage.append(" " + newItemsString);


        // How many of those NEW items are highlighted
        QString hlItemsString;
        if (highlightedCount > 0
         && feedType == PumpController::MinorFeedMainRequest) // HL only matter in the Meanwhile feed
        {
            if (highlightedCount == 1)
            {
                hlItemsString = tr("1 highlighted.",
                                   "singular, refers to an activity");
            }
            else
            {
                hlItemsString = tr("%1 highlighted.",
                                   "plural, refers to activities")
                                .arg(highlightedCount);
            }
            statusBarMessage.append(" " + hlItemsString);
        }

        // How many more items are PENDING to receive on next update
        QString pendingItemsString;
        if (pendingItemsCount > 0)
        {
            if (pendingItemsCount == 1)
            {
                pendingItemsString = tr("1 more pending to receive.",
                                        "singular, 1 activity");
            }
            else
            {
                pendingItemsString = tr("%1 more pending to receive.",
                                        "plural, several activities")
                                     .arg(pendingItemsCount);
            }
            statusBarMessage.append(" " + pendingItemsString);
        }


        // Also: how many are filtered out
        QString filteredItemsString;
        if (filteredCount > 0)
        {
            statusBarMessage.append(" " + tr("Also:"));

            if (filteredCount == 1)
            {
                filteredItemsString = tr("1 filtered out.",
                                         "singular, refers to one activity");
            }
            else
            {
                filteredItemsString = tr("%1 filtered out.",
                                         "plural, several activities")
                                      .arg(filteredCount);
            }

            statusBarMessage.append(" " + filteredItemsString);
        }


        // Some stuff is only done for the Meanwhile feed: HL counts, notifications...
        if (feedType == PumpController::MinorFeedMainRequest)
        {
            if (highlightedCount > 0)
            {
                if (m_fdNotifications->getNotifyHLMeanwhile())
                {
                    m_fdNotifications->showMessage(mwUpdatedAt + "\n\n"
                                              + newItemsString + "\n"
                                              + hlItemsString + "\n"
                                              + pendingItemsString);

                    // FIXME: needs to depend on notification style
                    if (globalObject->getNotifyInTaskbar())
                    {
                        qApp->alert(this);
                    }
                }
            }
            else
            {
                if (m_fdNotifications->getNotifyNewMeanwhile())
                {
                    m_fdNotifications->showMessage(mwUpdatedAt + "\n\n"
                                              + newItemsString + "\n"
                                              + pendingItemsString);

                    if (globalObject->getNotifyInTaskbar())
                    {
                        qApp->alert(this);
                    }
                }
            }

            // Store statusBarMessage, to restore after the Mentions and
            // Actions feeds are updated, in case they have nothing new
            if (feedType == PumpController::MinorFeedMainRequest)
            {
                this->previousStatusFeedInfo = statusBarMessage;

                // Some highlighted in the Meanwhile could mean some new mentions, so update that feed
                if (highlightedCount > 0 || pendingItemsCount > 0) // Also if more than max new activities
                {
                    if (initializationComplete) // But not the first time
                    {
                        this->mentionsFeed->updateFeed();
                    }
                }
            }
        }

        this->setStatusBarMessage(statusBarMessage);
        m_logViewer->addToLog(statusBarMessage);
    }
    else
    {
        if (feedType != PumpController::MinorFeedActivityRequest)
        {
            // Don't add redundant 'no activities' for Actions feed
            statusBarMessage.append(QString::fromUtf8(" \342\210\205 ") // Empty set
                                    + tr("No new activities."));
        }

        // For Mentions/Actions, use previously stored useful message, if any
        if (feedType != PumpController::MinorFeedMainRequest
         && !this->previousStatusFeedInfo.isEmpty())
        {
            statusBarMessage = previousStatusFeedInfo;
        }

        previousStatusFeedInfo.clear();
        this->setStatusBarMessage(statusBarMessage);
    }
}





/*
 * Store avatars on disk
 *
 */
void MainWindow::storeAvatar(QByteArray avatarData, QString avatarUrl)
{
    QString fileName = MiscHelpers::getCachedAvatarFilename(avatarUrl);
    qDebug() << "Saving avatar to disk: " << fileName;

    QFile avatarFile(fileName);
    avatarFile.open(QFile::WriteOnly);
    avatarFile.write(avatarData);
    avatarFile.close();

    this->pumpController->notifyAvatarStored(avatarUrl,
                                             avatarFile.fileName());

    if (avatarUrl == m_avatarUrl)
    {
        this->avatarIconButton->setIcon(QIcon(QPixmap(avatarFile.fileName())
                                              .scaled(64, 64,
                                                      Qt::KeepAspectRatio,
                                                      Qt::SmoothTransformation)));
    }

    qDebug() << "avatarData size:" << avatarData.size();
}





/*
 * Store images on disk
 *
 */
void MainWindow::storeImage(QByteArray imageData, QString imageUrl)
{
    QString fileName = MiscHelpers::getCachedImageFilename(imageUrl);
    QFile imageFile(fileName);
    bool fileOpenedOk = imageFile.open(QFile::WriteOnly);
    imageFile.write(imageData);
    imageFile.close();

    qDebug() << "Saving image to disk: " << fileName
             << "; File opened OK? " << fileOpenedOk;

    if (fileOpenedOk && imageFile.size() > 0)
    {
        qDebug() << "Saved " << imageFile.size() << "bytes";

        this->pumpController->notifyImageStored(imageUrl);
    }
    else
    {
        qDebug() << "Couldn't save" << fileName << "to disk!";

        QString message = tr("Error storing image!")
                          + " ("
                          + tr("%1 bytes").arg(imageData.size())
                          + ", "
                          + imageUrl
                          + ")";
        this->setStatusBarMessage(message);
        m_logViewer->addToLog(message);

        if (fileOpenedOk) // Opened OK but no disk space, or something
        {
            imageFile.remove();
        }

        this->pumpController->notifyImageFailed(imageUrl);
    }

    qDebug() << "imageData size:" << imageData.size();
}



/*
 * Update status bar message, from PumpController's signals
 *
 */
void MainWindow::setStatusBarMessage(QString message)
{
    this->oldStatusBarMessage = "[" + QTime::currentTime().toString() + "] "
                                + message;
    this->statusBar()->showMessage(oldStatusBarMessage, 0);
}


/*
 * Show a temporary message in the statusBar
 *
 * If message is empty, restore the old one
 *
 */
void MainWindow::setTransientStatusMessage(QString message)
{
    if (!message.isEmpty())
    {
        if (message.startsWith("http"))
        {
            message = tr("Link to: %1").arg(message);
        }
        this->statusBar()->showMessage(message, 0);
    }
    else
    {
        // Old status message was saved in setStatusBarMessage()
        this->statusBar()->showMessage(oldStatusBarMessage, 0);
    }
}


/*
 * Mark all posts in all timelines as read
 * and clear their counters
 *
 */
void MainWindow::markAllAsRead()
{
    this->setTransientStatusMessage(tr("Marking everything as read..."));
    qApp->processEvents();

    mainTimeline->markPostsAsRead();
    directTimeline->markPostsAsRead();
    // activityTimeline and favoritesTimeline don't need this; they're never "unread"

    meanwhileFeed->markAllAsRead();
    this->setMinorFeedTitle(0, 0);

    mentionsFeed->markAllAsRead();
    this->setMentionsFeedTitle(0, 0);
    // activitiesFeed doesn't need this, either

    this->setTransientStatusMessage(QString()); // Restore
}


void MainWindow::startPost(QString title, QString content)
{
    if (this->isHidden())
    {
        this->toggleMainWindow();
    }

    this->publisher->setFullMode();

    // Don't try setting title and content if called via menu, only via D-Bus
    if (!title.isEmpty() || !content.isEmpty())
    {
        this->publisher->setTitleAndContent(title, content);
    }
}


/*
 * Refresh all timestamps in the minor feed, in timelines posts,
 * and in comments
 *
 */
void MainWindow::refreshAllTimestamps()
{
    this->mainTimeline->updateFuzzyTimestamps();
    this->directTimeline->updateFuzzyTimestamps();
    this->activityTimeline->updateFuzzyTimestamps();
    this->favoritesTimeline->updateFuzzyTimestamps();

    this->meanwhileFeed->updateFuzzyTimestamps();
    this->mentionsFeed->updateFuzzyTimestamps();
    this->actionsFeed->updateFuzzyTimestamps();
}



/*
 * Adjust timelines maximum widths according to their scrollareas sizes,
 * which in turn depend on the window size
 *
 * Called from the resizeEvent(), among other places
 *
 */
void MainWindow::adjustTimelineSizes()
{
    int timelineWidth;
    int timelineHeight;

    // Get the right timelinewidth based on currently active tab's timeline width
    switch (this->tabWidget->currentIndex())
    {
    case 0: // main
        timelineWidth = mainTimelineScrollArea->viewport()->width();
        timelineHeight = mainTimelineScrollArea->viewport()->height();
        break;
    case 1: // direct
        timelineWidth = directTimelineScrollArea->viewport()->width();
        timelineHeight = directTimelineScrollArea->viewport()->height();
        break;
    case 2: // activity
        timelineWidth = activityTimelineScrollArea->viewport()->width();
        timelineHeight = activityTimelineScrollArea->viewport()->height();
        break;
    case 3: // favorites
        timelineWidth = favoritesTimelineScrollArea->viewport()->width();
        timelineHeight = favoritesTimelineScrollArea->viewport()->height();
        break;

    default: // Contacts tab, ATM
        timelineWidth = contactManager->width();
                //->visibleRegion().boundingRect().width();
                //->width() - scrollbarWidth;
        timelineHeight = contactManager->height();
    }

    // Then set the maximum width for all of them based on that
    mainTimeline->setMaximumWidth(timelineWidth);
    directTimeline->setMaximumWidth(timelineWidth);
    activityTimeline->setMaximumWidth(timelineWidth);
    favoritesTimeline->setMaximumWidth(timelineWidth);


    // Some basic 1:2 proportions TMP FIXME
    if (timelineHeight > (timelineWidth * 2))
    {
        timelineHeight = timelineWidth * 2;
    }
    this->globalObject->storeTimelineHeight(timelineHeight);

    // Resize
    switch (this->tabWidget->currentIndex())
    {
    case 0: // main
        mainTimeline->resizePosts(QList<Post *>(), true); // resizeAll=true
        break;
    case 1: // direct
        directTimeline->resizePosts(QList<Post *>(), true);
        break;
    case 2: // activity
        activityTimeline->resizePosts(QList<Post *>(), true);
        break;
    case 3: // favorites
        favoritesTimeline->resizePosts(QList<Post *>(), true);
        break;
    default: // Contacts tab, ATM
        break;
    }
}

void MainWindow::showUserTimeline(QString userId, QString userName,
                                  QIcon userAvatar, QString userOutbox)
{
    UserPosts *userTimeline = new UserPosts(userId, userName,
                                            userAvatar, userOutbox,
                                            this->pumpController,
                                            this->globalObject,
                                            m_filterChecker,
                                            nullptr); // No parent, independent window
    userTimeline->show();
}


/*
 * Lock or unlock toolbars and docks
 *
 */
void MainWindow::toggleLockedPanels(bool locked)
{
    if (locked)
    {
        sideDockWidget->setTitleBarWidget(sideDockTitleWidget);
        sideDockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures);
    }
    else
    {
        sideDockWidget->setTitleBarWidget(nullptr);
        sideDockWidget->setFeatures(QDockWidget::DockWidgetMovable);
    }

    m_mainToolBar->setMovable(!locked);
}


/*
 * Hide or show the side panel
 *
 */
void MainWindow::toggleSidePanel(bool shown)
{
    // Unless the publisher (really, the composer) has focus already,
    // give focus to timeline before, to avoid giving focus to the publisher
    if (!this->publisher->hasFocus())
    {
        // FIXME: check if publisher's composer has focus!
        this->mainTimeline->setFocus();
    }

    qDebug() << "Showing side panel:" << shown;
    this->sideDockWidget->setVisible(shown);

    if (!shown)
    {
        qApp->processEvents();
        this->adjustTimelineSizes();
    }
}


void MainWindow::toggleToolbar(bool shown)
{
    qDebug() << "Showing toolbar:" << shown;
    m_mainToolBar->setVisible(shown);
}


/*
 * Hide or show the status bar
 *
 */
void MainWindow::toggleStatusBar(bool shown)
{
    qDebug() << "Showing status bar:" << shown;
    this->statusBar()->setVisible(shown);
}


/*
 * Toggle between fullscreen mode and normal window mode
 *
 */
void MainWindow::toggleFullscreen(bool enabled)
{
    if (enabled)
    {
        this->showFullScreen();
    }
    else
    {
        this->showNormal();
    }
}


void MainWindow::toggleMeanwhileFeed()
{
    this->leftPanel->setCurrentIndex(0);
    this->meanwhileFeed->setFocus();
}

void MainWindow::toggleMentionsFeed()
{
    this->leftPanel->setCurrentIndex(1);
    this->mentionsFeed->setFocus();
}

void MainWindow::toggleActionsFeed()
{
    this->leftPanel->setCurrentIndex(2);
    this->actionsFeed->setFocus();
}


void MainWindow::showFirstRunWizard()
{
    FirstRunWizard *frWizard = new FirstRunWizard(m_accountDialog,
                                                  m_profileEditor,
                                                  m_configDialog,
                                                  m_helpWidget,
                                                  this->globalObject,
                                                  this);
    frWizard->show();
}


/*
 * Open website in browser
 *
 */
void MainWindow::visitWebSite()
{
    qDebug() << "Opening website in browser";
    MiscHelpers::openUrl(QUrl("https://jancoding.wordpress.com/dianara"),
                         this);
}

void MainWindow::visitBugTracker()
{
    qDebug() << "Opening bugtracker in browser";
    MiscHelpers::openUrl(QUrl("https://gitlab.com/dianara/dianara-dev/issues"),
                         this);
}

/*
 * Open Pump.io's User Guide in web browser
 * (currently at readthedocs.org/.io)
 *
 */
void MainWindow::visitPumpGuide()
{
    qDebug() << "Opening Pump.io User Guide in browser";
    MiscHelpers::openUrl(QUrl("https://pumpio.readthedocs.io/"
                              "en/latest/userguide.html"),
                         this);
}

void MainWindow::visitTips()
{
    qDebug() << "Opening Pump.io tips in browser";
    MiscHelpers::openUrl(QUrl("https://communicationfreedom.wordpress.com/"
                              "2014/03/17/pump-io-tips/"),
                         this);
}

void MainWindow::visitUserList()
{
    qDebug() << "Opening Pump.io users-by-language wiki page in browser";
    MiscHelpers::openUrl(QUrl("https://github.com/pump-io/"
                              "pump.io/wiki/Users-by-language"),
                         this);
}

void MainWindow::visitPumpStatus()
{
    qDebug() << "Opening Pump.io Network Status website in browser";
    MiscHelpers::openUrl(QUrl("https://sjoberg.fi/pumpcheck.txt"), // TEMPORARY URL -- FIXME
                         this);
    // http://pumpstatus.strugee.net should be used in the near future
    // and something more official further down the road
}




/*
 * About... message
 *
 */
void MainWindow::aboutDianara()
{
    QMessageBox::about(this, tr("About Dianara"),
                       QString("<big><b>Dianara v%1</b></big>")
                       .arg(qApp->applicationVersion())
                       + "<br />"
                         "Copyright 2012-2019  JanKusanagi<br />"
                         "<a href=\"https://jancoding.wordpress.com/dianara\">"
                         "https://jancoding.wordpress.com/dianara</a>"
                         "<br />"
                         "<br />"

                       + tr("Dianara is a pump.io social networking client.")
                       + "<br />"
                         "<br />"

                       + tr("With Dianara you can see your timelines, "
                            "create new posts, upload pictures and "
                            "other media, interact with posts, manage "
                            "your contacts and follow new people.")
                       + "<hr>" // ---

                       + tr("English translation by JanKusanagi.",
                            "TRANSLATORS: Change this with your language and "
                            "name. If there was another translator before you, "
                            "add your name after theirs ;)")
                       + "<br />"
                         "<br />"

                       + tr("Thanks to all the testers, translators and "
                            "packagers, who help make Dianara better!")
                       + "<hr>" // ---

                       + tr("Dianara is Free Software, licensed under the "
                            "GNU GPL license, and uses some Oxygen icons "
                            "under LGPL license.")
                       + "<br />"
                         "<br />"

                         "<a href=\"http://www.gnu.org/licenses/gpl-2.0.html\">"
                         "GNU GPL v2</a> - "
                         "<a href=\"http://www.gnu.org/licenses/lgpl-2.1.html\">"
                         "GNU LGPL v2.1</a>"
                         "<br />"
                         "<a href=\"https://techbase.kde.org/Projects/Oxygen\">"
                         "techbase.kde.org/Projects/Oxygen</a>"
                         "<br />"
                         "<br />"

#ifdef HAVE_KF5
                       + tr("This version of Dianara was built with support "
                            "for some extra features that use %1, also under "
                            "the GNU LGPL.")
                         .arg("<a href=\"https://kde.org/products/frameworks/\">"
                              "KDE Frameworks 5</a>")
                       + "<br />"
                         "<br />"
#endif

                       + QString("<b><a href=\"https://www.qt.io\">"
                                 "Qt</a></b> v%1").arg(qVersion())
                       + QString(" &mdash; %1").arg(qApp->platformName()));
}



void MainWindow::toggleMainWindow(bool firstTime)
{
    bool shouldHide = false;
    if (firstTime)
    {
        shouldHide = this->globalObject->getTrayHideOnStartup();
        if (shouldHide)
        {
            qDebug() << "Hiding window on startup due to configuration";
        }
    }

    if ((this->isHidden() && !shouldHide) || !this->trayIconAvailable)
    {
        this->trayShowWindowAction->setText(tr("&Hide Window"));
        this->show();
        this->activateWindow(); // Try to avoid WM putting the window behind others
        qDebug() << "SHOWING main window";

        // Call adjustTimelineSizes() half a second later
        delayedResizeTimer->stop();
        delayedResizeTimer->start(500);
    }
    else
    {
        this->trayShowWindowAction->setText(tr("&Show Window"));
        this->hide();
        qDebug() << "HIDING main window";
    }
}


/*
 * React to buttons being pressed on a system notification,
 * previously sent by Dianara.
 *
 * Slot connected to a DBus signal.
 *
 */
void MainWindow::onNotificationAction(uint id, QString action)
{
    qDebug() << "Received from org.freedesktop.Notifications:"
             << id << action;
    if (action == QString("dianara_%1_show").arg(qApp->applicationPid()))
    {
        qDebug() << "It's for us!";
        if (this->isHidden())
        {
            this->toggleMainWindow();
        }
        else
        {
            this->activateWindow();
        }
    }
}


void MainWindow::showAuthError(QString title, QString message)
{
    QMessageBox::warning(this, title, message);
}



void MainWindow::onSessionManagerQuitRequest(QSessionManager &manager)
{
    Q_UNUSED(manager)

    std::cout << "Session manager requested shutdown\n";
    std::cout.flush();

    this->reallyQuitProgram = true;
    this->quitProgram(tr("Closing due to environment shutting down..."));
}



/*
 * Close the program. Needed to quit correctly from context menu
 *
 */
void MainWindow::quitProgram(QString reason)
{
    if (this->isHidden())
    {
        this->show();
    }

    saveSettings();


    if (!reallyQuitProgram
     && this->publisher->isFullMode())  // If composer is active, ask for confirmation
    {                                   // FIXME: composing comments should also block here
        int confirmation = QMessageBox::question(this,
                              tr("Quit?"),
                              tr("You are composing a note or a comment.")
                              + "\n\n"
                              + tr("Do you really want to close Dianara?"),
                              tr("&Yes, close the program"), tr("&No"),
                              QString(), 1, 1);
        if (confirmation == 0)
        {
            qDebug() << "Exit confirmed; quitting, bye!";
            reallyQuitProgram = true;
        }
        else
        {
            qDebug() << "Confirmation cancelled, not exiting";
            reallyQuitProgram = false;
        }
    }
    else // FIXME: some redundancies here...
    {
        reallyQuitProgram = true;
    }


    if (reallyQuitProgram)
    {
        if (reason.isEmpty())
        {
            reason = tr("Shutting down Dianara...");
        }
        this->setStatusBarMessage(reason);
        this->globalObject->logMessage(reason);

        std::cout << "\nShutting down Dianara ("
                     + m_userId.toStdString()
                     + ")...\n";
        std::cout.flush();

        this->globalObject->notifyProgramShutdown();

        // Manually stop and remove some stuff; trying to minimize shutdown time
        this->updateTimer->stop();
        this->timestampsTimer->stop();
        this->favoritesReloadTimer->stop();
        this->userDidSomethingTimer->stop();

        this->mainTimeline->clearTimeLineContents(false); // Don't show 'loading'
        this->directTimeline->clearTimeLineContents(false);
        this->activityTimeline->clearTimeLineContents(false);
        this->favoritesTimeline->clearTimeLineContents(false);

        this->meanwhileFeed->clearContents();
        this->mentionsFeed->clearContents();
        this->actionsFeed->clearContents();

        this->publisher->deleteLater();
        this->contactManager->deleteLater();

        m_logViewer->close();
        m_logViewer->deleteLater();
        m_helpWidget->close();
        m_helpWidget->deleteLater();
        m_filterEditor->close();
        m_filterEditor->deleteLater();

        qApp->setQuitOnLastWindowClosed(true);

        // Add more needed shutdown stuff here

        qApp->processEvents(QEventLoop::ExcludeUserInputEvents
                          | QEventLoop::ExcludeSocketNotifiers);

        qApp->closeAllWindows();

        //qApp->processEvents();
        std::cout << "All windows closed, bye! o/\n\n";
        std::cout.flush();


        /* This is needed to avoid problems when building with Qt 5, and
         * running under Plasma 5
         *
         */
        qApp->quit();
    }
}




//////////////////////////////////////////////////////////////////////////////
///////////////////////////////// PROTECTED //////////////////////////////////
//////////////////////////////////////////////////////////////////////////////



void MainWindow::closeEvent(QCloseEvent *event)
{
    qDebug() << "MainWindow::closeEvent()";

    // FIXME: handle cases where tray icon isn't available
    // Handle also really closing via environment shutdown

    if (!trayIconAvailable && !reallyQuitProgram)
    {
        int confirmation = QMessageBox::question(this,
                              tr("Quit?"),
                              tr("System tray icon is not available.")
                              + " "
                              + tr("Dianara cannot be hidden in the "
                                   "system tray.")
                              + "\n\n"
                              + tr("Do you want to close the program "
                                   "completely?"),
                              tr("&Yes, close the program"), tr("&No"),
                              QString(), 1, 1);
        if (confirmation == 0)
        {
            qDebug() << "Main window closed without a tray icon, shutting down";
            reallyQuitProgram = true;
            this->quitProgram();
        }
        else
        {
            qDebug() << "Confirmation cancelled, not closing main window";
            event->ignore();
            return;
        }
    }


    if (reallyQuitProgram)
    {
        event->accept(); // really close, if called from Quit menu
        qDebug() << "Quit called from menu, shutting down program";
    }
    else
    {
        this->toggleMainWindow(); // Hide window, app accessible via tray icon
        qDebug() << "Tried to close main window, so hiding to tray";
        event->ignore();  // ignore the closeEvent
    }
}


void MainWindow::resizeEvent(QResizeEvent *event)
{
    qDebug() << "MainWindow::resizeEvent()" << event->size();

    delayedResizeTimer->stop();
    delayedResizeTimer->start(500); // call adjustTimelineSizes() a little later

    event->accept();
}
