/*
 *  Copyright (C) 1999-2002 Bernd Gehrmann
 *                          bernd@mail.berlios.de
 *  Copyright (c) 2002-2005 Christian Loose <christian.loose@kdemail.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "cervisiapart.h"

#include <QList>
#include <QSplitter>
#include <QStatusBar>
#include <qlabel.h>
#include <qmenu.h>
#include <qmessagebox.h>
#include <qpushbutton.h>
#include <qtextstream.h>

#include <KConfig>
#include <KConfigGroup>
#include <KIO/ApplicationLauncherJob>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QDebug>
#include <kaboutdata.h>
#include <kactioncollection.h>
#include <kmessagebox.h>
#include <knotification.h>
#include <kparts/guiactivateevent.h>
#include <kpropertiesdialog.h>
#include <krecentfilesaction.h>
#include <krun.h>
#include <kshell.h>
#include <kstandardaction.h>
#include <ktoggleaction.h>
#include <ktoolinvocation.h>
#include <kxmlguifactory.h>

#include "addignoremenu.h"
#include "annotatecontroller.h"
#include "annotatedialog.h"
#include "changelogdialog.h"
#include "cvsinitdialog.h"
#include "cvsserviceinterface.h"
#include "debug.h"
#include "diffdialog.h"
#include "editwithmenu.h"
#include "globalignorelist.h"
#include "historydialog.h"
#include "logdialog.h"
#include "mergedialog.h"
#include "misc.h"
#include "patchoptiondialog.h"
#include "progressdialog.h"
#include "protocolview.h"
#include "repositorydialog.h"
#include "resolvedialog.h"
#include "settingsdialog.h"
#include "updatedialog.h"
#include "updateview.h"
#include "updateview_items.h"
#include "watchersdialog.h"
#include <QFileDialog>
#include <repositoryinterface.h>

#include "cervisia_version.h"
#include "cvsjobinterface.h"

using Cervisia::TagDialog;

K_PLUGIN_FACTORY(CervisiaFactory, registerPlugin<CervisiaPart>();)
#include <cervisiapart.moc>

CervisiaPart::CervisiaPart(QWidget *parentWidget, QObject *parent, const QVariantList & /*args*/)
    : KParts::ReadOnlyPart(parent)
    , hasRunningJob(false)
    , opt_hideFiles(false)
    , opt_hideUpToDate(false)
    , opt_hideRemoved(false)
    , opt_hideNotInCVS(false)
    , opt_hideEmptyDirectories(false)
    , opt_createDirs(false)
    , opt_pruneDirs(false)
    , opt_updateRecursive(true)
    , opt_commitRecursive(true)
    , opt_doCVSEdit(false)
    , recent(0)
    , cvsService(0)
    , m_statusBar(new KParts::StatusBarExtension(this))
    , m_browserExt(0)
    , filterLabel(0)
    , m_editWithAction(0)
    , m_currentEditMenu(0)
    , m_addIgnoreAction(0)
    , m_currentIgnoreMenu(0)
    , m_jobType(Unknown)
{
    setComponentName("cervisiapart", i18n("Cervisia"));

    m_browserExt = new CervisiaBrowserExtension(this);

    // start the cvs D-Bus service
    QString error;
    if (KToolInvocation::startServiceByDesktopName("org.kde.cvsservice5", QStringList(), &error, &m_cvsServiceInterfaceName)) {
        KMessageBox::error(0, i18n("Starting cvsservice failed with message: ") + error, "Cervisia");
    } else
        // create a reference to the service
        cvsService = new OrgKdeCervisia5CvsserviceCvsserviceInterface(m_cvsServiceInterfaceName, "/CvsService", QDBusConnection::sessionBus(), this);
    // qCDebug(log_cervisia) << "m_cvsServiceInterfaceName:" << m_cvsServiceInterfaceName;
    // kdDebug(8050) << "cvsService->service():" << cvsService->service()<<endl;
    //  Create UI
    KConfigGroup conf(config(), "LookAndFeel");
    bool splitHorz = conf.readEntry("SplitHorizontally", true);

    // When we couldn't start the D-Bus service, we just display a QLabel with
    // an explanation
    if (cvsService) {
        Qt::Orientation o = splitHorz ? Qt::Vertical : Qt::Horizontal;
        splitter = new QSplitter(o, parentWidget);
        // avoid PartManager's warning that Part's window can't handle focus
        splitter->setFocusPolicy(Qt::StrongFocus);

        update = new UpdateView(*config(), splitter);
        update->setFocusPolicy(Qt::StrongFocus);
        update->setContextMenuPolicy(Qt::CustomContextMenu);
        update->setFocus();
        connect(update, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(popupRequested(QPoint)));

        connect(update, SIGNAL(fileOpened(QString)), this, SLOT(openFile(QString)));
        protocol = new ProtocolView(m_cvsServiceInterfaceName, splitter);
        protocol->setFocusPolicy(Qt::StrongFocus);

        setWidget(splitter);
    } else {
        setWidget(new QLabel(i18n("This KPart is non-functional, because the "
                                  "cvs D-Bus service could not be started."),
                             parentWidget));
    }

    if (cvsService) {
        setupActions();
        readSettings();
        connect(update, SIGNAL(itemSelectionChanged()), this, SLOT(updateActions()));
    }

    setXMLFile("cervisiaui.rc");

    QTimer::singleShot(0, this, SLOT(slotSetupStatusBar()));
}

CervisiaPart::~CervisiaPart()
{
    // stop the cvs DCOP service and delete reference
    if (cvsService) {
        writeSettings();
        cvsService->quit();
        delete cvsService;
    }
}

KConfig *CervisiaPart::config()
{
    KSharedConfigPtr tmp = KSharedConfig::openConfig();
    return tmp.data(); // the pointer won't get invalid even if the temporary tmp object is
                       // destroyed
}

bool CervisiaPart::openUrl(const QUrl &u)
{
    // right now, we are unfortunately not network-aware
    if (!u.isLocalFile()) {
        KMessageBox::error(widget(),
                           i18n("Remote CVS working folders are not "
                                "supported."),
                           "Cervisia");
        return false;
    }

    if (hasRunningJob) {
        KMessageBox::error(widget(),
                           i18n("You cannot change to a different folder "
                                "while there is a running cvs job."),
                           "Cervisia");
        return false;
    }

    // make a deep copy as if we're called via KRecentFilesAction::urlSelected()
    // KRecentFilesAction::addUrl() makes the URL invalid
    const QUrl deepCopy(u);

    return openSandbox(deepCopy);
}

void CervisiaPart::slotSetupStatusBar()
{
    // create the active filter indicator and add it to the statusbar
    filterLabel = new QLabel("UR", m_statusBar->statusBar());
    filterLabel->setFixedSize(filterLabel->sizeHint());
    filterLabel->setText("");
    filterLabel->setToolTip(
        i18n("F - All files are hidden, the tree shows only folders\n"
             "N - All up-to-date files are hidden\n"
             "R - All removed files are hidden"));
    m_statusBar->addStatusBarItem(filterLabel, 0, true);
}

void CervisiaPart::setupActions()
{
    QAction *action;
    QString hint;
    //
    // File Menu
    //
    action = new QAction(QIcon::fromTheme("document-open"), i18n("Open Sandbox..."), this);
    actionCollection()->addAction("file_open", action);
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_O));
    connect(action, SIGNAL(triggered(bool)), SLOT(slotOpenSandbox()));
    hint = i18n("Opens a CVS working folder in the main window");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    recent = new KRecentFilesAction(i18n("Recent Sandboxes"), this);
    actionCollection()->addAction("file_open_recent", recent);
    connect(recent, SIGNAL(urlSelected(QUrl)), SLOT(openUrl(QUrl))),

        action = new QAction(i18n("&Insert ChangeLog Entry..."), this);
    actionCollection()->addAction("insert_changelog_entry", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotChangeLog()));
    hint = i18n("Inserts a new intro into the file ChangeLog in the toplevel folder");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(QIcon::fromTheme("vcs-update-cvs-cervisia"), i18n("&Update"), this);
    actionCollection()->addAction("file_update", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotUpdate()));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_U));
    hint = i18n("Updates (cvs update) the selected files and folders");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(QIcon::fromTheme("vcs-status-cvs-cervisia"), i18n("&Status"), this);
    actionCollection()->addAction("file_status", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotStatus()));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_F5));
    hint = i18n("Updates the status (cvs -n update) of the selected files and folders");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Edit"), this);
    actionCollection()->addAction("file_edit", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotOpen()));
    hint = i18n("Opens the marked file for editing");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Reso&lve..."), this);
    actionCollection()->addAction("file_resolve", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotResolve()));
    hint = i18n("Opens the resolve dialog with the selected file");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(QIcon::fromTheme("vcs-commit-cvs-cervisia"), i18n("&Commit..."), this);
    actionCollection()->addAction("file_commit", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotCommit()));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_NumberSign));
    hint = i18n("Commits the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(QIcon::fromTheme("vcs-add-cvs-cervisia"), i18n("&Add to Repository..."), this);
    actionCollection()->addAction("file_add", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotAdd()));
    action->setIconText(i18n("Add"));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Insert));
    hint = i18n("Adds (cvs add) the selected files to the repository");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Add &Binary..."), this);
    actionCollection()->addAction("file_add_binary", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotAddBinary()));
    hint = i18n("Adds (cvs -kb add) the selected files as binaries to the repository");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(QIcon::fromTheme("vcs-remove-cvs-cervisia"), i18n("&Remove From Repository..."), this);
    actionCollection()->addAction("file_remove", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotRemove()));
    action->setIconText(i18n("Remove"));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Delete));
    hint = i18n("Removes (cvs remove) the selected files from the repository");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Rever&t"), this);
    actionCollection()->addAction("file_revert_local_changes", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotRevert()));
    hint = i18n("Reverts (cvs update -C) the selected files (only cvs 1.11)");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Properties"), this);
    actionCollection()->addAction("file_properties", action);
    connect(action, SIGNAL(triggered()), SLOT(slotFileProperties()));

    //
    // View Menu
    //
    action = new QAction(QIcon::fromTheme("process-stop"), i18n("Stop"), this);
    actionCollection()->addAction("stop_job", action);
    connect(action, SIGNAL(triggered(bool)), protocol, SLOT(cancelJob()));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::Key_Escape));
    action->setEnabled(false);
    hint = i18n("Stops any running sub-processes");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Browse &Log..."), this);
    actionCollection()->addAction("view_log", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotBrowseLog()));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L));
    hint = i18n("Shows the revision tree of the selected file");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

#if 0
    action = new QAction( i18n("Browse Multi-File Log..."), 0,
                          this, SLOT(slotBrowseMultiLog()),
                          actionCollection() );
#endif
    action = new QAction(i18n("&Annotate..."), this);
    actionCollection()->addAction("view_annotate", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotAnnotate()));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_A));
    hint = i18n("Shows a blame-annotated view of the selected file");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(QIcon::fromTheme("vcs-diff-cvs-cervisia"), i18n("&Difference to Repository (BASE)..."), this);
    actionCollection()->addAction("view_diff_base", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotDiffBase()));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_D));
    hint = i18n("Shows the differences of the selected file to the checked out version (tag BASE)");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(QIcon::fromTheme("vcs-diff-cvs-cervisia"), i18n("Difference to Repository (HEAD)..."), this);
    actionCollection()->addAction("view_diff_head", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotDiffHead()));
    actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_H));
    hint = i18n("Shows the differences of the selected file to the newest version in the repository (tag HEAD)");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Last &Change..."), this);
    actionCollection()->addAction("view_last_change", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotLastChange()));
    hint = i18n("Shows the differences between the last two revisions of the selected file");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&History..."), this);
    actionCollection()->addAction("view_history", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotHistory()));
    hint = i18n("Shows the CVS history as reported by the server");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Unfold File Tree"), this);
    actionCollection()->addAction("view_unfold_tree", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotUnfoldTree()));

    hint = i18n("Opens all branches of the file tree");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Fold File Tree"), this);
    actionCollection()->addAction("view_fold_tree", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotFoldTree()));
    hint = i18n("Closes all branches of the file tree");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    //
    // Advanced Menu
    //
    action = new QAction(i18n("&Tag/Branch..."), this);
    actionCollection()->addAction("create_tag", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotCreateTag()));
    hint = i18n("Creates a tag or branch for the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Delete Tag..."), this);
    actionCollection()->addAction("delete_tag", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotDeleteTag()));
    hint = i18n("Deletes a tag from the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Update to Tag/Date..."), this);
    actionCollection()->addAction("update_to_tag", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotUpdateToTag()));
    hint = i18n("Updates the selected files to a given tag, branch or date");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Update to &HEAD"), this);
    actionCollection()->addAction("update_to_head", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotUpdateToHead()));
    hint = i18n("Updates the selected files to the HEAD revision");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Merge..."), this);
    actionCollection()->addAction("merge", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotMerge()));
    hint = i18n("Merges a branch or a set of modifications into the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Add Watch..."), this);
    actionCollection()->addAction("add_watch", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotAddWatch()));
    hint = i18n("Adds a watch for the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Remove Watch..."), this);
    actionCollection()->addAction("remove_watch", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotRemoveWatch()));
    hint = i18n("Removes a watch from the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Show &Watchers"), this);
    actionCollection()->addAction("show_watchers", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotShowWatchers()));
    hint = i18n("Shows the watchers of the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Ed&it Files"), this);
    actionCollection()->addAction("edit_files", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotEdit()));
    hint = i18n("Edits (cvs edit) the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("U&nedit Files"), this);
    actionCollection()->addAction("unedit_files", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotUnedit()));
    hint = i18n("Unedits (cvs unedit) the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Show &Editors"), this);
    actionCollection()->addAction("show_editors", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotShowEditors()));
    hint = i18n("Shows the editors of the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Lock Files"), this);
    actionCollection()->addAction("lock_files", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotLock()));
    hint = i18n("Locks the selected files, so that others cannot modify them");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Unl&ock Files"), this);
    actionCollection()->addAction("unlock_files", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotUnlock()));
    hint = i18n("Unlocks the selected files");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("Create &Patch Against Repository..."), this);
    actionCollection()->addAction("make_patch", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotMakePatch()));
    hint = i18n("Creates a patch from the modifications in your sandbox");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    //
    // Repository Menu
    //
    action = new QAction(i18n("&Create..."), this);
    actionCollection()->addAction("repository_create", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotCreateRepository()));

    action = new QAction(i18n("&Checkout..."), this);
    actionCollection()->addAction("repository_checkout", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotCheckout()));
    hint = i18n("Allows you to checkout a module from a repository");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Import..."), this);
    actionCollection()->addAction("repository_import", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotImport()));
    hint = i18n("Allows you to import a module into a repository");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(i18n("&Repositories..."), this);
    actionCollection()->addAction("show_repositories", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotRepositories()));
    hint = i18n("Configures a list of repositories you regularly use");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    //
    // Settings menu
    //
    action = new KToggleAction(i18n("Hide All &Files"), this);
    actionCollection()->addAction("settings_hide_files", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotHideFiles()));
    hint = i18n("Determines whether only folders are shown");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new KToggleAction(i18n("Hide Unmodified Files"), this);
    actionCollection()->addAction("settings_hide_uptodate", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotHideUpToDate()));
    hint = i18n(
        "Determines whether files with status up-to-date or "
        "unknown are hidden");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new KToggleAction(i18n("Hide Removed Files"), this);
    actionCollection()->addAction("settings_hide_removed", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotHideRemoved()));
    hint = i18n("Determines whether removed files are hidden");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new KToggleAction(i18n("Hide Non-CVS Files"), this);
    actionCollection()->addAction("settings_hide_notincvs", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotHideNotInCVS()));
    hint = i18n("Determines whether files not in CVS are hidden");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new KToggleAction(i18n("Hide Empty Folders"), this);
    actionCollection()->addAction("settings_hide_empty_directories", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotHideEmptyDirectories()));
    hint = i18n("Determines whether folders without visible entries are hidden");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new KToggleAction(i18n("Create &Folders on Update"), this);
    actionCollection()->addAction("settings_create_dirs", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotCreateDirs()));
    hint = i18n("Determines whether updates create folders");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new KToggleAction(i18n("&Prune Empty Folders on Update"), this);
    actionCollection()->addAction("settings_prune_dirs", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotPruneDirs()));
    hint = i18n("Determines whether updates remove empty folders");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new KToggleAction(i18n("&Update Recursively"), this);
    actionCollection()->addAction("settings_update_recursively", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotUpdateRecursive()));
    hint = i18n("Determines whether updates are recursive");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new KToggleAction(i18n("C&ommit && Remove Recursively"), this);
    actionCollection()->addAction("settings_commit_recursively", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotCommitRecursive()));
    hint = i18n("Determines whether commits and removes are recursive");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new KToggleAction(i18n("Do cvs &edit Automatically When Necessary"), this);
    actionCollection()->addAction("settings_do_cvs_edit", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotDoCVSEdit()));
    hint = i18n("Determines whether automatic cvs editing is active");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    action = new QAction(QIcon::fromTheme("configure"), i18n("Configure Cervisia..."), this);
    actionCollection()->addAction("configure_cervisia", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotConfigure()));
    hint = i18n("Allows you to configure the Cervisia KPart");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    //
    // Help Menu
    //

    action = new QAction(i18n("CVS &Manual"), this);
    actionCollection()->addAction("help_cvs_manual", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotCVSInfo()));
    hint = i18n("Opens the help browser with the CVS documentation");
    action->setToolTip(hint);
    action->setWhatsThis(hint);

    //
    // Folder context menu
    //
    action = new KToggleAction(i18n("Unfold Folder"), this);
    actionCollection()->addAction("unfold_folder", action);
    connect(action, SIGNAL(triggered(bool)), SLOT(slotUnfoldFolder()));
}

void CervisiaPart::popupRequested(const QPoint &p)
{
    QString xmlName = "context_popup";
    QTreeWidgetItem *item = update->itemAt(p);

    // context menu for non-cvs files
    if (isFileItem(item)) {
        auto fileItem = static_cast<UpdateItem *>(item);
        if (fileItem->entry().m_status == Cervisia::NotInCVS)
            xmlName = "noncvs_context_popup";
    }

    // context menu for folders
    if (isDirItem(item) && update->fileSelection().isEmpty()) {
        xmlName = "folder_context_popup";
        KToggleAction *action = static_cast<KToggleAction *>(actionCollection()->action("unfold_folder"));
        action->setChecked(item->isExpanded());
    }

    if (auto popup = static_cast<QMenu *>(hostContainer(xmlName))) {
        if (isFileItem(item)) {
            // get name of selected file
            QString selectedFile;
            update->getSingleSelection(&selectedFile);

            if (!selectedFile.isEmpty()) {
                m_currentEditMenu = new Cervisia::EditWithMenu(QUrl::fromLocalFile(sandbox + '/' + selectedFile), popup);

                if (m_currentEditMenu->menu())
                    m_editWithAction = popup->insertMenu(popup->actions().at(1), m_currentEditMenu->menu());
            }
        }

        // Add to Ignore List Menu
        if (xmlName == "noncvs_context_popup") {
            // remove old 'Add to Ignore List' menu
            if (m_addIgnoreAction && popup->actions().contains(m_addIgnoreAction)) {
                popup->removeAction(m_addIgnoreAction);
                delete m_currentIgnoreMenu;

                m_addIgnoreAction = 0;
                m_currentIgnoreMenu = 0;
            }

            QStringList list = update->multipleSelection();
            m_currentIgnoreMenu = new Cervisia::AddIgnoreMenu(sandbox, list, popup);
            if (m_currentIgnoreMenu->menu())
                m_addIgnoreAction = popup->insertMenu(actionCollection()->action("file_add"), m_currentIgnoreMenu->menu());
        }

        popup->exec(update->viewport()->mapToGlobal(p));

        // remove old 'Edit with...' menu
        if (m_editWithAction && popup->actions().contains(m_editWithAction)) {
            popup->removeAction(m_editWithAction);
            delete m_currentEditMenu;

            m_editWithAction = 0;
            m_currentEditMenu = 0;
        }
    } else
        qCDebug(log_cervisia) << "can't get XML definition for" << xmlName << ", factory()=" << factory();
}

void CervisiaPart::updateActions()
{
    bool hassandbox = !sandbox.isEmpty();
    stateChanged("has_sandbox", hassandbox ? StateNoReverse : StateReverse);

    bool single = update->hasSingleSelection();
    stateChanged("has_single_selection", single ? StateNoReverse : StateReverse);

    bool singleFolder = (update->multipleSelection().count() == 1);
    stateChanged("has_single_folder", singleFolder ? StateNoReverse : StateReverse);

    //    bool nojob = !( actionCollection()->action( "stop_job" )->isEnabled() );
    bool selected = (update->currentItem() != 0);
    bool nojob = !hasRunningJob && selected;

    stateChanged("item_selected", selected ? StateNoReverse : StateReverse);
    stateChanged("has_no_job", nojob ? StateNoReverse : StateReverse);
    stateChanged("has_running_job", hasRunningJob ? StateNoReverse : StateReverse);
}

KAboutData *CervisiaPart::createAboutData()
{
    auto about = new KAboutData("cervisiapart",
                                i18n("Cervisia Part"),
                                CERVISIA_VERSION_STRING,
                                i18n("A CVS frontend"),
                                KAboutLicense::GPL,
                                i18n("Copyright (c) 1999-2002 Bernd Gehrmann\n"
                                     "Copyright (c) 2002-2008 the Cervisia authors"),
                                QString(),
                                QLatin1String("http://cervisia.kde.org"));

    about->addAuthor(i18n("Bernd Gehrmann"),
                     i18n("Original author and former "
                          "maintainer"),
                     "bernd@mail.berlios.de");
    about->addAuthor(i18n("Christian Loose"), i18n("Maintainer"), "christian.loose@kdemail.net");
    about->addAuthor(i18n("Andr\303\251 W\303\266bbeking"), i18n("Developer"), "woebbeking@kde.org");
    about->addAuthor(i18n("Carlos Woelz"), i18n("Documentation"), "carloswoelz@imap-mail.com");

    about->addCredit(i18n("Richard Moore"), i18n("Conversion to KPart"), "rich@kde.org");
    about->addCredit(i18n("Laurent Montel"), i18n("Conversion to D-Bus"), "montel@kde.org");
    about->addCredit(i18n("Martin Koller"), i18n("Port to KDE Frameworks 5"), "kollix@aon.at");

    return about;
}

void CervisiaPart::slotOpenSandbox()
{
    QString dirname = QFileDialog::getExistingDirectory(widget(), i18n("Open Sandbox"));

    if (dirname.isEmpty())
        return;

    openSandbox(QUrl::fromLocalFile(dirname));
}

void CervisiaPart::slotChangeLog()
{
    // Modal dialog
    ChangeLogDialog dlg(*config(), widget());
    if (dlg.readFile(sandbox + "/ChangeLog")) {
        if (dlg.exec())
            changelogstr = dlg.message();
    }
}

void CervisiaPart::slotOpen()
{
    QStringList filenames = update->fileSelection();
    if (filenames.isEmpty())
        return;
    openFiles(filenames);
}

void CervisiaPart::openFile(QString filename)
{
    QStringList files;
    files << filename;
    openFiles(files);
}

void CervisiaPart::openFiles(const QStringList &filenames)
{
    // call cvs edit automatically?
    if (opt_doCVSEdit) {
        QStringList files;

        // only edit read-only files
        QStringList::ConstIterator it = filenames.begin();
        QStringList::ConstIterator end = filenames.end();
        for (; it != end; ++it) {
            if (!QFileInfo(*it).isWritable())
                files << *it;
        }

        if (files.count()) {
            QDBusReply<QDBusObjectPath> job = cvsService->edit(files);

            ProgressDialog dlg(widget(), "Edit", cvsService->service(), job, "edit", i18n("CVS Edit"));
            if (!dlg.execute())
                return;
        }
    }

    // Now open the files by using KRun
    QDir dir(sandbox);

    QStringList::ConstIterator it = filenames.begin();
    QStringList::ConstIterator end = filenames.end();
    for (; it != end; ++it) {
        KRun *run = new KRun(QUrl::fromLocalFile(dir.absoluteFilePath(*it)), 0);
        run->setRunExecutables(false);
    }
}

void CervisiaPart::slotResolve()
{
    QString filename;
    update->getSingleSelection(&filename);
    if (filename.isEmpty())
        return;

    // Non-modal dialog
    auto l = new ResolveDialog(*config());
    if (l->parseFile(filename))
        l->show();
    else
        delete l;
}

void CervisiaPart::slotUpdate()
{
    updateSandbox();
}

void CervisiaPart::slotStatus()
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    update->prepareJob(opt_updateRecursive, UpdateView::UpdateNoAct);

    QDBusReply<QDBusObjectPath> cvsJobPath = cvsService->simulateUpdate(list, opt_updateRecursive, opt_createDirs, opt_pruneDirs);

    // get command line from cvs job
    QString cmdline;
    QDBusObjectPath cvsJob = cvsJobPath;
    if (cvsJob.path().isEmpty())
        return;

    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();
    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob(true)) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(receivedLine(QString)), update, SLOT(processUpdateLine(QString)));
        connect(protocol, SIGNAL(jobFinished(bool, int)), update, SLOT(finishJob(bool, int)));
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::slotUpdateToTag()
{
    auto l = new UpdateDialog(cvsService, widget());

    if (l->exec()) {
        QString tagopt;
        if (l->byTag()) {
            tagopt = "-r ";
            tagopt += l->tag();
        } else {
            tagopt = "-D ";
            tagopt += KShell::quoteArg(l->date());
        }
        tagopt += ' ';
        updateSandbox(tagopt);
    }
    delete l;
}

void CervisiaPart::slotUpdateToHead()
{
    updateSandbox("-A");
}

void CervisiaPart::slotRevert()
{
    updateSandbox("-C");
}

void CervisiaPart::slotMerge()
{
    MergeDialog dlg(cvsService, widget());

    if (dlg.exec()) {
        QString tagopt;
        if (dlg.byBranch()) {
            tagopt = "-j ";
            tagopt += dlg.branch();
        } else {
            tagopt = "-j ";
            tagopt += dlg.tag1();
            tagopt += " -j ";
            tagopt += dlg.tag2();
        }
        tagopt += ' ';
        updateSandbox(tagopt);
    }
}

void CervisiaPart::slotCommit()
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    // modal dialog
    CommitDialog dlg(*config(), cvsService, widget());
    dlg.setLogMessage(changelogstr);
    dlg.setLogHistory(recentCommits);
    dlg.setFileList(list);

    if (dlg.exec()) {
        // get new list of files
        list = dlg.fileList();
        if (list.isEmpty())
            return;

        QString msg = dlg.logMessage();
        if (!recentCommits.contains(msg)) {
            recentCommits.prepend(msg);
            while (recentCommits.count() > 50)
                recentCommits.removeLast();

            KConfigGroup conf(config(), "CommitLogs");
            conf.writeEntry(sandbox, recentCommits);
        }

        update->prepareJob(opt_commitRecursive, UpdateView::Commit);

        QDBusReply<QDBusObjectPath> cvsJobPath = cvsService->commit(list, dlg.logMessage(), opt_commitRecursive);
        QString cmdline;
        QDBusObjectPath cvsJob = cvsJobPath;
        qCDebug(log_cervisia) << " commit: cvsJob.path():" << cvsJob.path();
        qCDebug(log_cervisia) << " list:" << list << "dlg.logMessage():" << dlg.logMessage() << "opt_commitRecursive" << opt_commitRecursive;
        if (cvsJob.path().isEmpty())
            return;

        OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
        QDBusReply<QString> reply = cvsjobinterface.cvsCommand();
        if (reply.isValid())
            cmdline = reply;

        if (protocol->startJob()) {
            m_jobType = Commit;
            showJobStart(cmdline);
            connect(protocol, SIGNAL(jobFinished(bool, int)), update, SLOT(finishJob(bool, int)));
            connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
        }
    }
}

void CervisiaPart::slotAdd()
{
    addOrRemove(AddRemoveDialog::Add);
}

void CervisiaPart::slotAddBinary()
{
    addOrRemove(AddRemoveDialog::AddBinary);
}

void CervisiaPart::slotRemove()
{
    addOrRemove(AddRemoveDialog::Remove);
}

void CervisiaPart::slotFileProperties()
{
    QString filename;
    update->getSingleSelection(&filename);
    if (filename.isEmpty())
        return;

    // Create URL from selected filename
    QDir dir(sandbox);

    // show file properties dialog
    KPropertiesDialog dlg(QUrl::fromLocalFile(dir.absoluteFilePath(filename)), widget());
    dlg.exec();
}

void CervisiaPart::updateSandbox(const QString &extraopt)
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    update->prepareJob(opt_updateRecursive, UpdateView::Update);

    QDBusReply<QDBusObjectPath> cvsJobPath = cvsService->update(list, opt_updateRecursive, opt_createDirs, opt_pruneDirs, extraopt);

    // get command line from cvs job
    QString cmdline;
    QDBusObjectPath cvsJob = cvsJobPath;
    if (cvsJob.path().isEmpty())
        return;
    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob(true)) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(receivedLine(QString)), update, SLOT(processUpdateLine(QString)));
        connect(protocol, SIGNAL(jobFinished(bool, int)), update, SLOT(finishJob(bool, int)));
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::addOrRemove(AddRemoveDialog::ActionType action)
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    // modal dialog
    AddRemoveDialog dlg(action, widget());
    dlg.setFileList(list);

    if (dlg.exec()) {
        QDBusReply<QDBusObjectPath> cvsJob;

        switch (action) {
        case AddRemoveDialog::Add:
            update->prepareJob(false, UpdateView::Add);
            cvsJob = cvsService->add(list, false);
            break;

        case AddRemoveDialog::AddBinary:
            update->prepareJob(false, UpdateView::Add);
            cvsJob = cvsService->add(list, true);
            break;

        case AddRemoveDialog::Remove:
            update->prepareJob(opt_commitRecursive, UpdateView::Remove);
            cvsJob = cvsService->remove(list, opt_commitRecursive);
            break;
        }

        // get command line from cvs job
        QString cmdline;
        QDBusObjectPath cvsJobPath = cvsJob;
        if (cvsJobPath.path().isEmpty())
            return;

        OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJobPath.path(), QDBusConnection::sessionBus(), this);
        QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

        if (reply.isValid())
            cmdline = reply;

        if (protocol->startJob()) {
            showJobStart(cmdline);
            connect(protocol, SIGNAL(jobFinished(bool, int)), update, SLOT(finishJob(bool, int)));
            connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
        }
    }
}

void CervisiaPart::slotBrowseLog()
{
    QString filename;
    update->getSingleSelection(&filename);
    if (filename.isEmpty())
        return;

    // Non-modal dialog
    auto l = new LogDialog(*CervisiaPart::config());
    if (l->parseCvsLog(cvsService, filename))
        l->show();
    else
        delete l;
}

void CervisiaPart::slotAnnotate()
{
    QString filename, revision;
    update->getSingleSelection(&filename, &revision);

    if (filename.isEmpty())
        return;

    // Non-modal dialog
    auto dlg = new AnnotateDialog(*config());
    AnnotateController ctl(dlg, cvsService);
    ctl.showDialog(filename, revision);
}

void CervisiaPart::slotDiffBase()
{
    showDiff(QLatin1String("BASE"));
}

void CervisiaPart::slotDiffHead()
{
    showDiff(QLatin1String("HEAD"));
}

void CervisiaPart::slotAddWatch()
{
    addOrRemoveWatch(WatchDialog::Add);
}

void CervisiaPart::slotRemoveWatch()
{
    addOrRemoveWatch(WatchDialog::Remove);
}

void CervisiaPart::addOrRemoveWatch(WatchDialog::ActionType action)
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    WatchDialog dlg(action, widget());

    if (dlg.exec() && dlg.events() != WatchDialog::None) {
        QDBusReply<QDBusObjectPath> cvsJob;

        if (action == WatchDialog::Add)
            cvsJob = cvsService->addWatch(list, dlg.events());
        else
            cvsJob = cvsService->removeWatch(list, dlg.events());

        QString cmdline;
        QDBusObjectPath cvsJobPath = cvsJob;
        if (cvsJobPath.path().isEmpty())
            return;

        OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJobPath.path(), QDBusConnection::sessionBus(), this);
        QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

        if (reply.isValid())
            cmdline = reply;

        if (protocol->startJob()) {
            showJobStart(cmdline);
            connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
        }
    }
}

void CervisiaPart::slotShowWatchers()
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    // Non-modal dialog
    auto dlg = new WatchersDialog(*config());
    if (dlg->parseWatchers(cvsService, list))
        dlg->show();
    else
        delete dlg;
}

void CervisiaPart::slotEdit()
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    QDBusReply<QDBusObjectPath> cvsJobPath = cvsService->edit(list);

    QString cmdline;
    QDBusObjectPath cvsJob = cvsJobPath;
    if (cvsJob.path().isEmpty())
        return;

    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob()) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::slotUnedit()
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    QDBusReply<QDBusObjectPath> cvsJob = cvsService->unedit(list);

    QString cmdline;
    QDBusObjectPath cvsJobPath = cvsJob;
    if (cvsJobPath.path().isEmpty())
        return;

    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJobPath.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob()) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::slotLock()
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    QDBusReply<QDBusObjectPath> cvsJobPath = cvsService->lock(list);
    QDBusObjectPath cvsJob = cvsJobPath;
    if (cvsJob.path().isEmpty())
        return;
    QString cmdline;
    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob()) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::slotUnlock()
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    QDBusReply<QDBusObjectPath> cvsJobPath = cvsService->unlock(list);
    QDBusObjectPath cvsJob = cvsJobPath;
    if (cvsJob.path().isEmpty())
        return;

    QString cmdline;
    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob()) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::slotShowEditors()
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    QDBusReply<QDBusObjectPath> cvsJobPath = cvsService->editors(list);
    QDBusObjectPath cvsJob = cvsJobPath;
    if (cvsJob.path().isEmpty())
        return;

    QString cmdline;
    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob()) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::slotMakePatch()
{
    Cervisia::PatchOptionDialog optionDlg;
    if (optionDlg.exec() == QDialog::Rejected)
        return;

    QString format = optionDlg.formatOption();
    QString diffOptions = optionDlg.diffOptions();

    QDBusReply<QDBusObjectPath> job = cvsService->makePatch(diffOptions, format);
    if (!job.isValid())
        return;

    ProgressDialog dlg(widget(), "Diff", cvsService->service(), job, "", i18n("CVS Diff"));
    if (!dlg.execute())
        return;

    QString fileName = QFileDialog::getSaveFileName();
    if (fileName.isEmpty())
        return;

    if (!Cervisia::CheckOverwrite(fileName))
        return;

    QFile f(fileName);
    if (!f.open(QIODevice::WriteOnly)) {
        KMessageBox::error(widget(), i18n("Could not open file for writing."), "Cervisia");
        return;
    }

    QTextStream t(&f);
    QString line;
    while (dlg.getLine(line))
        t << line << '\n';

    f.close();
}

void CervisiaPart::slotImport()
{
    CheckoutDialog dlg(*config(), cvsService, CheckoutDialog::Import, widget());

    if (!dlg.exec())
        return;

    QDBusReply<QDBusObjectPath> cvsJobPath = cvsService->import(dlg.workingDirectory(),
                                                                dlg.repository(),
                                                                dlg.module(),
                                                                dlg.ignoreFiles(),
                                                                dlg.comment(),
                                                                dlg.vendorTag(),
                                                                dlg.releaseTag(),
                                                                dlg.importBinary(),
                                                                dlg.useModificationTime());

    QDBusObjectPath cvsJob = cvsJobPath;
    QString cmdline;
    ////qDebug()<<" cvsJob.path() :"<<cvsJob.path()<<endl;
    if (cvsJob.path().isEmpty())
        return;
    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob()) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::slotCreateRepository()
{
    Cervisia::CvsInitDialog dlg(widget());

    if (!dlg.exec())
        return;

    QDBusReply<QDBusObjectPath> cvsJobPath = cvsService->createRepository(dlg.directory());
    QDBusObjectPath cvsJob = cvsJobPath;
    QString cmdline;
    if (cvsJob.path().isEmpty())
        return;
    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob()) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::slotCheckout()
{
    CheckoutDialog dlg(*config(), cvsService, CheckoutDialog::Checkout, widget());

    if (!dlg.exec())
        return;

    QDBusReply<QDBusObjectPath> cvsJobPath =
        cvsService
            ->checkout(dlg.workingDirectory(), dlg.repository(), dlg.module(), dlg.branch(), opt_pruneDirs, dlg.alias(), dlg.exportOnly(), dlg.recursive());
    QDBusObjectPath cvsJob = cvsJobPath;
    QString cmdline;
    OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJob.path(), QDBusConnection::sessionBus(), this);
    QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

    if (reply.isValid())
        cmdline = reply;

    if (protocol->startJob()) {
        showJobStart(cmdline);
        connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
    }
}

void CervisiaPart::slotRepositories()
{
    auto l = new RepositoryDialog(*config(), cvsService, m_cvsServiceInterfaceName, widget());
    l->show();
}

void CervisiaPart::slotCreateTag()
{
    createOrDeleteTag(TagDialog::Create);
}

void CervisiaPart::slotDeleteTag()
{
    createOrDeleteTag(TagDialog::Delete);
}

void CervisiaPart::createOrDeleteTag(TagDialog::ActionType action)
{
    QStringList list = update->multipleSelection();
    if (list.isEmpty())
        return;

    TagDialog dlg(action, cvsService, widget());

    if (dlg.exec()) {
        QDBusReply<QDBusObjectPath> cvsJob;

        if (action == TagDialog::Create)
            cvsJob = cvsService->createTag(list, dlg.tag(), dlg.branchTag(), dlg.forceTag());
        else
            cvsJob = cvsService->deleteTag(list, dlg.tag(), dlg.branchTag(), dlg.forceTag());
        QDBusObjectPath cvsJobPath = cvsJob;
        QString cmdline;
        OrgKdeCervisia5CvsserviceCvsjobInterface cvsjobinterface(m_cvsServiceInterfaceName, cvsJobPath.path(), QDBusConnection::sessionBus(), this);
        QDBusReply<QString> reply = cvsjobinterface.cvsCommand();

        if (reply.isValid())
            cmdline = reply;

        if (protocol->startJob()) {
            showJobStart(cmdline);
            connect(protocol, SIGNAL(jobFinished(bool, int)), this, SLOT(slotJobFinished()));
        }
    }
}

void CervisiaPart::slotLastChange()
{
    QString filename, revA, revB;
    update->getSingleSelection(&filename, &revA);
    if (filename.isEmpty())
        return;

    int pos, lastnumber = 0;
    bool ok;
    if ((pos = revA.lastIndexOf('.')) == -1 || (lastnumber = revA.right(revA.length() - pos - 1).toUInt(&ok), !ok)) {
        KMessageBox::error(widget(), i18n("The revision looks invalid."), "Cervisia");
        return;
    }
    if (lastnumber == 0) {
        KMessageBox::error(widget(), i18n("This is the first revision of the branch."), "Cervisia");
        return;
    }
    revB = revA.left(pos + 1);
    revB += QString::number(lastnumber - 1);

    // Non-modal dialog
    auto l = new DiffDialog(*config());
    if (l->parseCvsDiff(cvsService, filename, revB, revA))
        l->show();
    else
        delete l;
}

void CervisiaPart::slotHistory()
{
    // Non-modal dialog
    auto l = new HistoryDialog(*config());
    if (l->parseHistory(cvsService))
        l->show();
    else
        delete l;
}

void CervisiaPart::slotHideFiles()
{
    opt_hideFiles = !opt_hideFiles;
    setFilter();
}

void CervisiaPart::slotHideUpToDate()
{
    opt_hideUpToDate = !opt_hideUpToDate;
    setFilter();
}

void CervisiaPart::slotHideRemoved()
{
    opt_hideRemoved = !opt_hideRemoved;
    setFilter();
}

void CervisiaPart::slotHideNotInCVS()
{
    opt_hideNotInCVS = !opt_hideNotInCVS;
    setFilter();
}

void CervisiaPart::slotHideEmptyDirectories()
{
    opt_hideEmptyDirectories = !opt_hideEmptyDirectories;
    setFilter();
}

void CervisiaPart::slotFoldTree()
{
    update->foldTree();
    setFilter();
}

void CervisiaPart::slotUnfoldTree()
{
    update->unfoldTree();
    setFilter();
}

void CervisiaPart::slotUnfoldFolder()
{
    update->unfoldSelectedFolders();
    setFilter();
}

void CervisiaPart::slotCreateDirs()
{
    opt_createDirs = !opt_createDirs;
}

void CervisiaPart::slotPruneDirs()
{
    opt_pruneDirs = !opt_pruneDirs;
}

void CervisiaPart::slotUpdateRecursive()
{
    opt_updateRecursive = !opt_updateRecursive;
}

void CervisiaPart::slotCommitRecursive()
{
    opt_commitRecursive = !opt_commitRecursive;
}

void CervisiaPart::slotDoCVSEdit()
{
    opt_doCVSEdit = !opt_doCVSEdit;
}

void CervisiaPart::slotConfigure()
{
    KConfig *conf = config();
    auto l = new SettingsDialog(conf, widget());
    l->exec();

    bool splitHorz = conf->group("LookAndFeel").readEntry("SplitHorizontally", true);
    splitter->setOrientation(splitHorz ? Qt::Vertical : Qt::Horizontal);
    delete l;
}

void CervisiaPart::slotCVSInfo()
{
    emit setStatusBarText(i18n("Invoking help on CVS"));
    auto job = new KIO::ApplicationLauncherJob(KService::serviceByDesktopName("org.kde.khelpcenter"));
    job->setUrls({QUrl(QStringLiteral("man:/(1)/cvs"))});
    job->start();
}

void CervisiaPart::showJobStart(const QString &cmdline)
{
    hasRunningJob = true;
    actionCollection()->action("stop_job")->setEnabled(true);

    emit setStatusBarText(cmdline);
    updateActions();
}

void CervisiaPart::showDiff(const QString &revision)
{
    QString fileName;
    update->getSingleSelection(&fileName);

    if (fileName.isEmpty())
        return;

    // Non-modal dialog
    auto l = new DiffDialog(*config());
    if (l->parseCvsDiff(cvsService, fileName, revision, QString()))
        l->show();
    else
        delete l;
}

void CervisiaPart::slotJobFinished()
{
    actionCollection()->action("stop_job")->setEnabled(false);
    hasRunningJob = false;
    emit setStatusBarText(i18n("Done"));
    updateActions();

    disconnect(protocol, SIGNAL(receivedLine(QString)), update, SLOT(processUpdateLine(QString)));

    if (m_jobType == Commit) {
        KNotification::event("cvs_commit_done", i18n("A CVS commit to repository %1 is done", repository), QPixmap(), widget()->parentWidget());
        m_jobType = Unknown;
    }
}

bool CervisiaPart::openSandbox(const QUrl &url)
{
    // Do we have a cvs service?
    if (!cvsService)
        return false;
    OrgKdeCervisia5RepositoryInterface cvsRepository(m_cvsServiceInterfaceName, "/CvsRepository", QDBusConnection::sessionBus());

    // change the working copy directory for the cvs D-Bus service
    QDBusReply<bool> reply = cvsRepository.setWorkingCopy(url.path());

    if (!reply.isValid() || !reply.value()) {
        KMessageBox::error(widget(),
                           i18n("This is not a CVS folder.\n"
                                "If you did not intend to use Cervisia, you can "
                                "switch view modes within Konqueror."),
                           "Cervisia");

        // remove path from recent sandbox menu
        recent->removeUrl(url);

        return false;
    }

    changelogstr = "";
    sandbox = "";
    repository = "";

    // get path of sandbox for recent sandbox menu
    sandbox = cvsRepository.workingCopy();
    recent->addUrl(url);

    // get repository for the caption of the window
    repository = cvsRepository.location();
    emit setWindowCaption(sandbox + '(' + repository + ')');

    // set m_url member for tabbed window modus of Konqueror
    setUrl(url);

    // *NOTICE*
    // The order is important here. We have to set the url member before
    // calling this function because the progress dialog uses the enter_loop()/
    // exit_loop() methods. Those methods result in a call to queryExit() in
    // cervisiashell.cpp which then uses the url member to save the last used
    // directory.
    if (cvsRepository.retrieveCvsignoreFile())
        Cervisia::GlobalIgnoreList().retrieveServerIgnoreList(cvsService, repository);
    QDir::setCurrent(sandbox);
    update->openDirectory(sandbox);
    setFilter();

    KConfig *conf = config();
    bool dostatus = conf->group("General").readEntry(repository.contains(":") ? "StatusForRemoteRepos" : "StatusForLocalRepos", false);
    if (dostatus) {
        update->topLevelItem(0)->setSelected(true);
        slotStatus();
    }

    // load the recentCommits for this app from the KConfig app
    recentCommits = conf->group("CommitLogs").readEntry(sandbox, QStringList());

    return true;
}

void CervisiaPart::setFilter()
{
    auto filter = UpdateView::Filter(0);
    if (opt_hideFiles)
        filter = UpdateView::Filter(filter | UpdateView::OnlyDirectories);
    if (opt_hideUpToDate)
        filter = UpdateView::Filter(filter | UpdateView::NoUpToDate);
    if (opt_hideRemoved)
        filter = UpdateView::Filter(filter | UpdateView::NoRemoved);
    if (opt_hideNotInCVS)
        filter = UpdateView::Filter(filter | UpdateView::NoNotInCVS);
    if (opt_hideEmptyDirectories)
        filter = UpdateView::Filter(filter | UpdateView::NoEmptyDirectories);
    update->setFilter(filter);

    QString str;
    if (opt_hideFiles)
        str = 'F';
    else {
        if (opt_hideUpToDate)
            str += 'N';
        if (opt_hideRemoved)
            str += 'R';
    }

    if (filterLabel)
        filterLabel->setText(str);
}

void CervisiaPart::readSettings()
{
    const KConfigGroup config(this->config(), "Session");
    recent->loadEntries(config);

    // Unfortunately, the KConfig systems sucks and we have to live
    // with all entries in one group for session management.

    opt_createDirs = config.readEntry("Create Dirs", true);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_create_dirs")))->setChecked(opt_createDirs);

    opt_pruneDirs = config.readEntry("Prune Dirs", true);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_prune_dirs")))->setChecked(opt_pruneDirs);

    opt_updateRecursive = config.readEntry("Update Recursive", false);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_update_recursively")))->setChecked(opt_updateRecursive);

    opt_commitRecursive = config.readEntry("Commit Recursive", false);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_commit_recursively")))->setChecked(opt_commitRecursive);

    opt_doCVSEdit = config.readEntry("Do cvs edit", false);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_do_cvs_edit")))->setChecked(opt_doCVSEdit);

    opt_hideFiles = config.readEntry("Hide Files", false);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_hide_files")))->setChecked(opt_hideFiles);

    opt_hideUpToDate = config.readEntry("Hide UpToDate Files", false);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_hide_uptodate")))->setChecked(opt_hideUpToDate);

    opt_hideRemoved = config.readEntry("Hide Removed Files", false);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_hide_removed")))->setChecked(opt_hideRemoved);

    opt_hideNotInCVS = config.readEntry("Hide Non CVS Files", false);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_hide_notincvs")))->setChecked(opt_hideNotInCVS);

    opt_hideEmptyDirectories = config.readEntry("Hide Empty Directories", false);
    (static_cast<KToggleAction *>(actionCollection()->action("settings_hide_empty_directories")))->setChecked(opt_hideEmptyDirectories);

    setFilter();

    int splitterpos1 = config.readEntry("Splitter Pos 1", 0);
    int splitterpos2 = config.readEntry("Splitter Pos 2", 0);
    if (splitterpos1) {
        QList<int> sizes;
        sizes << splitterpos1;
        sizes << splitterpos2;
        splitter->setSizes(sizes);
    }
}

void CervisiaPart::writeSettings()
{
    KConfigGroup config(this->config(), "Session");
    recent->saveEntries(config);

    config.writeEntry("Create Dirs", opt_createDirs);
    config.writeEntry("Prune Dirs", opt_pruneDirs);
    config.writeEntry("Update Recursive", opt_updateRecursive);
    config.writeEntry("Commit Recursive", opt_commitRecursive);
    config.writeEntry("Do cvs edit", opt_doCVSEdit);
    config.writeEntry("Hide Files", opt_hideFiles);
    config.writeEntry("Hide UpToDate Files", opt_hideUpToDate);
    config.writeEntry("Hide Removed Files", opt_hideRemoved);
    config.writeEntry("Hide Non CVS Files", opt_hideNotInCVS);
    config.writeEntry("Hide Empty Directories", opt_hideEmptyDirectories);
    QList<int> sizes = splitter->sizes();
    config.writeEntry("Splitter Pos 1", sizes[0]);
    config.writeEntry("Splitter Pos 2", sizes[1]);

    // write to disk
    config.sync();
}

void CervisiaPart::guiActivateEvent(KParts::GUIActivateEvent *event)
{
    if (event->activated() && cvsService) {
        // initial setup of the menu items' state
        updateActions();
    }

    // don't call this as it overwrites Konqueror's caption (if you have a
    // Konqueror with more than one view and switch back to Cervisia)
    //
    // KParts::ReadOnlyPart::guiActivateEvent(event);
}

CervisiaBrowserExtension::CervisiaBrowserExtension(CervisiaPart *p)
    : KParts::BrowserExtension(p)
{
}

CervisiaBrowserExtension::~CervisiaBrowserExtension() = default;

// Local Variables:
// c-basic-offset: 4
// End:
