/***************************************************************************
*   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
*                                                                         *
*   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, see <http://www.gnu.org/licenses/>  *
***************************************************************************/
/** @file
* This file defines classes skgmainpanel.
*
* @author Stephane MANKOWSKI / Guillaume DE BURE
*/
#include "skgmainpanel.h"

#include <kaboutdata.h>
#include <kactioncollection.h>
#include <kactionmenu.h>
#include <kcombobox.h>
#include <kconfigdialog.h>
#include <kencodingfiledialog.h>
#include <kformat.h>
#include <kmessagebox.h>
#include <kmessagewidget.h>
#include <knotification.h>
#include <knotifyconfigwidget.h>
#include <kselectaction.h>
#include <kservice.h>
#include <kservicetypetrader.h>
#include <kstandardaction.h>
#include <kstatusnotifieritem.h>
#include <kstringhandler.h>
#include <ktoggleaction.h>
#include <ktoolbar.h>
#include <ktoolbarpopupaction.h>
#include <kxmlguifactory.h>

#ifdef KActivities_FOUND
#include <kactivities/resourceinstance.h>
#endif

#include <QDBusConnection>
#include <QDBusMessage>
#include <qaction.h>
#include <qapplication.h>
#include <qcollator.h>
#include <qcompleter.h>
#include <qdesktopservices.h>
#include <qdir.h>
#include <qdom.h>
#include <qevent.h>
#include <qfiledialog.h>
#include <qheaderview.h>
#include <qlineedit.h>
#include <qmenu.h>
#include <qprocess.h>
#include <qprogressbar.h>
#include <qpushbutton.h>
#include <qscrollarea.h>
#include <qsplashscreen.h>
#include <qtabbar.h>
#include <qtabwidget.h>
#include <qtextcodec.h>
#include <qurlquery.h>

#include <algorithm>    // std::sort

#include "skgdefine.h"
#include "skginterfaceplugin.h"
#include "skgservices.h"
#include "skgtraces.h"
#include "skgtransactionmng.h"
#include "skgtreeview.h"
#include "skgwebview.h"
#include "skgzoomselector.h"

struct doublePointer {
    void* p1;
    void* p2;
};

struct historyPage {
    SKGTabPage::SKGPageHistoryItem current;
    SKGTabPage::SKGPageHistoryItemList next;
    SKGTabPage::SKGPageHistoryItemList previous;
};

struct actionDetails {
    QPointer<QAction> action;
    QStringList tables;
    int min{};
    int max{};
    int ranking{};
    bool focus{};
};
class SKGMainPanelPrivate
{
public:
    Ui::skgmainpanel_base ui{};
    Ui::skgmainpanel_pref uipref{};

    SKGTabWidget* m_tabWidget;
    QSplashScreen* m_splashScreen;

    SKGDocument* m_currentDocument;

    QList<SKGInterfacePlugin*> m_pluginsList;
    QMap<QString, actionDetails > m_registeredGlogalAction;
    QList<historyPage> m_historyClosedPages;

    QMenu* m_contextMenu;
    QAction* m_actHideContextItem;
    QAction* m_actShowAllContextItems;
    QAction* m_actDelete;
    QAction* m_closePageAction;
    QAction* m_closeAllOtherPagesAction;
    QAction* m_switchPinState;
    QAction* m_saveDefaultStateAction;
    QAction* m_resetDefaultStateAction;
    QAction* m_overwriteBookmarkStateAction;
    QAction* m_configureAction;
    QAction* m_enableEditorAction;
    QAction* m_fullScreenAction;
    QAction* m_actLock;
    QAction* m_actUnLock;
    QAction* m_reopenLastClosed;
    KStatusNotifierItem* m_kSystemTrayIcon;
    QLabel* m_kNormalMessage;
    SKGZoomSelector* m_zoomSelector;

    KToolBarPopupAction* m_previousAction;
    KToolBarPopupAction* m_nextAction;
    KToolBarPopupAction* m_buttonMenuAction;
    KToggleAction* m_showMenuBarAction;

    QMenu* m_previousMenu;
    QMenu* m_nextMenu;
    QMenu* m_buttonMenu;
    QWidget* m_mainWidget;
    QVBoxLayout* m_mainLayout{};

    doublePointer m_progressObjects{};
    bool m_middleClick;
    bool m_saveOnClose;
    QString m_fileName;
    SKGWidget* m_widgetHavingSelection;
    QStringList m_tipsOfTheDay;

#ifdef KActivities_FOUND
    KActivities::ResourceInstance* m_activityResourceInstance;
#endif

    static bool m_currentActionCanceled;
    static SKGMainPanel* m_mainPanel;

    SKGMainPanelPrivate()
    {
        m_actHideContextItem = nullptr; m_actShowAllContextItems = nullptr; m_actDelete = nullptr;
        m_closePageAction = nullptr; m_closeAllOtherPagesAction = nullptr; m_switchPinState = nullptr; m_saveDefaultStateAction = nullptr;
        m_resetDefaultStateAction = nullptr; m_overwriteBookmarkStateAction = nullptr; m_configureAction = nullptr; m_enableEditorAction = nullptr; m_fullScreenAction = nullptr; m_actLock = nullptr;
        m_actUnLock = nullptr;
        m_kSystemTrayIcon = nullptr;
        m_kNormalMessage = nullptr; m_zoomSelector = nullptr;  m_previousAction = nullptr; m_nextAction = nullptr; m_buttonMenuAction = nullptr;
        m_mainWidget = nullptr; m_mainLayout = nullptr; m_middleClick = false; m_saveOnClose = false;
        m_widgetHavingSelection = nullptr;
        m_tabWidget = nullptr; m_splashScreen = nullptr; m_currentDocument = nullptr;
        m_contextMenu = nullptr;
        m_reopenLastClosed = nullptr;
        m_showMenuBarAction = nullptr;
        m_previousMenu = nullptr;
        m_nextMenu = nullptr;
        m_buttonMenu = nullptr;
        m_progressObjects.p1 = nullptr;
        m_progressObjects.p2 = nullptr;
#ifdef KActivities_FOUND
        m_activityResourceInstance = nullptr;
#endif
    }

    static int progressBarCallBack(int iPos, qint64 iTime, const QString& iName, void* iProgressBar)
    {
        Q_UNUSED(iTime);
        QProgressBar* progressBar = nullptr;
        QPushButton* button = nullptr;
        auto* pointers = static_cast<doublePointer*>(iProgressBar);
        if (pointers != nullptr) {
            progressBar = static_cast<QProgressBar*>(pointers->p1);
            button = static_cast<QPushButton*>(pointers->p2);
        }

        bool visible = (iPos > 0 && iPos <= 100);
        if (progressBar != nullptr) {
            QString commonFormat = QStringLiteral("%p%");
            /*if (iPos > 0) {
             *           qint64 estimatedTime = 100 * iTime / iPos;
             *           qint64 remainingTime = estimatedTime - iTime;
             *           commonFormat = commonFormat % " - " % i18nc("To print a remaining time (in seconde) / estimated time (in second)", "%1s / %2s",
             *                          SKGServices::intToString(remainingTime / 1000),
             *                          SKGServices::intToString(estimatedTime / 1000));
            }*/
            progressBar->setFormat(iName.isEmpty() ? commonFormat : commonFormat % '\n' % iName);
            progressBar->setValue(iPos);
            progressBar->setVisible(visible);
            if (iPos == 100) {
                QTimer::singleShot(300, Qt::CoarseTimer, progressBar, &QProgressBar::hide);
            }
            progressBar->setToolTip(iName);

            QDBusMessage message = QDBusMessage::createSignal(QLatin1String("/org/") + KAboutData::applicationData().componentName() + QLatin1String("/UnityLauncher"),
                                   QStringLiteral("com.canonical.Unity.LauncherEntry"),
                                   QStringLiteral("Update"));
            message << "application://org.kde.skrooge.desktop";

            QVariantMap setProperty;
            if (iPos == 100) {
                setProperty.insert(QStringLiteral("progress"), 0.0);
                setProperty.insert(QStringLiteral("progress-visible"), false);
            } else {
                setProperty.insert(QStringLiteral("progress"), static_cast<double>(iPos / 100.0));
                setProperty.insert(QStringLiteral("progress-visible"), true);
            }
            message << setProperty;
            QDBusConnection::sessionBus().send(message);
        }
        if (button != nullptr) {
            button->setVisible(visible);
            if (iPos == 100) {
                QTimer::singleShot(300, Qt::CoarseTimer, button, &QPushButton::hide);
            }
        }


        SKGMainPanelPrivate::m_currentActionCanceled = false;
        if (iPos != 0 && iPos != 100) {
            qApp->processEvents(QEventLoop::AllEvents, 500);
        }
        return (SKGMainPanelPrivate::m_currentActionCanceled ? 1 : 0);
    }

    static bool adviceLessThan(const SKGAdvice& s1, const SKGAdvice& s2)
    {
        if (s1.getPriority() == s2.getPriority()) {
            return (s1.getShortMessage() > s2.getShortMessage());
        }
        return (s1.getPriority() > s2.getPriority());
    }

    static void setAttribute(QDomElement& iRoot, const QString& iPath, const QString& iValue)
    {
        int pos = iPath.indexOf('.');
        if (pos == -1) {
            iRoot.setAttribute(iPath, iValue);
        } else {
            QString newElementName = iPath.left(pos);
            QString newAttribue = iPath.right(iPath.count() - pos - 1);

            QDomDocument doc(QStringLiteral("SKGML"));
            doc.setContent(iRoot.attribute(newElementName));
            QDomElement root = doc.documentElement();
            if (root.isNull()) {
                root = doc.createElement(QStringLiteral("parameters"));
                doc.appendChild(root);
            }
            setAttribute(root, newAttribue, iValue);

            iRoot.setAttribute(newElementName, doc.toString());
        }
    }

    void refreshTabPosition()
    {
        m_tabWidget->setTabPosition(static_cast<QTabWidget::TabPosition>(skgbasegui_settings::main_tabs_position()));
    }

    void rebuildSystemTray()
    {
        if (skgbasegui_settings::icon_in_system_tray()) {
            if (m_kSystemTrayIcon == nullptr) {
                m_kSystemTrayIcon = new KStatusNotifierItem(m_mainPanel);
                m_kSystemTrayIcon->setStandardActionsEnabled(true);
                m_kSystemTrayIcon->setAssociatedWidget(m_mainPanel);
                KAboutData about = KAboutData::applicationData();
                m_kSystemTrayIcon->setIconByName(about.programIconName());
            }
        } else {
            if (m_kSystemTrayIcon != nullptr) {
                delete m_kSystemTrayIcon;
                m_kSystemTrayIcon = nullptr;
            }
        }
    }
};


bool SKGMainPanelPrivate::m_currentActionCanceled = false;
SKGMainPanel* SKGMainPanelPrivate::m_mainPanel = nullptr;

SKGMainPanel::SKGMainPanel(QSplashScreen* iSplashScreen, SKGDocument* iDocument)
    :  d(new SKGMainPanelPrivate)
{
    SKGTRACEINFUNC(1);
    d->m_tabWidget = new SKGTabWidget(this);
    d->m_splashScreen = iSplashScreen;
    d->m_currentDocument = iDocument;

    setComponentName(QStringLiteral("skg"), KAboutData::applicationData().displayName());
    setObjectName(qApp->applicationDisplayName());

    // Set main panel
    SKGMainPanelPrivate::m_mainPanel = this;
    auto w = new QScrollArea(this);
    w->setFrameShape(QFrame::NoFrame);
    w->setFocusPolicy(Qt::NoFocus);
    d->m_mainLayout = new QVBoxLayout(w);
    d->m_mainLayout->setSpacing(0);
    d->m_mainLayout->setContentsMargins(0, 0, 0, 0);
    d->m_mainLayout->addWidget(d->m_tabWidget);
    d->ui.setupUi(this);

    // setMainWidget(new QLabel("hello"));

    // Initialize settings
    KSharedConfigPtr config = KSharedConfig::openConfig();
    KConfigGroup prefskg = config->group("Main Panel");
    int option = prefskg.readEntry("update_modified_contexts", 100);
    if (option == 100) {
        // First call, we set default values
        prefskg.writeEntry("update_modified_contexts", 2, KConfigBase::Normal);
        // NEVER: set following setting
        KMessageBox::saveDontShowAgainYesNo(QStringLiteral("updateContextOnClose"), KMessageBox::No);
        SKGTRACEL(1) << "update_modified_contexts set to NEVER" << endl;
        SKGTRACEL(1) << "updateContextOnClose set to No" << endl;
    }

    option = prefskg.readEntry("update_modified_bookmarks", 100);
    if (option == 100) {
        // First call, we set default values
        prefskg.writeEntry("update_modified_bookmarks", 2, KConfigBase::Normal);
        // NEVER: set following setting
        KMessageBox::saveDontShowAgainYesNo(QStringLiteral("updateBookmarkOnClose"), KMessageBox::No);
        SKGTRACEL(1) << "update_modified_bookmarks set to NEVER" << endl;
        SKGTRACEL(1) << "updateBookmarkOnClose set to No" << endl;
    }

    // Search plugins
    KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("SKG GUI/Plugin"));

    // Load plugins
    int nb = offers.count();
    SKGTRACEL(1) << nb << " plugins found" << endl;
    if (d->m_splashScreen != nullptr) {
        d->m_splashScreen->showMessage(i18nc("Splash screen message", "Loading plugins..."), Qt::AlignLeft, QColor(221, 130, 8));    // krazy:exclude=qmethods
    }
    SKGError err;
    QStringList listAuthors;
    QStringList listTasks;
    QStringList listEmails;
    QStringList listOscs;
    for (int i = 0; i < nb; ++i) {
        KService::Ptr service = offers.at(i);
        QString name = service->name();
        QString version = service->property(QStringLiteral("X-KDE-PluginInfo-Version"), QVariant::String).toString();
        QString id = service->property(QStringLiteral("X-Krunner-ID"), QVariant::String).toString();
        QString msg = i18nc("Splash screen message", "Loading plugin %1/%2: %3...", i + 1, nb, name);
        SKGTRACEL(1) << msg << endl;
        if (d->m_splashScreen != nullptr) {
            d->m_splashScreen->showMessage(msg, Qt::AlignLeft, QColor(221, 130, 8));    // krazy:exclude=qmethods
        }
        KPluginLoader loader(service->library());
        if (version.isEmpty() || SKGServices::stringToDouble(version) < 1) {
            SKGTRACE << "WARNING: plugin [" << name << "] not loaded because of version too old (<1)" << endl;
        } else {
            KPluginFactory* factory2 = loader.factory();
            if (factory2 != nullptr) {
                auto* pluginInterface = factory2->create<SKGInterfacePlugin> (this);
                if (pluginInterface != nullptr) {
                    if (pluginInterface->isEnabled() && pluginInterface->setupActions(getDocument())) {
                        // Add plugin in about
                        QStringList listOfAuthors;
                        QStringList listOfEmails;
                        QStringList listOfPlugins;
                        QString author = service->property(QStringLiteral("X-KDE-PluginInfo-Author"), QVariant::String).toString();
                        QString email = service->property(QStringLiteral("X-KDE-PluginInfo-Email"), QVariant::String).toString();
                        if (!author.isEmpty()) {
                            listOfAuthors.push_back(author);
                            listOfEmails.push_back(email);
                            listOfPlugins.push_back(name);
                        }
                        const auto subPlugins = pluginInterface->subPlugins();
                        for (const QString& subPlugin : subPlugins) {
                            KService::List subOffers = KServiceTypeTrader::self()->query(subPlugin);
                            int nbSubOffers = subOffers.count();
                            for (int j = 0; j < nbSubOffers; ++j) {
                                KService::Ptr subService = subOffers.at(j);
                                QString author2 = subService->property(QStringLiteral("X-KDE-PluginInfo-Author"), QVariant::String).toString();
                                QString email2 = subService->property(QStringLiteral("X-KDE-PluginInfo-Email"), QVariant::String).toString();
                                if (!author2.isEmpty()) {
                                    listOfAuthors.push_back(author2);
                                    listOfEmails.push_back(email2);
                                    listOfPlugins.push_back(subService->name());
                                }
                            }
                        }

                        int nbAuthors = listOfAuthors.count();
                        for (int j = 0; j < nbAuthors; ++j) {
                            QString authorId;
                            QString author = listOfAuthors.at(j);
                            QStringList authors = SKGServices::splitCSVLine(author, ',');
                            if (authors.count() == 2) {
                                author = authors.at(0);
                                authorId = authors.at(1);
                            }
                            int pos2 = listAuthors.indexOf(author);
                            if (pos2 == -1) {
                                listAuthors.push_back(author);
                                listTasks.push_back(i18n("Developer of plugin '%1'", listOfPlugins.at(j)));
                                listEmails.push_back(listOfEmails.at(j));
                                listOscs.push_back(authorId);
                            } else {
                                listTasks[pos2] += i18n(", '%1'", listOfPlugins.at(j));
                            }
                        }

                        // Store plugin
                        int nbplugin = d->m_pluginsList.count();
                        int pos3 = nbplugin;
                        for (int j = nbplugin - 1; j >= 0; --j) {
                            if (pluginInterface->getOrder() < d->m_pluginsList.at(j)->getOrder()) {
                                pos3 = j;
                            }
                        }

                        d->m_pluginsList.insert(pos3, pluginInterface);
                        pluginInterface->setObjectName(id);

                        // Add tips
                        d->m_tipsOfTheDay += pluginInterface->tips();
                    }
                }
            } else {
                QString msg2 = i18nc("An information message",  "Loading plugin %1 failed because the factory could not be found in %2: %3", id, service->library(), loader.errorString());
                getDocument()->sendMessage(msg2);
                SKGTRACEL(1) << "WARNING:" << msg2 << endl;
            }
        }
    }

    // Add credits
    KAboutData about = KAboutData::applicationData();
    nb = listAuthors.count();
    for (int i = 0; i < nb; ++i) {
        about.addCredit(listAuthors.at(i), listTasks.at(i), listEmails.at(i), QLatin1String(""), listOscs.at(i));
    }
    KAboutData::setApplicationData(about);

    // accept dnd
    setAcceptDrops(true);

    // tell the KXmlGuiWindow that this is indeed the main widget
    setCentralWidget(w);

    d->refreshTabPosition();
    d->m_tabWidget->setMovable(true);
    d->m_tabWidget->setTabsClosable(true);
    d->m_tabWidget->setUsesScrollButtons(true);
    d->m_tabWidget->tabBar()->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab);
    d->m_tabWidget->setDocumentMode(true);
    d->m_tabWidget->show();

    // System tray
    d->rebuildSystemTray();

    // then, setup our actions
    setupActions();

    displayErrorMessage(err);

    // Add a status bar
    statusBar()->show();

    QPalette palette2 = QApplication::palette();
    palette2.setColor(QPalette::Base, Qt::transparent);
    d->ui.kContextList->setPalette(palette2);

    QAction* toggle = d->ui.kDockContext->toggleViewAction();
    QAction* panelAction = actionCollection()->addAction(QStringLiteral("view_context"));
    registerGlobalAction(QStringLiteral("view_context"), panelAction);
    panelAction->setCheckable(true);
    panelAction->setChecked(toggle->isChecked());
    panelAction->setText(toggle->text());
    actionCollection()->setDefaultShortcut(panelAction, Qt::SHIFT + Qt::Key_F9);
    connect(panelAction, &QAction::triggered, toggle, &QAction::trigger);
    connect(toggle, &QAction::toggled, panelAction, &QAction::setChecked);

    QAction* toggle2 = d->ui.kDockMessages->toggleViewAction();
    QAction* panelAction2 = actionCollection()->addAction(QStringLiteral("view_messages"));
    registerGlobalAction(QStringLiteral("view_messages"), panelAction2);
    panelAction2->setCheckable(true);
    panelAction2->setChecked(toggle2->isChecked());
    panelAction2->setText(toggle2->text());
    actionCollection()->setDefaultShortcut(panelAction2, Qt::SHIFT + Qt::Key_F8);
    connect(panelAction2, &QAction::triggered, toggle2, &QAction::trigger);
    connect(toggle2, &QAction::toggled, panelAction2, &QAction::setChecked);

    auto contextMenu = new KSelectAction(SKGServices::fromTheme(QStringLiteral("tab-new")), i18nc("Noun", "Pages"), this);
    registerGlobalAction(QStringLiteral("view_contextmenu"), contextMenu);

    // Add plugin in client in right order
    QList<QListWidgetItem*> pageNotVisible;
    KConfigGroup prefContextVisibility = config->group("Context Visibility");
    int shortCutIndex = 0;
    QString shortCutPrefix = QStringLiteral("Ctrl+");
    int nbplugin = d->m_pluginsList.count();
    QList<QAction*> contextActionList;
    for (int j = 0; j < nbplugin; ++j) {
        SKGInterfacePlugin* pluginInterface = d->m_pluginsList.at(j);
        if (pluginInterface != nullptr) {
            // Creation of the item
            QString title = pluginInterface->title();
            SKGTRACEL(1) << "Add plugin (" << pluginInterface->getOrder() << ") : " << title << endl;
            if (!title.isEmpty()) {
                // Add menu item with shortcuts
                if (pluginInterface->isInPagesChooser()) {
                    bool visible = prefContextVisibility.readEntry(pluginInterface->objectName(), true);
                    QIcon icon = SKGServices::fromTheme(pluginInterface->icon());

                    auto contextItem = new QListWidgetItem(icon, title);
                    contextItem->setStatusTip(pluginInterface->statusTip());
                    contextItem->setToolTip(pluginInterface->toolTip());
                    contextItem->setData(12, j);     // context item ==> plugin
                    int page2 = d->ui.kContextList->count();
                    pluginInterface->setProperty("contextItem", page2);  // plugin ==> context item

                    d->ui.kContextList->addItem(contextItem);

                    QAction* newAction = contextMenu->addAction(icon, title);
                    if (newAction != nullptr) {
                        newAction->setCheckable(false);
                        newAction->setData(page2);
                        contextItem->setData(15, QVariant::fromValue(static_cast<void*>(newAction)));     // context item ==> action
                        if (!shortCutPrefix.isEmpty()) {
                            ++shortCutIndex;
                            if (shortCutIndex == 10) {
                                shortCutIndex = 0;
                                if (shortCutPrefix == QStringLiteral("Ctrl+")) {
                                    shortCutPrefix += QStringLiteral("Alt+");
                                } else {
                                    shortCutPrefix = QLatin1String("");
                                }
                            }
                            if (!shortCutPrefix.isEmpty()) {
                                actionCollection()->setDefaultShortcut(newAction, QString(shortCutPrefix % SKGServices::intToString(shortCutIndex)));
                            }
                        }
                        connect(newAction, &QAction::triggered, this, &SKGMainPanel::onOpenContext);

                        contextActionList.append(newAction);

                        if (!visible) {
                            pageNotVisible.push_back(contextItem);
                        }

                        // Register action
                        QString id = "page_" % pluginInterface->objectName();
                        registerGlobalAction(id, newAction);
                        registerGlobalAction(id, newAction);
                    }
                }

                // Create dock if needed
                QDockWidget* dockWidget = pluginInterface->getDockWidget();
                if (dockWidget != nullptr) {
                    addDockWidget(Qt::LeftDockWidgetArea, dockWidget);
                    tabifyDockWidget(d->ui.kDockContext, dockWidget);
                    tabifyDockWidget(dockWidget, d->ui.kDockContext);
                }
            }
        }
    }

    // Lock docs if needed
    KConfigGroup pref = getMainConfigGroup();
    if (pref.readEntry("docks_locked", false)) {
        onLockDocks();
    }

    if (!pref.readEntry("first_launch", false)) {
        this->setWindowState(windowState() | Qt::WindowMaximized);
        pref.writeEntry("first_launch", true);
    }

    // a call to KXmlGuiWindow::setupGUI() populates the GUI
    // with actions, using KXMLGUI.
    // It also applies the saved mainwindow settings, if any, and ask the
    // mainwindow to automatically save settings if changed: window size,
    // toolbar position, icon size, etc.
    setupGUI(Default, QStringLiteral("skgmainpanel.rc"));
    plugActionList(QStringLiteral("context_actionlist"), contextActionList);

    for (int j = 0; j < nbplugin; ++j) {
        SKGInterfacePlugin* pluginInterface = d->m_pluginsList.at(j);
        if (pluginInterface != nullptr) {
            QString title = pluginInterface->title();
            SKGTRACEL(1) << "Add plugin client (" << pluginInterface->getOrder() << ") : " << title << endl;
            guiFactory()->addClient(pluginInterface);
        }
    }

    // Hide items in context
    nb = pageNotVisible.count();
    for (int i = 0; i < nb; ++i) {
        setContextVisibility(pageNotVisible.at(i), false);
    }

    // Set status bar
    d->m_kNormalMessage = new QLabel(this);
    d->m_kNormalMessage->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred));

    auto kProgressBar = new QProgressBar(this);
    kProgressBar->setObjectName(QStringLiteral("kProgressBar"));
    kProgressBar->setValue(0);
    kProgressBar->setMinimumSize(100, 20);
    QSizePolicy sizePolicyProgessBar(QSizePolicy::Fixed, QSizePolicy::Expanding);
    kProgressBar->setSizePolicy(sizePolicyProgessBar);
    kProgressBar->setToolTip(i18nc("Widget description", "Progress of the current action"));
    QFont f = kProgressBar->font();
    f.setPointSize(6.0);
    kProgressBar->setFont(f);
    kProgressBar->setVisible(false);
    kProgressBar->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
    kProgressBar->setRange(0, 100);

    d->m_zoomSelector = new SKGZoomSelector(this);
    d->m_zoomSelector->setSizePolicy(sizePolicyProgessBar);
    connect(d->m_zoomSelector, &SKGZoomSelector::changed, this, &SKGMainPanel::onZoomChanged);

    statusBar()->addWidget(d->m_kNormalMessage, 1);
    statusBar()->addPermanentWidget(kProgressBar);

    // Set Cancel button
    auto kCancelButton = new QPushButton();
    kCancelButton->setObjectName(QStringLiteral("kCancelButton"));
    kCancelButton->setIcon(SKGServices::fromTheme(QStringLiteral("media-playback-stop")));
    kCancelButton->setToolTip(i18nc("Widget description", "Cancel the current action"));
    kCancelButton->setStatusTip(i18nc("Widget description", "Cancel the current action"));
    kCancelButton->setVisible(false);
    connect(kCancelButton, &QPushButton::clicked, this, &SKGMainPanel::onCancelCurrentAction);

    //
    d->ui.kClearMessageBtn->setIcon(SKGServices::fromTheme(QStringLiteral("edit-clear")));
    connect(d->ui.kClearMessageBtn, &QPushButton::clicked, this, &SKGMainPanel::onClearMessages);

    // Add special button in toolbar
    KToolBar* tb = toolBar();
    if (tb != nullptr) {
        auto label = new QLabel(this);
        QSizePolicy sizePolicyLabel(QSizePolicy::Expanding, QSizePolicy::Preferred);
        sizePolicyLabel.setHorizontalStretch(0);
        sizePolicyLabel.setVerticalStretch(0);
        sizePolicyLabel.setHeightForWidth(label->sizePolicy().hasHeightForWidth());
        label->setSizePolicy(sizePolicyLabel);
        tb->addWidget(label);
        tb->addAction(d->m_buttonMenuAction);
    }

    statusBar()->addPermanentWidget(kCancelButton);
    statusBar()->addPermanentWidget(d->m_zoomSelector);

    // Set progress bar call back
    if (getDocument() != nullptr) {
        d->m_progressObjects.p1 = static_cast<void*>(kProgressBar);
        d->m_progressObjects.p2 = static_cast<void*>(kCancelButton);
        getDocument()->setProgressCallback(&SKGMainPanelPrivate::progressBarCallBack, static_cast<void*>(&d->m_progressObjects));
    }

    // Connection
    if (getDocument() != nullptr) {
        connect(getDocument(), &SKGDocument::transactionSuccessfullyEnded, this, &SKGMainPanel::refresh, Qt::QueuedConnection);
        connect(getDocument(), &SKGDocument::transactionSuccessfullyEnded, this, &SKGMainPanel::notify, Qt::QueuedConnection);
    }
    connect(d->ui.kContextList, &QListWidget::itemPressed, this, &SKGMainPanel::onBeforeOpenContext);
    connect(d->ui.kContextList, &QListWidget::itemPressed, this, &SKGMainPanel::onOpenContext);
    connect(d->m_tabWidget->tabBar(), &QTabBar::tabCloseRequested, this, &SKGMainPanel::closePageByIndex);
    connect(d->m_tabWidget->tabBar(), &QTabBar::tabBarClicked, this, [ = ](int index) {
        if ((QApplication::mouseButtons() & Qt::MidButton) != 0u) {
            closePageByIndex(index);
        }
    });
    connect(d->m_tabWidget->tabBar(), &QTabBar::tabBarDoubleClicked, this, &SKGMainPanel::addTab);
    connect(d->m_tabWidget->tabBar(), &QTabBar::currentChanged, this, &SKGMainPanel::currentPageChanged);
    connect(this, &SKGMainPanel::currentPageChanged, this, &SKGMainPanel::refresh);
    connect(this, &SKGMainPanel::currentPageChanged, this, &SKGMainPanel::selectionChanged);

    // Refresh
    refresh();

    d->ui.kContextList->installEventFilter(this);

    // Build contextual menu
    d->ui.kContextList->setContextMenuPolicy(Qt::CustomContextMenu);

    d->m_contextMenu = new QMenu(d->ui.kContextList);
    d->m_actHideContextItem = d->m_contextMenu->addAction(SKGServices::fromTheme(QStringLiteral("layer-visible-off")), i18nc("Verb", "Hide"));
    connect(d->m_actHideContextItem, &QAction::triggered, this, &SKGMainPanel::onHideContextItem);

    d->m_actShowAllContextItems = d->m_contextMenu->addAction(SKGServices::fromTheme(QStringLiteral("layer-visible-on")), i18nc("Verb", "Show all"));
    connect(d->m_actShowAllContextItems, &QAction::triggered, this, &SKGMainPanel::onShowAllContextItems);

    d->m_contextMenu->addSeparator();
    d->m_contextMenu->addAction(getGlobalAction(QStringLiteral("tab_savedefaultstate")));
    d->m_contextMenu->addAction(getGlobalAction(QStringLiteral("tab_resetdefaultstate")));

    connect(d->ui.kContextList, &QListWidget::customContextMenuRequested, this, &SKGMainPanel::showMenu);

#ifdef KActivities_FOUND
    // Initialize kactivities resource instance
    d->m_activityResourceInstance = new KActivities::ResourceInstance(window()->winId());
    d->m_activityResourceInstance->setParent(this);
#endif

    // Show menu bar and status bar
    menuBar()->show();
    statusBar()->show();

    notify();  // Due to sendMessage not in a transaction
    d->m_splashScreen = nullptr;
}

SKGMainPanel::~SKGMainPanel()
{
    SKGTRACEINFUNC(1);
    SKGMainPanelPrivate::m_mainPanel = nullptr;
    disconnect(getDocument(), nullptr, this, nullptr);

    // close plugins
    int nb = d->m_pluginsList.count();
    for (int i = 0; i < nb; ++i) {
        getPluginByIndex(i)->close();
    }

    if (getDocument() != nullptr) {
        getDocument()->close();
    }
    delete d;
}

void SKGMainPanel::showMenu(const QPoint iPos)
{
    if (d->m_contextMenu != nullptr) {
        d->m_contextMenu->popup(d->ui.kContextList->mapToGlobal(iPos));
    }
}

void SKGMainPanel::setContextVisibility(QListWidgetItem* iItem, bool iVisibility)
{
    if (iItem != nullptr) {
        // Hide item in context
        iItem->setHidden(!iVisibility);

        // Hide corresponding action
        QAction* act = static_cast< QAction* >(iItem->data(15).value<void*>());
        if (act != nullptr) {
            act->setVisible(iVisibility);
        }

        // Save state in settings
        SKGInterfacePlugin* plugin = getPluginByIndex(iItem->data(12).toInt());
        if (plugin != nullptr) {
            KSharedConfigPtr config = KSharedConfig::openConfig();
            KConfigGroup pref = config->group("Context Visibility");
            pref.writeEntry(plugin->objectName(), iVisibility);
        }
    }
}

void SKGMainPanel::setContextVisibility(int iPage, bool iVisibility)
{
    setContextVisibility(d->ui.kContextList->item(iPage), iVisibility);
}

void SKGMainPanel::onHideContextItem()
{
    setContextVisibility(d->ui.kContextList->currentRow(), false);
}

void SKGMainPanel::onShowAllContextItems()
{
    int nb = d->ui.kContextList->count();
    for (int i = 0; i < nb; ++i) {
        setContextVisibility(i, true);
    }
}

SKGDocument* SKGMainPanel::getDocument() const
{
    return d->m_currentDocument;
}

QStringList SKGMainPanel::processArguments(const QStringList& iArgument)
{
    QStringList output = iArgument;
    for (auto plugin : qAsConst(d->m_pluginsList)) {
        if (plugin != nullptr) {
            output = plugin->processArguments(output);
        }
    }
    return output;
}

void SKGMainPanel::registerGlobalAction(const QString& iIdentifier, QAction* iAction, bool iAddInCollection,
                                        const QStringList& iListOfTable, int iMinSelection, int iMaxSelection,
                                        int iRanking, bool iSelectionMustHaveFocus)
{
    if (iAction == nullptr) {
        SKGTRACE << "WARNING: registerGlobalAction(" << iIdentifier << ",nullptr)" << endl;
    } else {
        QStringList keys = d->m_registeredGlogalAction.keys();
        for (const auto& id : qAsConst(keys)) {
            QPointer<QAction> act = d->m_registeredGlogalAction.value(id).action;
            if ((act != nullptr) && iIdentifier != id && act != iAction && !act->shortcut().isEmpty() && act->shortcut() == iAction->shortcut()) {
                SKGTRACE << "WARNING: The actions [" << iAction->text() << " (" << iIdentifier << ")] and [" << act->text() << " (" << id << ")] has same shortcut [" << iAction->shortcut().toString() << "]" << endl;
            }
        }
        actionDetails actDetails;
        actDetails.action = iAction;
        actDetails.tables = iListOfTable;
        actDetails.min = iMinSelection;
        actDetails.max = iMaxSelection;
        actDetails.focus = iSelectionMustHaveFocus;
        actDetails.ranking = (iRanking == -1 ? 10 * (d->m_registeredGlogalAction.count() + 1) : iRanking);
        d->m_registeredGlogalAction[iIdentifier] = actDetails;

        // This connect has not been migrated on new connect mechanism to avoid crash when leaving the application
        connect(iAction, SIGNAL(destroyed(QObject*)), this, SLOT(unRegisterGlobalAction(QObject*)));  // clazy:exclude=old-style-connect
        if (iAddInCollection) {
            QKeySequence shortCut = iAction->shortcut();
            if (!shortCut.isEmpty()) {
                iAction->setShortcut(QKeySequence());
            }
            actionCollection()->addAction(iIdentifier, iAction);
            if (!shortCut.isEmpty()) {
                actionCollection()->setDefaultShortcut(iAction, shortCut);
            }
        }
    }
}

void SKGMainPanel::unRegisterGlobalAction(QObject* iAction)
{
    auto* act = qobject_cast< QAction* >(iAction);
    if (act != nullptr) {
        const auto keys = d->m_registeredGlogalAction.keys();
        for (const auto& id : keys) {
            if (d->m_registeredGlogalAction.value(id).action == QPointer<QAction>(act)) {
                d->m_registeredGlogalAction.remove(id);
            }
        }
    }
}

QPointer<QAction> SKGMainPanel::getGlobalAction(const QString& iIdentifier, bool iWarnIfNotExist)
{
    QAction* act = d->m_registeredGlogalAction.value(iIdentifier).action;
    if (act == nullptr && iWarnIfNotExist) {
        SKGTRACE << "WARNING: getGlobalAction(" << iIdentifier << ")=nullptr" << endl;
    }
    return act;
}

QList< QPointer< QAction > > SKGMainPanel::getActionsForContextualMenu(const QString& iTable)
{
    // Filter action
    QVector<actionDetails> tmp;
    for (const auto& actDetails : qAsConst(d->m_registeredGlogalAction)) {
        if (actDetails.ranking > 0 && actDetails.min > 0) {
            if (actDetails.tables.isEmpty() || actDetails.tables.contains(iTable)) {
                tmp.push_back(actDetails);
            } else if (actDetails.tables.count() == 1 && actDetails.tables.at(0).startsWith(QLatin1String("query:"))) {
                // Dynamic mode
                QStringList tmpListTable;
                getDocument()->getDistinctValues(QStringLiteral("sqlite_master"), QStringLiteral("name"), actDetails.tables.at(0).right(actDetails.tables.at(0).count() - 6), tmpListTable);
                if (tmpListTable.contains(iTable)) {
                    tmp.push_back(actDetails);
                }
            }
        }
    }

    // Sort
    std::sort(tmp.begin(), tmp.end(), [&](const actionDetails & a, const actionDetails & b) {
        return a.ranking < b.ranking;
    });

    // Generate output
    int previousGroup = -1;
    QList< QPointer< QAction > > output;
    output.reserve(tmp.count());
    for (const auto& actDetails : qAsConst(tmp)) {
        int currentGroup = (actDetails.ranking) / 100;
        if (currentGroup != previousGroup) {
            output.push_back(nullptr);
            previousGroup = currentGroup;
        }
        output.push_back(actDetails.action);
    }
    return output;
}

QMap<QString, QPointer<QAction> > SKGMainPanel::getGlobalActions() const
{
    QMap<QString, QPointer<QAction> > map;
    const auto keys = d->m_registeredGlogalAction.keys();
    for (const auto& id : keys) {
        map[id] = d->m_registeredGlogalAction[id].action;
    }
    return map;
}

SKGInterfacePlugin* SKGMainPanel::getPluginByIndex(int iIndex)
{
    SKGInterfacePlugin* output = nullptr;
    if (iIndex >= 0 && iIndex < d->m_pluginsList.count()) {
        output = d->m_pluginsList.value(iIndex);
    }

    return output;
}

SKGInterfacePlugin* SKGMainPanel::getPluginByName(const QString& iName)
{
    SKGInterfacePlugin* output = nullptr;
    int nbplugin = d->m_pluginsList.count();
    QString name = iName.toLower();
    for (int j = 0; (output == nullptr) && j < nbplugin; ++j) {
        QString namep = d->m_pluginsList.at(j)->objectName().toLower();
        if (namep == name || namep.replace(' ', '_') == name) {
            output = d->m_pluginsList.at(j);
        }
    }

    return output;
}

void SKGMainPanel::setupActions()
{
    SKGTRACEINFUNC(1);

    // Std File
    KStandardAction::quit(this, SLOT(onQuitAction()), actionCollection());
    KStandardAction::configureNotifications(this, SLOT(onConfigureNotifications()), actionCollection());

    // Preferences
    KStandardAction::preferences(this, SLOT(optionsPreferences()), actionCollection());

    // New Tab
    auto actAddTab = new QAction(SKGServices::fromTheme(QStringLiteral("tab-new-background")), i18nc("Noun, user action", "New Tab"), this);
    actionCollection()->setDefaultShortcut(actAddTab, Qt::CTRL + Qt::SHIFT + Qt::Key_W);
    connect(actAddTab, &QAction::triggered, this, &SKGMainPanel::addTab);
    registerGlobalAction(QStringLiteral("new_tab"), actAddTab, true, QStringList(), -1);

    // Add new tab widget
    auto addTabButton = new QToolButton(this);
    addTabButton->setIcon(actAddTab->icon());
    addTabButton->setAutoRaise(true);
    addTabButton->raise();
    addTabButton->setDefaultAction(actAddTab);
    addTabButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
    addTabButton->setFocusPolicy(Qt::NoFocus);
    d->m_tabWidget->setCornerWidget(addTabButton);

    d->m_actLock = new QAction(SKGServices::fromTheme(QStringLiteral("document-encrypt")), i18nc("Verb", "Lock panels"), this);
    connect(d->m_actLock, &QAction::triggered, this, &SKGMainPanel::onLockDocks);
    registerGlobalAction(QStringLiteral("view_lock"), d->m_actLock);

    d->m_actUnLock = new QAction(SKGServices::fromTheme(QStringLiteral("document-decrypt")), i18nc("Verb", "Unlock panels"), this);
    connect(d->m_actUnLock, &QAction::triggered, this, &SKGMainPanel::onUnlockDocks);
    registerGlobalAction(QStringLiteral("view_unlock"), d->m_actUnLock);

    d->m_switchPinState = new QAction(SKGServices::fromTheme(QStringLiteral("document-encrypt")), i18nc("Noun, user action", "Pin this page"), this);
    connect(d->m_switchPinState, &QAction::triggered, this, [ = ] {this->switchPinPage(nullptr);});
    registerGlobalAction(QStringLiteral("tab_switchpin"), d->m_switchPinState);

    //
    d->m_closePageAction = actionCollection()->addAction(KStandardAction::Close, QStringLiteral("tab_close"), this, SLOT(closeCurrentPage()));
    registerGlobalAction(QStringLiteral("tab_close"), d->m_closePageAction);

    //
    auto actCloseAllPages = new QAction(SKGServices::fromTheme(QStringLiteral("window-close")), i18nc("Noun, user action", "Close All"), this);
    actionCollection()->setDefaultShortcut(actCloseAllPages, Qt::ALT + Qt::Key_W);
    connect(actCloseAllPages, &QAction::triggered, this, &SKGMainPanel::closeAllPages);
    registerGlobalAction(QStringLiteral("tab_closeall"), actCloseAllPages, true, QStringList(), -1);

    //
    d->m_closeAllOtherPagesAction = new QAction(SKGServices::fromTheme(QStringLiteral("window-close")), i18nc("Noun, user action", "Close All Other"), this);
    actionCollection()->setDefaultShortcut(d->m_closeAllOtherPagesAction, Qt::CTRL + Qt::ALT + Qt::Key_W);
    connect(d->m_closeAllOtherPagesAction, &QAction::triggered, this, [ = ] {this->closeAllOtherPages(nullptr);});
    registerGlobalAction(QStringLiteral("tab_closeallother"), d->m_closeAllOtherPagesAction);

    //
    d->m_saveDefaultStateAction = new QAction(SKGServices::fromTheme(QStringLiteral("document-save")), i18nc("Noun, user action", "Save page state"), this);
    actionCollection()->setDefaultShortcut(d->m_saveDefaultStateAction, Qt::CTRL + Qt::ALT + Qt::Key_S);
    connect(d->m_saveDefaultStateAction, &QAction::triggered, this, &SKGMainPanel::saveDefaultState);
    registerGlobalAction(QStringLiteral("tab_savedefaultstate"), d->m_saveDefaultStateAction);

    //
    d->m_resetDefaultStateAction = new QAction(SKGServices::fromTheme(QStringLiteral("edit-clear")), i18nc("Noun, user action", "Reset page state"), this);
    actionCollection()->setDefaultShortcut(d->m_resetDefaultStateAction, Qt::CTRL + Qt::ALT + Qt::Key_R);
    connect(d->m_resetDefaultStateAction, &QAction::triggered, this, &SKGMainPanel::resetDefaultState);
    registerGlobalAction(QStringLiteral("tab_resetdefaultstate"), d->m_resetDefaultStateAction);

    //
    d->m_reopenLastClosed = new QAction(SKGServices::fromTheme(QStringLiteral("tab-new")), i18nc("Noun, user action", "Reopen last page closed"), this);
    actionCollection()->setDefaultShortcut(d->m_reopenLastClosed, Qt::CTRL + Qt::ALT + Qt::Key_T);
    connect(d->m_reopenLastClosed, &QAction::triggered, this, &SKGMainPanel::onReopenLastClosed);
    registerGlobalAction(QStringLiteral("tab_reopenlastclosed"), d->m_reopenLastClosed);

    //
    QStringList overlay;
    overlay.push_back(QStringLiteral("bookmarks"));
    d->m_overwriteBookmarkStateAction = new QAction(SKGServices::fromTheme(QStringLiteral("document-save"), overlay), i18nc("Noun, user action", "Overwrite bookmark state"), this);
    connect(d->m_overwriteBookmarkStateAction, &QAction::triggered, this, &SKGMainPanel::overwriteBookmarkState);
    actionCollection()->setDefaultShortcut(d->m_overwriteBookmarkStateAction, Qt::CTRL + Qt::ALT + Qt::Key_B);
    registerGlobalAction(QStringLiteral("tab_overwritebookmark"), d->m_overwriteBookmarkStateAction);

    //
    d->m_configureAction = new QAction(SKGServices::fromTheme(QStringLiteral("configure")), i18nc("Noun, user action", "Configure..."), this);
    connect(d->m_configureAction, &QAction::triggered, this, [ = ] {this->optionsPreferences();});
    registerGlobalAction(QStringLiteral("tab_configure"), d->m_configureAction);

    // Menu
    d->m_buttonMenuAction = new KToolBarPopupAction(SKGServices::fromTheme(QStringLiteral("configure")), QLatin1String(""), this);
    d->m_buttonMenuAction->setToolTip(i18nc("Noun, user action", "Menu"));
    d->m_buttonMenu = d->m_buttonMenuAction->menu();
    connect(d->m_buttonMenu, &QMenu::aboutToShow, this, &SKGMainPanel::onShowButtonMenu);
    d->m_buttonMenuAction->setDelayed(false);
    registerGlobalAction(QStringLiteral("view_menu"), d->m_buttonMenuAction);

    d->m_showMenuBarAction = KStandardAction::showMenubar(this, SLOT(onShowMenuBar()), actionCollection());
    KConfigGroup pref = getMainConfigGroup();
    d->m_showMenuBarAction->setChecked(pref.readEntry("menubar_shown", true));
    QTimer::singleShot(200, Qt::CoarseTimer, this, &SKGMainPanel::onShowMenuBar);
    registerGlobalAction(QStringLiteral("options_show_menubar"), d->m_showMenuBarAction);

    //
    d->m_previousAction = new KToolBarPopupAction(SKGServices::fromTheme(QStringLiteral("go-previous")), i18nc("Noun, user action", "Previous"), this);
    connect(d->m_previousAction, &QAction::triggered, this, &SKGMainPanel::onPrevious);
    actionCollection()->setDefaultShortcut(d->m_previousAction, Qt::ALT + Qt::Key_Left);
    d->m_previousAction->setPriority(QAction::LowPriority);
    d->m_previousMenu = d->m_previousAction->menu();
    connect(d->m_previousMenu, &QMenu::aboutToShow, this, &SKGMainPanel::onShowPreviousMenu);
    d->m_previousAction->setStickyMenu(false);
    d->m_previousAction->setData(0);
    registerGlobalAction(QStringLiteral("go_previous"), d->m_previousAction);

    //
    d->m_nextAction = new KToolBarPopupAction(SKGServices::fromTheme(QStringLiteral("go-next")), i18nc("Noun, user action", "Next"), this);
    connect(d->m_nextAction, &QAction::triggered, this, &SKGMainPanel::onNext);
    actionCollection()->setDefaultShortcut(d->m_nextAction, Qt::ALT + Qt::Key_Right);
    d->m_nextAction->setPriority(QAction::LowPriority);
    d->m_nextMenu = d->m_nextAction->menu();
    connect(d->m_nextMenu, &QMenu::aboutToShow, this, &SKGMainPanel::onShowNextMenu);
    d->m_nextAction->setStickyMenu(false);
    d->m_nextAction->setData(0);
    registerGlobalAction(QStringLiteral("go_next"), d->m_nextAction);

    //
    d->m_fullScreenAction = actionCollection()->addAction(KStandardAction::FullScreen, QStringLiteral("fullscreen"), this, SLOT(onFullScreen()));
    registerGlobalAction(QStringLiteral("fullscreen"), d->m_fullScreenAction);

    //
    d->m_enableEditorAction = new QAction(SKGServices::fromTheme(QStringLiteral("appointment-new")), i18nc("Noun, user action", "Enable editor"), this);
    actionCollection()->setDefaultShortcut(d->m_enableEditorAction, Qt::CTRL + Qt::Key_Insert);
    connect(d->m_enableEditorAction, &QAction::triggered, this, &SKGMainPanel::enableEditor);
    registerGlobalAction(QStringLiteral("enable_editor"), d->m_enableEditorAction);

    //
    auto migrateSQLCipher = new QAction(SKGServices::fromTheme(QStringLiteral("run-build")), i18nc("Noun, user action", "Migrate to SQLCipher format"), this);
    connect(migrateSQLCipher, &QAction::triggered, this, &SKGMainPanel::onMigrateToSQLCipher);
    registerGlobalAction(QStringLiteral("migrate_sqlcipher"), migrateSQLCipher);

    // Contextual menu
    d->m_tabWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("new_tab")));
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("tab_reopenlastclosed")));
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("tab_close")));
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("tab_closeall")));
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("tab_closeallother")));
    {
        auto sep = new QAction(this);
        sep->setSeparator(true);
        d->m_tabWidget->insertAction(nullptr, sep);
    }
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("tab_switchpin")));
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("tab_resetdefaultstate")));
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("tab_savedefaultstate")));
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("tab_overwritebookmark")));
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("tab_configure")));
    {
        auto sep = new QAction(this);
        sep->setSeparator(true);
        d->m_tabWidget->insertAction(nullptr, sep);
    }
    d->m_tabWidget->insertAction(nullptr, getGlobalAction(QStringLiteral("fullscreen")));
}

void SKGMainPanel::enableEditor()
{
    SKGTabPage* cPage = currentPage();
    if (cPage != nullptr) {
        cPage->activateEditor();
    }
}

void SKGMainPanel::onPrevious()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);

    SKGTabPage* cPage = currentPage();
    if (cPage != nullptr) {
        // Get index in history of page to refresh
        int indexPrevious = qobject_cast<QAction*>(sender())->data().toInt();

        // Get lists
        SKGTabPage::SKGPageHistoryItemList listPrevious = cPage->getPreviousPages();
        if (indexPrevious < listPrevious.count()) {
            SKGTabPage::SKGPageHistoryItemList listNext = cPage->getNextPages();
            SKGTabPage::SKGPageHistoryItem current = currentPageHistoryItem();

            // Get item to refresh
            SKGTabPage::SKGPageHistoryItem item = listPrevious.at(indexPrevious);

            // Open page
            cPage = openPage(getPluginByName(item.plugin), currentPageIndex(), item.state, item.name, item.bookmarkID);
            if (cPage != nullptr) {
                cPage->setBookmarkID(item.bookmarkID);

                // Update lists
                listNext.insert(0, current);
                listPrevious.removeAt(indexPrevious);
                for (int i = 0; i < indexPrevious; ++i) {
                    SKGTabPage::SKGPageHistoryItem itemPrevious = listPrevious.at(0);  // Because the list is modified
                    listNext.insert(0, itemPrevious);
                    listPrevious.removeAt(0);
                }

                // Set lists
                cPage->setPreviousPages(listPrevious);
                cPage->setNextPages(listNext);
            }

            refresh();
        }
    }
}

void SKGMainPanel::onShowPreviousMenu()
{
    if (d->m_previousMenu != nullptr) {
        d->m_previousMenu->clear();

        SKGTabPage* cPage = currentPage();
        if (cPage != nullptr) {
            SKGTabPage::SKGPageHistoryItemList list = cPage->getPreviousPages();
            int nb = list.count();
            for (int i = 0; i < nb; ++i) {
                QAction* act = d->m_previousMenu->addAction(SKGServices::fromTheme(list.at(i).icon), list.at(i).name);
                if (act != nullptr) {
                    act->setData(i);
                    connect(act, &QAction::triggered, this, &SKGMainPanel::onPrevious);
                }
            }
        }
    }
}

void SKGMainPanel::onShowMenuBar()
{
    bool test = d->m_showMenuBarAction->isChecked();
    menuBar()->setVisible(test);
    d->m_buttonMenuAction->setVisible(!test);

    KConfigGroup pref = getMainConfigGroup();
    pref.writeEntry("menubar_shown", test);
}

void SKGMainPanel::onFullScreen()
{
    auto* p = d->m_tabWidget;
    if (p != nullptr) {
        if (!d->m_fullScreenAction->isChecked()) {
            // No Full screen
            p->setWindowState(p->windowState() & ~Qt::WindowFullScreen);   // reset
            d->m_mainLayout->addWidget(d->m_tabWidget);
        } else {
            bool atLeastOnePageOpened = (d->m_tabWidget->count() > 0);
            if (atLeastOnePageOpened) {
                // Activate Full screen mode
                p->setParent(nullptr);
                p->setWindowFlags(p->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint);
                p->setWindowState(p->windowState() | Qt::WindowFullScreen);   // set
                p->show();

                displayMessage(i18nc("Information message", "You can exit full screen mode with %1 or with the contextual menu", d->m_fullScreenAction->shortcut().toString()));
            } else {
                d->m_fullScreenAction->setChecked(false);
                displayMessage(i18nc("Information message", "At least one page must be opened to enable full screen mode"), SKGDocument::Error);
            }
        }
    }
}

void SKGMainPanel::onNext()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);

    SKGTabPage* cPage = currentPage();
    if (cPage != nullptr) {
        // Get index in history of page to refresh
        int posNext = qobject_cast<QAction*>(sender())->data().toInt();

        // Get lists
        SKGTabPage::SKGPageHistoryItemList listPrevious = cPage->getPreviousPages();
        SKGTabPage::SKGPageHistoryItemList listNext = cPage->getNextPages();
        SKGTabPage::SKGPageHistoryItem current = currentPageHistoryItem();

        // Get item to refresh
        SKGTabPage::SKGPageHistoryItem item = listNext.at(posNext);

        // Open page
        cPage = openPage(getPluginByName(item.plugin), currentPageIndex(), item.state, item.name, item.bookmarkID);
        if (cPage != nullptr) {
            cPage->setBookmarkID(item.bookmarkID);

            // Update lists
            listPrevious.insert(0, current);
            listNext.removeAt(posNext);
            for (int i = 0; i < posNext; ++i) {
                SKGTabPage::SKGPageHistoryItem itemNext = listNext.at(0);  // Because the list is modified
                listPrevious.insert(0, itemNext);
                listNext.removeAt(0);
            }

            // Set lists
            cPage->setPreviousPages(listPrevious);
            cPage->setNextPages(listNext);
        }

        refresh();
    }
}

void SKGMainPanel::onReopenLastClosed()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);

    SKGTabPage::SKGPageHistoryItem current = currentPageHistoryItem();

    // Get item to refresh
    historyPage item = d->m_historyClosedPages.takeLast();

    // Open page
    SKGTabPage* cPage = openPage(getPluginByName(item.current.plugin), -1, item.current.state, item.current.name, item.current.bookmarkID);
    if (cPage != nullptr) {
        cPage->setBookmarkID(item.current.bookmarkID);
        cPage->setNextPages(item.next);
        cPage->setPreviousPages(item.previous);
    }

    refresh();
}

void SKGMainPanel::onLockDocks()
{
    QObjectList cs = children();
    for (auto c : qAsConst(cs)) {
        auto* doc = qobject_cast<QDockWidget*>(c);
        if (doc != nullptr) {
            doc->setFeatures(QDockWidget::NoDockWidgetFeatures);
        }
    }

    KConfigGroup pref = getMainConfigGroup();
    pref.writeEntry("docks_locked", true);

    refresh();
}

void SKGMainPanel::onUnlockDocks()
{
    QObjectList cs = children();
    for (auto c : qAsConst(cs)) {
        auto* doc = qobject_cast<QDockWidget*>(c);
        if (doc != nullptr) {
            doc->setFeatures(QDockWidget::AllDockWidgetFeatures);
        }
    }

    KConfigGroup pref = getMainConfigGroup();
    pref.writeEntry("docks_locked", false);

    refresh();
}

void SKGMainPanel::onConfigureNotifications()
{
    KNotifyConfigWidget::configure(this);
}

void SKGMainPanel::onShowNextMenu()
{
    if (d->m_nextMenu != nullptr) {
        d->m_nextMenu->clear();

        SKGTabPage* cPage = currentPage();
        if (cPage != nullptr) {
            SKGTabPage::SKGPageHistoryItemList list = cPage->getNextPages();
            int nb = list.count();
            for (int i = 0; i < nb; ++i) {
                QAction* act = d->m_nextMenu->addAction(SKGServices::fromTheme(list.at(i).icon), list.at(i).name);
                if (act != nullptr) {
                    act->setData(i);
                    connect(act, &QAction::triggered, this, &SKGMainPanel::onNext);
                }
            }
        }
    }
}

void SKGMainPanel::onShowButtonMenu()
{
    if (d->m_buttonMenu != nullptr) {
        d->m_buttonMenu->clear();

        QMenuBar* mb = menuBar();
        if (mb != nullptr) {
            d->m_buttonMenu->addActions(mb->actions());
        }
    }
}

bool SKGMainPanel::queryClose()
{
    SKGTRACEINFUNC(1);
    // Bug 2777697: To be sure that all page modifications are closed
    closeAllPages();
    // Bug 2777697:

    bool output = queryFileClose();

    // To be sure that the application is not closed in fullscreen mode
    if (output) {
        if (d->m_fullScreenAction->isChecked()) {
            d->m_fullScreenAction->trigger();
        }
    }

    return output;
}

void SKGMainPanel::setSaveOnClose(bool iSaveOnClose)
{
    d->m_saveOnClose = iSaveOnClose;
}

bool SKGMainPanel::queryFileClose()
{
    SKGTRACEINFUNC(1);
    bool output = true;
    if (getDocument()->getCurrentTransaction() != 0) {
        displayMessage(i18nc("skgtestimportskg", "The application cannot be closed when an operation is running."), SKGDocument::Error);
        output = false;
    } else if (getDocument()->isFileModified()) {
        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
        int code = KMessageBox::Yes;
        QString fileName = getDocument()->getCurrentFileName();
        QAction* save = getGlobalAction(fileName.isEmpty() ? QStringLiteral("file_save_as") : QStringLiteral("file_save"));
        if (save != nullptr) {
            if (!d->m_saveOnClose) {
                code = KMessageBox::questionYesNoCancel(this, i18nc("Question", "The document has been modified.\nDo you want to save it before closing?"),
                                                        QString(),
                                                        KGuiItem(fileName.isEmpty() ?  i18nc("Question", "Save as") : i18nc("Question", "Save"),
                                                                SKGServices::fromTheme(fileName.isEmpty() ?  QStringLiteral("document-save-as") : QStringLiteral("document-save"))),
                                                        KGuiItem(i18nc("Question", "Do not save")));
            }
            if (code == KMessageBox::Yes) {
                save->trigger();
            }
            output = (code == KMessageBox::No || code == KMessageBox::Yes);
        } else {
            code = KMessageBox::questionYesNo(this, i18nc("Question", "Current modifications will not be saved.\nDo you want to continue?"));
            output = (code == KMessageBox::Yes);
        }
        QApplication::restoreOverrideCursor();
    }

    return output;
}

SKGObjectBase SKGMainPanel::getFirstSelectedObject() const
{
    SKGObjectBase selection;
    SKGWidget* cPage = (d->m_widgetHavingSelection != nullptr ? d->m_widgetHavingSelection : currentPage());
    if (cPage != nullptr) {
        selection = cPage->getFirstSelectedObject();
    }
    return selection;
}

SKGObjectBase::SKGListSKGObjectBase SKGMainPanel::getSelectedObjects() const
{
    SKGObjectBase::SKGListSKGObjectBase selection;
    SKGWidget* cPage = (d->m_widgetHavingSelection != nullptr ? d->m_widgetHavingSelection : currentPage());
    if (cPage != nullptr) {
        selection = cPage->getSelectedObjects();
    }
    return selection;
}

int SKGMainPanel::getNbSelectedObjects() const
{
    int nb = 0;
    SKGWidget* cPage = (d->m_widgetHavingSelection != nullptr ? d->m_widgetHavingSelection : currentPage());
    if (cPage != nullptr) {
        nb = cPage->getNbSelectedObjects();
    }
    return nb;
}

bool SKGMainPanel::hasSelectionWithFocus()
{
    bool output = false;
    SKGWidget* cPage = (d->m_widgetHavingSelection != nullptr ? d->m_widgetHavingSelection : currentPage());
    if (cPage != nullptr) {
        output = cPage->hasSelectionWithFocus();
    }
    return output;
}

SKGAdviceList SKGMainPanel::getAdvice() const
{
    SKGTRACEINFUNC(1);
    // Get list of ignored advice
    QString currentMonth = QDate::currentDate().toString(QStringLiteral("yyyy-MM"));
    QStringList ignoredAdvice = getDocument()->getParameters(QStringLiteral("advice"), "t_value='I' OR t_value='I_" % currentMonth % '\'');

    // Build the list of all advice by requesting all plugins
    SKGAdviceList globalAdviceList;
    int index = 0;
    while (index >= 0) {
        SKGInterfacePlugin* plugin = SKGMainPanel::getMainPanel()->getPluginByIndex(index);
        if (plugin != nullptr) {
            const auto list = plugin->advice(ignoredAdvice);
            for (const auto& ad : list) {
                if (!ignoredAdvice.contains(ad.getUUID()) && !ignoredAdvice.contains(SKGServices::splitCSVLine(ad.getUUID(), '|').at(0))) {
                    globalAdviceList.push_back(ad);
                }
            }
        } else {
            index = -2;
        }
        ++index;
    }
    std::sort(globalAdviceList.begin(), globalAdviceList.end(), SKGMainPanelPrivate::adviceLessThan);

    return globalAdviceList;
}

KConfigGroup SKGMainPanel::getMainConfigGroup()
{
    KSharedConfigPtr config = KSharedConfig::openConfig();
    return config->group("Main Panel");
}

void SKGMainPanel::optionsPreferences(const QString& iPluginName)
{
    SKGTRACEINFUNC(1);
    // Compute page
    QString pluginName = iPluginName;
    if (pluginName.isEmpty()) {
        auto* act = qobject_cast<QAction*>(sender());
        if (act != nullptr) {
            pluginName = act->property("page").toString();
        }
    }
    if (pluginName.isEmpty() && (this->currentPage() != nullptr)) {
        pluginName = this->currentPage()->objectName();
    }
    SKGTRACEL(1) << "Open setting page: " << pluginName << endl;
    // Synchronize setting with confirmation panel
    if (skgbasegui_settings::update_modified_bookmarks() == 0) {
        KMessageBox::ButtonCode confirm;
        bool ask = KMessageBox::shouldBeShownYesNo(QStringLiteral("updateBookmarkOnClose"), confirm);

        KConfigGroup pref = getMainConfigGroup();
        if (ask) {
            pref.writeEntry("update_modified_bookmarks", 0, KConfigBase::Normal);
            SKGTRACEL(1) << "update_modified_bookmarks set to ASK" << endl;
        } else if (confirm == KMessageBox::Yes) {
            pref.writeEntry("update_modified_bookmarks", 1, KConfigBase::Normal);
            SKGTRACEL(1) << "update_modified_bookmarks set to ALWAYS" << endl;
        } else {
            pref.writeEntry("update_modified_bookmarks", 2, KConfigBase::Normal);
            SKGTRACEL(1) << "update_modified_bookmarks set to NEVER" << endl;
        }
    }

    if (skgbasegui_settings::update_modified_contexts() == 0) {
        KMessageBox::ButtonCode confirm;
        bool ask = KMessageBox::shouldBeShownYesNo(QStringLiteral("updateContextOnClose"), confirm);

        KConfigGroup pref = getMainConfigGroup();
        if (ask) {
            pref.writeEntry("update_modified_contexts", 0, KConfigBase::Normal);
            SKGTRACEL(1) << "update_modified_contexts set to ASK" << endl;
        } else if (confirm == KMessageBox::Yes) {
            pref.writeEntry("update_modified_contexts", 1, KConfigBase::Normal);
            SKGTRACEL(1) << "update_modified_contexts set to ALWAYS" << endl;
        } else {
            pref.writeEntry("update_modified_contexts", 2, KConfigBase::Normal);
            SKGTRACEL(1) << "update_modified_contexts set to NEVER" << endl;
        }
    }
    skgbasegui_settings::self()->load();


    if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
        return;
    }

    auto dialog = new KConfigDialog(this, QStringLiteral("settings"), skgbasegui_settings::self());

    // Add main
    auto w = new QWidget();
    d->uipref.setupUi(w);
    d->uipref.kcfg_date_format->addItem(i18nc("Date format", "Short date (%1, %2)",
                                        QLocale().toString(QDate::currentDate(), QLocale::ShortFormat),
                                        QLocale().toString(QDate::currentDate().addDays(-10), QLocale::ShortFormat)));
    d->uipref.kcfg_date_format->addItem(i18nc("Date format", "Long date (%1, %2)",
                                        QLocale().toString(QDate::currentDate(), QLocale::LongFormat),
                                        QLocale().toString(QDate::currentDate().addDays(-10), QLocale::LongFormat)));
    d->uipref.kcfg_date_format->addItem(i18nc("Date format", "Fancy short date (%1, %2)",
                                        KFormat().formatRelativeDate(QDate::currentDate(), QLocale::ShortFormat),
                                        KFormat().formatRelativeDate(QDate::currentDate().addDays(-10), QLocale::ShortFormat)));
    d->uipref.kcfg_date_format->addItem(i18nc("Date format", "Fancy long date (%1, %2)",
                                        KFormat().formatRelativeDate(QDate::currentDate(), QLocale::LongFormat),
                                        KFormat().formatRelativeDate(QDate::currentDate().addDays(-10), QLocale::LongFormat)));
    d->uipref.kcfg_date_format->addItem(i18nc("Date format", "ISO date (%1, %2)",
                                        QDate::currentDate().toString(Qt::ISODate),
                                        QDate::currentDate().addDays(-10).toString(Qt::ISODate)));
    dialog->addPage(w, skgbasegui_settings::self(), i18nc("Noun", "General"), QStringLiteral("preferences-other"));

    // Add plugin in client in right order
    int nbplugin = d->m_pluginsList.count();
    for (int j = 0; j < nbplugin; ++j) {
        SKGInterfacePlugin* pluginInterface = getPluginByIndex(j);
        if (pluginInterface != nullptr) {
            QWidget* w2 = pluginInterface->getPreferenceWidget();
            if (w2 != nullptr) {
                auto icon = SKGServices::fromTheme(pluginInterface->icon());
                KPageWidgetItem* p = dialog->addPage(w2, pluginInterface->getPreferenceSkeleton(), pluginInterface->title(), icon.name());
                if ((p != nullptr) && pluginName == pluginInterface->objectName()) {
                    dialog->setCurrentPage(p);
                }
            }
        }
    }

    connect(dialog, &KConfigDialog::settingsChanged, this, &SKGMainPanel::onSettingsChanged);

    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->show();

    // Refresh
    refresh();
}

void SKGMainPanel::onSettingsChanged()
{
    SKGError err;
    SKGTRACEINFUNCRC(1, err);
    {
        int nb = d->m_pluginsList.count();
        SKGBEGINPROGRESSTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Save settings"), err, nb);

        // Refresh plugins
        for (int i = 0; !err && i < nb; ++i) {
            err = getPluginByIndex(i)->savePreferences();
            IFOKDO(err, getDocument()->stepForward(i + 1))
        }

        // Setting for tab position
        d->refreshTabPosition();

        // Setting for bookmarks modification
        {
            int option = skgbasegui_settings::update_modified_bookmarks();
            if (option == 0) {
                // ASK: remove following setting
                KMessageBox::enableMessage(QStringLiteral("updateBookmarkOnClose"));
                SKGTRACEL(1) << "updateBookmarkOnClose set to ASK" << endl;
            } else if (option == 1) {
                // ALWAYS: set following setting
                KMessageBox::saveDontShowAgainYesNo(QStringLiteral("updateBookmarkOnClose"), KMessageBox::Yes);
                SKGTRACEL(1) << "updateBookmarkOnClose set to Yes" << endl;
            } else {
                // NEVER: set following setting
                KMessageBox::saveDontShowAgainYesNo(QStringLiteral("updateBookmarkOnClose"), KMessageBox::No);
                SKGTRACEL(1) << "updateBookmarkOnClose set to No" << endl;
            }
        }
        {
            int option = skgbasegui_settings::update_modified_contexts();
            if (option == 0) {
                // ASK: remove following setting
                KMessageBox::enableMessage(QStringLiteral("updateContextOnClose"));
                SKGTRACEL(1) << "updateContextOnClose set to ASK" << endl;
            } else if (option == 1) {
                // ALWAYS: set following setting
                KMessageBox::saveDontShowAgainYesNo(QStringLiteral("updateContextOnClose"), KMessageBox::Yes);
                SKGTRACEL(1) << "updateContextOnClose set to Yes" << endl;
            } else {
                // NEVER: set following setting
                KMessageBox::saveDontShowAgainYesNo(QStringLiteral("updateContextOnClose"), KMessageBox::No);
                SKGTRACEL(1) << "updateContextOnClose set to No" << endl;
            }
        }
        skgbasegui_settings::self()->load();
    }

    // Rebuild system tray
    d->rebuildSystemTray();

    emit settingsChanged();

    // Display error
    displayErrorMessage(err);
}

void SKGMainPanel::refresh()
{
    SKGTRACEINFUNC(1);

    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

    // Show/hide main widget
    bool atLeastOnePageOpened = (d->m_tabWidget->count() > 0);
    d->m_tabWidget->setVisible(atLeastOnePageOpened);
    if (d->m_mainWidget != nullptr) {
        d->m_mainWidget->setVisible(!atLeastOnePageOpened);
    }

    // Refresh actions
    d->m_widgetHavingSelection = qobject_cast<SKGWidget*>(sender());
    SKGObjectBase selection = SKGMainPanel::getMainPanel()->getFirstSelectedObject();
    int nbSelectedItems = SKGMainPanel::getMainPanel()->getNbSelectedObjects();
    bool hasFocus = SKGMainPanel::getMainPanel()->hasSelectionWithFocus();

    QString selectedTable = (nbSelectedItems > 0 ? selection.getRealTable() : QLatin1String(""));
    for (const auto& actDetails : qAsConst(d->m_registeredGlogalAction)) {
        QStringList tables = actDetails.tables;
        if (tables.count() == 1 && tables.at(0).startsWith(QLatin1String("query:"))) {
            // Dynamic mode
            getDocument()->getDistinctValues(QStringLiteral("sqlite_master"), QStringLiteral("name"), tables.at(0).right(tables.at(0).count() - 6), tables);
        }

        bool enabled = (tables.contains(selectedTable) || tables.isEmpty()) &&
                       (nbSelectedItems >= actDetails.min) &&
                       (nbSelectedItems <= actDetails.max || actDetails.max == -1) &&
                       (!actDetails.focus || hasFocus);

        if (enabled && nbSelectedItems == 0 && (actDetails.min == 0 || actDetails.min == -1)) {
            // Check if a page is opened
            SKGTabPage* page = SKGMainPanel::getMainPanel()->currentPage();
            if (page != nullptr) {
                auto* view = qobject_cast<QAbstractItemView*>(page->mainWidget());
                enabled = (actDetails.min == -1 || view != nullptr);
            } else {
                enabled = false;
            }
        }
        if (actDetails.action != nullptr) {
            actDetails.action->setEnabled(enabled);
        }
    }

    // Refresh plugins
    int nb = d->m_pluginsList.count();
    for (int i = 0; i < nb; ++i) {
        getPluginByIndex(i)->refresh();
    }

    // Enable addTabeAction
    SKGTabPage* toSave = currentPage();
    if (d->m_switchPinState != nullptr) {
        if ((toSave != nullptr) && toSave->isPin()) {
            d->m_switchPinState->setText(i18nc("Noun, user action", "Unpin this page"));
        } else {
            d->m_switchPinState->setText(i18nc("Noun, user action", "Pin this page"));
        }
    }
    if (d->m_closePageAction != nullptr) {
        d->m_closePageAction->setEnabled(atLeastOnePageOpened && (toSave != nullptr) && !toSave->isPin());
    }
    if (d->m_switchPinState != nullptr) {
        d->m_switchPinState->setEnabled(atLeastOnePageOpened);
    }
    if (d->m_closeAllOtherPagesAction != nullptr) {
        d->m_closeAllOtherPagesAction->setEnabled(d->m_tabWidget->count() > 1);
    }
    if (d->m_reopenLastClosed != nullptr) {
        d->m_reopenLastClosed->setEnabled(!d->m_historyClosedPages.isEmpty());
    }

    if (d->m_saveDefaultStateAction != nullptr) {
        d->m_saveDefaultStateAction->setEnabled((toSave != nullptr) && !toSave->getDefaultStateAttribute().isEmpty());
    }
    if (d->m_resetDefaultStateAction != nullptr) {
        d->m_resetDefaultStateAction->setEnabled((toSave != nullptr) && !toSave->getDefaultStateAttribute().isEmpty());
    }
    if (d->m_overwriteBookmarkStateAction != nullptr) {
        d->m_overwriteBookmarkStateAction->setEnabled((toSave != nullptr) && !toSave->getBookmarkID().isEmpty());
    }
    if (d->m_enableEditorAction != nullptr) {
        d->m_enableEditorAction->setEnabled((toSave != nullptr) && toSave->isEditor());
    }
    if (d->m_zoomSelector != nullptr) {
        d->m_zoomSelector->setVisible((toSave != nullptr) && toSave->isZoomable());
        if (toSave != nullptr) {
            d->m_zoomSelector->setValue(toSave->zoomPosition());
            QWidget* zoomWidget = toSave->zoomableWidget();
            auto* treeView = qobject_cast<SKGTreeView*>(zoomWidget);
            if (treeView != nullptr) {
                disconnect(treeView, &SKGTreeView::zoomChanged, nullptr, nullptr);
                connect(treeView, &SKGTreeView::zoomChanged, this, [ = ](int val) {
                    d->m_zoomSelector->setValue(val);
                });
            } else {
                auto* webView = qobject_cast<SKGWebView*>(zoomWidget);
                if (webView != nullptr) {
                    disconnect(webView, &SKGWebView::zoomChanged, nullptr, nullptr);
                    connect(webView, &SKGWebView::zoomChanged, this, [ = ](int val) {
                        d->m_zoomSelector->setValue(val);
                    });
                }
            }
        }
    }

    if (d->m_actLock != nullptr) {
        d->m_actLock->setVisible(d->ui.kDockContext->features() == QDockWidget::AllDockWidgetFeatures);
    }
    if (d->m_actUnLock != nullptr) {
        d->m_actUnLock->setVisible(d->ui.kDockContext->features() == QDockWidget::NoDockWidgetFeatures);
    }

    if (d->m_previousAction != nullptr) {
        SKGTabPage::SKGPageHistoryItemList list;
        if (toSave != nullptr) {
            list = toSave->getPreviousPages();
        }
        d->m_previousAction->setEnabled(!list.isEmpty());
    }
    if (d->m_nextAction != nullptr) {
        SKGTabPage::SKGPageHistoryItemList list;
        if (toSave != nullptr) {
            list = toSave->getNextPages();
        }
        d->m_nextAction->setEnabled(!list.isEmpty());
    }

    // Set current selection of context
    d->ui.kContextList->clearSelection();
    if (toSave != nullptr) {
        // Get plugin of current page
        SKGInterfacePlugin* plugin = getPluginByName(toSave->objectName());
        int index = (plugin != nullptr ? plugin->property("contextItem").toInt() : -1);
        if (index != -1) {
            d->ui.kContextList->setCurrentItem(d->ui.kContextList->item(index));
        }
    }

    // Set window title
    QString modified;
    if (getDocument()->isFileModified()) {
        modified += i18nc("Noun, indicate that current document is modified", " [modified]");
    }
    if (getDocument()->isReadOnly()) {
        modified += i18nc("Noun, indicate that current document is loaded in read only", " [read only]");
    }
    QString fileName = getDocument()->getCurrentFileName();
    if (fileName.isEmpty()) {
        fileName = i18nc("Noun, default name for a new document", "Untitled");
    } else {
        if (fileName != d->m_fileName) {
            // The file name has been changed
            onClearMessages();

            d->m_fileName = fileName;
#ifdef KActivities_FOUND
            if (!d->m_fileName.isEmpty()) {
                d->m_activityResourceInstance->setUri(d->m_fileName);
            }
#endif
        }
    }
    setWindowTitle(i18nc("Title of the main window", "%1%2", fileName, modified));

    QApplication::restoreOverrideCursor();
}

SKGTabPage::SKGPageHistoryItem SKGMainPanel::currentPageHistoryItem() const
{
    SKGTabPage::SKGPageHistoryItem cpage;
    int currentIndex = currentPageIndex();
    SKGTabPage* cPage = currentPage();
    if (currentIndex >= 0 && (cPage != nullptr)) {
        cpage.plugin = cPage->objectName();
        SKGInterfacePlugin* plugin = SKGMainPanel::getMainPanel()->getPluginByName(cpage.plugin);
        if (plugin != nullptr) {
            cpage.name = d->m_tabWidget->tabText(currentIndex);
            cpage.icon = plugin->icon();
        }
        cpage.state = cPage->getState();
        cpage.bookmarkID = cPage->getBookmarkID();
    }

    return cpage;
}

SKGTabPage* SKGMainPanel::openPage(SKGInterfacePlugin* plugin, int index, const QString& parameters, const QString& title, const QString& iID, bool iSetCurrent)
{
    SKGTRACEINFUNC(1);
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
    bool previous = d->m_tabWidget->blockSignals(true);
    // If the current page is pin, then open new page
    SKGTabPage* cPage = currentPage();
    if ((cPage != nullptr) && cPage->isPin()) {
        index = -1;
        iSetCurrent = true;
    }

    SKGTabPage* w = nullptr;

    SKGTabPage::SKGPageHistoryItemList previousPages;
    if (index != -1) {
        int currentIndex = currentPageIndex();
        if (currentIndex >= 0 && (cPage != nullptr)) {
            previousPages = cPage->getPreviousPages();
            previousPages.insert(0, currentPageHistoryItem());

            d->m_tabWidget->removeTab(currentIndex);
            closePage(cPage);

            // Repair the history of closed page
            if (!d->m_historyClosedPages.isEmpty()) {
                d->m_historyClosedPages.removeLast();
            }
        }
    }

    if (plugin != nullptr) {
        w = plugin->getWidget();
        if (w != nullptr) {
            // Title
            QString title2 = (title.isEmpty() ? plugin->title() : title);
            w->setObjectName(plugin->objectName());
            if (!iID.isEmpty()) {
                w->setBookmarkID(iID);
            }

            QString param = parameters;
            if (param.isEmpty()) {
                QString def = w->getDefaultStateAttribute();
                if (!def.isEmpty()) {
                    param = getDocument()->getParameter(def);
                }
            }
            SKGTRACEL(10) << "state=[" << param << "]" << endl;
            w->setState(param);
            connect(w, &SKGTabPage::selectionChanged, this, &SKGMainPanel::refresh);
            connect(w, &SKGTabPage::selectionChanged, this, &SKGMainPanel::selectionChanged);
            connect(w, &SKGTabPage::selectionFocusChanged, this, &SKGMainPanel::refresh);

            if (index == -1) {
                SKGTRACEINFUNC(20);
                d->m_tabWidget->addTab(w, SKGServices::fromTheme(plugin->icon()), title2);
                if (iSetCurrent) {
                    d->m_tabWidget->setCurrentWidget(w);
                }
            } else {
                SKGTRACEINFUNC(20);
                d->m_tabWidget->insertTab(index, w, SKGServices::fromTheme(plugin->icon()), title2);
                if (iSetCurrent) {
                    d->m_tabWidget->setCurrentWidget(w);
                }

                w->setPreviousPages(previousPages);
                SKGTabPage::SKGPageHistoryItemList empty;
                w->setNextPages(empty);
            }
            SKGTRACEL(1) << "opening plugin [" << plugin->objectName() << ']' << endl;
            Q_EMIT pageOpened();
        }
    } else {
        getDocument()->sendMessage(i18nc("An information message",  "Impossible to open the page because the plugin was not found"), SKGDocument::Error);
        notify();  // Due to sendMessage not in a transaction
    }

    // Show/hide main widget
    bool atLeastOnePageOpened = (d->m_tabWidget->count() > 0);
    d->m_tabWidget->setVisible(atLeastOnePageOpened);
    if (d->m_mainWidget != nullptr) {
        d->m_mainWidget->setVisible(!atLeastOnePageOpened);
    }

    d->m_tabWidget->blockSignals(previous);
    if (iSetCurrent) {
        Q_EMIT currentPageChanged();
    }
    QApplication::restoreOverrideCursor();
    return w;
}

int SKGMainPanel::currentPageIndex() const
{
    return d->m_tabWidget->currentIndex();
}

SKGTabPage* SKGMainPanel::currentPage() const
{
    return qobject_cast< SKGTabPage* >(d->m_tabWidget->currentWidget());
}

int SKGMainPanel::pageIndex(SKGTabPage* iPage) const
{
    int nb = countPages();
    for (int i = 0; i < nb; ++i) {
        if (page(i) == iPage) {
            return i;
        }
    }

    return -1;
}

QSplashScreen* SKGMainPanel::splashScreen() const
{
    return d->m_splashScreen;
}

int SKGMainPanel::countPages() const
{
    return d->m_tabWidget->count();
}

SKGTabPage* SKGMainPanel::page(int iIndex) const
{
    return qobject_cast<SKGTabPage*>(d->m_tabWidget->widget(iIndex));
}

void SKGMainPanel::setCurrentPage(int iIndex)
{
    d->m_tabWidget->setCurrentIndex(iIndex);
}

void SKGMainPanel::onBeforeOpenContext()
{
    d->m_middleClick = ((QApplication::mouseButtons() & Qt::MidButton) != 0u);
}

bool SKGMainPanel::openPage(const QUrl& iUrl, bool iNewPage)
{
    const QUrl& url(iUrl);
    if (url.scheme() == QStringLiteral("skg")) {
        // Get plugin
        SKGInterfacePlugin* plugin = getPluginByName(url.host());
        if (plugin != nullptr) {
            // Open special page
            SKGTabPage* w = plugin->getWidget();
            if (w != nullptr) {
                // Create xml
                QString path = url.path().remove('/');
                QDomDocument doc(QStringLiteral("SKGML"));
                doc.setContent(getDocument()->getParameter(path.isEmpty() ? w->getDefaultStateAttribute() : path));
                QDomElement root = doc.documentElement();
                if (root.isNull()) {
                    root = doc.createElement(QStringLiteral("parameters"));
                    doc.appendChild(root);
                }
                auto params = QUrlQuery(url).queryItems();
                for (const auto& p : qAsConst(params)) {
                    QString value = QUrl::fromPercentEncoding(p.second.toUtf8());
                    SKGMainPanelPrivate::setAttribute(root, p.first, value);
                }

                // Open page
                openPage(plugin, iNewPage ? -1 : currentPageIndex(), doc.toString());
                return true;
            }
        } else {
            // Trigger action
            QAction* act = SKGMainPanel::getMainPanel()->getGlobalAction(url.host());
            if (act != nullptr) {
                auto params = QUrlQuery(url).queryItems();
                for (const auto& p : qAsConst(params)) {
                    QString value = QUrl::fromPercentEncoding(p.second.toUtf8());
                    act->setProperty(p.first.toUtf8().data(), value);
                }

                act->trigger();
                return true;
            }
        }
    } else {
        QDesktopServices::openUrl(iUrl);
        return true;
    }
    displayErrorMessage(SKGError(ERR_ABORT, i18nc("Error message", "Unknown plugin or action [%1] in url [%2]", url.host(), iUrl.toString())));

    return false;
}

bool SKGMainPanel::openPage()
{
    return openPage(QString());
}

bool SKGMainPanel::openPage(const QString& iUrl, bool iNewPage)
{
    // Get the url
    QString urlString(iUrl);
    if (urlString.isEmpty()) {
        auto* act = qobject_cast< QAction* >(sender());
        if (act != nullptr) {
            urlString = act->data().toString();
        }
    }

    return openPage(QUrl(urlString), iNewPage);
}

SKGTabPage* SKGMainPanel::openPage(int iPage, bool iNewPage)
{
    SKGTRACEINFUNC(1);
    SKGTRACEL(1) << "iPage=" << iPage << endl;
    int index = d->ui.kContextList->item(iPage)->data(12).toInt();
    return openPage(getPluginByIndex(index), iNewPage ? -1 : currentPageIndex());
}

void SKGMainPanel::onOpenContext()
{
    SKGTRACEINFUNC(1);
    if (!(QApplication::mouseButtons() & Qt::RightButton)) {
        int cpage = -1;
        auto* s = qobject_cast<QAction*>(this->sender());
        if (s != nullptr) {
            cpage = s->data().toInt();
        } else {
            cpage = d->ui.kContextList->currentRow();
        }
        if (cpage != -1) {
            openPage(cpage, ((QApplication::keyboardModifiers() &Qt::ControlModifier) != 0u) || d->m_middleClick || ((QGuiApplication::mouseButtons() & Qt::MidButton) != 0u));
        }
    }
    d->m_middleClick = false;
}

void SKGMainPanel::switchPinPage(QWidget* iWidget)
{
    auto* toSwitch = qobject_cast< SKGTabPage* >(iWidget);
    if (toSwitch == nullptr) {
        toSwitch = currentPage();
    }

    if (toSwitch != nullptr) {
        toSwitch->setPin(!toSwitch->isPin());
        Q_EMIT currentPageChanged();
    }
}

void SKGMainPanel::closePageByIndex(int iIndex)
{
    QWidget* w = nullptr;
    if (iIndex >= 0) {
        w = d->m_tabWidget->widget(iIndex);
    } else {
        w = d->m_tabWidget->currentWidget();
    }

    closePage(w);
}

void SKGMainPanel::closeCurrentPage()
{
    closePage(nullptr);
}

void SKGMainPanel::closePage(QWidget* iWidget, bool iForce)
{
    SKGTRACEINFUNC(1);
    if (getDocument()->getCurrentTransaction() != 0) {
        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
        displayMessage(i18nc("Information message", "A page cannot be closed when an operation is running."), SKGDocument::Information);
        QApplication::restoreOverrideCursor();
    } else {
        auto* toRemove = qobject_cast< SKGTabPage* >(iWidget);
        if (toRemove == nullptr) {
            toRemove = currentPage();
        }
        if ((toRemove != nullptr) && toRemove->close(iForce)) {
            historyPage item;
            item.current = currentPageHistoryItem();
            item.next = toRemove->getNextPages();
            item.previous = toRemove->getPreviousPages();
            d->m_historyClosedPages.push_back(item);
            delete toRemove;

            emit pageClosed();
        }
    }

    // Show/hide main widget
    bool atLeastOnePageOpened = (d->m_tabWidget->count() > 0);
    d->m_tabWidget->setVisible(atLeastOnePageOpened);
    if (d->m_mainWidget != nullptr) {
        d->m_mainWidget->setVisible(!atLeastOnePageOpened);
    }

    if (!atLeastOnePageOpened) {
        d->m_fullScreenAction->setChecked(false);
        onFullScreen();
    }
}

void SKGMainPanel::closeAllPages(bool iForce)
{
    SKGTRACEINFUNC(1);
    bool previous = d->m_tabWidget->blockSignals(true);
    int nb = d->m_tabWidget->count();
    for (int i = nb - 1; i >= 0; --i) {
        auto* w = qobject_cast< SKGTabPage* >(d->m_tabWidget->widget(i));
        if ((w != nullptr) && (iForce || !w->isPin())) {
            closePage(w, iForce);
        }
    }
    d->m_tabWidget->blockSignals(previous);
    KMessageBox::enableMessage(QStringLiteral("closepinnedpage"));
    Q_EMIT currentPageChanged();
}

void SKGMainPanel::closeAllOtherPages(QWidget* iWidget)
{
    SKGTRACEINFUNC(1);
    bool previous = d->m_tabWidget->blockSignals(true);
    QWidget* toKeep = iWidget;
    if (toKeep == nullptr) {
        toKeep = currentPage();
    }

    int nb = d->m_tabWidget->count();
    for (int i = nb - 1; i >= 0; --i) {
        auto* w = qobject_cast< SKGTabPage* >(d->m_tabWidget->widget(i));
        if ((w != nullptr) && w != toKeep && !w->isPin()) {
            closePage(w);
        }
    }
    d->m_tabWidget->blockSignals(previous);
    Q_EMIT currentPageChanged();
}

void SKGMainPanel::saveDefaultState()
{
    SKGTRACEINFUNC(1);
    SKGError err;
    SKGTabPage* toSave = currentPage();
    if (toSave != nullptr) {
        // Get bookmarks uuid
        QString uuid = toSave->getBookmarkID();

        // Reset bookmarks uuid to overwrite page state
        toSave->setBookmarkID(QLatin1String(""));

        // Overwrite
        toSave->overwrite(false);

        // Set original bookmarks uuid
        toSave->setBookmarkID(uuid);
    }
}

void SKGMainPanel::overwriteBookmarkState()
{
    SKGTRACEINFUNC(1);
    SKGError err;
    SKGTabPage* toSave = currentPage();
    if (toSave != nullptr) {
        // Get bookmarks uuid
        QString uuid = toSave->getBookmarkID();
        if (!uuid.isEmpty()) {
            // Overwrite
            toSave->overwrite(false);
        }
    }
}

void SKGMainPanel::resetDefaultState()
{
    SKGTRACEINFUNC(1);
    SKGError err;
    SKGTabPage* toSave = currentPage();
    if (toSave != nullptr) {
        QString name = toSave->getDefaultStateAttribute();
        if (!name.isEmpty()) {
            SKGBEGINLIGHTTRANSACTION(*getDocument(), i18nc("Noun, name of the user action", "Reset default state"), err);
            IFOKDO(err, getDocument()->setParameter(name, QStringLiteral("<!DOCTYPE SKGML>")))

            // Refresh panel
            IFOK(err) toSave->setState(QLatin1String(""));
        }
    }

    // status bar
    IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Default state has been reset")))
    displayErrorMessage(err);
}

void SKGMainPanel::addTab()
{
    SKGTRACEINFUNC(1);
    SKGTabPage* cPage = currentPage();
    if (cPage != nullptr) {
        openPage(getPluginByName(cPage->objectName()));
    }
}

bool SKGMainPanel::eventFilter(QObject* iObject, QEvent* iEvent)
{
    if ((iObject != nullptr) && (iEvent != nullptr) && iEvent->type() == QEvent::Resize) {
        auto* rEvent = dynamic_cast<QResizeEvent*>(iEvent);
        if (rEvent != nullptr) {
            QSize newSize = rEvent->size();

            // Compute icon size
            int s = qMax(qMin(newSize.width() / 5, 64), 16);
            d->ui.kContextList->setIconSize(QSize(s, s));
        }
    }
    return KXmlGuiWindow::eventFilter(iObject, iEvent);
}

QStringList SKGMainPanel::getTipsOfDay() const
{
    return d->m_tipsOfTheDay;
}

QString SKGMainPanel::getTipOfDay() const
{
    auto tips = getTipsOfDay();
    return SKGServices::htmlToString(tips.at(qrand() % tips.size()));
}

void SKGMainPanel::notify(int iTransaction)
{
    SKGTRACEINFUNC(1);
    SKGTRACEL(1) << "iTransaction=" << iTransaction << endl;

    // Notify
    SKGObjectBase transaction(getDocument(), QStringLiteral("doctransaction"), iTransaction);
    if (iTransaction == 0 || transaction.getAttribute(QStringLiteral("t_mode")) != QStringLiteral("R")) {
        SKGDocument::SKGMessageList msg;
        getDocument()->getMessages(iTransaction, msg, false);
        int nbMessages = msg.count();
        if (nbMessages != 0) {
            // Build list of types
            SKGDocument::MessageType maxType = SKGDocument::Positive;
            QList<SKGDocument::MessageType> listGroups;
            listGroups.reserve(nbMessages);
            for (int i = 0; i < nbMessages; ++i) {
                SKGDocument::SKGMessage m = msg.at(i);
                // if the message has an action, it can not be grouped
                if (!m.Action.isEmpty()) {
                    displayMessage(m.Text, m.Type, m.Action);
                    msg.removeAt(i);
                    i--;
                    nbMessages--;
                } else {
                    if (listGroups.isEmpty() || m.Type != listGroups.at(listGroups.count() - 1)) {
                        listGroups.push_back(m.Type);
                    }
                    if (static_cast<int>(m.Type) >= static_cast<int>(maxType)) {
                        maxType = m.Type;
                    }
                }
            }

            // Is the number of type acceptable?
            bool modeGrouped = false;
            if (listGroups.count() > 5 || nbMessages > 20) {
                // Too many group ==> simplification
                listGroups.clear();
                listGroups.push_back(maxType);
                modeGrouped = true;
            }

            // Build message
            if (nbMessages != 0) {
                QString message;
                int indexGroup = 0;
                for (int i = 0; i < nbMessages; ++i) {
                    auto m = msg.at(i);
                    auto t = m.Type;
                    if (modeGrouped) {
                        if (t == SKGDocument::Warning) {
                            m.Text = i18nc("Warning header", "Warning: %1", m.Text);
                        } else if (t == SKGDocument::Error) {
                            m.Text = i18nc("Error header", "Error: %1", m.Text);
                        } else if (t == SKGDocument::Information) {
                            m.Text = i18nc("Information header", "Information: %1", m.Text);
                        } else if (t == SKGDocument::Positive) {
                            m.Text = i18nc("Done header", "Done: %1", m.Text);
                        }
                    }
                    if (modeGrouped || t == listGroups.at(indexGroup)) {
                        // Same group
                        if (!message.isEmpty()) {
                            message += QStringLiteral("<br>");
                        }
                        message += m.Text;
                    } else {
                        // Different group
                        displayMessage(message, listGroups.at(indexGroup));

                        // Reset message
                        message = m.Text;

                        indexGroup++;
                    }
                }

                if (nbMessages < 21 || !SKGServices::getEnvVariable(QStringLiteral("SKGTEST")).isEmpty()) {
                    // Display a simple notification
                    /*auto notify = new KNotification(KAboutData::applicationData().componentName() % "_info_event" , this);
                    notify->setText(message);
                    notify->sendEvent();*/

                    displayMessage(message, listGroups.at(indexGroup));
                } else {
                    // Too many message, display a warning panel
                    KMessageBox::information(SKGMainPanel::getMainPanel(), message, i18nc("Noun",  "Notification"));
                }
            }
        }
    }
}

void SKGMainPanel::changeEvent(QEvent* e)
{
    KXmlGuiWindow::changeEvent(e);
}

QLabel* SKGMainPanel::statusNormalMessage() const
{
    return d->m_kNormalMessage;
}

KMessageWidget* SKGMainPanel::getMessageWidget(const QString& iMessage, SKGDocument::MessageType iType, const QString& iAction, bool iAutoKillOnClick)
{
    KMessageWidget* msg = nullptr;
    if (!iMessage.isEmpty()) {
        msg = new KMessageWidget(this);
        msg->setText(iMessage);
        msg->setMessageType(static_cast<KMessageWidget::MessageType>(iType));
        if (!iAction.isEmpty()) {
            QUrl url(iAction);
            if (url.scheme() == QStringLiteral("skg")) {
                QAction* action = SKGMainPanel::getMainPanel()->getGlobalAction(url.host(), false);
                QAction* act = nullptr;
                if (action != nullptr) {
                    // This is an action
                    act = new QAction(action->icon(), action->text(), SKGMainPanel::getMainPanel());
                } else {
                    // This is a default action
                    act = new QAction(SKGServices::fromTheme(QStringLiteral("open")), i18nc("Verb", "Open ..."), SKGMainPanel::getMainPanel());
                }

                act->setData(iAction);

                msg->addAction(act);
                connect(act, &QAction::triggered, this, [ = ] { openPage(QUrl(qobject_cast< QAction* >(sender())->data().toString()), true);});
                if (iAutoKillOnClick) {
                    connect(act, &QAction::triggered, msg, &KMessageWidget::deleteLater, Qt::QueuedConnection);
                }
            }
        }
    }
    return msg;
}

KMessageWidget* SKGMainPanel::displayMessage(const QString& iMessage, SKGDocument::MessageType iType, const QString& iAction)
{
    // Create message widget
    KMessageWidget* msg = nullptr;
    if (!iMessage.isEmpty()) {
        msg = getMessageWidget(iMessage, iType, iAction, true);
        QTimer::singleShot(iType == SKGDocument::Positive ? 5000 : iType == SKGDocument::Information ? 10000 : 20000, Qt::CoarseTimer, msg, &KMessageWidget::deleteLater);
        d->m_mainLayout->insertWidget(qMax(d->m_mainLayout->indexOf(d->m_mainWidget) - 1, 0), msg);

        // Store message
        auto msg2 = getMessageWidget(iMessage, iType, iAction, false);
        auto* l = qobject_cast< QVBoxLayout* >(d->ui.kMessagesLayout->layout());
        if (l != nullptr) {
            l->insertWidget(0, msg2);
        }
    }

    // Emit message
    //  [Event/error]
    //  [Event/neutral]
    //  [Event/positive]
    auto notification = new KNotification(iType == SKGDocument::Error ? QStringLiteral("error") :
                                          (iType == SKGDocument::Positive ? QStringLiteral("positive") :
                                           (iType == SKGDocument::Warning ? QStringLiteral("negative") :
                                            QStringLiteral("neutral"))), this);
    notification->setText(iMessage);
    notification->sendEvent();

    // Alert
    if (iType == SKGDocument::Error || iType == SKGDocument::Warning) {
        qApp->alert(this);
    }

    return msg;
}

KMessageWidget* SKGMainPanel::displayErrorMessage(const QString& iMessage)
{
    QString msg = iMessage;
    if (msg.isEmpty()) {
        auto* act = qobject_cast< QAction* >(sender());
        if (act != nullptr) {
            msg = act->data().toString();
        }
    }
    return displayMessage(msg, SKGDocument::Error);
}

KMessageWidget* SKGMainPanel::displayErrorMessage(const SKGError& iError, bool iNotifyIfNoError)
{
    return displayErrorMessage(iError, nullptr, iNotifyIfNoError);
}

KMessageWidget* SKGMainPanel::displayErrorMessage(const SKGError& iError, QAction* iAction, bool iNotifyIfNoError)
{
    SKGTRACEINFUNC(1);
    KMessageWidget* msg = nullptr;
    SKGMainPanel* parent = SKGMainPanel::getMainPanel();
    if (parent != nullptr) {
        if (iError) {
            // Get the message
            msg = parent->displayMessage(iError.getFullMessage(), SKGDocument::Error, iError.getAction());

            // Add history action in case of
            if (iError.getHistoricalSize() != 0) {
                auto history = new QAction(i18nc("Noun", "History"), msg);
                history->setIcon(SKGServices::fromTheme(QStringLiteral("dialog-information")));
                history->setData(iError.getFullMessageWithHistorical());
                msg->addAction(history);
                connect(history, &QAction::triggered, parent, [ = ] { parent->displayErrorMessage();});
                connect(history, &QAction::triggered, msg, &KMessageWidget::deleteLater, Qt::QueuedConnection);
            }

            // Add the additional action
            if (iAction != nullptr) {
                iAction->setParent(msg);
                msg->addAction(iAction);
                connect(iAction, &QAction::triggered, msg, &KMessageWidget::deleteLater, Qt::QueuedConnection);
            }
        } else {
            if (iNotifyIfNoError) {
                auto notification = new KNotification(QStringLiteral("positive"), parent);
                notification->setText(iError.getFullMessage());
                notification->sendEvent();
            }
            // Status bar
            QLabel* label = parent->statusNormalMessage();
            QString message = iError.getMessage();
            if ((label != nullptr) && !message.isEmpty()) {
                label->setText(message);
            }
        }
    }
    return msg;
}

void SKGMainPanel::onCancelCurrentAction()
{
    SKGMainPanelPrivate::m_currentActionCanceled = true;
}

void SKGMainPanel::onQuitAction()
{
    // Bug 2777697: To be sure that all page modifications are closed
    closeAllPages(true);
    // Bug 2777697:

    qApp->closeAllWindows();
}

QString SKGMainPanel::getSaveFileName(const QString& iStartDir, const QString& iFilter, QWidget* iParent, QString* iCodec)
{
    QString fileName;

    QString lastCodecUsed = QTextCodec::codecForLocale()->name();
    KEncodingFileDialog::Result result = KEncodingFileDialog::getSaveUrlAndEncoding(lastCodecUsed, QUrl(iStartDir), iFilter, iParent);
    if (!result.URLs.isEmpty()) {
        fileName = result.URLs.at(0).toLocalFile();
    }
    if (iCodec != nullptr) {
        *iCodec = result.encoding;
    }
    if (fileName.isEmpty()) {
        return QLatin1String("");
    }
    QFile f(fileName);
    if (f.exists() && KMessageBox::warningContinueCancel(iParent,
            i18nc("Question", "File <b>%1</b> already exists. Do you really want to overwrite it?", fileName),
            i18nc("Question", "Warning"),
            KGuiItem(i18nc("Verb",  "Save"), SKGServices::fromTheme(QStringLiteral("document-save")))) != KMessageBox::Continue) {
        return QLatin1String("");
    }

    return fileName;
}

void SKGMainPanel::fillWithDistinctValue(
    const QList<QWidget*>& iWidgets,
    SKGDocument* iDoc,
    const QString& iTable,
    const QString& iAttribut,
    const QString& iWhereClause,
    bool iAddoperators)
{
    SKGTRACEINFUNC(10);

    if (iDoc != nullptr) {
        {
            // Get list
            QStringList list;
            {
                SKGTRACEIN(10, "SKGMainPanel::fillWithDistinctValue-build list " % iTable % " " % iAttribut)
                iDoc->getDistinctValues(iTable, iAttribut, iWhereClause, list);
                if (!list.isEmpty() && !list.at(0).isEmpty()) {
                    list.insert(0, QLatin1String(""));
                }

                // Sorting list
                {
                    SKGTRACEIN(10, "SKGMainPanel::fillWithDistinctValue-build list sorting " % iTable % " " % iAttribut)
                    // Correction bug 202341 vvv
                    QCollator c;
                    std::sort(list.begin(), list.end(), [&](const QString & a, const QString & b) {
                        return c.compare(a, b) < 0;
                    });
                }

                // Add operator
                if (iAddoperators) {
                    list.push_back('=' % i18nc("Key word to modify a string into a field", "capitalize"));
                    list.push_back('=' % i18nc("Key word to modify a string into a field", "capwords"));
                    list.push_back('=' % i18nc("Key word to modify a string into a field", "lower"));
                    list.push_back('=' % i18nc("Key word to modify a string into a field", "trim"));
                    list.push_back('=' % i18nc("Key word to modify a string into a field", "upper"));
                }
            }

            {
                SKGTRACEIN(10, "SKGMainPanel::fillWithDistinctValue-fill " % iTable % " " % iAttribut)
                SKGTRACEL(10) << "list.count()=" << list.count() << endl;
                for (auto w : qAsConst(iWidgets)) {
                    auto comp = new QCompleter(list);
                    if (comp != nullptr) {
                        comp->setCaseSensitivity(Qt::CaseInsensitive);
                        comp->setFilterMode(Qt::MatchContains);

                        // Fill completion
                        auto* kcmb = qobject_cast<KComboBox*> (w);
                        if (kcmb != nullptr) {
                            // Fill combo
                            kcmb->clear();
                            kcmb->addItems(list);

                            kcmb->setCompleter(comp);
                        } else {
                            auto* kline = qobject_cast<QLineEdit*> (w);
                            if (kline != nullptr) {
                                kline->setClearButtonEnabled(true);
                                kline->setCompleter(comp);
                            }
                        }
                    }
                }
            }
        }
    }
}

SKGMainPanel* SKGMainPanel::getMainPanel()
{
    return SKGMainPanelPrivate::m_mainPanel;
}

void SKGMainPanel::onZoomChanged()
{
    SKGTabPage* toSave = currentPage();
    if (toSave != nullptr) {
        toSave->setZoomPosition(d->m_zoomSelector->value());
        d->m_zoomSelector->setValue(toSave->zoomPosition());  // In case of a limit is reached
    }
}

void SKGMainPanel::setMainWidget(QWidget* iWidget)
{
    if (d->m_mainWidget == nullptr && d->m_mainLayout != nullptr && iWidget != nullptr) {
        d->m_mainWidget = iWidget;
        d->m_mainLayout->addWidget(d->m_mainWidget);

        // Show/hide main widget
        d->m_tabWidget->setVisible(d->m_tabWidget->count() != 0);
        if (d->m_mainWidget != nullptr) {
            d->m_mainWidget->setVisible(!d->m_tabWidget->isVisible());
        }
    }
}

SKGTabWidget* SKGMainPanel::getTabWidget() const
{
    return d->m_tabWidget;
}

void SKGMainPanel::onClearMessages()
{
    QLayout* l = d->ui.kMessagesLayout->layout();
    if (l != nullptr) {
        // Remove all item of the layout
        while (l->count() > 1) {
            QLayoutItem* child = l->takeAt(0);
            if (child != nullptr) {
                QWidget* w = child->widget();
                delete w;
                delete child;
            }
        }
    }
}

void SKGMainPanel::onMigrateToSQLCipher()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    if (getDocument()->isFileModified()) {
        err = SKGError(ERR_ABORT, i18nc("An information message",  "The document must be saved to be migrated."), QStringLiteral("skg://file_save"));
    } else {
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

        // Set parameters
        QString input = getDocument()->getCurrentFileName();
        QString tmp = input % ".sqlcipher";
        QString output = input % "_migrated.skg";
        output = output.replace(QStringLiteral(".skg_migrated"), QStringLiteral("_migrated"));

        // Build argument
        QStringList arg;
        arg.push_back(QStringLiteral("--in"));
        arg.push_back(input);
        arg.push_back(QStringLiteral("--out"));
        arg.push_back(tmp);


        QString password = getDocument()->getPassword();
        if (!password.isEmpty()) {
            arg.push_back(QStringLiteral("--param"));
            arg.push_back(QStringLiteral("password"));
            arg.push_back(QStringLiteral("--value"));
            arg.push_back(password);
            password = " --param password --value \"" % password % "\"";
        }

        // Conversion skg => sqlcipher
        QString cmd = "skroogeconvert --in \"" % input % "\" --out \"" % tmp % "\"" % password;
        int rc = QProcess::execute(QStringLiteral("skroogeconvert"), arg);
        if (rc != 0) {
            err.setReturnCode(ERR_FAIL).setMessage(i18nc("Error message",  "The following command line failed with code %2:\n'%1'", cmd, rc));
        } else {
            cmd = "skroogeconvert --in \"" % tmp % "\" --out \"" % output % "\"" % password;
            arg[1] = tmp;
            arg[3] = output;
            rc = QProcess::execute(QStringLiteral("skroogeconvert"), arg);
            if (rc != 0) {
                err.setReturnCode(ERR_FAIL).setMessage(i18nc("Error message",  "The following command line failed with code %2:\n'%1'", cmd, rc));
            } else {
                getDocument()->sendMessage(i18nc("Positive message", "You document has been migrated.\nHere is the new file:\n%1", output), SKGDocument::Positive, "skg://file_open/?filename=" % output);
                notify();
            }
        }
        QFile(tmp).remove();

        QApplication::restoreOverrideCursor();
    }

    // Display error
    SKGMainPanel::displayErrorMessage(err);
}

QString SKGMainPanel::dateToString(QDate  iDate)
{
    switch (skgbasegui_settings::date_format()) {
    case 0:
        return QLocale().toString(iDate, QLocale::ShortFormat);
    case 1:
        return QLocale().toString(iDate, QLocale::LongFormat);
    case 3:
        return KFormat().formatRelativeDate(iDate, QLocale::LongFormat);
    case 4:
        return iDate.toString(Qt::ISODate);
    case 2:
    default:
        return KFormat().formatRelativeDate(iDate, QLocale::ShortFormat);
    }
}
