Chris@57: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ jtkorhonen@0: Chris@57: /* Chris@57: EasyMercurial Chris@57: Chris@98: Based on hgExplorer by Jari Korhonen Chris@57: Copyright (c) 2010 Jari Korhonen Chris@244: Copyright (c) 2011 Chris Cannam Chris@244: Copyright (c) 2011 Queen Mary, University of London Chris@57: Chris@57: This program is free software; you can redistribute it and/or Chris@57: modify it under the terms of the GNU General Public License as Chris@57: published by the Free Software Foundation; either version 2 of the Chris@57: License, or (at your option) any later version. See the file Chris@57: COPYING included with this distribution for more information. Chris@57: */ Chris@50: jtkorhonen@0: #include jtkorhonen@0: #include jtkorhonen@24: #include jtkorhonen@17: #include jtkorhonen@17: #include jtkorhonen@34: #include Chris@50: #include Chris@50: #include Chris@50: #include Chris@50: #include Chris@50: #include Chris@61: #include Chris@50: #include Chris@90: #include Chris@125: #include Chris@199: #include Chris@237: #include Chris@238: #include jtkorhonen@0: Chris@53: #include "mainwindow.h" Chris@69: #include "multichoicedialog.h" Chris@64: #include "startupdialog.h" Chris@53: #include "colourset.h" Chris@62: #include "debug.h" Chris@74: #include "logparser.h" Chris@103: #include "confirmcommentdialog.h" Chris@125: #include "incomingdialog.h" Chris@175: #include "settingsdialog.h" Chris@275: #include "moreinformationdialog.h" Chris@229: #include "version.h" Chris@53: jtkorhonen@0: Chris@172: MainWindow::MainWindow(QString myDirPath) : Chris@238: m_myDirPath(myDirPath), Chris@238: m_fsWatcherGeneralTimer(0), Chris@241: m_fsWatcherRestoreTimer(0), Chris@241: m_fsWatcherSuspended(false) jtkorhonen@0: { Chris@197: setWindowIcon(QIcon(":images/easyhg-icon.png")); Chris@197: jtkorhonen@0: QString wndTitle; jtkorhonen@0: Chris@284: m_showAllFiles = false; Chris@273: Chris@284: m_fsWatcher = 0; Chris@284: m_commitsSincePush = 0; Chris@284: m_shouldHgStat = true; Chris@90: jtkorhonen@0: createActions(); jtkorhonen@0: createMenus(); jtkorhonen@0: createToolBars(); jtkorhonen@0: createStatusBar(); jtkorhonen@0: Chris@284: m_runner = new HgRunner(m_myDirPath, this); Chris@284: connect(m_runner, SIGNAL(commandStarting(HgAction)), Chris@241: this, SLOT(commandStarting(HgAction))); Chris@284: connect(m_runner, SIGNAL(commandCompleted(HgAction, QString)), Chris@109: this, SLOT(commandCompleted(HgAction, QString))); Chris@284: connect(m_runner, SIGNAL(commandFailed(HgAction, QString)), Chris@109: this, SLOT(commandFailed(HgAction, QString))); Chris@284: statusBar()->addPermanentWidget(m_runner); jtkorhonen@0: Chris@61: setWindowTitle(tr("EasyMercurial")); jtkorhonen@0: Chris@284: m_remoteRepoPath = ""; Chris@284: m_workFolderPath = ""; jtkorhonen@0: jtkorhonen@0: readSettings(); jtkorhonen@0: Chris@284: m_justMerged = false; Chris@210: Chris@210: QWidget *central = new QWidget(this); Chris@210: setCentralWidget(central); Chris@210: Chris@284: m_hgTabs = new HgTabWidget(central, m_remoteRepoPath, m_workFolderPath); Chris@141: connectTabsSignals(); Chris@210: Chris@210: // Instead of setting the tab widget as our central widget Chris@210: // directly, put it in a layout, so that we can have some space Chris@210: // around it on the Mac where it looks very strange without Chris@210: Chris@210: QGridLayout *cl = new QGridLayout(central); Chris@284: cl->addWidget(m_hgTabs, 0, 0); Chris@210: Chris@210: #ifndef Q_OS_MAC Chris@210: cl->setMargin(0); Chris@210: #endif jtkorhonen@0: Chris@284: connect(m_hgTabs, SIGNAL(selectionChanged()), Chris@95: this, SLOT(enableDisableActions())); Chris@284: connect(m_hgTabs, SIGNAL(showAllChanged(bool)), Chris@199: this, SLOT(showAllChanged(bool))); Chris@95: jtkorhonen@0: setUnifiedTitleAndToolBarOnMac(true); jtkorhonen@0: connectActions(); Chris@120: clearState(); jtkorhonen@0: enableDisableActions(); jtkorhonen@0: Chris@284: if (m_firstStart) { Chris@64: startupDialog(); jtkorhonen@0: } jtkorhonen@0: Chris@239: SettingsDialog::findDefaultLocations(m_myDirPath); Chris@112: Chris@64: ColourSet *cs = ColourSet::instance(); Chris@64: cs->clearDefaultNames(); Chris@64: cs->addDefaultName(""); Chris@153: cs->addDefaultName("default"); Chris@64: cs->addDefaultName(getUserInfo()); Chris@62: Chris@175: hgTest(); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: jtkorhonen@0: void MainWindow::closeEvent(QCloseEvent *) jtkorhonen@0: { jtkorhonen@0: writeSettings(); Chris@284: delete m_fsWatcher; jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: Chris@64: QString MainWindow::getUserInfo() const Chris@64: { Chris@64: QSettings settings; Chris@64: settings.beginGroup("User Information"); Chris@64: QString name = settings.value("name", getUserRealName()).toString(); Chris@64: QString email = settings.value("email", "").toString(); Chris@64: Chris@64: QString identifier; Chris@64: Chris@64: if (email != "") { Chris@64: identifier = QString("%1 <%2>").arg(name).arg(email); Chris@64: } else { Chris@64: identifier = name; Chris@64: } Chris@64: Chris@64: return identifier; Chris@64: } Chris@64: jtkorhonen@0: void MainWindow::about() jtkorhonen@0: { Chris@97: QMessageBox::about(this, tr("About EasyMercurial"), Chris@229: tr("

EasyMercurial v%1

" Chris@228: #ifdef Q_OS_MAC Chris@228: "" Chris@228: #endif Chris@97: "

EasyMercurial is a simple user interface for the " Chris@186: "Mercurial version control system.

" Chris@186: "

Credits and Copyright

" Chris@186: "

Development carried out by Chris Cannam for " Chris@186: "SoundSoftware.ac.uk at the Centre for Digital Music, " Chris@186: "Queen Mary, University of London.

" Chris@186: "

EasyMercurial is based on HgExplorer by " Chris@186: "Jari Korhonen, with thanks.

" Chris@186: "

" Chris@244: "Copyright © 2011 Queen Mary, University of London.
" Chris@186: "Copyright © 2010 Jari Korhonen.
" Chris@244: "Copyright © 2011 Chris Cannam." Chris@186: "

" Chris@186: "

" Chris@186: "This program requires Mercurial, by Matt Mackall and others.
" Chris@186: "This program uses Qt by Nokia.
" Chris@186: "This program uses Nuvola icons by David Vignoni.
" Chris@186: "This program may use KDiff3 by Joachim Eibl.
" Chris@186: "This program may use PyQt by River Bank Computing.
" Chris@186: "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others." Chris@186: "

" Chris@186: "

License

" Chris@186: "

This program is free software; you can redistribute it and/or " Chris@97: "modify it under the terms of the GNU General Public License as " Chris@97: "published by the Free Software Foundation; either version 2 of the " Chris@97: "License, or (at your option) any later version. See the file " Chris@223: "COPYING included with this distribution for more information.

" Chris@228: #ifdef Q_OS_MAC Chris@228: "
" Chris@228: #endif Chris@229: ).arg(EASYHG_VERSION)); jtkorhonen@0: } jtkorhonen@0: Chris@94: void MainWindow::clearSelections() Chris@94: { Chris@284: m_hgTabs->clearSelections(); Chris@94: } jtkorhonen@0: Chris@199: void MainWindow::showAllChanged(bool s) Chris@199: { Chris@284: m_showAllFiles = s; Chris@199: hgQueryPaths(); Chris@199: } Chris@199: Chris@120: void MainWindow::hgRefresh() Chris@120: { Chris@120: clearState(); Chris@120: hgQueryPaths(); Chris@120: } Chris@120: Chris@175: void MainWindow::hgTest() Chris@175: { Chris@175: QStringList params; Chris@207: //!!! should we test version output? Really we want at least 1.7.x Chris@207: //!!! for options such as merge --tool Chris@175: params << "--version"; Chris@284: m_runner->requestAction(HgAction(ACT_TEST_HG, m_myDirPath, params)); Chris@175: } Chris@175: Chris@200: void MainWindow::hgTestExtension() Chris@200: { Chris@200: QStringList params; Chris@200: params << "--version"; Chris@284: m_runner->requestAction(HgAction(ACT_TEST_HG_EXT, m_myDirPath, params)); Chris@200: } Chris@200: jtkorhonen@0: void MainWindow::hgStat() jtkorhonen@0: { Chris@109: QStringList params; Chris@199: Chris@284: if (m_showAllFiles) { Chris@199: params << "stat" << "-A"; Chris@199: } else { Chris@199: params << "stat" << "-ardum"; Chris@199: } Chris@153: Chris@284: m_lastStatOutput = ""; Chris@273: Chris@284: m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: Chris@109: void MainWindow::hgQueryPaths() Chris@74: { Chris@210: // Quickest is to just read the file Chris@198: Chris@284: QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc"); Chris@198: Chris@210: QString path; Chris@210: Chris@198: if (hgrc.exists()) { Chris@198: QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat); Chris@198: s.beginGroup("paths"); Chris@210: path = s.value("default").toString(); Chris@210: } Chris@198: Chris@284: m_remoteRepoPath = path; Chris@198: Chris@210: // We have to do this here, because commandCompleted won't be called Chris@284: MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); Chris@284: MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); Chris@284: m_hgTabs->setWorkFolderAndRepoNames(m_workFolderPath, m_remoteRepoPath); Chris@210: Chris@210: hgQueryBranch(); Chris@210: return; Chris@210: Chris@210: /* The classic method! Chris@198: Chris@109: QStringList params; Chris@109: params << "paths"; Chris@284: m_runner->requestAction(HgAction(ACT_QUERY_PATHS, m_workFolderPath, params)); Chris@210: */ Chris@74: } Chris@74: Chris@109: void MainWindow::hgQueryBranch() Chris@106: { Chris@210: // Quickest is to just read the file Chris@198: Chris@284: QFile hgbr(m_workFolderPath + "/.hg/branch"); Chris@198: Chris@210: QString br = "default"; Chris@210: Chris@198: if (hgbr.exists() && hgbr.open(QFile::ReadOnly)) { Chris@210: QByteArray ba = hgbr.readLine(); Chris@210: br = QString::fromUtf8(ba).trimmed(); Chris@210: } Chris@210: Chris@284: m_currentBranch = br; Chris@210: Chris@210: // We have to do this here, because commandCompleted won't be called Chris@210: hgStat(); Chris@210: return; Chris@198: Chris@210: /* The classic method! Chris@198: Chris@109: QStringList params; Chris@109: params << "branch"; Chris@284: m_runner->requestAction(HgAction(ACT_QUERY_BRANCH, m_workFolderPath, params)); Chris@210: */ Chris@106: } Chris@106: Chris@109: void MainWindow::hgQueryHeads() jtkorhonen@0: { Chris@109: QStringList params; Chris@137: // On empty repos, "hg heads" will fail -- we don't care about Chris@137: // that. Use --closed option so as to include closed branches; Chris@137: // otherwise we'll be stuck if the user updates into one, and our Chris@137: // incremental log will end up with spurious stuff in it because Chris@137: // we won't be pruning at the ends of closed branches Chris@137: params << "heads" << "--closed"; Chris@284: m_runner->requestAction(HgAction(ACT_QUERY_HEADS, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::hgLog() jtkorhonen@0: { Chris@109: QStringList params; Chris@109: params << "log"; Chris@109: params << "--template"; Chris@125: params << Changeset::getLogTemplate(); Chris@109: Chris@284: m_runner->requestAction(HgAction(ACT_LOG, m_workFolderPath, params)); Chris@109: } Chris@109: Chris@150: void MainWindow::hgLogIncremental(QStringList prune) Chris@120: { Chris@305: // Sometimes we can be called with prune empty -- it represents Chris@305: // the current heads, but if we have none already and for some Chris@305: // reason are being prompted for an incremental update, we may run Chris@305: // into trouble. In that case, make this a full log instead Chris@305: Chris@305: if (prune.empty()) { Chris@305: hgLog(); Chris@305: return; Chris@305: } Chris@305: Chris@120: QStringList params; Chris@120: params << "log"; Chris@120: Chris@150: foreach (QString p, prune) { Chris@153: params << "--prune" << Changeset::hashOf(p); Chris@120: } Chris@120: Chris@120: params << "--template"; Chris@125: params << Changeset::getLogTemplate(); Chris@120: Chris@284: m_runner->requestAction(HgAction(ACT_LOG_INCREMENTAL, m_workFolderPath, params)); Chris@120: } Chris@109: Chris@109: void MainWindow::hgQueryParents() Chris@109: { Chris@109: QStringList params; Chris@109: params << "parents"; Chris@284: m_runner->requestAction(HgAction(ACT_QUERY_PARENTS, m_workFolderPath, params)); Chris@109: } Chris@109: Chris@109: void MainWindow::hgAnnotate() Chris@109: { Chris@109: QStringList params; Chris@284: QString currentFile;//!!! = m_hgTabs -> getCurrentFileListLine(); Chris@109: Chris@109: if (!currentFile.isEmpty()) jtkorhonen@0: { Chris@109: params << "annotate" << "--" << currentFile.mid(2); //Jump over status marker characters (e.g "M ") jtkorhonen@0: Chris@284: m_runner->requestAction(HgAction(ACT_ANNOTATE, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: } jtkorhonen@0: Chris@109: void MainWindow::hgResolveList() Chris@109: { Chris@109: QStringList params; jtkorhonen@0: Chris@109: params << "resolve" << "--list"; Chris@284: m_runner->requestAction(HgAction(ACT_RESOLVE_LIST, m_workFolderPath, params)); Chris@109: } Chris@109: Chris@109: void MainWindow::hgAdd() jtkorhonen@0: { Chris@109: QStringList params; jtkorhonen@0: Chris@109: // hgExplorer permitted adding "all" files -- I'm not sure Chris@109: // that one is a good idea, let's require the user to select jtkorhonen@0: Chris@284: QStringList files = m_hgTabs->getSelectedAddableFiles(); Chris@109: Chris@109: if (!files.empty()) { Chris@109: params << "add" << "--" << files; Chris@284: m_runner->requestAction(HgAction(ACT_ADD, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: Chris@98: void MainWindow::hgRemove() Chris@98: { Chris@109: QStringList params; Chris@98: Chris@284: QStringList files = m_hgTabs->getSelectedRemovableFiles(); Chris@98: Chris@109: if (!files.empty()) { Chris@109: params << "remove" << "--after" << "--force" << "--" << files; Chris@284: m_runner->requestAction(HgAction(ACT_REMOVE, m_workFolderPath, params)); Chris@109: } jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::hgCommit() jtkorhonen@0: { Chris@109: QStringList params; Chris@109: QString comment; Chris@94: Chris@284: if (m_justMerged) { Chris@284: comment = m_mergeCommitComment; Chris@157: } Chris@157: Chris@284: QStringList files = m_hgTabs->getSelectedCommittableFiles(); Chris@284: QStringList allFiles = m_hgTabs->getAllCommittableFiles(); Chris@127: QStringList reportFiles = files; Chris@237: if (reportFiles.empty()) { Chris@237: reportFiles = allFiles; Chris@237: } Chris@103: Chris@237: QString subsetNote; Chris@237: if (reportFiles != allFiles) { Chris@237: subsetNote = tr("

Note: you are committing only the files you have selected, not all of the files that have been changed!"); Chris@237: } Chris@237: Chris@155: QString cf(tr("Commit files")); Chris@155: Chris@109: if (ConfirmCommentDialog::confirmAndGetLongComment Chris@109: (this, Chris@155: cf, Chris@237: tr("

%1

%2%3").arg(cf) Chris@237: .arg(tr("You are about to commit the following files.")) Chris@237: .arg(subsetNote), Chris@237: tr("

%1

%2%3").arg(cf) Chris@237: .arg(tr("You are about to commit %n file(s).", "", reportFiles.size())) Chris@237: .arg(subsetNote), Chris@127: reportFiles, Chris@193: comment, Chris@193: tr("Commit"))) { Chris@103: Chris@284: if (!m_justMerged && !files.empty()) { Chris@157: // User wants to commit selected file(s) (and this is not Chris@157: // merge commit, which would fail if we selected files) Chris@157: params << "commit" << "--message" << comment Chris@157: << "--user" << getUserInfo() << "--" << files; Chris@109: } else { Chris@109: // Commit all changes Chris@157: params << "commit" << "--message" << comment Chris@157: << "--user" << getUserInfo(); jtkorhonen@0: } Chris@109: Chris@284: m_runner->requestAction(HgAction(ACT_COMMIT, m_workFolderPath, params)); Chris@284: m_mergeCommitComment = ""; jtkorhonen@0: } jtkorhonen@0: } jtkorhonen@0: jtkorhonen@34: QString MainWindow::filterTag(QString tag) jtkorhonen@34: { jtkorhonen@34: for(int i = 0; i < tag.size(); i++) jtkorhonen@34: { jtkorhonen@34: if (tag[i].isLower() || tag[i].isUpper() || tag[i].isDigit() || (tag[i] == QChar('.'))) jtkorhonen@34: { jtkorhonen@34: //ok jtkorhonen@34: } jtkorhonen@34: else jtkorhonen@34: { jtkorhonen@34: tag[i] = QChar('_'); jtkorhonen@34: } jtkorhonen@34: } jtkorhonen@34: return tag; jtkorhonen@34: } jtkorhonen@34: jtkorhonen@34: Chris@164: void MainWindow::hgTag(QString id) jtkorhonen@34: { Chris@109: QStringList params; Chris@109: QString tag; jtkorhonen@34: Chris@109: if (ConfirmCommentDialog::confirmAndGetShortComment Chris@109: (this, Chris@109: tr("Tag"), Chris@109: tr("Enter tag:"), Chris@193: tag, Chris@193: tr("Add Tag"))) { Chris@164: if (!tag.isEmpty()) {//!!! do something better if it is empty Chris@164: Chris@164: params << "tag" << "--user" << getUserInfo(); Chris@164: params << "--rev" << Changeset::hashOf(id) << filterTag(tag); Chris@109: Chris@284: m_runner->requestAction(HgAction(ACT_TAG, m_workFolderPath, params)); jtkorhonen@34: } jtkorhonen@34: } jtkorhonen@34: } jtkorhonen@34: jtkorhonen@34: jtkorhonen@34: void MainWindow::hgIgnore() jtkorhonen@34: { Chris@109: QString hgIgnorePath; Chris@109: QStringList params; Chris@178: Chris@284: hgIgnorePath = m_workFolderPath; Chris@178: hgIgnorePath += "/.hgignore"; Chris@249: Chris@284: if (!QDir(m_workFolderPath).exists()) return; Chris@249: QFile f(hgIgnorePath); Chris@249: if (!f.exists()) { Chris@249: f.open(QFile::WriteOnly); Chris@249: QTextStream *ts = new QTextStream(&f); Chris@249: *ts << "syntax: glob\n"; Chris@249: delete ts; Chris@249: f.close(); Chris@249: } Chris@109: Chris@109: params << hgIgnorePath; Chris@179: Chris@239: QString editor = getEditorBinaryName(); Chris@112: Chris@179: if (editor == "") { Chris@179: DEBUG << "Failed to find a text editor" << endl; Chris@179: //!!! visible error! Chris@179: return; Chris@179: } Chris@179: Chris@284: HgAction action(ACT_HG_IGNORE, m_workFolderPath, params); Chris@179: action.executable = editor; Chris@179: Chris@284: m_runner->requestAction(action); Chris@179: } Chris@179: Chris@239: QString MainWindow::getDiffBinaryName() Chris@179: { Chris@179: QSettings settings; Chris@179: settings.beginGroup("Locations"); Chris@239: return settings.value("extdiffbinary", "").toString(); Chris@179: } Chris@179: Chris@239: QString MainWindow::getMergeBinaryName() Chris@179: { Chris@179: QSettings settings; Chris@179: settings.beginGroup("Locations"); Chris@239: return settings.value("mergebinary", "").toString(); Chris@179: } Chris@179: Chris@239: QString MainWindow::getEditorBinaryName() Chris@179: { Chris@178: QSettings settings; Chris@178: settings.beginGroup("Locations"); Chris@239: return settings.value("editorbinary", "").toString(); Chris@163: } Chris@163: Chris@168: void MainWindow::hgShowSummary() Chris@168: { Chris@168: QStringList params; Chris@168: Chris@168: params << "diff" << "--stat"; Chris@168: Chris@288: m_runner->requestAction(HgAction(ACT_UNCOMMITTED_SUMMARY, m_workFolderPath, params)); Chris@168: } Chris@168: jtkorhonen@0: void MainWindow::hgFolderDiff() jtkorhonen@0: { Chris@239: QString diff = getDiffBinaryName(); Chris@179: if (diff == "") return; Chris@112: Chris@109: QStringList params; jtkorhonen@0: Chris@112: // Diff parent against working folder (folder diff) Chris@112: Chris@148: params << "--config" << "extensions.extdiff=" << "extdiff"; Chris@179: params << "--program" << diff; Chris@109: Chris@284: params << m_hgTabs->getSelectedCommittableFiles(); // may be none: whole dir Chris@273: Chris@284: m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: Chris@148: void MainWindow::hgDiffToCurrent(QString id) jtkorhonen@0: { Chris@239: QString diff = getDiffBinaryName(); Chris@179: if (diff == "") return; Chris@163: Chris@148: QStringList params; jtkorhonen@0: Chris@148: // Diff given revision against working folder jtkorhonen@0: Chris@148: params << "--config" << "extensions.extdiff=" << "extdiff"; Chris@179: params << "--program" << diff; Chris@153: params << "--rev" << Changeset::hashOf(id); Chris@148: Chris@284: m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: Chris@148: void MainWindow::hgDiffToParent(QString child, QString parent) Chris@148: { Chris@239: QString diff = getDiffBinaryName(); Chris@179: if (diff == "") return; Chris@163: Chris@148: QStringList params; Chris@148: Chris@281: // Diff given revision against parent revision Chris@148: Chris@148: params << "--config" << "extensions.extdiff=" << "extdiff"; Chris@179: params << "--program" << diff; Chris@153: params << "--rev" << Changeset::hashOf(parent) Chris@153: << "--rev" << Changeset::hashOf(child); Chris@148: Chris@284: m_runner->requestAction(HgAction(ACT_CHGSETDIFF, m_workFolderPath, params)); Chris@273: } Chris@273: Chris@273: Chris@289: void MainWindow::hgShowSummaryFor(Changeset *cs) Chris@288: { Chris@288: QStringList params; Chris@288: Chris@289: // This will pick a default parent if there is more than one Chris@289: // (whereas with diff we need to supply one). But it does need a Chris@289: // bit more parsing Chris@289: params << "log" << "--stat" << "--rev" << Changeset::hashOf(cs->id()); Chris@289: Chris@289: m_runner->requestAction(HgAction(ACT_DIFF_SUMMARY, m_workFolderPath, Chris@289: params, cs)); Chris@148: } Chris@148: jtkorhonen@0: jtkorhonen@0: void MainWindow::hgUpdate() jtkorhonen@0: { Chris@109: QStringList params; jtkorhonen@0: Chris@109: params << "update"; Chris@109: Chris@284: m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: Chris@148: void MainWindow::hgUpdateToRev(QString id) jtkorhonen@0: { Chris@148: QStringList params; jtkorhonen@0: Chris@153: params << "update" << "--rev" << Changeset::hashOf(id) << "--check"; jtkorhonen@0: Chris@284: m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: jtkorhonen@0: void MainWindow::hgRevert() jtkorhonen@0: { Chris@109: QStringList params; Chris@109: QString comment; Chris@237: bool all = false; Chris@98: Chris@284: QStringList files = m_hgTabs->getSelectedRevertableFiles(); Chris@284: QStringList allFiles = m_hgTabs->getAllRevertableFiles(); Chris@237: if (files.empty() || files == allFiles) { Chris@237: files = allFiles; Chris@237: all = true; Chris@237: } Chris@237: Chris@237: QString subsetNote; Chris@237: if (!all) { Chris@237: subsetNote = tr("

Note: you are reverting only the files you have selected, not all of the files that have been changed!"); Chris@237: } Chris@155: Chris@155: QString rf(tr("Revert files")); Chris@237: Chris@237: // Set up params before asking for confirmation, because there is Chris@237: // a failure case here that we would need to report on early Chris@237: Chris@284: DEBUG << "hgRevert: m_justMerged = " << m_justMerged << ", m_mergeTargetRevision = " << m_mergeTargetRevision << endl; Chris@273: Chris@284: if (m_justMerged) { Chris@237: Chris@237: // This is a little fiddly. The proper way to "revert" the Chris@237: // whole of an uncommitted merge is with "hg update --clean ." Chris@237: // But if the user has selected only some files, we're sort of Chris@237: // promising to revert only those, which means we need to Chris@237: // specify which parent to revert to. We can only do that if Chris@237: // we have a record of it, which we do if you just did the Chris@237: // merge from within easyhg but don't if you've exited and Chris@237: // restarted, or changed repository, since then. Hmmm. Chris@237: Chris@237: if (all) { Chris@237: params << "update" << "--clean" << "."; Chris@237: } else { Chris@284: if (m_mergeTargetRevision != "") { Chris@237: params << "revert" << "--rev" Chris@284: << Changeset::hashOf(m_mergeTargetRevision) Chris@237: << "--" << files; Chris@237: } else { Chris@237: QMessageBox::information Chris@237: (this, tr("Unable to revert"), Chris@237: tr("Sorry, unable to revert these files

EasyMercurial can only revert a subset of files during a merge if it still has a record of which parent was the original merge target; that information is no longer available.

This is a limitation of EasyMercurial. Consider reverting all files, or using hg revert with a specific revision at the command-line instead.
")); Chris@237: return; Chris@237: } Chris@237: } Chris@237: } else { Chris@237: params << "revert" << "--" << files; Chris@237: } Chris@237: Chris@109: if (ConfirmCommentDialog::confirmDangerousFilesAction Chris@109: (this, Chris@155: rf, Chris@237: tr("

%1

%2%3").arg(rf) Chris@237: .arg(tr("You are about to revert the following files to their previous committed state.

This will throw away any changes that you have made to these files but have not committed.")) Chris@237: .arg(subsetNote), Chris@237: tr("

%1

%2%3").arg(rf) Chris@237: .arg(tr("You are about to revert %n file(s).

This will throw away any changes that you have made to these files but have not committed.", "", files.size())) Chris@237: .arg(subsetNote), Chris@193: files, Chris@193: tr("Revert"))) { Chris@163: Chris@284: m_lastRevertedFiles = files; Chris@109: Chris@284: m_runner->requestAction(HgAction(ACT_REVERT, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: } jtkorhonen@0: Chris@163: Chris@163: void MainWindow::hgMarkResolved(QStringList files) Chris@163: { Chris@163: QStringList params; Chris@163: Chris@163: params << "resolve" << "--mark"; Chris@163: Chris@163: if (files.empty()) { Chris@163: params << "--all"; Chris@163: } else { Chris@164: params << "--" << files; Chris@163: } Chris@163: Chris@284: m_runner->requestAction(HgAction(ACT_RESOLVE_MARK, m_workFolderPath, params)); Chris@163: } Chris@163: Chris@163: jtkorhonen@33: void MainWindow::hgRetryMerge() jtkorhonen@33: { Chris@109: QStringList params; jtkorhonen@33: Chris@163: params << "resolve"; Chris@163: Chris@239: QString merge = getMergeBinaryName(); Chris@179: if (merge != "") { Chris@179: params << "--tool" << merge; Chris@163: } Chris@163: Chris@284: QStringList files = m_hgTabs->getSelectedUnresolvedFiles(); Chris@163: if (files.empty()) { Chris@163: params << "--all"; Chris@163: } else { Chris@164: params << "--" << files; Chris@163: } Chris@163: Chris@284: if (m_currentParents.size() == 1) { Chris@284: m_mergeTargetRevision = m_currentParents[0]->id(); Chris@237: } Chris@237: Chris@284: m_runner->requestAction(HgAction(ACT_RETRY_MERGE, m_workFolderPath, params)); Chris@273: Chris@284: m_mergeCommitComment = tr("Merge"); jtkorhonen@33: } jtkorhonen@33: jtkorhonen@33: jtkorhonen@0: void MainWindow::hgMerge() jtkorhonen@0: { Chris@284: if (m_hgTabs->canResolve()) { Chris@163: hgRetryMerge(); Chris@163: return; Chris@163: } Chris@163: Chris@109: QStringList params; jtkorhonen@0: Chris@109: params << "merge"; Chris@163: Chris@239: QString merge = getMergeBinaryName(); Chris@179: if (merge != "") { Chris@179: params << "--tool" << merge; Chris@163: } Chris@163: Chris@284: if (m_currentParents.size() == 1) { Chris@284: m_mergeTargetRevision = m_currentParents[0]->id(); Chris@163: } Chris@163: Chris@284: m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params)); Chris@273: Chris@284: m_mergeCommitComment = tr("Merge"); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: Chris@148: void MainWindow::hgMergeFrom(QString id) Chris@148: { Chris@148: QStringList params; Chris@148: Chris@148: params << "merge"; Chris@153: params << "--rev" << Changeset::hashOf(id); Chris@163: Chris@239: QString merge = getMergeBinaryName(); Chris@179: if (merge != "") { Chris@179: params << "--tool" << merge; Chris@163: } Chris@148: Chris@284: if (m_currentParents.size() == 1) { Chris@284: m_mergeTargetRevision = m_currentParents[0]->id(); Chris@237: } Chris@237: Chris@284: m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params)); Chris@273: Chris@284: m_mergeCommitComment = ""; Chris@273: Chris@284: foreach (Changeset *cs, m_currentHeads) { Chris@284: if (cs->id() == id && !cs->isOnBranch(m_currentBranch)) { Chris@157: if (cs->branch() == "" || cs->branch() == "default") { Chris@284: m_mergeCommitComment = tr("Merge from the default branch"); Chris@157: } else { Chris@284: m_mergeCommitComment = tr("Merge from branch \"%1\"").arg(cs->branch()); Chris@157: } Chris@157: } Chris@157: } Chris@157: Chris@284: if (m_mergeCommitComment == "") { Chris@284: m_mergeCommitComment = tr("Merge from %1").arg(id); Chris@157: } Chris@148: } Chris@148: Chris@148: jtkorhonen@0: void MainWindow::hgCloneFromRemote() jtkorhonen@0: { Chris@109: QStringList params; jtkorhonen@0: Chris@284: if (!QDir(m_workFolderPath).exists()) { Chris@284: if (!QDir().mkpath(m_workFolderPath)) { Chris@109: DEBUG << "hgCloneFromRemote: Failed to create target path " Chris@284: << m_workFolderPath << endl; Chris@109: //!!! report error Chris@109: return; Chris@104: } Chris@109: } Chris@104: Chris@284: params << "clone" << m_remoteRepoPath << m_workFolderPath; Chris@109: Chris@284: m_hgTabs->setWorkFolderAndRepoNames(m_workFolderPath, m_remoteRepoPath); Chris@284: m_hgTabs->updateWorkFolderFileList(""); Chris@273: Chris@284: m_runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::hgInit() jtkorhonen@0: { Chris@109: QStringList params; jtkorhonen@0: Chris@109: params << "init"; Chris@284: params << m_workFolderPath; Chris@273: Chris@284: m_runner->requestAction(HgAction(ACT_INIT, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::hgIncoming() jtkorhonen@0: { Chris@109: QStringList params; jtkorhonen@0: Chris@284: params << "incoming" << "--newest-first" << m_remoteRepoPath; Chris@125: params << "--template" << Changeset::getLogTemplate(); jtkorhonen@0: Chris@284: m_runner->requestAction(HgAction(ACT_INCOMING, m_workFolderPath, params)); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::hgPull() jtkorhonen@0: { Chris@193: if (ConfirmCommentDialog::confirm Chris@126: (this, tr("Confirm pull"), Chris@299: tr("

Pull from remote repository?

"), Chris@299: tr("

You are about to pull changes from the remote repository at %1.

").arg(xmlEncode(m_remoteRepoPath)), Chris@194: tr("Pull"))) { jtkorhonen@0: Chris@126: QStringList params; Chris@284: params << "pull" << m_remoteRepoPath; Chris@284: m_runner->requestAction(HgAction(ACT_PULL, m_workFolderPath, params)); Chris@126: } jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::hgPush() jtkorhonen@0: { Chris@193: if (ConfirmCommentDialog::confirm Chris@126: (this, tr("Confirm push"), Chris@299: tr("

Push to remote repository?

"), Chris@299: tr("

You are about to push your changes to the remote repository at %1.

").arg(xmlEncode(m_remoteRepoPath)), Chris@194: tr("Push"))) { jtkorhonen@0: Chris@126: QStringList params; Chris@284: params << "push" << "--new-branch" << m_remoteRepoPath; Chris@284: m_runner->requestAction(HgAction(ACT_PUSH, m_workFolderPath, params)); Chris@126: } jtkorhonen@0: } jtkorhonen@0: Chris@182: QStringList MainWindow::listAllUpIpV4Addresses() jtkorhonen@26: { Chris@182: QStringList ret; jtkorhonen@26: QList ifaces = QNetworkInterface::allInterfaces(); jtkorhonen@26: Chris@182: for (int i = 0; i < ifaces.count(); i++) { jtkorhonen@26: QNetworkInterface iface = ifaces.at(i); Chris@182: if (iface.flags().testFlag(QNetworkInterface::IsUp) Chris@182: && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) { Chris@182: for (int j=0; j

%1

") Chris@182: .arg(tr("Running temporary server at %n address(es):", "", addrs.size())); Chris@182: foreach (QString addr, addrs) { Chris@182: ts << QString("
  http://%1:8000
").arg(xmlEncode(addr)); Chris@182: } Chris@182: ts << tr("

Press Close to stop the server and return.

"); Chris@182: ts.flush(); Chris@182: Chris@109: params << "serve"; jtkorhonen@11: Chris@284: m_runner->requestAction(HgAction(ACT_SERVE, m_workFolderPath, params)); Chris@109: Chris@182: QMessageBox::information(this, tr("Serve"), msg, QMessageBox::Close); Chris@182: Chris@284: m_runner->killCurrentActions(); jtkorhonen@11: } jtkorhonen@11: Chris@64: void MainWindow::startupDialog() Chris@64: { Chris@64: StartupDialog *dlg = new StartupDialog(this); Chris@284: if (dlg->exec()) m_firstStart = false; Chris@64: } jtkorhonen@11: Chris@69: void MainWindow::open() Chris@69: { Chris@86: bool done = false; Chris@69: Chris@86: while (!done) { Chris@69: Chris@86: MultiChoiceDialog *d = new MultiChoiceDialog Chris@86: (tr("Open Repository"), Chris@86: tr("What would you like to open?"), Chris@86: this); Chris@69: Chris@86: d->addChoice("remote", Chris@86: tr("

Remote repository
"), Chris@86: tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."), Chris@86: MultiChoiceDialog::UrlToDirectoryArg); Chris@69: Chris@86: d->addChoice("local", Chris@86: tr("

Local repository
"), Chris@86: tr("Open an existing local Mercurial repository."), Chris@86: MultiChoiceDialog::DirectoryArg); Chris@69: Chris@86: d->addChoice("init", Chris@86: tr("

File folder
"), Chris@86: tr("Open a local folder, by creating a Mercurial repository in it."), Chris@86: MultiChoiceDialog::DirectoryArg); Chris@79: Chris@248: QSettings settings; Chris@248: settings.beginGroup("General"); Chris@248: QString lastChoice = settings.value("lastopentype", "local").toString(); Chris@248: if (lastChoice != "local" && Chris@248: lastChoice != "remote" && Chris@248: lastChoice != "init") { Chris@248: lastChoice = "local"; Chris@248: } Chris@248: Chris@248: d->setCurrentChoice(lastChoice); Chris@86: Chris@86: if (d->exec() == QDialog::Accepted) { Chris@86: Chris@86: QString choice = d->getCurrentChoice(); Chris@248: settings.setValue("lastopentype", choice); Chris@248: Chris@86: QString arg = d->getArgument().trimmed(); Chris@86: Chris@86: bool result = false; Chris@86: Chris@86: if (choice == "local") { Chris@86: result = openLocal(arg); Chris@86: } else if (choice == "remote") { Chris@86: result = openRemote(arg, d->getAdditionalArgument().trimmed()); Chris@86: } else if (choice == "init") { Chris@86: result = openInit(arg); Chris@86: } Chris@86: Chris@86: if (result) { Chris@86: enableDisableActions(); Chris@120: clearState(); Chris@109: hgQueryPaths(); Chris@91: done = true; Chris@91: } Chris@86: Chris@86: } else { Chris@86: Chris@86: // cancelled Chris@86: done = true; Chris@69: } Chris@79: Chris@86: delete d; Chris@69: } Chris@69: } Chris@69: Chris@182: void MainWindow::changeRemoteRepo() Chris@182: { Chris@183: // This will involve rewriting the local .hgrc Chris@183: Chris@284: QDir hgDir(m_workFolderPath + "/.hg"); Chris@184: if (!hgDir.exists()) { Chris@184: //!!! visible error! Chris@184: return; Chris@184: } Chris@184: Chris@284: QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc"); Chris@184: if (hgrc.exists() && !hgrc.isWritable()) { Chris@183: //!!! visible error! Chris@183: return; Chris@183: } Chris@183: Chris@183: MultiChoiceDialog *d = new MultiChoiceDialog Chris@183: (tr("Change Remote Location"), Chris@183: tr("Change the remote location"), Chris@183: this); Chris@183: Chris@183: d->addChoice("remote", Chris@183: tr("

Remote repository
"), Chris@183: tr("Provide a new URL to use for push and pull actions from the current local repository."), Chris@183: MultiChoiceDialog::UrlArg); Chris@183: Chris@183: if (d->exec() == QDialog::Accepted) { Chris@210: Chris@210: // New block to ensure QSettings is deleted before Chris@210: // hgQueryPaths called. NB use of absoluteFilePath instead of Chris@210: // canonicalFilePath, which would fail if the file did not yet Chris@210: // exist Chris@210: Chris@210: { Chris@210: QSettings s(hgrc.absoluteFilePath(), QSettings::IniFormat); Chris@210: s.beginGroup("paths"); Chris@210: s.setValue("default", d->getArgument()); Chris@210: } Chris@210: Chris@284: m_stateUnknown = true; Chris@183: hgQueryPaths(); Chris@183: } Chris@183: Chris@183: delete d; Chris@182: } Chris@182: Chris@145: void MainWindow::open(QString local) Chris@145: { Chris@145: if (openLocal(local)) { Chris@145: enableDisableActions(); Chris@145: clearState(); Chris@145: hgQueryPaths(); Chris@145: } Chris@145: } Chris@145: Chris@79: bool MainWindow::complainAboutFilePath(QString arg) Chris@79: { Chris@79: QMessageBox::critical Chris@79: (this, tr("File chosen"), Chris@84: tr("Folder required

You asked to open \"%1\".
This is a file; to open a repository, you need to choose a folder.
").arg(xmlEncode(arg))); Chris@79: return false; Chris@79: } Chris@79: Chris@248: bool MainWindow::askAboutUnknownFolder(QString arg) Chris@248: { Chris@248: bool result = (QMessageBox::question Chris@248: (this, tr("Path does not exist"), Chris@248: tr("Path does not exist: create it?

You asked to open a remote repository by cloning it to \"%1\". This folder does not exist, and neither does its parent.

Would you like to create the parent folder as well?
").arg(xmlEncode(arg)), Chris@248: QMessageBox::Ok | QMessageBox::Cancel, Chris@248: QMessageBox::Cancel) Chris@248: == QMessageBox::Ok); Chris@248: if (result) { Chris@248: QDir dir(arg); Chris@248: dir.cdUp(); Chris@248: if (!dir.mkpath(dir.absolutePath())) { Chris@248: QMessageBox::critical Chris@248: (this, tr("Failed to create folder"), Chris@248: tr("Failed to create folder

Sorry, the path for the parent folder \"%1\" could not be created.
").arg(dir.absolutePath())); Chris@248: return false; Chris@248: } Chris@248: return true; Chris@248: } Chris@248: return false; Chris@248: } Chris@248: Chris@79: bool MainWindow::complainAboutUnknownFolder(QString arg) Chris@79: { Chris@79: QMessageBox::critical Chris@79: (this, tr("Folder does not exist"), Chris@84: tr("Folder does not exist

You asked to open \"%1\".
This folder does not exist, and it cannot be created because its parent does not exist either.
").arg(xmlEncode(arg))); Chris@84: return false; Chris@84: } Chris@84: Chris@84: bool MainWindow::complainAboutInitInRepo(QString arg) Chris@84: { Chris@84: QMessageBox::critical Chris@84: (this, tr("Path is in existing repository"), Chris@84: tr("Path is in an existing repository

You asked to initialise a repository at \"%1\".
This path is already inside an existing repository.
").arg(xmlEncode(arg))); Chris@84: return false; Chris@84: } Chris@84: Chris@84: bool MainWindow::complainAboutInitFile(QString arg) Chris@84: { Chris@84: QMessageBox::critical Chris@84: (this, tr("Path is a file"), Chris@84: tr("Path is a file

You asked to initialise a repository at \"%1\".
This is an existing file; it is only possible to initialise in folders.
").arg(xmlEncode(arg))); Chris@84: return false; Chris@84: } Chris@84: Chris@84: bool MainWindow::complainAboutCloneToExisting(QString arg) Chris@84: { Chris@84: QMessageBox::critical Chris@84: (this, tr("Path is in existing repository"), Chris@237: tr("Local path is in an existing repository

You asked to open a remote repository by cloning it to the local path \"%1\".
This path is already inside an existing repository.
Please provide a different folder name for the local repository.
").arg(xmlEncode(arg))); Chris@84: return false; Chris@84: } Chris@84: Chris@84: bool MainWindow::complainAboutCloneToFile(QString arg) Chris@84: { Chris@84: QMessageBox::critical Chris@84: (this, tr("Path is a file"), Chris@84: tr("Local path is a file

You asked to open a remote repository by cloning it to the local path \"%1\".
This path is an existing file.
Please provide a new folder name for the local repository.
").arg(xmlEncode(arg))); Chris@84: return false; Chris@84: } Chris@84: Chris@237: QString MainWindow::complainAboutCloneToExistingFolder(QString arg, QString remote) Chris@84: { Chris@237: // If the directory "arg" exists but "arg" plus the last path Chris@237: // component of "remote" does not, then offer the latter as an Chris@237: // alternative path Chris@237: Chris@237: QString offer; Chris@237: Chris@237: QDir d(arg); Chris@237: if (d.exists()) { Chris@237: if (QRegExp("^\\w+://").indexIn(remote) >= 0) { Chris@237: QString rpath = QUrl(remote).path(); Chris@237: if (rpath != "") { Chris@237: rpath = QDir(rpath).dirName(); Chris@237: if (rpath != "" && !d.exists(rpath)) { Chris@237: offer = d.filePath(rpath); Chris@237: } Chris@237: } Chris@237: } Chris@237: } Chris@237: Chris@237: if (offer != "") { Chris@237: bool result = (QMessageBox::question Chris@237: (this, tr("Folder exists"), Chris@237: tr("Local folder already exists

You asked to open a remote repository by cloning it to \"%1\", but this folder already exists and so cannot be cloned to.

Would you like to create the new folder \"%2\" instead?
") Chris@237: .arg(xmlEncode(arg)).arg(xmlEncode(offer)), Chris@237: QMessageBox::Ok | QMessageBox::Cancel, Chris@237: QMessageBox::Cancel) Chris@237: == QMessageBox::Ok); Chris@237: if (result) return offer; Chris@237: else return ""; Chris@237: } Chris@237: Chris@84: QMessageBox::critical Chris@84: (this, tr("Folder exists"), Chris@237: tr("Local folder already exists

You asked to open a remote repository by cloning it to \"%1\", but this file or folder already exists and so cannot be cloned to.
Please provide a different folder name for the local repository.
").arg(xmlEncode(arg))); Chris@237: return ""; Chris@79: } Chris@79: Chris@79: bool MainWindow::askToOpenParentRepo(QString arg, QString parent) Chris@79: { Chris@79: return (QMessageBox::question Chris@84: (this, tr("Path is inside a repository"), Chris@86: tr("Open the repository that contains this path?

You asked to open \"%1\".
This is not the root folder of a repository.
But it is inside a repository, whose root is at \"%2\".

Would you like to open that repository instead?
") Chris@79: .arg(xmlEncode(arg)).arg(xmlEncode(parent)), Chris@79: QMessageBox::Ok | QMessageBox::Cancel, Chris@79: QMessageBox::Ok) Chris@79: == QMessageBox::Ok); Chris@79: } Chris@79: Chris@79: bool MainWindow::askToInitExisting(QString arg) Chris@79: { Chris@79: return (QMessageBox::question Chris@84: (this, tr("Folder has no repository"), Chris@84: tr("Initialise a repository here?

You asked to open \"%1\".
This folder does not contain a Mercurial repository.

Would you like to initialise a repository here?
") Chris@79: .arg(xmlEncode(arg)), Chris@79: QMessageBox::Ok | QMessageBox::Cancel, Chris@79: QMessageBox::Ok) Chris@79: == QMessageBox::Ok); Chris@79: } Chris@79: Chris@79: bool MainWindow::askToInitNew(QString arg) Chris@79: { Chris@79: return (QMessageBox::question Chris@84: (this, tr("Folder does not exist"), Chris@84: tr("Initialise a new repository?

You asked to open \"%1\".
This folder does not yet exist.

Would you like to create the folder and initialise a new empty repository in it?
") Chris@84: .arg(xmlEncode(arg)), Chris@84: QMessageBox::Ok | QMessageBox::Cancel, Chris@84: QMessageBox::Ok) Chris@84: == QMessageBox::Ok); Chris@84: } Chris@84: Chris@84: bool MainWindow::askToOpenInsteadOfInit(QString arg) Chris@84: { Chris@84: return (QMessageBox::question Chris@84: (this, tr("Repository exists"), Chris@84: tr("Open existing repository?

You asked to initialise a new repository at \"%1\".
This folder already contains a repository. Would you like to open it?
") Chris@79: .arg(xmlEncode(arg)), Chris@79: QMessageBox::Ok | QMessageBox::Cancel, Chris@79: QMessageBox::Ok) Chris@79: == QMessageBox::Ok); Chris@79: } Chris@79: Chris@79: bool MainWindow::openLocal(QString local) Chris@79: { Chris@79: DEBUG << "open " << local << endl; Chris@79: Chris@79: FolderStatus status = getFolderStatus(local); Chris@79: QString containing = getContainingRepoFolder(local); Chris@79: Chris@79: switch (status) { Chris@79: Chris@79: case FolderHasRepo: Chris@79: // fine Chris@79: break; Chris@79: Chris@79: case FolderExists: Chris@79: if (containing != "") { Chris@79: if (!askToOpenParentRepo(local, containing)) return false; Chris@84: local = containing; Chris@79: } else { Chris@86: //!!! No -- this is likely to happen far more by accident Chris@86: // than because the user actually wanted to init something. Chris@86: // Don't ask, just politely reject. Chris@79: if (!askToInitExisting(local)) return false; Chris@79: return openInit(local); Chris@79: } Chris@79: break; Chris@79: Chris@79: case FolderParentExists: Chris@79: if (containing != "") { Chris@79: if (!askToOpenParentRepo(local, containing)) return false; Chris@84: local = containing; Chris@79: } else { Chris@79: if (!askToInitNew(local)) return false; Chris@79: return openInit(local); Chris@79: } Chris@79: break; Chris@79: Chris@79: case FolderUnknown: Chris@84: if (containing != "") { Chris@84: if (!askToOpenParentRepo(local, containing)) return false; Chris@84: local = containing; Chris@84: } else { Chris@84: return complainAboutUnknownFolder(local); Chris@84: } Chris@84: break; Chris@79: Chris@79: case FolderIsFile: Chris@79: return complainAboutFilePath(local); Chris@79: } Chris@79: Chris@284: m_workFolderPath = local; Chris@284: m_remoteRepoPath = ""; Chris@79: return true; Chris@79: } Chris@79: Chris@79: bool MainWindow::openRemote(QString remote, QString local) Chris@79: { Chris@79: DEBUG << "clone " << remote << " to " << local << endl; Chris@84: Chris@84: FolderStatus status = getFolderStatus(local); Chris@84: QString containing = getContainingRepoFolder(local); Chris@84: Chris@84: DEBUG << "status = " << status << ", containing = " << containing << endl; Chris@84: Chris@84: if (status == FolderHasRepo || containing != "") { Chris@84: return complainAboutCloneToExisting(local); Chris@84: } Chris@84: Chris@84: if (status == FolderIsFile) { Chris@84: return complainAboutCloneToFile(local); Chris@84: } Chris@84: Chris@84: if (status == FolderUnknown) { Chris@248: if (!askAboutUnknownFolder(local)) { Chris@248: return false; Chris@248: } Chris@84: } Chris@84: Chris@84: if (status == FolderExists) { Chris@237: local = complainAboutCloneToExistingFolder(local, remote); Chris@237: if (local == "") return false; Chris@84: } Chris@84: Chris@284: m_workFolderPath = local; Chris@284: m_remoteRepoPath = remote; Chris@84: hgCloneFromRemote(); Chris@84: Chris@79: return true; Chris@79: } Chris@79: Chris@84: bool MainWindow::openInit(QString local) Chris@79: { Chris@84: DEBUG << "openInit " << local << endl; Chris@84: Chris@84: FolderStatus status = getFolderStatus(local); Chris@84: QString containing = getContainingRepoFolder(local); Chris@84: Chris@84: DEBUG << "status = " << status << ", containing = " << containing << endl; Chris@84: Chris@84: if (status == FolderHasRepo) { Chris@84: if (!askToOpenInsteadOfInit(local)) return false; Chris@84: } Chris@84: Chris@84: if (containing != "") { Chris@84: return complainAboutInitInRepo(local); Chris@84: } Chris@84: Chris@84: if (status == FolderIsFile) { Chris@84: return complainAboutInitFile(local); Chris@84: } Chris@84: Chris@84: if (status == FolderUnknown) { Chris@84: return complainAboutUnknownFolder(local); Chris@84: } Chris@84: Chris@284: m_workFolderPath = local; Chris@284: m_remoteRepoPath = ""; Chris@84: hgInit(); Chris@79: return true; Chris@79: } Chris@79: jtkorhonen@0: void MainWindow::settings() jtkorhonen@0: { jtkorhonen@0: SettingsDialog *settingsDlg = new SettingsDialog(this); jtkorhonen@0: settingsDlg->exec(); Chris@230: Chris@230: if (settingsDlg->presentationChanged()) { Chris@284: m_hgTabs->updateFileStates(); Chris@230: updateToolBarStyle(); Chris@273: hgRefresh(); Chris@230: } jtkorhonen@0: } jtkorhonen@0: jtkorhonen@2: #define STDOUT_NEEDS_BIG_WINDOW 512 jtkorhonen@2: #define SMALL_WND_W 500 jtkorhonen@2: #define SMALL_WND_H 300 jtkorhonen@2: jtkorhonen@2: #define BIG_WND_W 1024 jtkorhonen@2: #define BIG_WND_H 768 jtkorhonen@2: jtkorhonen@2: jtkorhonen@2: void MainWindow::presentLongStdoutToUser(QString stdo) jtkorhonen@0: { jtkorhonen@2: if (!stdo.isEmpty()) jtkorhonen@2: { jtkorhonen@2: QDialog dlg; jtkorhonen@0: jtkorhonen@2: if (stdo.length() > STDOUT_NEEDS_BIG_WINDOW) jtkorhonen@2: { jtkorhonen@2: dlg.setMinimumWidth(BIG_WND_W); jtkorhonen@2: dlg.setMinimumHeight(BIG_WND_H); jtkorhonen@2: } jtkorhonen@2: else jtkorhonen@2: { jtkorhonen@2: dlg.setMinimumWidth(SMALL_WND_W); jtkorhonen@2: dlg.setMinimumHeight(SMALL_WND_H); jtkorhonen@2: } jtkorhonen@0: jtkorhonen@2: QVBoxLayout *box = new QVBoxLayout; jtkorhonen@2: QListWidget *list = new QListWidget; jtkorhonen@2: list-> addItems(stdo.split("\n")); jtkorhonen@2: QPushButton *btn = new QPushButton(tr("Ok")); jtkorhonen@2: connect(btn, SIGNAL(clicked()), &dlg, SLOT(accept())); jtkorhonen@0: jtkorhonen@2: box -> addWidget(list); jtkorhonen@2: box -> addWidget(btn); jtkorhonen@2: dlg.setLayout(box); jtkorhonen@2: jtkorhonen@2: dlg.exec(); jtkorhonen@2: } jtkorhonen@2: else jtkorhonen@2: { Chris@98: QMessageBox::information(this, tr("EasyMercurial"), tr("Mercurial command did not return any output.")); jtkorhonen@2: } jtkorhonen@0: } jtkorhonen@0: Chris@90: void MainWindow::updateFileSystemWatcher() Chris@90: { Chris@199: bool justCreated = false; Chris@284: if (!m_fsWatcher) { Chris@284: m_fsWatcher = new QFileSystemWatcher(); Chris@199: justCreated = true; Chris@199: } Chris@199: Chris@199: // QFileSystemWatcher will refuse to add a file or directory to Chris@199: // its watch list that it is already watching -- fine, that's what Chris@199: // we want -- but it prints a warning when this happens, which is Chris@199: // annoying because it would be the normal case for us. So we'll Chris@199: // check for duplicates ourselves. Chris@199: QSet alreadyWatched; Chris@284: QStringList dl(m_fsWatcher->directories()); Chris@199: foreach (QString d, dl) alreadyWatched.insert(d); Chris@199: Chris@90: std::deque pending; Chris@284: pending.push_back(m_workFolderPath); Chris@199: Chris@90: while (!pending.empty()) { Chris@199: Chris@90: QString path = pending.front(); Chris@90: pending.pop_front(); Chris@199: if (!alreadyWatched.contains(path)) { Chris@284: m_fsWatcher->addPath(path); Chris@199: DEBUG << "Added to file system watcher: " << path << endl; Chris@199: } Chris@199: Chris@90: QDir d(path); Chris@90: if (d.exists()) { Chris@199: d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | Chris@199: QDir::Readable | QDir::NoSymLinks); Chris@90: foreach (QString entry, d.entryList()) { Chris@199: if (entry.startsWith('.')) continue; Chris@90: QString entryPath = d.absoluteFilePath(entry); Chris@90: pending.push_back(entryPath); Chris@90: } Chris@90: } Chris@90: } Chris@199: Chris@238: // The general timer isn't really related to the fs watcher Chris@238: // object, it just does something similar -- every now and then we Chris@238: // do a refresh just to update the history dates etc Chris@238: Chris@238: m_fsWatcherGeneralTimer = new QTimer(this); Chris@238: connect(m_fsWatcherGeneralTimer, SIGNAL(timeout()), Chris@238: this, SLOT(checkFilesystem())); Chris@238: m_fsWatcherGeneralTimer->setInterval(30 * 60 * 1000); // half an hour Chris@238: m_fsWatcherGeneralTimer->start(); Chris@238: Chris@199: if (justCreated) { Chris@284: connect(m_fsWatcher, SIGNAL(directoryChanged(QString)), Chris@199: this, SLOT(fsDirectoryChanged(QString))); Chris@284: connect(m_fsWatcher, SIGNAL(fileChanged(QString)), Chris@199: this, SLOT(fsFileChanged(QString))); Chris@199: } Chris@90: } Chris@90: Chris@238: void MainWindow::suspendFileSystemWatcher() Chris@238: { Chris@238: DEBUG << "MainWindow::suspendFileSystemWatcher" << endl; Chris@284: if (m_fsWatcher) { Chris@241: m_fsWatcherSuspended = true; Chris@241: if (m_fsWatcherRestoreTimer) { Chris@241: delete m_fsWatcherRestoreTimer; Chris@241: m_fsWatcherRestoreTimer = 0; Chris@241: } Chris@238: m_fsWatcherGeneralTimer->stop(); Chris@238: } Chris@238: } Chris@238: Chris@238: void MainWindow::restoreFileSystemWatcher() Chris@238: { Chris@238: DEBUG << "MainWindow::restoreFileSystemWatcher" << endl; Chris@238: if (m_fsWatcherRestoreTimer) delete m_fsWatcherRestoreTimer; Chris@238: Chris@238: // The restore timer is used to leave a polite interval between Chris@238: // being asked to restore the watcher and actually doing so. It's Chris@238: // a single shot timer each time it's used, but we don't use Chris@238: // QTimer::singleShot because we want to stop the previous one if Chris@238: // it's running (via deleting it) Chris@238: Chris@238: m_fsWatcherRestoreTimer = new QTimer(this); Chris@238: connect(m_fsWatcherRestoreTimer, SIGNAL(timeout()), Chris@238: this, SLOT(actuallyRestoreFileSystemWatcher())); Chris@238: m_fsWatcherRestoreTimer->setInterval(1000); Chris@238: m_fsWatcherRestoreTimer->setSingleShot(true); Chris@238: m_fsWatcherRestoreTimer->start(); Chris@238: } Chris@238: Chris@238: void MainWindow::actuallyRestoreFileSystemWatcher() Chris@238: { Chris@238: DEBUG << "MainWindow::actuallyRestoreFileSystemWatcher" << endl; Chris@284: if (m_fsWatcher) { Chris@241: m_fsWatcherSuspended = false; Chris@241: m_fsWatcherGeneralTimer->start(); Chris@241: } Chris@238: } Chris@238: Chris@238: void MainWindow::checkFilesystem() Chris@238: { Chris@238: DEBUG << "MainWindow::checkFilesystem" << endl; Chris@273: hgRefresh(); Chris@238: } Chris@238: Chris@122: void MainWindow::fsDirectoryChanged(QString d) Chris@90: { Chris@122: DEBUG << "MainWindow::fsDirectoryChanged " << d << endl; Chris@241: if (!m_fsWatcherSuspended) { Chris@241: hgStat(); Chris@241: } Chris@90: } Chris@90: Chris@122: void MainWindow::fsFileChanged(QString f) Chris@90: { Chris@122: DEBUG << "MainWindow::fsFileChanged " << f << endl; Chris@241: if (!m_fsWatcherSuspended) { Chris@241: hgStat(); Chris@241: } Chris@90: } Chris@90: Chris@275: QString MainWindow::format1(QString head) Chris@275: { Chris@275: return QString("

%1

").arg(head); Chris@275: } Chris@275: Chris@125: QString MainWindow::format3(QString head, QString intro, QString code) Chris@125: { Chris@196: code = xmlEncode(code).replace("\n", "
") Chris@196: #ifndef Q_OS_WIN32 Chris@196: // The hard hyphen comes out funny on Windows Chris@196: .replace("-", "‑") Chris@196: #endif Chris@196: .replace(" ", " "); Chris@125: if (intro == "") { Chris@168: return QString("

%1

%2

") Chris@168: .arg(head).arg(code); Chris@126: } else if (code == "") { Chris@126: return QString("

%1

%2

") Chris@126: .arg(head).arg(intro); Chris@125: } else { Chris@168: return QString("

%1

%2

%3

") Chris@168: .arg(head).arg(intro).arg(code); Chris@125: } Chris@125: } Chris@125: Chris@120: void MainWindow::showIncoming(QString output) Chris@120: { Chris@284: m_runner->hide(); Chris@125: IncomingDialog *d = new IncomingDialog(this, output); Chris@125: d->exec(); Chris@125: delete d; Chris@125: } Chris@125: Chris@125: int MainWindow::extractChangeCount(QString text) Chris@125: { Chris@125: QRegExp re("added (\\d+) ch\\w+ with (\\d+) ch\\w+ to (\\d+) f\\w+"); Chris@125: if (re.indexIn(text) >= 0) { Chris@125: return re.cap(1).toInt(); Chris@125: } else if (text.contains("no changes")) { Chris@125: return 0; Chris@125: } else { Chris@125: return -1; // unknown Chris@125: } Chris@120: } Chris@120: Chris@120: void MainWindow::showPushResult(QString output) Chris@120: { Chris@291: QString head; Chris@125: QString report; Chris@125: int n = extractChangeCount(output); Chris@125: if (n > 0) { Chris@291: head = tr("Pushed %n changeset(s)", "", n); Chris@300: report = tr("Successfully pushed to the remote repository at %1.").arg(xmlEncode(m_remoteRepoPath)); Chris@125: } else if (n == 0) { Chris@291: head = tr("No changes to push"); Chris@291: report = tr("The remote repository already contains all changes that have been committed locally."); Chris@294: if (m_hgTabs->canCommit()) { Chris@291: report = tr("%1

You do have some uncommitted changes. If you wish to push those to the remote repository, commit them locally first.").arg(report); Chris@291: } Chris@125: } else { Chris@291: head = tr("Push complete"); Chris@125: } Chris@284: m_runner->hide(); Chris@291: Chris@291: MoreInformationDialog::information(this, tr("Push complete"), Chris@291: head, report, output); Chris@120: } Chris@120: Chris@120: void MainWindow::showPullResult(QString output) Chris@120: { Chris@291: QString head; Chris@125: QString report; Chris@125: int n = extractChangeCount(output); Chris@125: if (n > 0) { Chris@291: head = tr("Pulled %n changeset(s)", "", n); Chris@303: report = tr("The new changes will be highlighted in the history.
Use Update to bring these changes into your working copy."); Chris@125: } else if (n == 0) { Chris@291: head = tr("No changes to pull"); Chris@291: report = tr("Your local repository already contains all changes found in the remote repository."); Chris@125: } else { Chris@291: head = tr("Pull complete"); Chris@125: } Chris@284: m_runner->hide(); Chris@275: Chris@275: MoreInformationDialog::information(this, tr("Pull complete"), Chris@291: head, report, output); Chris@120: } Chris@120: Chris@174: void MainWindow::reportNewRemoteHeads(QString output) Chris@174: { Chris@174: bool headsAreLocal = false; Chris@174: Chris@284: if (m_currentParents.size() == 1) { Chris@284: int m_currentBranchHeads = 0; Chris@174: bool parentIsHead = false; Chris@284: Changeset *parent = m_currentParents[0]; Chris@284: foreach (Changeset *head, m_currentHeads) { Chris@284: if (head->isOnBranch(m_currentBranch)) { Chris@284: ++m_currentBranchHeads; Chris@174: } Chris@174: if (parent->id() == head->id()) { Chris@174: parentIsHead = true; Chris@174: } Chris@174: } Chris@284: if (m_currentBranchHeads == 2 && parentIsHead) { Chris@174: headsAreLocal = true; Chris@174: } Chris@174: } Chris@174: Chris@174: if (headsAreLocal) { Chris@291: MoreInformationDialog::warning Chris@291: (this, Chris@291: tr("Push failed"), Chris@291: tr("Push failed"), Chris@291: tr("Your local repository could not be pushed to the remote repository.

You may need to merge the changes locally first."), Chris@291: output); Chris@174: } else { Chris@291: MoreInformationDialog::warning Chris@291: (this, Chris@291: tr("Push failed"), Chris@291: tr("Push failed"), Chris@291: tr("Your local repository could not be pushed to the remote repository.

The remote repository may have been changed by someone else since you last pushed. Try pulling and merging their changes into your local repository first."), Chris@291: output); Chris@174: } Chris@174: } Chris@174: Chris@238: void MainWindow::commandStarting(HgAction action) Chris@238: { Chris@238: // Annoyingly, hg stat actually modifies the working directory -- Chris@238: // it creates files called hg-checklink and hg-checkexec to test Chris@238: // properties of the filesystem. For safety's sake, suspend the Chris@238: // fs watcher while running commands, and restore it shortly after Chris@238: // a command has finished. Chris@238: Chris@246: if (action.action == ACT_STAT) { Chris@246: suspendFileSystemWatcher(); Chris@246: } Chris@238: } Chris@238: Chris@143: void MainWindow::commandFailed(HgAction action, QString output) Chris@62: { Chris@62: DEBUG << "MainWindow::commandFailed" << endl; Chris@238: restoreFileSystemWatcher(); Chris@74: Chris@210: QString setstr; Chris@210: #ifdef Q_OS_MAC Chris@210: setstr = tr("Preferences"); Chris@210: #else Chris@210: setstr = tr("Settings"); Chris@210: #endif Chris@210: Chris@113: // Some commands we just have to ignore bad return values from: Chris@113: Chris@113: switch(action.action) { Chris@113: case ACT_NONE: Chris@113: // uh huh Chris@113: return; Chris@175: case ACT_TEST_HG: Chris@291: MoreInformationDialog::warning Chris@291: (this, Chris@291: tr("Failed to run Mercurial"), Chris@291: tr("Failed to run Mercurial"), Chris@291: tr("The Mercurial program either could not be found or failed to run.
Check that the Mercurial program path is correct in %1.").arg(setstr), Chris@291: output); Chris@200: settings(); Chris@200: return; Chris@200: case ACT_TEST_HG_EXT: Chris@200: QMessageBox::warning Chris@291: (this, Chris@291: tr("Failed to run Mercurial"), Chris@291: tr("Failed to run Mercurial with extension enabled"), Chris@291: tr("The Mercurial program failed to run with the EasyMercurial interaction extension enabled.
This may indicate an installation problem with EasyMercurial.

You may be able to continue working if you switch off “Use EasyHg Mercurial Extension” in %1. Note that remote repositories that require authentication may not work if you do this.").arg(setstr), Chris@291: output); Chris@175: settings(); Chris@175: return; Chris@252: case ACT_CLONEFROMREMOTE: Chris@252: // if clone fails, we have no repo Chris@284: m_workFolderPath = ""; Chris@252: enableDisableActions(); Chris@252: break; Chris@113: case ACT_INCOMING: Chris@211: // returns non-zero code and no output if the check was Chris@211: // successful but there are no changes pending Chris@280: if (output.replace(QRegExp("(^|\\n)warning: [^\\n]*\\n"), "").trimmed() == "") { Chris@211: showIncoming(""); Chris@211: return; Chris@211: } Chris@211: break; Chris@162: case ACT_QUERY_HEADS: Chris@162: // fails if repo is empty; we don't care (if there's a genuine Chris@202: // problem, something else will fail too). Pretend it Chris@202: // succeeded, so that any further actions that are contingent Chris@202: // on the success of the heads query get carried out properly. Chris@202: commandCompleted(action, ""); Chris@162: return; Chris@113: case ACT_FOLDERDIFF: Chris@113: case ACT_CHGSETDIFF: Chris@113: // external program, unlikely to be anything useful in stderr Chris@113: // and some return with failure codes when something as basic Chris@113: // as the user closing the window via the wm happens Chris@113: return; Chris@174: case ACT_PUSH: Chris@174: if (output.contains("creates new remote heads")) { Chris@174: reportNewRemoteHeads(output); Chris@174: return; Chris@174: } Chris@199: case ACT_STAT: Chris@199: break; // go on and report Chris@113: default: Chris@114: break; Chris@113: } Chris@113: Chris@113: QString command = action.executable; Chris@113: if (command == "") command = "hg"; Chris@113: foreach (QString arg, action.params) { Chris@113: command += " " + arg; Chris@113: } Chris@113: Chris@291: //!!! Chris@291: Chris@113: QString message = tr("

Command failed

" Chris@113: "

The following command failed:

" Chris@113: "%1" Chris@113: "%2
") Chris@113: .arg(command) Chris@143: .arg((output.trimmed() != "") ? Chris@113: tr("

Its output said:

%1") Chris@143: .arg(xmlEncode(output.left(800)) Chris@113: .replace("\n", "
")) Chris@113: : ""); Chris@113: Chris@113: QMessageBox::warning(this, tr("Command failed"), message); Chris@62: } Chris@62: Chris@109: void MainWindow::commandCompleted(HgAction completedAction, QString output) jtkorhonen@0: { Chris@238: restoreFileSystemWatcher(); Chris@109: HGACTIONS action = completedAction.action; Chris@109: Chris@109: if (action == ACT_NONE) return; Chris@109: Chris@150: bool headsChanged = false; Chris@150: QStringList oldHeadIds; Chris@150: Chris@150: switch (action) { Chris@109: Chris@175: case ACT_TEST_HG: Chris@175: break; Chris@175: Chris@200: case ACT_TEST_HG_EXT: Chris@200: break; Chris@200: Chris@109: case ACT_QUERY_PATHS: jtkorhonen@0: { Chris@109: DEBUG << "stdout is " << output << endl; Chris@109: LogParser lp(output, "="); Chris@109: LogList ll = lp.parse(); Chris@109: DEBUG << ll.size() << " results" << endl; Chris@109: if (!ll.empty()) { Chris@284: m_remoteRepoPath = lp.parse()[0]["default"].trimmed(); Chris@284: DEBUG << "Set remote path to " << m_remoteRepoPath << endl; Chris@210: } else { Chris@284: m_remoteRepoPath = ""; Chris@109: } Chris@284: MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); Chris@284: MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); Chris@284: m_hgTabs->setWorkFolderAndRepoNames(m_workFolderPath, m_remoteRepoPath); Chris@109: break; Chris@109: } jtkorhonen@0: Chris@109: case ACT_QUERY_BRANCH: Chris@284: m_currentBranch = output.trimmed(); Chris@109: break; jtkorhonen@0: Chris@109: case ACT_STAT: Chris@284: m_lastStatOutput = output; Chris@109: updateFileSystemWatcher(); Chris@109: break; Chris@163: Chris@163: case ACT_RESOLVE_LIST: Chris@163: if (output != "") { Chris@163: // Remove lines beginning with R (they are resolved, Chris@163: // and the file stat parser treats R as removed) Chris@163: QStringList outList = output.split('\n'); Chris@163: QStringList winnowed; Chris@163: foreach (QString line, outList) { Chris@163: if (!line.startsWith("R ")) winnowed.push_back(line); Chris@163: } Chris@163: output = winnowed.join("\n"); Chris@163: } Chris@284: DEBUG << "m_lastStatOutput = " << m_lastStatOutput << endl; Chris@199: DEBUG << "resolve output = " << output << endl; Chris@284: m_hgTabs->updateWorkFolderFileList(m_lastStatOutput + output); Chris@163: break; Chris@163: Chris@163: case ACT_RESOLVE_MARK: Chris@284: m_shouldHgStat = true; Chris@163: break; Chris@109: Chris@109: case ACT_INCOMING: Chris@120: showIncoming(output); Chris@120: break; Chris@120: Chris@109: case ACT_ANNOTATE: Chris@109: presentLongStdoutToUser(output); Chris@284: m_shouldHgStat = true; Chris@109: break; Chris@109: Chris@109: case ACT_PULL: Chris@120: showPullResult(output); Chris@284: m_shouldHgStat = true; Chris@109: break; Chris@109: Chris@109: case ACT_PUSH: Chris@120: showPushResult(output); Chris@109: break; Chris@109: Chris@109: case ACT_INIT: Chris@284: MultiChoiceDialog::addRecentArgument("init", m_workFolderPath); Chris@284: MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); Chris@109: enableDisableActions(); Chris@284: m_shouldHgStat = true; Chris@109: break; Chris@109: Chris@109: case ACT_CLONEFROMREMOTE: Chris@284: MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); Chris@284: MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); Chris@284: MultiChoiceDialog::addRecentArgument("remote", m_workFolderPath, true); Chris@291: MoreInformationDialog::information Chris@291: (this, Chris@291: tr("Clone"), Chris@291: tr("Clone successful"), Chris@295: tr("The remote repository was successfully cloned to the local folder %1.").arg(xmlEncode(m_workFolderPath)), Chris@291: output); Chris@109: enableDisableActions(); Chris@284: m_shouldHgStat = true; Chris@109: break; Chris@109: Chris@109: case ACT_LOG: Chris@284: m_hgTabs->setNewLog(output); Chris@284: m_needNewLog = false; Chris@120: break; Chris@120: Chris@120: case ACT_LOG_INCREMENTAL: Chris@284: m_hgTabs->addIncrementalLog(output); Chris@109: break; Chris@109: Chris@109: case ACT_QUERY_PARENTS: Chris@152: { Chris@284: foreach (Changeset *cs, m_currentParents) delete cs; Chris@284: m_currentParents = Changeset::parseChangesets(output); Chris@284: QStringList parentIds = Changeset::getIds(m_currentParents); Chris@284: m_hgTabs->setCurrent(parentIds, m_currentBranch); Chris@152: } Chris@109: break; Chris@109: Chris@109: case ACT_QUERY_HEADS: Chris@150: { Chris@284: oldHeadIds = Changeset::getIds(m_currentHeads); Chris@150: Changesets newHeads = Changeset::parseChangesets(output); Chris@150: QStringList newHeadIds = Changeset::getIds(newHeads); Chris@150: if (oldHeadIds != newHeadIds) { Chris@150: DEBUG << "Heads changed, will prompt an incremental log if appropriate" << endl; Chris@305: DEBUG << "Old heads: " << oldHeadIds.join(",") << endl; Chris@305: DEBUG << "New heads: " << newHeadIds.join(",") << endl; Chris@150: headsChanged = true; Chris@284: foreach (Changeset *cs, m_currentHeads) delete cs; Chris@284: m_currentHeads = newHeads; Chris@150: } Chris@150: } Chris@109: break; Chris@130: Chris@130: case ACT_COMMIT: Chris@284: m_hgTabs->clearSelections(); Chris@284: m_justMerged = false; Chris@284: m_shouldHgStat = true; Chris@130: break; Chris@163: Chris@163: case ACT_REVERT: Chris@284: hgMarkResolved(m_lastRevertedFiles); Chris@284: m_justMerged = false; Chris@163: break; Chris@109: Chris@109: case ACT_REMOVE: Chris@109: case ACT_ADD: Chris@284: m_hgTabs->clearSelections(); Chris@284: m_shouldHgStat = true; Chris@116: break; Chris@116: Chris@164: case ACT_TAG: Chris@284: m_needNewLog = true; Chris@284: m_shouldHgStat = true; Chris@164: break; Chris@164: Chris@288: case ACT_UNCOMMITTED_SUMMARY: Chris@168: QMessageBox::information(this, tr("Change summary"), Chris@168: format3(tr("Summary of uncommitted changes"), Chris@168: "", Chris@168: output)); Chris@168: break; Chris@168: Chris@288: case ACT_DIFF_SUMMARY: Chris@289: { Chris@289: // Output has log info first, diff following after a blank line Chris@289: output.replace("\r\n", "\n"); Chris@289: QStringList olist = output.split("\n\n", QString::SkipEmptyParts); Chris@289: if (olist.size() > 1) output = olist[1]; Chris@289: Chris@289: Changeset *cs = (Changeset *)completedAction.extraData; Chris@289: if (cs) { Chris@289: QMessageBox::information Chris@289: (this, tr("Change summary"), Chris@289: format3(tr("Summary of changes"), Chris@289: cs->formatHtml(), Chris@289: output)); Chris@289: } else if (output == "") { Chris@289: // Can happen, for a merge commit (depending on parent) Chris@288: QMessageBox::information(this, tr("Change summary"), Chris@288: format3(tr("Summary of changes"), Chris@288: tr("No changes"), Chris@288: output)); Chris@288: } else { Chris@288: QMessageBox::information(this, tr("Change summary"), Chris@288: format3(tr("Summary of changes"), Chris@288: "", Chris@288: output)); Chris@288: } Chris@288: break; Chris@289: } Chris@288: Chris@109: case ACT_FOLDERDIFF: Chris@109: case ACT_CHGSETDIFF: Chris@109: case ACT_SERVE: Chris@109: case ACT_HG_IGNORE: Chris@284: m_shouldHgStat = true; Chris@109: break; Chris@109: Chris@109: case ACT_UPDATE: Chris@162: QMessageBox::information(this, tr("Update"), tr("

Update successful

%1

").arg(xmlEncode(output))); Chris@284: m_shouldHgStat = true; Chris@109: break; Chris@109: Chris@109: case ACT_MERGE: Chris@294: MoreInformationDialog::information Chris@294: (this, tr("Merge"), tr("Merge successful"), Chris@302: tr("Remember to test and commit the result before making any further changes."), Chris@294: output); Chris@284: m_shouldHgStat = true; Chris@284: m_justMerged = true; Chris@109: break; Chris@109: Chris@109: case ACT_RETRY_MERGE: Chris@163: QMessageBox::information(this, tr("Resolved"), Chris@302: tr("

Merge resolved

Merge resolved successfully.
Remember to test and commit the result before making any further changes.

")); Chris@284: m_shouldHgStat = true; Chris@284: m_justMerged = true; Chris@109: break; Chris@109: Chris@109: default: Chris@109: break; Chris@109: } Chris@108: Chris@121: // Sequence when no full log required: Chris@163: // paths -> branch -> stat -> resolve-list -> heads -> Chris@150: // incremental-log (only if heads changed) -> parents Chris@150: // Chris@121: // Sequence when full log required: Chris@163: // paths -> branch -> stat -> resolve-list -> heads -> parents -> log Chris@150: // Chris@150: // Note we want to call enableDisableActions only once, at the end Chris@150: // of whichever sequence is in use. Chris@150: Chris@156: bool noMore = false; Chris@156: Chris@150: switch (action) { Chris@175: Chris@175: case ACT_TEST_HG: Chris@248: { Chris@248: QSettings settings; Chris@248: settings.beginGroup("General"); Chris@248: if (settings.value("useextension", true).toBool()) { Chris@248: hgTestExtension(); Chris@284: } else if (m_workFolderPath == "") { Chris@248: open(); Chris@248: } else { Chris@248: hgQueryPaths(); Chris@248: } Chris@200: break; Chris@248: } Chris@200: Chris@200: case ACT_TEST_HG_EXT: Chris@284: if (m_workFolderPath == "") { Chris@248: open(); Chris@248: } else{ Chris@248: hgQueryPaths(); Chris@248: } Chris@175: break; Chris@150: Chris@150: case ACT_QUERY_PATHS: Chris@109: hgQueryBranch(); Chris@150: break; Chris@150: Chris@150: case ACT_QUERY_BRANCH: Chris@109: hgStat(); Chris@150: break; Chris@150: Chris@150: case ACT_STAT: Chris@163: hgResolveList(); Chris@163: break; Chris@163: Chris@163: case ACT_RESOLVE_LIST: Chris@150: hgQueryHeads(); Chris@150: break; Chris@150: Chris@150: case ACT_QUERY_HEADS: Chris@284: if (headsChanged && !m_needNewLog) { Chris@150: hgLogIncremental(oldHeadIds); Chris@121: } else { Chris@150: hgQueryParents(); Chris@121: } Chris@150: break; Chris@150: Chris@150: case ACT_LOG_INCREMENTAL: Chris@109: hgQueryParents(); Chris@150: break; Chris@150: Chris@150: case ACT_QUERY_PARENTS: Chris@284: if (m_needNewLog) { Chris@120: hgLog(); Chris@150: } else { Chris@150: // we're done Chris@156: noMore = true; Chris@120: } Chris@150: break; Chris@150: Chris@150: case ACT_LOG: Chris@150: // we're done Chris@156: noMore = true; Chris@198: break; Chris@150: Chris@150: default: Chris@284: if (m_shouldHgStat) { Chris@284: m_shouldHgStat = false; Chris@109: hgQueryPaths(); Chris@150: } else { Chris@156: noMore = true; jtkorhonen@0: } Chris@150: break; Chris@150: } Chris@156: Chris@156: if (noMore) { Chris@284: m_stateUnknown = false; Chris@156: enableDisableActions(); Chris@284: m_hgTabs->updateHistory(); Chris@156: } jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::connectActions() jtkorhonen@0: { Chris@284: connect(m_exitAct, SIGNAL(triggered()), this, SLOT(close())); Chris@284: connect(m_aboutAct, SIGNAL(triggered()), this, SLOT(about())); Chris@273: Chris@284: connect(m_hgRefreshAct, SIGNAL(triggered()), this, SLOT(hgRefresh())); Chris@284: connect(m_hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove())); Chris@284: connect(m_hgAddAct, SIGNAL(triggered()), this, SLOT(hgAdd())); Chris@284: connect(m_hgCommitAct, SIGNAL(triggered()), this, SLOT(hgCommit())); Chris@284: connect(m_hgFolderDiffAct, SIGNAL(triggered()), this, SLOT(hgFolderDiff())); Chris@284: connect(m_hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate())); Chris@284: connect(m_hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert())); Chris@284: connect(m_hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge())); Chris@284: connect(m_hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore())); Chris@273: Chris@284: connect(m_settingsAct, SIGNAL(triggered()), this, SLOT(settings())); Chris@284: connect(m_openAct, SIGNAL(triggered()), this, SLOT(open())); Chris@284: connect(m_changeRemoteRepoAct, SIGNAL(triggered()), this, SLOT(changeRemoteRepo())); Chris@273: Chris@284: connect(m_hgIncomingAct, SIGNAL(triggered()), this, SLOT(hgIncoming())); Chris@284: connect(m_hgPullAct, SIGNAL(triggered()), this, SLOT(hgPull())); Chris@284: connect(m_hgPushAct, SIGNAL(triggered()), this, SLOT(hgPush())); Chris@273: Chris@284: connect(m_hgAnnotateAct, SIGNAL(triggered()), this, SLOT(hgAnnotate())); Chris@284: connect(m_hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe())); jtkorhonen@0: } Chris@141: Chris@141: void MainWindow::connectTabsSignals() Chris@141: { Chris@284: connect(m_hgTabs, SIGNAL(commit()), Chris@141: this, SLOT(hgCommit())); Chris@141: Chris@284: connect(m_hgTabs, SIGNAL(revert()), Chris@141: this, SLOT(hgRevert())); Chris@141: Chris@284: connect(m_hgTabs, SIGNAL(diffWorkingFolder()), Chris@141: this, SLOT(hgFolderDiff())); Chris@168: Chris@284: connect(m_hgTabs, SIGNAL(showSummary()), Chris@168: this, SLOT(hgShowSummary())); Chris@148: Chris@284: connect(m_hgTabs, SIGNAL(updateTo(QString)), Chris@148: this, SLOT(hgUpdateToRev(QString))); Chris@141: Chris@284: connect(m_hgTabs, SIGNAL(diffToCurrent(QString)), Chris@148: this, SLOT(hgDiffToCurrent(QString))); Chris@141: Chris@284: connect(m_hgTabs, SIGNAL(diffToParent(QString, QString)), Chris@148: this, SLOT(hgDiffToParent(QString, QString))); Chris@141: Chris@289: connect(m_hgTabs, SIGNAL(showSummary(Changeset *)), Chris@289: this, SLOT(hgShowSummaryFor(Changeset *))); Chris@288: Chris@284: connect(m_hgTabs, SIGNAL(mergeFrom(QString)), Chris@148: this, SLOT(hgMergeFrom(QString))); Chris@164: Chris@284: connect(m_hgTabs, SIGNAL(tag(QString)), Chris@148: this, SLOT(hgTag(QString))); Chris@141: } Chris@141: jtkorhonen@0: void MainWindow::enableDisableActions() jtkorhonen@0: { Chris@90: DEBUG << "MainWindow::enableDisableActions" << endl; Chris@90: Chris@284: QString dirname = QDir(m_workFolderPath).dirName(); Chris@202: if (dirname != "") { Chris@202: setWindowTitle(tr("EasyMercurial: %1").arg(dirname)); Chris@202: } else { Chris@202: setWindowTitle(tr("EasyMercurial")); Chris@202: } Chris@202: Chris@115: //!!! should also do things like set the status texts for the Chris@115: //!!! actions appropriately by context Chris@115: jtkorhonen@0: QDir localRepoDir; jtkorhonen@0: QDir workFolderDir; Chris@145: bool workFolderExist = true; Chris@145: bool localRepoExist = true; jtkorhonen@0: Chris@284: m_remoteRepoActionsEnabled = true; Chris@284: if (m_remoteRepoPath.isEmpty()) { Chris@284: m_remoteRepoActionsEnabled = false; jtkorhonen@0: } jtkorhonen@0: Chris@284: m_localRepoActionsEnabled = true; Chris@284: if (m_workFolderPath.isEmpty()) { Chris@284: m_localRepoActionsEnabled = false; jtkorhonen@0: workFolderExist = false; jtkorhonen@0: } jtkorhonen@0: Chris@284: if (m_workFolderPath == "" || !workFolderDir.exists(m_workFolderPath)) { Chris@284: m_localRepoActionsEnabled = false; jtkorhonen@0: workFolderExist = false; Chris@90: } else { jtkorhonen@0: workFolderExist = true; jtkorhonen@0: } jtkorhonen@0: Chris@284: if (!localRepoDir.exists(m_workFolderPath + "/.hg")) { Chris@284: m_localRepoActionsEnabled = false; jtkorhonen@0: localRepoExist = false; jtkorhonen@0: } jtkorhonen@0: Chris@284: m_hgIncomingAct -> setEnabled(m_remoteRepoActionsEnabled && m_remoteRepoActionsEnabled); Chris@284: m_hgPullAct -> setEnabled(m_remoteRepoActionsEnabled && m_remoteRepoActionsEnabled); Chris@284: m_hgPushAct -> setEnabled(m_remoteRepoActionsEnabled && m_remoteRepoActionsEnabled); Chris@169: Chris@179: bool haveDiff = false; Chris@179: QSettings settings; Chris@179: settings.beginGroup("Locations"); Chris@179: if (settings.value("extdiffbinary", "").toString() != "") { Chris@179: haveDiff = true; Chris@179: } Chris@179: settings.endGroup(); Chris@112: Chris@284: m_hgRefreshAct -> setEnabled(m_localRepoActionsEnabled); Chris@284: m_hgFolderDiffAct -> setEnabled(m_localRepoActionsEnabled && haveDiff); Chris@284: m_hgRevertAct -> setEnabled(m_localRepoActionsEnabled); Chris@284: m_hgAddAct -> setEnabled(m_localRepoActionsEnabled); Chris@284: m_hgRemoveAct -> setEnabled(m_localRepoActionsEnabled); Chris@284: m_hgUpdateAct -> setEnabled(m_localRepoActionsEnabled); Chris@284: m_hgCommitAct -> setEnabled(m_localRepoActionsEnabled); Chris@284: m_hgMergeAct -> setEnabled(m_localRepoActionsEnabled); Chris@284: m_hgAnnotateAct -> setEnabled(m_localRepoActionsEnabled); Chris@284: m_hgServeAct -> setEnabled(m_localRepoActionsEnabled); Chris@284: m_hgIgnoreAct -> setEnabled(m_localRepoActionsEnabled); Chris@273: Chris@284: DEBUG << "m_localRepoActionsEnabled = " << m_localRepoActionsEnabled << endl; Chris@284: DEBUG << "canCommit = " << m_hgTabs->canCommit() << endl; Chris@273: Chris@284: m_hgAddAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canAdd()); Chris@284: m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRemove()); Chris@284: m_hgCommitAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canCommit()); Chris@284: m_hgRevertAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRevert()); Chris@284: m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canDiff()); Chris@90: Chris@108: // A default merge makes sense if: Chris@108: // * there is only one parent (if there are two, we have an uncommitted merge) and Chris@108: // * there are exactly two heads that have the same branch as the current branch and Chris@108: // * our parent is one of those heads Chris@108: // Chris@108: // A default update makes sense if: Chris@108: // * there is only one parent and Chris@108: // * the parent is not one of the current heads Chris@156: Chris@108: bool canMerge = false; Chris@108: bool canUpdate = false; Chris@156: bool haveMerge = false; Chris@162: bool emptyRepo = false; Chris@225: bool noWorkingCopy = false; Chris@235: bool newBranch = false; Chris@284: int m_currentBranchHeads = 0; Chris@273: Chris@284: if (m_currentParents.size() == 1) { Chris@156: bool parentIsHead = false; Chris@284: Changeset *parent = m_currentParents[0]; Chris@284: foreach (Changeset *head, m_currentHeads) { Chris@284: DEBUG << "head branch " << head->branch() << ", current branch " << m_currentBranch << endl; Chris@284: if (head->isOnBranch(m_currentBranch)) { Chris@284: ++m_currentBranchHeads; Chris@235: } Chris@235: if (parent->id() == head->id()) { Chris@235: parentIsHead = true; Chris@108: } Chris@108: } Chris@284: if (m_currentBranchHeads == 2 && parentIsHead) { Chris@108: canMerge = true; Chris@108: } Chris@284: if (m_currentBranchHeads == 0 && parentIsHead) { Chris@235: // Just created a new branch Chris@235: newBranch = true; Chris@235: } Chris@108: if (!parentIsHead) { Chris@108: canUpdate = true; Chris@108: DEBUG << "parent id = " << parent->id() << endl; Chris@108: DEBUG << " head ids "<setState(tr("No repository open")); Chris@248: } else { Chris@284: m_hgTabs->setState(tr("(Examining repository)")); Chris@248: } Chris@173: } else if (emptyRepo) { Chris@284: m_hgTabs->setState(tr("Nothing committed to this repository yet")); Chris@225: } else if (noWorkingCopy) { Chris@284: m_hgTabs->setState(tr("No working copy yet: consider updating")); Chris@162: } else if (canMerge) { Chris@284: m_hgTabs->setState(tr("Awaiting merge on %1").arg(branchText)); Chris@284: } else if (!m_hgTabs->getAllUnresolvedFiles().empty()) { Chris@284: m_hgTabs->setState(tr("Have unresolved files following merge on %1").arg(branchText)); Chris@156: } else if (haveMerge) { Chris@284: m_hgTabs->setState(tr("Have merged but not yet committed on %1").arg(branchText)); Chris@235: } else if (newBranch) { Chris@284: m_hgTabs->setState(tr("On %1. New branch: has not yet been committed").arg(branchText)); Chris@156: } else if (canUpdate) { Chris@284: if (m_hgTabs->haveChangesToCommit()) { Chris@163: // have uncommitted changes Chris@284: m_hgTabs->setState(tr("On %1. Not at the head of the branch").arg(branchText)); Chris@163: } else { Chris@163: // no uncommitted changes Chris@284: m_hgTabs->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText)); Chris@163: } Chris@284: } else if (m_currentBranchHeads > 1) { Chris@284: m_hgTabs->setState(tr("At one of %n heads of %1", "", m_currentBranchHeads).arg(branchText)); Chris@115: } else { Chris@284: m_hgTabs->setState(tr("At the head of %1").arg(branchText)); Chris@115: } jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::createActions() jtkorhonen@0: { jtkorhonen@0: //File actions Chris@284: m_openAct = new QAction(QIcon(":/images/fileopen.png"), tr("Open..."), this); Chris@284: m_openAct -> setStatusTip(tr("Open an existing repository or working folder")); Chris@273: Chris@284: m_changeRemoteRepoAct = new QAction(tr("Change Remote Location..."), this); Chris@284: m_changeRemoteRepoAct->setStatusTip(tr("Change the default remote repository for pull and push actions")); Chris@273: Chris@284: m_settingsAct = new QAction(QIcon(":/images/settings.png"), tr("Settings..."), this); Chris@284: m_settingsAct -> setStatusTip(tr("View and change application settings")); Chris@273: Chris@284: m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("Quit"), this); Chris@284: m_exitAct->setShortcuts(QKeySequence::Quit); Chris@284: m_exitAct->setStatusTip(tr("Quit EasyMercurial")); jtkorhonen@0: jtkorhonen@0: //Repository actions Chris@284: m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("Refresh"), this); Chris@284: m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder")); Chris@273: Chris@284: m_hgIncomingAct = new QAction(QIcon(":/images/incoming.png"), tr("Preview"), this); Chris@284: m_hgIncomingAct -> setStatusTip(tr("See what changes are available in the remote repository waiting to be pulled")); Chris@273: Chris@284: m_hgPullAct = new QAction(QIcon(":/images/pull.png"), tr("Pull"), this); Chris@284: m_hgPullAct -> setStatusTip(tr("Pull changes from the remote repository to the local repository")); Chris@273: Chris@284: m_hgPushAct = new QAction(QIcon(":/images/push.png"), tr("Push"), this); Chris@284: m_hgPushAct->setStatusTip(tr("Push changes from the local repository to the remote repository")); jtkorhonen@0: jtkorhonen@0: //Workfolder actions Chris@284: m_hgFolderDiffAct = new QAction(QIcon(":/images/folderdiff.png"), tr("Diff"), this); Chris@284: m_hgFolderDiffAct->setStatusTip(tr("See what has changed in the working folder compared with the last committed state")); Chris@273: Chris@284: m_hgRevertAct = new QAction(QIcon(":/images/undo.png"), tr("Revert"), this); Chris@284: m_hgRevertAct->setStatusTip(tr("Throw away your changes and return to the last committed state")); Chris@273: Chris@284: m_hgAddAct = new QAction(QIcon(":/images/add.png"), tr("Add"), this); Chris@284: m_hgAddAct -> setStatusTip(tr("Mark the selected file(s) to be added on the next commit")); jtkorhonen@0: Chris@169: //!!! needs to be modified for number Chris@284: m_hgRemoveAct = new QAction(QIcon(":/images/remove.png"), tr("Remove"), this); Chris@284: m_hgRemoveAct -> setStatusTip(tr("Mark the selected file(s) to be removed from version control on the next commit")); Chris@273: Chris@284: m_hgUpdateAct = new QAction(QIcon(":/images/update.png"), tr("Update"), this); Chris@284: m_hgUpdateAct->setStatusTip(tr("Update the working folder to the head of the current repository branch")); jtkorhonen@0: Chris@169: //!!! needs to be modified when files selected Chris@284: m_hgCommitAct = new QAction(QIcon(":/images/commit.png"), tr("Commit"), this); Chris@284: m_hgCommitAct->setStatusTip(tr("Commit your changes to the local repository")); Chris@273: Chris@284: m_hgMergeAct = new QAction(QIcon(":/images/merge.png"), tr("Merge"), this); Chris@284: m_hgMergeAct->setStatusTip(tr("Merge the two independent sets of changes in the local repository into the working folder")); jtkorhonen@0: jtkorhonen@0: //Advanced actions Chris@169: //!!! needs to be modified for number Chris@284: m_hgAnnotateAct = new QAction(tr("Annotate"), this); Chris@284: m_hgAnnotateAct -> setStatusTip(tr("Show line-by-line version information for selected file")); Chris@273: Chris@284: m_hgIgnoreAct = new QAction(tr("Edit .hgignore File"), this); Chris@284: m_hgIgnoreAct -> setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial")); Chris@273: Chris@284: m_hgServeAct = new QAction(tr("Serve via HTTP"), this); Chris@284: m_hgServeAct -> setStatusTip(tr("Serve local repository via http for workgroup access")); jtkorhonen@11: jtkorhonen@0: //Help actions Chris@284: m_aboutAct = new QAction(tr("About EasyMercurial"), this); Chris@94: Chris@94: // Miscellaneous Chris@199: QShortcut *clearSelectionsShortcut = new QShortcut(Qt::Key_Escape, this); Chris@199: connect(clearSelectionsShortcut, SIGNAL(activated()), Chris@199: this, SLOT(clearSelections())); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::createMenus() jtkorhonen@0: { Chris@284: m_fileMenu = menuBar()->addMenu(tr("File")); Chris@273: Chris@284: m_fileMenu -> addAction(m_openAct); Chris@284: m_fileMenu -> addAction(m_changeRemoteRepoAct); Chris@284: m_fileMenu -> addSeparator(); Chris@273: Chris@284: m_advancedMenu = m_fileMenu->addMenu(tr("Advanced")); Chris@273: Chris@284: m_fileMenu -> addAction(m_settingsAct); Chris@273: Chris@284: m_fileMenu -> addSeparator(); Chris@284: m_fileMenu -> addAction(m_exitAct); Chris@273: Chris@284: m_advancedMenu -> addAction(m_hgIgnoreAct); Chris@284: m_advancedMenu -> addSeparator(); Chris@284: m_advancedMenu -> addAction(m_hgServeAct); Chris@273: Chris@284: m_helpMenu = menuBar()->addMenu(tr("Help")); Chris@284: m_helpMenu->addAction(m_aboutAct); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: void MainWindow::createToolBars() jtkorhonen@0: { Chris@284: m_fileToolBar = addToolBar(tr("File")); Chris@284: m_fileToolBar -> setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); Chris@284: m_fileToolBar -> addAction(m_openAct); Chris@284: m_fileToolBar -> addAction(m_hgRefreshAct); Chris@284: m_fileToolBar -> addSeparator(); Chris@284: m_fileToolBar -> setMovable(false); Chris@273: Chris@284: m_repoToolBar = addToolBar(tr(REPOMENU_TITLE)); Chris@284: m_repoToolBar -> setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); Chris@284: m_repoToolBar->addAction(m_hgIncomingAct); Chris@284: m_repoToolBar->addAction(m_hgPullAct); Chris@284: m_repoToolBar->addAction(m_hgPushAct); Chris@284: m_repoToolBar -> setMovable(false); Chris@273: Chris@284: m_workFolderToolBar = addToolBar(tr(WORKFOLDERMENU_TITLE)); Chris@284: addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar); Chris@284: m_workFolderToolBar -> setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); Chris@284: m_workFolderToolBar->addAction(m_hgFolderDiffAct); Chris@284: m_workFolderToolBar->addSeparator(); Chris@284: m_workFolderToolBar->addAction(m_hgRevertAct); Chris@284: m_workFolderToolBar->addAction(m_hgUpdateAct); Chris@284: m_workFolderToolBar->addAction(m_hgCommitAct); Chris@284: m_workFolderToolBar->addAction(m_hgMergeAct); Chris@284: m_workFolderToolBar->addSeparator(); Chris@284: m_workFolderToolBar->addAction(m_hgAddAct); Chris@284: m_workFolderToolBar->addAction(m_hgRemoveAct); Chris@284: m_workFolderToolBar -> setMovable(false); Chris@61: Chris@230: updateToolBarStyle(); jtkorhonen@0: } jtkorhonen@0: Chris@230: void MainWindow::updateToolBarStyle() Chris@230: { Chris@230: QSettings settings; Chris@230: settings.beginGroup("Presentation"); Chris@230: bool showText = settings.value("showiconlabels", true).toBool(); Chris@230: settings.endGroup(); Chris@230: Chris@230: foreach (QToolButton *tb, findChildren()) { Chris@230: tb->setToolButtonStyle(showText ? Chris@230: Qt::ToolButtonTextUnderIcon : Chris@230: Qt::ToolButtonIconOnly); Chris@230: } Chris@230: } jtkorhonen@0: jtkorhonen@0: void MainWindow::createStatusBar() jtkorhonen@0: { jtkorhonen@0: statusBar()->showMessage(tr("Ready")); jtkorhonen@0: } jtkorhonen@0: Chris@69: Chris@69: //!!! review these: Chris@69: jtkorhonen@0: void MainWindow::readSettings() jtkorhonen@0: { jtkorhonen@0: QDir workFolder; jtkorhonen@0: Chris@61: QSettings settings; jtkorhonen@0: Chris@284: m_remoteRepoPath = settings.value("remoterepopath", "").toString(); Chris@284: m_workFolderPath = settings.value("workfolderpath", "").toString(); Chris@284: if (!workFolder.exists(m_workFolderPath)) jtkorhonen@0: { Chris@284: m_workFolderPath = ""; jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint(); jtkorhonen@0: QSize size = settings.value("size", QSize(400, 400)).toSize(); Chris@284: m_firstStart = settings.value("firststart", QVariant(true)).toBool(); jtkorhonen@0: Chris@109: //!!! initialFileTypesBits = (unsigned char) settings.value("viewFileTypes", QVariant(DEFAULT_HG_STAT_BITS)).toInt(); jtkorhonen@0: resize(size); jtkorhonen@0: move(pos); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@17: jtkorhonen@0: void MainWindow::writeSettings() jtkorhonen@0: { Chris@61: QSettings settings; jtkorhonen@0: settings.setValue("pos", pos()); jtkorhonen@0: settings.setValue("size", size()); Chris@284: settings.setValue("remoterepopath", m_remoteRepoPath); Chris@284: settings.setValue("workfolderpath", m_workFolderPath); Chris@284: settings.setValue("firststart", m_firstStart); Chris@284: //!!!settings.setValue("viewFileTypes", m_hgTabs -> getFileTypesBits()); jtkorhonen@0: } jtkorhonen@0: jtkorhonen@0: jtkorhonen@0: jtkorhonen@0: