| Chris@57 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| jtkorhonen@0 | 2 | 
| Chris@57 | 3 /* | 
| Chris@57 | 4     EasyMercurial | 
| Chris@57 | 5 | 
| Chris@98 | 6     Based on hgExplorer by Jari Korhonen | 
| Chris@57 | 7     Copyright (c) 2010 Jari Korhonen | 
| Chris@244 | 8     Copyright (c) 2011 Chris Cannam | 
| Chris@244 | 9     Copyright (c) 2011 Queen Mary, University of London | 
| Chris@57 | 10 | 
| Chris@57 | 11     This program is free software; you can redistribute it and/or | 
| Chris@57 | 12     modify it under the terms of the GNU General Public License as | 
| Chris@57 | 13     published by the Free Software Foundation; either version 2 of the | 
| Chris@57 | 14     License, or (at your option) any later version.  See the file | 
| Chris@57 | 15     COPYING included with this distribution for more information. | 
| Chris@57 | 16 */ | 
| Chris@50 | 17 | 
| jtkorhonen@0 | 18 #include <QStringList> | 
| jtkorhonen@0 | 19 #include <QDir> | 
| jtkorhonen@24 | 20 #include <QNetworkInterface> | 
| jtkorhonen@17 | 21 #include <QHostAddress> | 
| jtkorhonen@17 | 22 #include <QHostInfo> | 
| jtkorhonen@34 | 23 #include <QDesktopServices> | 
| Chris@50 | 24 #include <QStatusBar> | 
| Chris@50 | 25 #include <QMessageBox> | 
| Chris@50 | 26 #include <QMenuBar> | 
| Chris@50 | 27 #include <QApplication> | 
| Chris@50 | 28 #include <QToolBar> | 
| Chris@61 | 29 #include <QToolButton> | 
| Chris@50 | 30 #include <QSettings> | 
| Chris@90 | 31 #include <QInputDialog> | 
| Chris@125 | 32 #include <QRegExp> | 
| Chris@199 | 33 #include <QShortcut> | 
| Chris@237 | 34 #include <QUrl> | 
| Chris@238 | 35 #include <QTimer> | 
| jtkorhonen@0 | 36 | 
| Chris@53 | 37 #include "mainwindow.h" | 
| Chris@69 | 38 #include "multichoicedialog.h" | 
| Chris@64 | 39 #include "startupdialog.h" | 
| Chris@53 | 40 #include "colourset.h" | 
| Chris@62 | 41 #include "debug.h" | 
| Chris@74 | 42 #include "logparser.h" | 
| Chris@103 | 43 #include "confirmcommentdialog.h" | 
| Chris@125 | 44 #include "incomingdialog.h" | 
| Chris@175 | 45 #include "settingsdialog.h" | 
| Chris@275 | 46 #include "moreinformationdialog.h" | 
| Chris@331 | 47 #include "annotatedialog.h" | 
| Chris@229 | 48 #include "version.h" | 
| Chris@287 | 49 #include "workstatuswidget.h" | 
| Chris@53 | 50 | 
| jtkorhonen@0 | 51 | 
| Chris@172 | 52 MainWindow::MainWindow(QString myDirPath) : | 
| Chris@238 | 53     m_myDirPath(myDirPath), | 
| Chris@238 | 54     m_fsWatcherGeneralTimer(0), | 
| Chris@241 | 55     m_fsWatcherRestoreTimer(0), | 
| Chris@241 | 56     m_fsWatcherSuspended(false) | 
| jtkorhonen@0 | 57 { | 
| Chris@197 | 58     setWindowIcon(QIcon(":images/easyhg-icon.png")); | 
| Chris@197 | 59 | 
| jtkorhonen@0 | 60     QString wndTitle; | 
| jtkorhonen@0 | 61 | 
| Chris@284 | 62     m_showAllFiles = false; | 
| Chris@273 | 63 | 
| Chris@284 | 64     m_fsWatcher = 0; | 
| Chris@284 | 65     m_commitsSincePush = 0; | 
| Chris@284 | 66     m_shouldHgStat = true; | 
| Chris@90 | 67 | 
| jtkorhonen@0 | 68     createActions(); | 
| jtkorhonen@0 | 69     createMenus(); | 
| jtkorhonen@0 | 70     createToolBars(); | 
| jtkorhonen@0 | 71     createStatusBar(); | 
| jtkorhonen@0 | 72 | 
| Chris@284 | 73     m_runner = new HgRunner(m_myDirPath, this); | 
| Chris@284 | 74     connect(m_runner, SIGNAL(commandStarting(HgAction)), | 
| Chris@241 | 75             this, SLOT(commandStarting(HgAction))); | 
| Chris@284 | 76     connect(m_runner, SIGNAL(commandCompleted(HgAction, QString)), | 
| Chris@109 | 77             this, SLOT(commandCompleted(HgAction, QString))); | 
| Chris@284 | 78     connect(m_runner, SIGNAL(commandFailed(HgAction, QString)), | 
| Chris@109 | 79             this, SLOT(commandFailed(HgAction, QString))); | 
| Chris@284 | 80     statusBar()->addPermanentWidget(m_runner); | 
| jtkorhonen@0 | 81 | 
| Chris@61 | 82     setWindowTitle(tr("EasyMercurial")); | 
| jtkorhonen@0 | 83 | 
| Chris@284 | 84     m_remoteRepoPath = ""; | 
| Chris@284 | 85     m_workFolderPath = ""; | 
| jtkorhonen@0 | 86 | 
| jtkorhonen@0 | 87     readSettings(); | 
| jtkorhonen@0 | 88 | 
| Chris@284 | 89     m_justMerged = false; | 
| Chris@210 | 90 | 
| Chris@210 | 91     QWidget *central = new QWidget(this); | 
| Chris@210 | 92     setCentralWidget(central); | 
| Chris@210 | 93 | 
| Chris@210 | 94     QGridLayout *cl = new QGridLayout(central); | 
| Chris@287 | 95     int row = 0; | 
| Chris@210 | 96 | 
| Chris@210 | 97 #ifndef Q_OS_MAC | 
| Chris@210 | 98     cl->setMargin(0); | 
| Chris@210 | 99 #endif | 
| jtkorhonen@0 | 100 | 
| Chris@287 | 101     m_workStatus = new WorkStatusWidget(this); | 
| Chris@287 | 102     cl->addWidget(m_workStatus, row++, 0); | 
| Chris@287 | 103 | 
| Chris@287 | 104     m_hgTabs = new HgTabWidget(central, m_workFolderPath); | 
| Chris@287 | 105     connectTabsSignals(); | 
| Chris@287 | 106 | 
| Chris@287 | 107     cl->addWidget(m_hgTabs, row++, 0); | 
| Chris@287 | 108 | 
| Chris@284 | 109     connect(m_hgTabs, SIGNAL(selectionChanged()), | 
| Chris@95 | 110             this, SLOT(enableDisableActions())); | 
| Chris@284 | 111     connect(m_hgTabs, SIGNAL(showAllChanged(bool)), | 
| Chris@199 | 112             this, SLOT(showAllChanged(bool))); | 
| Chris@95 | 113 | 
| jtkorhonen@0 | 114     setUnifiedTitleAndToolBarOnMac(true); | 
| jtkorhonen@0 | 115     connectActions(); | 
| Chris@120 | 116     clearState(); | 
| jtkorhonen@0 | 117     enableDisableActions(); | 
| jtkorhonen@0 | 118 | 
| Chris@284 | 119     if (m_firstStart) { | 
| Chris@64 | 120         startupDialog(); | 
| jtkorhonen@0 | 121     } | 
| jtkorhonen@0 | 122 | 
| Chris@239 | 123     SettingsDialog::findDefaultLocations(m_myDirPath); | 
| Chris@112 | 124 | 
| Chris@64 | 125     ColourSet *cs = ColourSet::instance(); | 
| Chris@64 | 126     cs->clearDefaultNames(); | 
| Chris@64 | 127     cs->addDefaultName(""); | 
| Chris@153 | 128     cs->addDefaultName("default"); | 
| Chris@64 | 129     cs->addDefaultName(getUserInfo()); | 
| Chris@62 | 130 | 
| Chris@175 | 131     hgTest(); | 
| jtkorhonen@0 | 132 } | 
| jtkorhonen@0 | 133 | 
| jtkorhonen@0 | 134 | 
| jtkorhonen@0 | 135 void MainWindow::closeEvent(QCloseEvent *) | 
| jtkorhonen@0 | 136 { | 
| jtkorhonen@0 | 137     writeSettings(); | 
| Chris@284 | 138     delete m_fsWatcher; | 
| jtkorhonen@0 | 139 } | 
| jtkorhonen@0 | 140 | 
| jtkorhonen@0 | 141 | 
| Chris@64 | 142 QString MainWindow::getUserInfo() const | 
| Chris@64 | 143 { | 
| Chris@64 | 144     QSettings settings; | 
| Chris@64 | 145     settings.beginGroup("User Information"); | 
| Chris@64 | 146     QString name = settings.value("name", getUserRealName()).toString(); | 
| Chris@64 | 147     QString email = settings.value("email", "").toString(); | 
| Chris@64 | 148 | 
| Chris@64 | 149     QString identifier; | 
| Chris@64 | 150 | 
| Chris@64 | 151     if (email != "") { | 
| Chris@64 | 152 	identifier = QString("%1 <%2>").arg(name).arg(email); | 
| Chris@64 | 153     } else { | 
| Chris@64 | 154 	identifier = name; | 
| Chris@64 | 155     } | 
| Chris@64 | 156 | 
| Chris@64 | 157     return identifier; | 
| Chris@64 | 158 } | 
| Chris@64 | 159 | 
| jtkorhonen@0 | 160 void MainWindow::about() | 
| jtkorhonen@0 | 161 { | 
| Chris@97 | 162    QMessageBox::about(this, tr("About EasyMercurial"), | 
| Chris@229 | 163                       tr("<qt><h2>EasyMercurial v%1</h2>" | 
| Chris@228 | 164 #ifdef Q_OS_MAC | 
| Chris@228 | 165                          "<font size=-1>" | 
| Chris@228 | 166 #endif | 
| Chris@97 | 167                          "<p>EasyMercurial is a simple user interface for the " | 
| Chris@186 | 168                          "Mercurial</a> version control system.</p>" | 
| Chris@186 | 169                          "<h4>Credits and Copyright</h4>" | 
| Chris@186 | 170                          "<p>Development carried out by Chris Cannam for " | 
| Chris@186 | 171                          "SoundSoftware.ac.uk at the Centre for Digital Music, " | 
| Chris@186 | 172                          "Queen Mary, University of London.</p>" | 
| Chris@186 | 173                          "<p>EasyMercurial is based on HgExplorer by " | 
| Chris@186 | 174                          "Jari Korhonen, with thanks.</p>" | 
| Chris@186 | 175                          "<p style=\"margin-left: 2em;\">" | 
| Chris@244 | 176                          "Copyright © 2011 Queen Mary, University of London.<br>" | 
| Chris@186 | 177                          "Copyright © 2010 Jari Korhonen.<br>" | 
| Chris@244 | 178                          "Copyright © 2011 Chris Cannam." | 
| Chris@186 | 179                          "</p>" | 
| Chris@186 | 180                          "<p style=\"margin-left: 2em;\">" | 
| Chris@186 | 181                          "This program requires Mercurial, by Matt Mackall and others.<br>" | 
| Chris@186 | 182                          "This program uses Qt by Nokia.<br>" | 
| Chris@186 | 183                          "This program uses Nuvola icons by David Vignoni.<br>" | 
| Chris@186 | 184                          "This program may use KDiff3 by Joachim Eibl.<br>" | 
| Chris@186 | 185                          "This program may use PyQt by River Bank Computing.<br>" | 
| Chris@186 | 186                          "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others." | 
| Chris@186 | 187                          "</p>" | 
| Chris@186 | 188                          "<h4>License</h4>" | 
| Chris@186 | 189                          "<p>This program is free software; you can redistribute it and/or " | 
| Chris@97 | 190                          "modify it under the terms of the GNU General Public License as " | 
| Chris@97 | 191                          "published by the Free Software Foundation; either version 2 of the " | 
| Chris@97 | 192                          "License, or (at your option) any later version.  See the file " | 
| Chris@223 | 193                          "COPYING included with this distribution for more information.</p>" | 
| Chris@228 | 194 #ifdef Q_OS_MAC | 
| Chris@228 | 195                          "</font>" | 
| Chris@228 | 196 #endif | 
| Chris@229 | 197                           ).arg(EASYHG_VERSION)); | 
| jtkorhonen@0 | 198 } | 
| jtkorhonen@0 | 199 | 
| Chris@94 | 200 void MainWindow::clearSelections() | 
| Chris@94 | 201 { | 
| Chris@284 | 202     m_hgTabs->clearSelections(); | 
| Chris@94 | 203 } | 
| jtkorhonen@0 | 204 | 
| Chris@199 | 205 void MainWindow::showAllChanged(bool s) | 
| Chris@199 | 206 { | 
| Chris@284 | 207     m_showAllFiles = s; | 
| Chris@199 | 208     hgQueryPaths(); | 
| Chris@199 | 209 } | 
| Chris@199 | 210 | 
| Chris@120 | 211 void MainWindow::hgRefresh() | 
| Chris@120 | 212 { | 
| Chris@120 | 213     clearState(); | 
| Chris@120 | 214     hgQueryPaths(); | 
| Chris@120 | 215 } | 
| Chris@120 | 216 | 
| Chris@175 | 217 void MainWindow::hgTest() | 
| Chris@175 | 218 { | 
| Chris@175 | 219     QStringList params; | 
| Chris@207 | 220     //!!! should we test version output? Really we want at least 1.7.x | 
| Chris@207 | 221     //!!! for options such as merge --tool | 
| Chris@175 | 222     params << "--version"; | 
| Chris@284 | 223     m_runner->requestAction(HgAction(ACT_TEST_HG, m_myDirPath, params)); | 
| Chris@175 | 224 } | 
| Chris@175 | 225 | 
| Chris@200 | 226 void MainWindow::hgTestExtension() | 
| Chris@200 | 227 { | 
| Chris@200 | 228     QStringList params; | 
| Chris@200 | 229     params << "--version"; | 
| Chris@284 | 230     m_runner->requestAction(HgAction(ACT_TEST_HG_EXT, m_myDirPath, params)); | 
| Chris@200 | 231 } | 
| Chris@200 | 232 | 
| jtkorhonen@0 | 233 void MainWindow::hgStat() | 
| jtkorhonen@0 | 234 { | 
| Chris@109 | 235     QStringList params; | 
| Chris@199 | 236 | 
| Chris@284 | 237     if (m_showAllFiles) { | 
| Chris@199 | 238         params << "stat" << "-A"; | 
| Chris@199 | 239     } else { | 
| Chris@199 | 240         params << "stat" << "-ardum"; | 
| Chris@199 | 241     } | 
| Chris@153 | 242 | 
| Chris@284 | 243     m_lastStatOutput = ""; | 
| Chris@273 | 244 | 
| Chris@284 | 245     m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params)); | 
| jtkorhonen@0 | 246 } | 
| jtkorhonen@0 | 247 | 
| Chris@109 | 248 void MainWindow::hgQueryPaths() | 
| Chris@74 | 249 { | 
| Chris@210 | 250     // Quickest is to just read the file | 
| Chris@198 | 251 | 
| Chris@284 | 252     QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc"); | 
| Chris@198 | 253 | 
| Chris@210 | 254     QString path; | 
| Chris@210 | 255 | 
| Chris@198 | 256     if (hgrc.exists()) { | 
| Chris@198 | 257         QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat); | 
| Chris@198 | 258         s.beginGroup("paths"); | 
| Chris@210 | 259         path = s.value("default").toString(); | 
| Chris@210 | 260     } | 
| Chris@198 | 261 | 
| Chris@284 | 262     m_remoteRepoPath = path; | 
| Chris@198 | 263 | 
| Chris@210 | 264     // We have to do this here, because commandCompleted won't be called | 
| Chris@284 | 265     MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); | 
| Chris@284 | 266     MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); | 
| Chris@287 | 267     updateWorkFolderAndRepoNames(); | 
| Chris@210 | 268 | 
| Chris@210 | 269     hgQueryBranch(); | 
| Chris@210 | 270     return; | 
| Chris@210 | 271 | 
| Chris@210 | 272 /* The classic method! | 
| Chris@198 | 273 | 
| Chris@109 | 274     QStringList params; | 
| Chris@109 | 275     params << "paths"; | 
| Chris@284 | 276     m_runner->requestAction(HgAction(ACT_QUERY_PATHS, m_workFolderPath, params)); | 
| Chris@210 | 277 */ | 
| Chris@74 | 278 } | 
| Chris@74 | 279 | 
| Chris@109 | 280 void MainWindow::hgQueryBranch() | 
| Chris@106 | 281 { | 
| Chris@210 | 282     // Quickest is to just read the file | 
| Chris@198 | 283 | 
| Chris@284 | 284     QFile hgbr(m_workFolderPath + "/.hg/branch"); | 
| Chris@198 | 285 | 
| Chris@210 | 286     QString br = "default"; | 
| Chris@210 | 287 | 
| Chris@198 | 288     if (hgbr.exists() && hgbr.open(QFile::ReadOnly)) { | 
| Chris@210 | 289         QByteArray ba = hgbr.readLine(); | 
| Chris@210 | 290         br = QString::fromUtf8(ba).trimmed(); | 
| Chris@210 | 291     } | 
| Chris@210 | 292 | 
| Chris@284 | 293     m_currentBranch = br; | 
| Chris@210 | 294 | 
| Chris@210 | 295     // We have to do this here, because commandCompleted won't be called | 
| Chris@210 | 296     hgStat(); | 
| Chris@210 | 297     return; | 
| Chris@198 | 298 | 
| Chris@210 | 299 /* The classic method! | 
| Chris@198 | 300 | 
| Chris@109 | 301     QStringList params; | 
| Chris@109 | 302     params << "branch"; | 
| Chris@284 | 303     m_runner->requestAction(HgAction(ACT_QUERY_BRANCH, m_workFolderPath, params)); | 
| Chris@210 | 304 */ | 
| Chris@106 | 305 } | 
| Chris@106 | 306 | 
| Chris@109 | 307 void MainWindow::hgQueryHeads() | 
| jtkorhonen@0 | 308 { | 
| Chris@109 | 309     QStringList params; | 
| Chris@137 | 310     // On empty repos, "hg heads" will fail -- we don't care about | 
| Chris@137 | 311     // that.  Use --closed option so as to include closed branches; | 
| Chris@137 | 312     // otherwise we'll be stuck if the user updates into one, and our | 
| Chris@137 | 313     // incremental log will end up with spurious stuff in it because | 
| Chris@137 | 314     // we won't be pruning at the ends of closed branches | 
| Chris@137 | 315     params << "heads" << "--closed"; | 
| Chris@284 | 316     m_runner->requestAction(HgAction(ACT_QUERY_HEADS, m_workFolderPath, params)); | 
| jtkorhonen@0 | 317 } | 
| jtkorhonen@0 | 318 | 
| jtkorhonen@0 | 319 void MainWindow::hgLog() | 
| jtkorhonen@0 | 320 { | 
| Chris@109 | 321     QStringList params; | 
| Chris@109 | 322     params << "log"; | 
| Chris@109 | 323     params << "--template"; | 
| Chris@125 | 324     params << Changeset::getLogTemplate(); | 
| Chris@109 | 325 | 
| Chris@284 | 326     m_runner->requestAction(HgAction(ACT_LOG, m_workFolderPath, params)); | 
| Chris@109 | 327 } | 
| Chris@109 | 328 | 
| Chris@150 | 329 void MainWindow::hgLogIncremental(QStringList prune) | 
| Chris@120 | 330 { | 
| Chris@305 | 331     // Sometimes we can be called with prune empty -- it represents | 
| Chris@305 | 332     // the current heads, but if we have none already and for some | 
| Chris@305 | 333     // reason are being prompted for an incremental update, we may run | 
| Chris@305 | 334     // into trouble.  In that case, make this a full log instead | 
| Chris@305 | 335 | 
| Chris@305 | 336     if (prune.empty()) { | 
| Chris@305 | 337         hgLog(); | 
| Chris@305 | 338         return; | 
| Chris@305 | 339     } | 
| Chris@305 | 340 | 
| Chris@120 | 341     QStringList params; | 
| Chris@120 | 342     params << "log"; | 
| Chris@120 | 343 | 
| Chris@150 | 344     foreach (QString p, prune) { | 
| Chris@153 | 345         params << "--prune" << Changeset::hashOf(p); | 
| Chris@120 | 346     } | 
| Chris@120 | 347 | 
| Chris@120 | 348     params << "--template"; | 
| Chris@125 | 349     params << Changeset::getLogTemplate(); | 
| Chris@120 | 350 | 
| Chris@284 | 351     m_runner->requestAction(HgAction(ACT_LOG_INCREMENTAL, m_workFolderPath, params)); | 
| Chris@120 | 352 } | 
| Chris@109 | 353 | 
| Chris@109 | 354 void MainWindow::hgQueryParents() | 
| Chris@109 | 355 { | 
| Chris@109 | 356     QStringList params; | 
| Chris@109 | 357     params << "parents"; | 
| Chris@284 | 358     m_runner->requestAction(HgAction(ACT_QUERY_PARENTS, m_workFolderPath, params)); | 
| Chris@109 | 359 } | 
| Chris@109 | 360 | 
| Chris@326 | 361 void MainWindow::hgAnnotateFiles(QStringList files) | 
| Chris@326 | 362 { | 
| Chris@326 | 363     QStringList params; | 
| Chris@326 | 364 | 
| Chris@326 | 365     if (!files.isEmpty()) { | 
| Chris@332 | 366         params << "annotate" << "-udqc" << "--" << files; | 
| Chris@326 | 367         m_runner->requestAction(HgAction(ACT_ANNOTATE, m_workFolderPath, params)); | 
| Chris@326 | 368     } | 
| Chris@326 | 369 } | 
| Chris@326 | 370 | 
| Chris@109 | 371 void MainWindow::hgResolveList() | 
| Chris@109 | 372 { | 
| Chris@109 | 373     QStringList params; | 
| jtkorhonen@0 | 374 | 
| Chris@109 | 375     params << "resolve" << "--list"; | 
| Chris@284 | 376     m_runner->requestAction(HgAction(ACT_RESOLVE_LIST, m_workFolderPath, params)); | 
| Chris@109 | 377 } | 
| Chris@109 | 378 | 
| Chris@109 | 379 void MainWindow::hgAdd() | 
| jtkorhonen@0 | 380 { | 
| Chris@109 | 381     // hgExplorer permitted adding "all" files -- I'm not sure | 
| Chris@109 | 382     // that one is a good idea, let's require the user to select | 
| jtkorhonen@0 | 383 | 
| Chris@326 | 384     hgAddFiles(m_hgTabs->getSelectedAddableFiles()); | 
| Chris@326 | 385 } | 
| Chris@326 | 386 | 
| Chris@326 | 387 void MainWindow::hgAddFiles(QStringList files) | 
| Chris@326 | 388 { | 
| Chris@326 | 389     QStringList params; | 
| Chris@109 | 390 | 
| Chris@109 | 391     if (!files.empty()) { | 
| Chris@109 | 392         params << "add" << "--" << files; | 
| Chris@284 | 393         m_runner->requestAction(HgAction(ACT_ADD, m_workFolderPath, params)); | 
| jtkorhonen@0 | 394     } | 
| jtkorhonen@0 | 395 } | 
| jtkorhonen@0 | 396 | 
| Chris@98 | 397 void MainWindow::hgRemove() | 
| Chris@98 | 398 { | 
| Chris@326 | 399     hgRemoveFiles(m_hgTabs->getSelectedRemovableFiles()); | 
| Chris@326 | 400 } | 
| Chris@326 | 401 | 
| Chris@326 | 402 void MainWindow::hgRemoveFiles(QStringList files) | 
| Chris@326 | 403 { | 
| Chris@109 | 404     QStringList params; | 
| Chris@98 | 405 | 
| Chris@109 | 406     if (!files.empty()) { | 
| Chris@109 | 407         params << "remove" << "--after" << "--force" << "--" << files; | 
| Chris@284 | 408         m_runner->requestAction(HgAction(ACT_REMOVE, m_workFolderPath, params)); | 
| Chris@109 | 409     } | 
| jtkorhonen@0 | 410 } | 
| jtkorhonen@0 | 411 | 
| jtkorhonen@0 | 412 void MainWindow::hgCommit() | 
| jtkorhonen@0 | 413 { | 
| Chris@326 | 414     hgCommitFiles(QStringList()); | 
| Chris@326 | 415 } | 
| Chris@326 | 416 | 
| Chris@326 | 417 void MainWindow::hgCommitFiles(QStringList files) | 
| Chris@326 | 418 { | 
| Chris@109 | 419     QStringList params; | 
| Chris@109 | 420     QString comment; | 
| Chris@94 | 421 | 
| Chris@284 | 422     if (m_justMerged) { | 
| Chris@284 | 423         comment = m_mergeCommitComment; | 
| Chris@157 | 424     } | 
| Chris@157 | 425 | 
| Chris@284 | 426     QStringList allFiles = m_hgTabs->getAllCommittableFiles(); | 
| Chris@127 | 427     QStringList reportFiles = files; | 
| Chris@237 | 428     if (reportFiles.empty()) { | 
| Chris@237 | 429         reportFiles = allFiles; | 
| Chris@237 | 430     } | 
| Chris@103 | 431 | 
| Chris@237 | 432     QString subsetNote; | 
| Chris@237 | 433     if (reportFiles != allFiles) { | 
| Chris@237 | 434         subsetNote = tr("<p><b>Note:</b> you are committing only the files you have selected, not all of the files that have been changed!"); | 
| Chris@237 | 435     } | 
| Chris@237 | 436 | 
| Chris@155 | 437     QString cf(tr("Commit files")); | 
| Chris@155 | 438 | 
| Chris@311 | 439     QString branchText; | 
| Chris@311 | 440     if (m_currentBranch == "" || m_currentBranch == "default") { | 
| Chris@311 | 441         branchText = tr("the default branch"); | 
| Chris@311 | 442     } else { | 
| Chris@311 | 443         branchText = tr("branch \"%1\"").arg(m_currentBranch); | 
| Chris@311 | 444     } | 
| Chris@311 | 445 | 
| Chris@109 | 446     if (ConfirmCommentDialog::confirmAndGetLongComment | 
| Chris@109 | 447         (this, | 
| Chris@155 | 448          cf, | 
| Chris@237 | 449          tr("<h3>%1</h3><p>%2%3").arg(cf) | 
| Chris@313 | 450          .arg(tr("You are about to commit the following files to %1:").arg(branchText)) | 
| Chris@237 | 451          .arg(subsetNote), | 
| Chris@237 | 452          tr("<h3>%1</h3><p>%2%3").arg(cf) | 
| Chris@311 | 453          .arg(tr("You are about to commit %n file(s) to %1.", "", reportFiles.size()).arg(branchText)) | 
| Chris@237 | 454          .arg(subsetNote), | 
| Chris@127 | 455          reportFiles, | 
| Chris@193 | 456          comment, | 
| Chris@193 | 457          tr("Commit"))) { | 
| Chris@103 | 458 | 
| Chris@284 | 459         if (!m_justMerged && !files.empty()) { | 
| Chris@157 | 460             // User wants to commit selected file(s) (and this is not | 
| Chris@157 | 461             // merge commit, which would fail if we selected files) | 
| Chris@157 | 462             params << "commit" << "--message" << comment | 
| Chris@157 | 463                    << "--user" << getUserInfo() << "--" << files; | 
| Chris@109 | 464         } else { | 
| Chris@109 | 465             // Commit all changes | 
| Chris@157 | 466             params << "commit" << "--message" << comment | 
| Chris@157 | 467                    << "--user" << getUserInfo(); | 
| jtkorhonen@0 | 468         } | 
| Chris@109 | 469 | 
| Chris@284 | 470         m_runner->requestAction(HgAction(ACT_COMMIT, m_workFolderPath, params)); | 
| Chris@284 | 471         m_mergeCommitComment = ""; | 
| jtkorhonen@0 | 472     } | 
| jtkorhonen@0 | 473 } | 
| jtkorhonen@0 | 474 | 
| jtkorhonen@34 | 475 QString MainWindow::filterTag(QString tag) | 
| jtkorhonen@34 | 476 { | 
| Chris@278 | 477     for(int i = 0; i < tag.size(); i++) { | 
| Chris@278 | 478         if (tag[i].isLower() || tag[i].isUpper() || | 
| Chris@278 | 479             tag[i].isDigit() || (tag[i] == QChar('.'))) { | 
| jtkorhonen@34 | 480             //ok | 
| Chris@278 | 481         } else { | 
| jtkorhonen@34 | 482             tag[i] = QChar('_'); | 
| jtkorhonen@34 | 483         } | 
| jtkorhonen@34 | 484     } | 
| jtkorhonen@34 | 485     return tag; | 
| jtkorhonen@34 | 486 } | 
| jtkorhonen@34 | 487 | 
| jtkorhonen@34 | 488 | 
| Chris@311 | 489 void MainWindow::hgNewBranch() | 
| Chris@278 | 490 { | 
| Chris@278 | 491     QStringList params; | 
| Chris@278 | 492     QString branch; | 
| Chris@278 | 493 | 
| Chris@278 | 494     if (ConfirmCommentDialog::confirmAndGetShortComment | 
| Chris@278 | 495         (this, | 
| Chris@278 | 496          tr("New Branch"), | 
| Chris@278 | 497          tr("Enter new branch name:"), | 
| Chris@278 | 498          branch, | 
| Chris@278 | 499          tr("Start Branch"))) { | 
| Chris@278 | 500         if (!branch.isEmpty()) {//!!! do something better if it is empty | 
| Chris@278 | 501 | 
| Chris@278 | 502             params << "branch" << filterTag(branch); | 
| Chris@307 | 503             m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params)); | 
| Chris@278 | 504         } | 
| Chris@278 | 505     } | 
| Chris@278 | 506 } | 
| Chris@278 | 507 | 
| Chris@311 | 508 void MainWindow::hgNoBranch() | 
| Chris@311 | 509 { | 
| Chris@311 | 510     if (m_currentParents.empty()) return; | 
| Chris@311 | 511 | 
| Chris@311 | 512     QString parentBranch = m_currentParents[0]->branch(); | 
| Chris@311 | 513     if (parentBranch == "") parentBranch = "default"; | 
| Chris@311 | 514 | 
| Chris@311 | 515     QStringList params; | 
| Chris@311 | 516     params << "branch" << parentBranch; | 
| Chris@311 | 517     m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params)); | 
| Chris@311 | 518 } | 
| Chris@311 | 519 | 
| Chris@164 | 520 void MainWindow::hgTag(QString id) | 
| jtkorhonen@34 | 521 { | 
| Chris@109 | 522     QStringList params; | 
| Chris@109 | 523     QString tag; | 
| jtkorhonen@34 | 524 | 
| Chris@109 | 525     if (ConfirmCommentDialog::confirmAndGetShortComment | 
| Chris@109 | 526         (this, | 
| Chris@109 | 527          tr("Tag"), | 
| Chris@109 | 528          tr("Enter tag:"), | 
| Chris@193 | 529          tag, | 
| Chris@193 | 530          tr("Add Tag"))) { | 
| Chris@164 | 531         if (!tag.isEmpty()) {//!!! do something better if it is empty | 
| Chris@164 | 532 | 
| Chris@164 | 533             params << "tag" << "--user" << getUserInfo(); | 
| Chris@164 | 534             params << "--rev" << Changeset::hashOf(id) << filterTag(tag); | 
| Chris@109 | 535 | 
| Chris@284 | 536             m_runner->requestAction(HgAction(ACT_TAG, m_workFolderPath, params)); | 
| jtkorhonen@34 | 537         } | 
| jtkorhonen@34 | 538     } | 
| jtkorhonen@34 | 539 } | 
| jtkorhonen@34 | 540 | 
| jtkorhonen@34 | 541 void MainWindow::hgIgnore() | 
| jtkorhonen@34 | 542 { | 
| Chris@109 | 543     QString hgIgnorePath; | 
| Chris@109 | 544     QStringList params; | 
| Chris@178 | 545 | 
| Chris@284 | 546     hgIgnorePath = m_workFolderPath; | 
| Chris@178 | 547     hgIgnorePath += "/.hgignore"; | 
| Chris@249 | 548 | 
| Chris@284 | 549     if (!QDir(m_workFolderPath).exists()) return; | 
| Chris@249 | 550     QFile f(hgIgnorePath); | 
| Chris@249 | 551     if (!f.exists()) { | 
| Chris@249 | 552         f.open(QFile::WriteOnly); | 
| Chris@249 | 553         QTextStream *ts = new QTextStream(&f); | 
| Chris@249 | 554         *ts << "syntax: glob\n"; | 
| Chris@249 | 555         delete ts; | 
| Chris@249 | 556         f.close(); | 
| Chris@249 | 557     } | 
| Chris@109 | 558 | 
| Chris@109 | 559     params << hgIgnorePath; | 
| Chris@179 | 560 | 
| Chris@239 | 561     QString editor = getEditorBinaryName(); | 
| Chris@112 | 562 | 
| Chris@179 | 563     if (editor == "") { | 
| Chris@179 | 564         DEBUG << "Failed to find a text editor" << endl; | 
| Chris@179 | 565         //!!! visible error! | 
| Chris@179 | 566         return; | 
| Chris@179 | 567     } | 
| Chris@179 | 568 | 
| Chris@284 | 569     HgAction action(ACT_HG_IGNORE, m_workFolderPath, params); | 
| Chris@179 | 570     action.executable = editor; | 
| Chris@179 | 571 | 
| Chris@284 | 572     m_runner->requestAction(action); | 
| Chris@179 | 573 } | 
| Chris@179 | 574 | 
| Chris@326 | 575 void MainWindow::hgIgnoreFiles(QStringList files) | 
| Chris@326 | 576 { | 
| Chris@326 | 577     //!!! not implemented yet | 
| Chris@326 | 578     DEBUG << "MainWindow::hgIgnoreFiles: Not implemented" << endl; | 
| Chris@326 | 579 } | 
| Chris@326 | 580 | 
| Chris@326 | 581 void MainWindow::hgUnIgnoreFiles(QStringList files) | 
| Chris@326 | 582 { | 
| Chris@326 | 583     //!!! not implemented yet | 
| Chris@326 | 584     DEBUG << "MainWindow::hgUnIgnoreFiles: Not implemented" << endl; | 
| Chris@326 | 585 } | 
| Chris@326 | 586 | 
| Chris@239 | 587 QString MainWindow::getDiffBinaryName() | 
| Chris@179 | 588 { | 
| Chris@179 | 589     QSettings settings; | 
| Chris@179 | 590     settings.beginGroup("Locations"); | 
| Chris@239 | 591     return settings.value("extdiffbinary", "").toString(); | 
| Chris@179 | 592 } | 
| Chris@179 | 593 | 
| Chris@239 | 594 QString MainWindow::getMergeBinaryName() | 
| Chris@179 | 595 { | 
| Chris@179 | 596     QSettings settings; | 
| Chris@179 | 597     settings.beginGroup("Locations"); | 
| Chris@239 | 598     return settings.value("mergebinary", "").toString(); | 
| Chris@179 | 599 } | 
| Chris@179 | 600 | 
| Chris@239 | 601 QString MainWindow::getEditorBinaryName() | 
| Chris@179 | 602 { | 
| Chris@178 | 603     QSettings settings; | 
| Chris@178 | 604     settings.beginGroup("Locations"); | 
| Chris@239 | 605     return settings.value("editorbinary", "").toString(); | 
| Chris@163 | 606 } | 
| Chris@163 | 607 | 
| Chris@168 | 608 void MainWindow::hgShowSummary() | 
| Chris@168 | 609 { | 
| Chris@168 | 610     QStringList params; | 
| Chris@168 | 611 | 
| Chris@168 | 612     params << "diff" << "--stat"; | 
| Chris@168 | 613 | 
| Chris@288 | 614     m_runner->requestAction(HgAction(ACT_UNCOMMITTED_SUMMARY, m_workFolderPath, params)); | 
| Chris@168 | 615 } | 
| Chris@168 | 616 | 
| jtkorhonen@0 | 617 void MainWindow::hgFolderDiff() | 
| jtkorhonen@0 | 618 { | 
| Chris@326 | 619     hgDiffFiles(QStringList()); | 
| Chris@326 | 620 } | 
| Chris@326 | 621 | 
| Chris@326 | 622 void MainWindow::hgDiffFiles(QStringList files) | 
| Chris@326 | 623 { | 
| Chris@239 | 624     QString diff = getDiffBinaryName(); | 
| Chris@179 | 625     if (diff == "") return; | 
| Chris@112 | 626 | 
| Chris@109 | 627     QStringList params; | 
| jtkorhonen@0 | 628 | 
| Chris@112 | 629     // Diff parent against working folder (folder diff) | 
| Chris@112 | 630 | 
| Chris@148 | 631     params << "--config" << "extensions.extdiff=" << "extdiff"; | 
| Chris@179 | 632     params << "--program" << diff; | 
| Chris@109 | 633 | 
| Chris@331 | 634     params << "--" << files; // may be none: whole dir | 
| Chris@273 | 635 | 
| Chris@284 | 636     m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params)); | 
| jtkorhonen@0 | 637 } | 
| jtkorhonen@0 | 638 | 
| Chris@148 | 639 void MainWindow::hgDiffToCurrent(QString id) | 
| jtkorhonen@0 | 640 { | 
| Chris@239 | 641     QString diff = getDiffBinaryName(); | 
| Chris@179 | 642     if (diff == "") return; | 
| Chris@163 | 643 | 
| Chris@148 | 644     QStringList params; | 
| jtkorhonen@0 | 645 | 
| Chris@148 | 646     // Diff given revision against working folder | 
| jtkorhonen@0 | 647 | 
| Chris@148 | 648     params << "--config" << "extensions.extdiff=" << "extdiff"; | 
| Chris@179 | 649     params << "--program" << diff; | 
| Chris@153 | 650     params << "--rev" << Changeset::hashOf(id); | 
| Chris@148 | 651 | 
| Chris@284 | 652     m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params)); | 
| jtkorhonen@0 | 653 } | 
| jtkorhonen@0 | 654 | 
| Chris@148 | 655 void MainWindow::hgDiffToParent(QString child, QString parent) | 
| Chris@148 | 656 { | 
| Chris@239 | 657     QString diff = getDiffBinaryName(); | 
| Chris@179 | 658     if (diff == "") return; | 
| Chris@163 | 659 | 
| Chris@148 | 660     QStringList params; | 
| Chris@148 | 661 | 
| Chris@281 | 662     // Diff given revision against parent revision | 
| Chris@148 | 663 | 
| Chris@148 | 664     params << "--config" << "extensions.extdiff=" << "extdiff"; | 
| Chris@179 | 665     params << "--program" << diff; | 
| Chris@153 | 666     params << "--rev" << Changeset::hashOf(parent) | 
| Chris@153 | 667            << "--rev" << Changeset::hashOf(child); | 
| Chris@148 | 668 | 
| Chris@284 | 669     m_runner->requestAction(HgAction(ACT_CHGSETDIFF, m_workFolderPath, params)); | 
| Chris@273 | 670 } | 
| Chris@326 | 671 | 
| Chris@273 | 672 | 
| Chris@289 | 673 void MainWindow::hgShowSummaryFor(Changeset *cs) | 
| Chris@288 | 674 { | 
| Chris@288 | 675     QStringList params; | 
| Chris@288 | 676 | 
| Chris@289 | 677     // This will pick a default parent if there is more than one | 
| Chris@289 | 678     // (whereas with diff we need to supply one).  But it does need a | 
| Chris@289 | 679     // bit more parsing | 
| Chris@289 | 680     params << "log" << "--stat" << "--rev" << Changeset::hashOf(cs->id()); | 
| Chris@289 | 681 | 
| Chris@289 | 682     m_runner->requestAction(HgAction(ACT_DIFF_SUMMARY, m_workFolderPath, | 
| Chris@289 | 683                                      params, cs)); | 
| Chris@148 | 684 } | 
| Chris@148 | 685 | 
| jtkorhonen@0 | 686 | 
| jtkorhonen@0 | 687 void MainWindow::hgUpdate() | 
| jtkorhonen@0 | 688 { | 
| Chris@109 | 689     QStringList params; | 
| jtkorhonen@0 | 690 | 
| Chris@109 | 691     params << "update"; | 
| Chris@109 | 692 | 
| Chris@284 | 693     m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params)); | 
| jtkorhonen@0 | 694 } | 
| jtkorhonen@0 | 695 | 
| jtkorhonen@0 | 696 | 
| Chris@148 | 697 void MainWindow::hgUpdateToRev(QString id) | 
| jtkorhonen@0 | 698 { | 
| Chris@148 | 699     QStringList params; | 
| jtkorhonen@0 | 700 | 
| Chris@153 | 701     params << "update" << "--rev" << Changeset::hashOf(id) << "--check"; | 
| jtkorhonen@0 | 702 | 
| Chris@284 | 703     m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params)); | 
| jtkorhonen@0 | 704 } | 
| jtkorhonen@0 | 705 | 
| jtkorhonen@0 | 706 | 
| jtkorhonen@0 | 707 void MainWindow::hgRevert() | 
| jtkorhonen@0 | 708 { | 
| Chris@326 | 709     hgRevertFiles(QStringList()); | 
| Chris@326 | 710 } | 
| Chris@326 | 711 | 
| Chris@326 | 712 void MainWindow::hgRevertFiles(QStringList files) | 
| Chris@326 | 713 { | 
| Chris@109 | 714     QStringList params; | 
| Chris@109 | 715     QString comment; | 
| Chris@237 | 716     bool all = false; | 
| Chris@98 | 717 | 
| Chris@284 | 718     QStringList allFiles = m_hgTabs->getAllRevertableFiles(); | 
| Chris@237 | 719     if (files.empty() || files == allFiles) { | 
| Chris@237 | 720         files = allFiles; | 
| Chris@237 | 721         all = true; | 
| Chris@237 | 722     } | 
| Chris@237 | 723 | 
| Chris@237 | 724     QString subsetNote; | 
| Chris@237 | 725     if (!all) { | 
| Chris@237 | 726         subsetNote = tr("<p><b>Note:</b> you are reverting only the files you have selected, not all of the files that have been changed!"); | 
| Chris@237 | 727     } | 
| Chris@155 | 728 | 
| Chris@155 | 729     QString rf(tr("Revert files")); | 
| Chris@237 | 730 | 
| Chris@237 | 731     // Set up params before asking for confirmation, because there is | 
| Chris@237 | 732     // a failure case here that we would need to report on early | 
| Chris@237 | 733 | 
| Chris@284 | 734     DEBUG << "hgRevert: m_justMerged = " << m_justMerged << ", m_mergeTargetRevision = " << m_mergeTargetRevision << endl; | 
| Chris@273 | 735 | 
| Chris@284 | 736     if (m_justMerged) { | 
| Chris@237 | 737 | 
| Chris@237 | 738         // This is a little fiddly.  The proper way to "revert" the | 
| Chris@237 | 739         // whole of an uncommitted merge is with "hg update --clean ." | 
| Chris@237 | 740         // But if the user has selected only some files, we're sort of | 
| Chris@237 | 741         // promising to revert only those, which means we need to | 
| Chris@237 | 742         // specify which parent to revert to.  We can only do that if | 
| Chris@237 | 743         // we have a record of it, which we do if you just did the | 
| Chris@237 | 744         // merge from within easyhg but don't if you've exited and | 
| Chris@237 | 745         // restarted, or changed repository, since then.  Hmmm. | 
| Chris@237 | 746 | 
| Chris@237 | 747         if (all) { | 
| Chris@237 | 748             params << "update" << "--clean" << "."; | 
| Chris@237 | 749         } else { | 
| Chris@284 | 750             if (m_mergeTargetRevision != "") { | 
| Chris@237 | 751                 params << "revert" << "--rev" | 
| Chris@284 | 752                        << Changeset::hashOf(m_mergeTargetRevision) | 
| Chris@237 | 753                        << "--" << files; | 
| Chris@237 | 754             } else { | 
| Chris@237 | 755                 QMessageBox::information | 
| Chris@237 | 756                     (this, tr("Unable to revert"), | 
| Chris@237 | 757                      tr("<qt><b>Sorry, unable to revert these files</b><br><br>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.<br><br>This is a limitation of EasyMercurial.  Consider reverting all files, or using hg revert with a specific revision at the command-line instead.</qt>")); | 
| Chris@237 | 758                 return; | 
| Chris@237 | 759             } | 
| Chris@237 | 760         } | 
| Chris@237 | 761     } else { | 
| Chris@237 | 762         params << "revert" << "--" << files; | 
| Chris@237 | 763     } | 
| Chris@237 | 764 | 
| Chris@109 | 765     if (ConfirmCommentDialog::confirmDangerousFilesAction | 
| Chris@109 | 766         (this, | 
| Chris@155 | 767          rf, | 
| Chris@237 | 768          tr("<h3>%1</h3><p>%2%3").arg(rf) | 
| Chris@237 | 769          .arg(tr("You are about to <b>revert</b> the following files to their previous committed state.<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed.")) | 
| Chris@237 | 770          .arg(subsetNote), | 
| Chris@237 | 771          tr("<h3>%1</h3><p>%2%3").arg(rf) | 
| Chris@237 | 772          .arg(tr("You are about to <b>revert</b> %n file(s).<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed.", "", files.size())) | 
| Chris@237 | 773          .arg(subsetNote), | 
| Chris@193 | 774          files, | 
| Chris@193 | 775          tr("Revert"))) { | 
| Chris@163 | 776 | 
| Chris@284 | 777         m_lastRevertedFiles = files; | 
| Chris@109 | 778 | 
| Chris@284 | 779         m_runner->requestAction(HgAction(ACT_REVERT, m_workFolderPath, params)); | 
| jtkorhonen@0 | 780     } | 
| jtkorhonen@0 | 781 } | 
| jtkorhonen@0 | 782 | 
| Chris@163 | 783 | 
| Chris@326 | 784 void MainWindow::hgMarkFilesResolved(QStringList files) | 
| Chris@163 | 785 { | 
| Chris@163 | 786     QStringList params; | 
| Chris@163 | 787 | 
| Chris@163 | 788     params << "resolve" << "--mark"; | 
| Chris@163 | 789 | 
| Chris@163 | 790     if (files.empty()) { | 
| Chris@163 | 791         params << "--all"; | 
| Chris@163 | 792     } else { | 
| Chris@164 | 793         params << "--" << files; | 
| Chris@163 | 794     } | 
| Chris@163 | 795 | 
| Chris@284 | 796     m_runner->requestAction(HgAction(ACT_RESOLVE_MARK, m_workFolderPath, params)); | 
| Chris@163 | 797 } | 
| Chris@163 | 798 | 
| Chris@163 | 799 | 
| Chris@326 | 800 void MainWindow::hgRedoMerge() | 
| Chris@326 | 801 { | 
| Chris@326 | 802     hgRedoFileMerges(QStringList()); | 
| Chris@326 | 803 } | 
| Chris@326 | 804 | 
| Chris@326 | 805 | 
| Chris@326 | 806 void MainWindow::hgRedoFileMerges(QStringList files) | 
| jtkorhonen@33 | 807 { | 
| Chris@109 | 808     QStringList params; | 
| jtkorhonen@33 | 809 | 
| Chris@163 | 810     params << "resolve"; | 
| Chris@163 | 811 | 
| Chris@239 | 812     QString merge = getMergeBinaryName(); | 
| Chris@179 | 813     if (merge != "") { | 
| Chris@179 | 814         params << "--tool" << merge; | 
| Chris@163 | 815     } | 
| Chris@163 | 816 | 
| Chris@163 | 817     if (files.empty()) { | 
| Chris@163 | 818         params << "--all"; | 
| Chris@163 | 819     } else { | 
| Chris@164 | 820         params << "--" << files; | 
| Chris@163 | 821     } | 
| Chris@163 | 822 | 
| Chris@284 | 823     if (m_currentParents.size() == 1) { | 
| Chris@284 | 824         m_mergeTargetRevision = m_currentParents[0]->id(); | 
| Chris@237 | 825     } | 
| Chris@237 | 826 | 
| Chris@284 | 827     m_runner->requestAction(HgAction(ACT_RETRY_MERGE, m_workFolderPath, params)); | 
| Chris@273 | 828 | 
| Chris@284 | 829     m_mergeCommitComment = tr("Merge"); | 
| jtkorhonen@33 | 830 } | 
| Chris@326 | 831 | 
| jtkorhonen@33 | 832 | 
| jtkorhonen@0 | 833 void MainWindow::hgMerge() | 
| jtkorhonen@0 | 834 { | 
| Chris@284 | 835     if (m_hgTabs->canResolve()) { | 
| Chris@326 | 836         hgRedoMerge(); | 
| Chris@163 | 837         return; | 
| Chris@163 | 838     } | 
| Chris@163 | 839 | 
| Chris@109 | 840     QStringList params; | 
| jtkorhonen@0 | 841 | 
| Chris@109 | 842     params << "merge"; | 
| Chris@163 | 843 | 
| Chris@239 | 844     QString merge = getMergeBinaryName(); | 
| Chris@179 | 845     if (merge != "") { | 
| Chris@179 | 846         params << "--tool" << merge; | 
| Chris@163 | 847     } | 
| Chris@163 | 848 | 
| Chris@284 | 849     if (m_currentParents.size() == 1) { | 
| Chris@284 | 850         m_mergeTargetRevision = m_currentParents[0]->id(); | 
| Chris@163 | 851     } | 
| Chris@163 | 852 | 
| Chris@284 | 853     m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params)); | 
| Chris@273 | 854 | 
| Chris@284 | 855     m_mergeCommitComment = tr("Merge"); | 
| jtkorhonen@0 | 856 } | 
| jtkorhonen@0 | 857 | 
| jtkorhonen@0 | 858 | 
| Chris@148 | 859 void MainWindow::hgMergeFrom(QString id) | 
| Chris@148 | 860 { | 
| Chris@148 | 861     QStringList params; | 
| Chris@148 | 862 | 
| Chris@148 | 863     params << "merge"; | 
| Chris@153 | 864     params << "--rev" << Changeset::hashOf(id); | 
| Chris@163 | 865 | 
| Chris@239 | 866     QString merge = getMergeBinaryName(); | 
| Chris@179 | 867     if (merge != "") { | 
| Chris@179 | 868         params << "--tool" << merge; | 
| Chris@163 | 869     } | 
| Chris@148 | 870 | 
| Chris@284 | 871     if (m_currentParents.size() == 1) { | 
| Chris@284 | 872         m_mergeTargetRevision = m_currentParents[0]->id(); | 
| Chris@237 | 873     } | 
| Chris@237 | 874 | 
| Chris@284 | 875     m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params)); | 
| Chris@273 | 876 | 
| Chris@284 | 877     m_mergeCommitComment = ""; | 
| Chris@273 | 878 | 
| Chris@284 | 879     foreach (Changeset *cs, m_currentHeads) { | 
| Chris@284 | 880         if (cs->id() == id && !cs->isOnBranch(m_currentBranch)) { | 
| Chris@157 | 881             if (cs->branch() == "" || cs->branch() == "default") { | 
| Chris@284 | 882                 m_mergeCommitComment = tr("Merge from the default branch"); | 
| Chris@157 | 883             } else { | 
| Chris@284 | 884                 m_mergeCommitComment = tr("Merge from branch \"%1\"").arg(cs->branch()); | 
| Chris@157 | 885             } | 
| Chris@157 | 886         } | 
| Chris@157 | 887     } | 
| Chris@157 | 888 | 
| Chris@284 | 889     if (m_mergeCommitComment == "") { | 
| Chris@284 | 890         m_mergeCommitComment = tr("Merge from %1").arg(id); | 
| Chris@157 | 891     } | 
| Chris@148 | 892 } | 
| Chris@148 | 893 | 
| Chris@148 | 894 | 
| jtkorhonen@0 | 895 void MainWindow::hgCloneFromRemote() | 
| jtkorhonen@0 | 896 { | 
| Chris@109 | 897     QStringList params; | 
| jtkorhonen@0 | 898 | 
| Chris@284 | 899     if (!QDir(m_workFolderPath).exists()) { | 
| Chris@284 | 900         if (!QDir().mkpath(m_workFolderPath)) { | 
| Chris@109 | 901             DEBUG << "hgCloneFromRemote: Failed to create target path " | 
| Chris@284 | 902                   << m_workFolderPath << endl; | 
| Chris@109 | 903             //!!! report error | 
| Chris@109 | 904             return; | 
| Chris@104 | 905         } | 
| Chris@109 | 906     } | 
| Chris@104 | 907 | 
| Chris@284 | 908     params << "clone" << m_remoteRepoPath << m_workFolderPath; | 
| Chris@109 | 909 | 
| Chris@287 | 910     updateWorkFolderAndRepoNames(); | 
| Chris@284 | 911     m_hgTabs->updateWorkFolderFileList(""); | 
| Chris@273 | 912 | 
| Chris@284 | 913     m_runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, m_workFolderPath, params)); | 
| jtkorhonen@0 | 914 } | 
| jtkorhonen@0 | 915 | 
| jtkorhonen@0 | 916 void MainWindow::hgInit() | 
| jtkorhonen@0 | 917 { | 
| Chris@109 | 918     QStringList params; | 
| jtkorhonen@0 | 919 | 
| Chris@109 | 920     params << "init"; | 
| Chris@284 | 921     params << m_workFolderPath; | 
| Chris@273 | 922 | 
| Chris@284 | 923     m_runner->requestAction(HgAction(ACT_INIT, m_workFolderPath, params)); | 
| jtkorhonen@0 | 924 } | 
| jtkorhonen@0 | 925 | 
| jtkorhonen@0 | 926 void MainWindow::hgIncoming() | 
| jtkorhonen@0 | 927 { | 
| Chris@109 | 928     QStringList params; | 
| jtkorhonen@0 | 929 | 
| Chris@284 | 930     params << "incoming" << "--newest-first" << m_remoteRepoPath; | 
| Chris@125 | 931     params << "--template" << Changeset::getLogTemplate(); | 
| jtkorhonen@0 | 932 | 
| Chris@284 | 933     m_runner->requestAction(HgAction(ACT_INCOMING, m_workFolderPath, params)); | 
| jtkorhonen@0 | 934 } | 
| jtkorhonen@0 | 935 | 
| jtkorhonen@0 | 936 void MainWindow::hgPull() | 
| jtkorhonen@0 | 937 { | 
| Chris@193 | 938     if (ConfirmCommentDialog::confirm | 
| Chris@126 | 939         (this, tr("Confirm pull"), | 
| Chris@299 | 940          tr("<qt><h3>Pull from remote repository?</h3></qt>"), | 
| Chris@299 | 941          tr("<qt><p>You are about to pull changes from the remote repository at <code>%1</code>.</p></qt>").arg(xmlEncode(m_remoteRepoPath)), | 
| Chris@194 | 942          tr("Pull"))) { | 
| jtkorhonen@0 | 943 | 
| Chris@126 | 944         QStringList params; | 
| Chris@284 | 945         params << "pull" << m_remoteRepoPath; | 
| Chris@284 | 946         m_runner->requestAction(HgAction(ACT_PULL, m_workFolderPath, params)); | 
| Chris@126 | 947     } | 
| jtkorhonen@0 | 948 } | 
| jtkorhonen@0 | 949 | 
| jtkorhonen@0 | 950 void MainWindow::hgPush() | 
| jtkorhonen@0 | 951 { | 
| Chris@193 | 952     if (ConfirmCommentDialog::confirm | 
| Chris@126 | 953         (this, tr("Confirm push"), | 
| Chris@299 | 954          tr("<qt><h3>Push to remote repository?</h3></qt>"), | 
| Chris@299 | 955          tr("<qt><p>You are about to push your changes to the remote repository at <code>%1</code>.</p></qt>").arg(xmlEncode(m_remoteRepoPath)), | 
| Chris@194 | 956          tr("Push"))) { | 
| jtkorhonen@0 | 957 | 
| Chris@126 | 958         QStringList params; | 
| Chris@284 | 959         params << "push" << "--new-branch" << m_remoteRepoPath; | 
| Chris@284 | 960         m_runner->requestAction(HgAction(ACT_PUSH, m_workFolderPath, params)); | 
| Chris@126 | 961     } | 
| jtkorhonen@0 | 962 } | 
| jtkorhonen@0 | 963 | 
| Chris@182 | 964 QStringList MainWindow::listAllUpIpV4Addresses() | 
| jtkorhonen@26 | 965 { | 
| Chris@182 | 966     QStringList ret; | 
| jtkorhonen@26 | 967     QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces(); | 
| jtkorhonen@26 | 968 | 
| Chris@182 | 969     for (int i = 0; i < ifaces.count(); i++) { | 
| jtkorhonen@26 | 970         QNetworkInterface iface = ifaces.at(i); | 
| Chris@182 | 971         if (iface.flags().testFlag(QNetworkInterface::IsUp) | 
| Chris@182 | 972             && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) { | 
| Chris@182 | 973             for (int j=0; j<iface.addressEntries().count(); j++) { | 
| jtkorhonen@28 | 974                 QHostAddress tmp = iface.addressEntries().at(j).ip(); | 
| Chris@182 | 975                 if (QAbstractSocket::IPv4Protocol == tmp.protocol()) { | 
| Chris@182 | 976                     ret.push_back(tmp.toString()); | 
| jtkorhonen@24 | 977                 } | 
| jtkorhonen@24 | 978             } | 
| jtkorhonen@22 | 979         } | 
| jtkorhonen@17 | 980     } | 
| jtkorhonen@28 | 981     return ret; | 
| jtkorhonen@28 | 982 } | 
| jtkorhonen@17 | 983 | 
| Chris@120 | 984 void MainWindow::clearState() | 
| Chris@120 | 985 { | 
| Chris@305 | 986     DEBUG << "MainWindow::clearState" << endl; | 
| Chris@284 | 987     foreach (Changeset *cs, m_currentParents) delete cs; | 
| Chris@284 | 988     m_currentParents.clear(); | 
| Chris@284 | 989     foreach (Changeset *cs, m_currentHeads) delete cs; | 
| Chris@284 | 990     m_currentHeads.clear(); | 
| Chris@284 | 991     m_currentBranch = ""; | 
| Chris@284 | 992     m_lastStatOutput = ""; | 
| Chris@284 | 993     m_lastRevertedFiles.clear(); | 
| Chris@284 | 994     m_mergeTargetRevision = ""; | 
| Chris@284 | 995     m_mergeCommitComment = ""; | 
| Chris@284 | 996     m_stateUnknown = true; | 
| Chris@284 | 997     m_needNewLog = true; | 
| Chris@284 | 998     if (m_fsWatcher) { | 
| Chris@241 | 999         delete m_fsWatcherGeneralTimer; | 
| Chris@241 | 1000         m_fsWatcherGeneralTimer = 0; | 
| Chris@241 | 1001         delete m_fsWatcherRestoreTimer; | 
| Chris@241 | 1002         m_fsWatcherRestoreTimer = 0; | 
| Chris@284 | 1003         delete m_fsWatcher; | 
| Chris@284 | 1004         m_fsWatcher = 0; | 
| Chris@199 | 1005     } | 
| Chris@120 | 1006 } | 
| jtkorhonen@17 | 1007 | 
| jtkorhonen@11 | 1008 void MainWindow::hgServe() | 
| jtkorhonen@11 | 1009 { | 
| Chris@109 | 1010     QStringList params; | 
| Chris@109 | 1011     QString msg; | 
| jtkorhonen@11 | 1012 | 
| Chris@182 | 1013     QStringList addrs = listAllUpIpV4Addresses(); | 
| Chris@182 | 1014 | 
| Chris@182 | 1015     if (addrs.empty()) { | 
| Chris@182 | 1016         QMessageBox::critical | 
| Chris@182 | 1017             (this, tr("Serve"), tr("Failed to identify an active IPv4 address")); | 
| Chris@182 | 1018         return; | 
| Chris@182 | 1019     } | 
| Chris@182 | 1020 | 
| Chris@182 | 1021     //!!! should find available port as well | 
| Chris@182 | 1022 | 
| Chris@182 | 1023     QTextStream ts(&msg); | 
| Chris@182 | 1024     ts << QString("<qt><p>%1</p>") | 
| Chris@182 | 1025         .arg(tr("Running temporary server at %n address(es):", "", addrs.size())); | 
| Chris@182 | 1026     foreach (QString addr, addrs) { | 
| Chris@182 | 1027         ts << QString("<pre>  http://%1:8000</pre>").arg(xmlEncode(addr)); | 
| Chris@182 | 1028     } | 
| Chris@182 | 1029     ts << tr("<p>Press Close to stop the server and return.</p>"); | 
| Chris@182 | 1030     ts.flush(); | 
| Chris@182 | 1031 | 
| Chris@109 | 1032     params << "serve"; | 
| jtkorhonen@11 | 1033 | 
| Chris@284 | 1034     m_runner->requestAction(HgAction(ACT_SERVE, m_workFolderPath, params)); | 
| Chris@109 | 1035 | 
| Chris@182 | 1036     QMessageBox::information(this, tr("Serve"), msg, QMessageBox::Close); | 
| Chris@182 | 1037 | 
| Chris@284 | 1038     m_runner->killCurrentActions(); | 
| jtkorhonen@11 | 1039 } | 
| jtkorhonen@11 | 1040 | 
| Chris@64 | 1041 void MainWindow::startupDialog() | 
| Chris@64 | 1042 { | 
| Chris@64 | 1043     StartupDialog *dlg = new StartupDialog(this); | 
| Chris@284 | 1044     if (dlg->exec()) m_firstStart = false; | 
| Chris@344 | 1045     else exit(0); | 
| Chris@64 | 1046 } | 
| jtkorhonen@11 | 1047 | 
| Chris@69 | 1048 void MainWindow::open() | 
| Chris@69 | 1049 { | 
| Chris@86 | 1050     bool done = false; | 
| Chris@69 | 1051 | 
| Chris@86 | 1052     while (!done) { | 
| Chris@69 | 1053 | 
| Chris@86 | 1054         MultiChoiceDialog *d = new MultiChoiceDialog | 
| Chris@86 | 1055                                (tr("Open Repository"), | 
| Chris@86 | 1056                                 tr("<qt><big>What would you like to open?</big></qt>"), | 
| Chris@86 | 1057                                 this); | 
| Chris@69 | 1058 | 
| Chris@345 | 1059         d->addChoice("remote", | 
| Chris@345 | 1060                      tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"), | 
| Chris@345 | 1061                      tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."), | 
| Chris@345 | 1062                      MultiChoiceDialog::UrlToDirectoryArg); | 
| Chris@345 | 1063 | 
| Chris@339 | 1064         d->addChoice("local", | 
| Chris@339 | 1065                      tr("<qt><center><img src=\":images/hglogo-64.png\"><br>Local repository</center></qt>"), | 
| Chris@339 | 1066                      tr("Open an existing local Mercurial repository."), | 
| Chris@339 | 1067                      MultiChoiceDialog::DirectoryArg); | 
| Chris@339 | 1068 | 
| Chris@339 | 1069         d->addChoice("init", | 
| Chris@339 | 1070                      tr("<qt><center><img src=\":images/hdd_unmount-64.png\"><br>File folder</center></qt>"), | 
| Chris@339 | 1071                      tr("Open a local folder, by creating a Mercurial repository in it."), | 
| Chris@339 | 1072                      MultiChoiceDialog::DirectoryArg); | 
| Chris@339 | 1073 | 
| Chris@248 | 1074         QSettings settings; | 
| Chris@248 | 1075         settings.beginGroup("General"); | 
| Chris@248 | 1076         QString lastChoice = settings.value("lastopentype", "local").toString(); | 
| Chris@248 | 1077         if (lastChoice != "local" && | 
| Chris@248 | 1078             lastChoice != "remote" && | 
| Chris@248 | 1079             lastChoice != "init") { | 
| Chris@248 | 1080             lastChoice = "local"; | 
| Chris@248 | 1081         } | 
| Chris@248 | 1082 | 
| Chris@248 | 1083         d->setCurrentChoice(lastChoice); | 
| Chris@86 | 1084 | 
| Chris@86 | 1085         if (d->exec() == QDialog::Accepted) { | 
| Chris@86 | 1086 | 
| Chris@86 | 1087             QString choice = d->getCurrentChoice(); | 
| Chris@248 | 1088             settings.setValue("lastopentype", choice); | 
| Chris@248 | 1089 | 
| Chris@86 | 1090             QString arg = d->getArgument().trimmed(); | 
| Chris@86 | 1091 | 
| Chris@86 | 1092             bool result = false; | 
| Chris@86 | 1093 | 
| Chris@86 | 1094             if (choice == "local") { | 
| Chris@86 | 1095                 result = openLocal(arg); | 
| Chris@86 | 1096             } else if (choice == "remote") { | 
| Chris@86 | 1097                 result = openRemote(arg, d->getAdditionalArgument().trimmed()); | 
| Chris@86 | 1098             } else if (choice == "init") { | 
| Chris@86 | 1099                 result = openInit(arg); | 
| Chris@86 | 1100             } | 
| Chris@86 | 1101 | 
| Chris@86 | 1102             if (result) { | 
| Chris@86 | 1103                 enableDisableActions(); | 
| Chris@120 | 1104                 clearState(); | 
| Chris@109 | 1105                 hgQueryPaths(); | 
| Chris@91 | 1106                 done = true; | 
| Chris@91 | 1107             } | 
| Chris@86 | 1108 | 
| Chris@86 | 1109         } else { | 
| Chris@86 | 1110 | 
| Chris@86 | 1111             // cancelled | 
| Chris@86 | 1112             done = true; | 
| Chris@69 | 1113         } | 
| Chris@79 | 1114 | 
| Chris@86 | 1115         delete d; | 
| Chris@69 | 1116     } | 
| Chris@69 | 1117 } | 
| Chris@69 | 1118 | 
| Chris@182 | 1119 void MainWindow::changeRemoteRepo() | 
| Chris@182 | 1120 { | 
| Chris@183 | 1121     // This will involve rewriting the local .hgrc | 
| Chris@183 | 1122 | 
| Chris@284 | 1123     QDir hgDir(m_workFolderPath + "/.hg"); | 
| Chris@184 | 1124     if (!hgDir.exists()) { | 
| Chris@184 | 1125         //!!! visible error! | 
| Chris@184 | 1126         return; | 
| Chris@184 | 1127     } | 
| Chris@184 | 1128 | 
| Chris@284 | 1129     QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc"); | 
| Chris@184 | 1130     if (hgrc.exists() && !hgrc.isWritable()) { | 
| Chris@183 | 1131         //!!! visible error! | 
| Chris@183 | 1132         return; | 
| Chris@183 | 1133     } | 
| Chris@183 | 1134 | 
| Chris@183 | 1135     MultiChoiceDialog *d = new MultiChoiceDialog | 
| Chris@183 | 1136         (tr("Change Remote Location"), | 
| Chris@183 | 1137          tr("<qt><big>Change the remote location</big></qt>"), | 
| Chris@183 | 1138          this); | 
| Chris@183 | 1139 | 
| Chris@183 | 1140     d->addChoice("remote", | 
| Chris@183 | 1141                  tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"), | 
| Chris@183 | 1142                  tr("Provide a new URL to use for push and pull actions from the current local repository."), | 
| Chris@183 | 1143                  MultiChoiceDialog::UrlArg); | 
| Chris@183 | 1144 | 
| Chris@183 | 1145     if (d->exec() == QDialog::Accepted) { | 
| Chris@210 | 1146 | 
| Chris@210 | 1147         // New block to ensure QSettings is deleted before | 
| Chris@210 | 1148         // hgQueryPaths called.  NB use of absoluteFilePath instead of | 
| Chris@210 | 1149         // canonicalFilePath, which would fail if the file did not yet | 
| Chris@210 | 1150         // exist | 
| Chris@210 | 1151 | 
| Chris@210 | 1152         { | 
| Chris@210 | 1153             QSettings s(hgrc.absoluteFilePath(), QSettings::IniFormat); | 
| Chris@210 | 1154             s.beginGroup("paths"); | 
| Chris@210 | 1155             s.setValue("default", d->getArgument()); | 
| Chris@210 | 1156         } | 
| Chris@210 | 1157 | 
| Chris@284 | 1158         m_stateUnknown = true; | 
| Chris@183 | 1159         hgQueryPaths(); | 
| Chris@183 | 1160     } | 
| Chris@183 | 1161 | 
| Chris@183 | 1162     delete d; | 
| Chris@182 | 1163 } | 
| Chris@182 | 1164 | 
| Chris@145 | 1165 void MainWindow::open(QString local) | 
| Chris@145 | 1166 { | 
| Chris@145 | 1167     if (openLocal(local)) { | 
| Chris@145 | 1168         enableDisableActions(); | 
| Chris@145 | 1169         clearState(); | 
| Chris@145 | 1170         hgQueryPaths(); | 
| Chris@145 | 1171     } | 
| Chris@145 | 1172 } | 
| Chris@145 | 1173 | 
| Chris@79 | 1174 bool MainWindow::complainAboutFilePath(QString arg) | 
| Chris@79 | 1175 { | 
| Chris@79 | 1176     QMessageBox::critical | 
| Chris@79 | 1177         (this, tr("File chosen"), | 
| Chris@84 | 1178          tr("<qt><b>Folder required</b><br><br>You asked to open \"%1\".<br>This is a file; to open a repository, you need to choose a folder.</qt>").arg(xmlEncode(arg))); | 
| Chris@79 | 1179     return false; | 
| Chris@79 | 1180 } | 
| Chris@79 | 1181 | 
| Chris@248 | 1182 bool MainWindow::askAboutUnknownFolder(QString arg) | 
| Chris@248 | 1183 { | 
| Chris@248 | 1184     bool result = (QMessageBox::question | 
| Chris@248 | 1185                    (this, tr("Path does not exist"), | 
| Chris@248 | 1186                     tr("<qt><b>Path does not exist: create it?</b><br><br>You asked to open a remote repository by cloning it to \"%1\". This folder does not exist, and neither does its parent.<br><br>Would you like to create the parent folder as well?</qt>").arg(xmlEncode(arg)), | 
| Chris@248 | 1187                     QMessageBox::Ok | QMessageBox::Cancel, | 
| Chris@248 | 1188                     QMessageBox::Cancel) | 
| Chris@248 | 1189                    == QMessageBox::Ok); | 
| Chris@248 | 1190     if (result) { | 
| Chris@248 | 1191         QDir dir(arg); | 
| Chris@248 | 1192         dir.cdUp(); | 
| Chris@248 | 1193         if (!dir.mkpath(dir.absolutePath())) { | 
| Chris@248 | 1194             QMessageBox::critical | 
| Chris@248 | 1195                 (this, tr("Failed to create folder"), | 
| Chris@248 | 1196                  tr("<qt><b>Failed to create folder</b><br><br>Sorry, the path for the parent folder \"%1\" could not be created.</qt>").arg(dir.absolutePath())); | 
| Chris@248 | 1197             return false; | 
| Chris@248 | 1198         } | 
| Chris@248 | 1199         return true; | 
| Chris@248 | 1200     } | 
| Chris@248 | 1201     return false; | 
| Chris@248 | 1202 } | 
| Chris@248 | 1203 | 
| Chris@79 | 1204 bool MainWindow::complainAboutUnknownFolder(QString arg) | 
| Chris@79 | 1205 { | 
| Chris@79 | 1206     QMessageBox::critical | 
| Chris@79 | 1207         (this, tr("Folder does not exist"), | 
| Chris@84 | 1208          tr("<qt><b>Folder does not exist</b><br><br>You asked to open \"%1\".<br>This folder does not exist, and it cannot be created because its parent does not exist either.</qt>").arg(xmlEncode(arg))); | 
| Chris@84 | 1209     return false; | 
| Chris@84 | 1210 } | 
| Chris@84 | 1211 | 
| Chris@84 | 1212 bool MainWindow::complainAboutInitInRepo(QString arg) | 
| Chris@84 | 1213 { | 
| Chris@84 | 1214     QMessageBox::critical | 
| Chris@84 | 1215         (this, tr("Path is in existing repository"), | 
| Chris@84 | 1216          tr("<qt><b>Path is in an existing repository</b><br><br>You asked to initialise a repository at \"%1\".<br>This path is already inside an existing repository.</qt>").arg(xmlEncode(arg))); | 
| Chris@84 | 1217     return false; | 
| Chris@84 | 1218 } | 
| Chris@84 | 1219 | 
| Chris@84 | 1220 bool MainWindow::complainAboutInitFile(QString arg) | 
| Chris@84 | 1221 { | 
| Chris@84 | 1222     QMessageBox::critical | 
| Chris@84 | 1223         (this, tr("Path is a file"), | 
| Chris@84 | 1224          tr("<qt><b>Path is a file</b><br><br>You asked to initialise a repository at \"%1\".<br>This is an existing file; it is only possible to initialise in folders.</qt>").arg(xmlEncode(arg))); | 
| Chris@84 | 1225     return false; | 
| Chris@84 | 1226 } | 
| Chris@84 | 1227 | 
| Chris@84 | 1228 bool MainWindow::complainAboutCloneToExisting(QString arg) | 
| Chris@84 | 1229 { | 
| Chris@84 | 1230     QMessageBox::critical | 
| Chris@84 | 1231         (this, tr("Path is in existing repository"), | 
| Chris@237 | 1232          tr("<qt><b>Local path is in an existing repository</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is already inside an existing repository.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg))); | 
| Chris@84 | 1233     return false; | 
| Chris@84 | 1234 } | 
| Chris@84 | 1235 | 
| Chris@84 | 1236 bool MainWindow::complainAboutCloneToFile(QString arg) | 
| Chris@84 | 1237 { | 
| Chris@84 | 1238     QMessageBox::critical | 
| Chris@84 | 1239         (this, tr("Path is a file"), | 
| Chris@84 | 1240          tr("<qt><b>Local path is a file</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is an existing file.<br>Please provide a new folder name for the local repository.</qt>").arg(xmlEncode(arg))); | 
| Chris@84 | 1241     return false; | 
| Chris@84 | 1242 } | 
| Chris@84 | 1243 | 
| Chris@237 | 1244 QString MainWindow::complainAboutCloneToExistingFolder(QString arg, QString remote) | 
| Chris@84 | 1245 { | 
| Chris@237 | 1246     // If the directory "arg" exists but "arg" plus the last path | 
| Chris@237 | 1247     // component of "remote" does not, then offer the latter as an | 
| Chris@237 | 1248     // alternative path | 
| Chris@237 | 1249 | 
| Chris@237 | 1250     QString offer; | 
| Chris@237 | 1251 | 
| Chris@237 | 1252     QDir d(arg); | 
| Chris@237 | 1253     if (d.exists()) { | 
| Chris@237 | 1254         if (QRegExp("^\\w+://").indexIn(remote) >= 0) { | 
| Chris@237 | 1255             QString rpath = QUrl(remote).path(); | 
| Chris@237 | 1256             if (rpath != "") { | 
| Chris@237 | 1257                 rpath = QDir(rpath).dirName(); | 
| Chris@237 | 1258                 if (rpath != "" && !d.exists(rpath)) { | 
| Chris@237 | 1259                     offer = d.filePath(rpath); | 
| Chris@237 | 1260                 } | 
| Chris@237 | 1261             } | 
| Chris@237 | 1262         } | 
| Chris@237 | 1263     } | 
| Chris@237 | 1264 | 
| Chris@237 | 1265     if (offer != "") { | 
| Chris@237 | 1266         bool result = (QMessageBox::question | 
| Chris@237 | 1267                        (this, tr("Folder exists"), | 
| Chris@237 | 1268                         tr("<qt><b>Local folder already exists</b><br><br>You asked to open a remote repository by cloning it to \"%1\", but this folder already exists and so cannot be cloned to.<br><br>Would you like to create the new folder \"%2\" instead?</qt>") | 
| Chris@237 | 1269                         .arg(xmlEncode(arg)).arg(xmlEncode(offer)), | 
| Chris@237 | 1270                         QMessageBox::Ok | QMessageBox::Cancel, | 
| Chris@237 | 1271                         QMessageBox::Cancel) | 
| Chris@237 | 1272                        == QMessageBox::Ok); | 
| Chris@237 | 1273         if (result) return offer; | 
| Chris@237 | 1274         else return ""; | 
| Chris@237 | 1275     } | 
| Chris@237 | 1276 | 
| Chris@84 | 1277     QMessageBox::critical | 
| Chris@84 | 1278         (this, tr("Folder exists"), | 
| Chris@237 | 1279          tr("<qt><b>Local folder already exists</b><br><br>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.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg))); | 
| Chris@237 | 1280     return ""; | 
| Chris@79 | 1281 } | 
| Chris@79 | 1282 | 
| Chris@79 | 1283 bool MainWindow::askToOpenParentRepo(QString arg, QString parent) | 
| Chris@79 | 1284 { | 
| Chris@79 | 1285     return (QMessageBox::question | 
| Chris@84 | 1286             (this, tr("Path is inside a repository"), | 
| Chris@86 | 1287              tr("<qt><b>Open the repository that contains this path?</b><br><br>You asked to open \"%1\".<br>This is not the root folder of a repository.<br>But it is inside a repository, whose root is at \"%2\". <br><br>Would you like to open that repository instead?</qt>") | 
| Chris@79 | 1288              .arg(xmlEncode(arg)).arg(xmlEncode(parent)), | 
| Chris@79 | 1289              QMessageBox::Ok | QMessageBox::Cancel, | 
| Chris@79 | 1290              QMessageBox::Ok) | 
| Chris@79 | 1291             == QMessageBox::Ok); | 
| Chris@79 | 1292 } | 
| Chris@79 | 1293 | 
| Chris@79 | 1294 bool MainWindow::askToInitExisting(QString arg) | 
| Chris@79 | 1295 { | 
| Chris@79 | 1296     return (QMessageBox::question | 
| Chris@84 | 1297             (this, tr("Folder has no repository"), | 
| Chris@84 | 1298              tr("<qt><b>Initialise a repository here?</b><br><br>You asked to open \"%1\".<br>This folder does not contain a Mercurial repository.<br><br>Would you like to initialise a repository here?</qt>") | 
| Chris@79 | 1299              .arg(xmlEncode(arg)), | 
| Chris@79 | 1300              QMessageBox::Ok | QMessageBox::Cancel, | 
| Chris@79 | 1301              QMessageBox::Ok) | 
| Chris@79 | 1302             == QMessageBox::Ok); | 
| Chris@79 | 1303 } | 
| Chris@79 | 1304 | 
| Chris@79 | 1305 bool MainWindow::askToInitNew(QString arg) | 
| Chris@79 | 1306 { | 
| Chris@79 | 1307     return (QMessageBox::question | 
| Chris@84 | 1308             (this, tr("Folder does not exist"), | 
| Chris@84 | 1309              tr("<qt><b>Initialise a new repository?</b><br><br>You asked to open \"%1\".<br>This folder does not yet exist.<br><br>Would you like to create the folder and initialise a new empty repository in it?</qt>") | 
| Chris@84 | 1310              .arg(xmlEncode(arg)), | 
| Chris@84 | 1311              QMessageBox::Ok | QMessageBox::Cancel, | 
| Chris@84 | 1312              QMessageBox::Ok) | 
| Chris@84 | 1313             == QMessageBox::Ok); | 
| Chris@84 | 1314 } | 
| Chris@84 | 1315 | 
| Chris@84 | 1316 bool MainWindow::askToOpenInsteadOfInit(QString arg) | 
| Chris@84 | 1317 { | 
| Chris@84 | 1318     return (QMessageBox::question | 
| Chris@84 | 1319             (this, tr("Repository exists"), | 
| Chris@84 | 1320              tr("<qt><b>Open existing repository?</b><br><br>You asked to initialise a new repository at \"%1\".<br>This folder already contains a repository.  Would you like to open it?</qt>") | 
| Chris@79 | 1321              .arg(xmlEncode(arg)), | 
| Chris@79 | 1322              QMessageBox::Ok | QMessageBox::Cancel, | 
| Chris@79 | 1323              QMessageBox::Ok) | 
| Chris@79 | 1324             == QMessageBox::Ok); | 
| Chris@79 | 1325 } | 
| Chris@79 | 1326 | 
| Chris@79 | 1327 bool MainWindow::openLocal(QString local) | 
| Chris@79 | 1328 { | 
| Chris@79 | 1329     DEBUG << "open " << local << endl; | 
| Chris@79 | 1330 | 
| Chris@79 | 1331     FolderStatus status = getFolderStatus(local); | 
| Chris@79 | 1332     QString containing = getContainingRepoFolder(local); | 
| Chris@79 | 1333 | 
| Chris@79 | 1334     switch (status) { | 
| Chris@79 | 1335 | 
| Chris@79 | 1336     case FolderHasRepo: | 
| Chris@79 | 1337         // fine | 
| Chris@79 | 1338         break; | 
| Chris@79 | 1339 | 
| Chris@79 | 1340     case FolderExists: | 
| Chris@79 | 1341         if (containing != "") { | 
| Chris@79 | 1342             if (!askToOpenParentRepo(local, containing)) return false; | 
| Chris@84 | 1343             local = containing; | 
| Chris@79 | 1344         } else { | 
| Chris@86 | 1345             //!!! No -- this is likely to happen far more by accident | 
| Chris@86 | 1346             // than because the user actually wanted to init something. | 
| Chris@86 | 1347             // Don't ask, just politely reject. | 
| Chris@79 | 1348             if (!askToInitExisting(local)) return false; | 
| Chris@79 | 1349             return openInit(local); | 
| Chris@79 | 1350         } | 
| Chris@79 | 1351         break; | 
| Chris@79 | 1352 | 
| Chris@79 | 1353     case FolderParentExists: | 
| Chris@79 | 1354         if (containing != "") { | 
| Chris@79 | 1355             if (!askToOpenParentRepo(local, containing)) return false; | 
| Chris@84 | 1356             local = containing; | 
| Chris@79 | 1357         } else { | 
| Chris@79 | 1358             if (!askToInitNew(local)) return false; | 
| Chris@79 | 1359             return openInit(local); | 
| Chris@79 | 1360         } | 
| Chris@79 | 1361         break; | 
| Chris@79 | 1362 | 
| Chris@79 | 1363     case FolderUnknown: | 
| Chris@84 | 1364         if (containing != "") { | 
| Chris@84 | 1365             if (!askToOpenParentRepo(local, containing)) return false; | 
| Chris@84 | 1366             local = containing; | 
| Chris@84 | 1367         } else { | 
| Chris@84 | 1368             return complainAboutUnknownFolder(local); | 
| Chris@84 | 1369         } | 
| Chris@84 | 1370         break; | 
| Chris@79 | 1371 | 
| Chris@79 | 1372     case FolderIsFile: | 
| Chris@79 | 1373         return complainAboutFilePath(local); | 
| Chris@79 | 1374     } | 
| Chris@79 | 1375 | 
| Chris@284 | 1376     m_workFolderPath = local; | 
| Chris@284 | 1377     m_remoteRepoPath = ""; | 
| Chris@79 | 1378     return true; | 
| Chris@79 | 1379 } | 
| Chris@79 | 1380 | 
| Chris@79 | 1381 bool MainWindow::openRemote(QString remote, QString local) | 
| Chris@79 | 1382 { | 
| Chris@79 | 1383     DEBUG << "clone " << remote << " to " << local << endl; | 
| Chris@84 | 1384 | 
| Chris@84 | 1385     FolderStatus status = getFolderStatus(local); | 
| Chris@84 | 1386     QString containing = getContainingRepoFolder(local); | 
| Chris@84 | 1387 | 
| Chris@84 | 1388     DEBUG << "status = " << status << ", containing = " << containing << endl; | 
| Chris@84 | 1389 | 
| Chris@84 | 1390     if (status == FolderHasRepo || containing != "") { | 
| Chris@84 | 1391         return complainAboutCloneToExisting(local); | 
| Chris@84 | 1392     } | 
| Chris@84 | 1393 | 
| Chris@84 | 1394     if (status == FolderIsFile) { | 
| Chris@84 | 1395         return complainAboutCloneToFile(local); | 
| Chris@84 | 1396     } | 
| Chris@84 | 1397 | 
| Chris@84 | 1398     if (status == FolderUnknown) { | 
| Chris@248 | 1399         if (!askAboutUnknownFolder(local)) { | 
| Chris@248 | 1400             return false; | 
| Chris@248 | 1401         } | 
| Chris@84 | 1402     } | 
| Chris@84 | 1403 | 
| Chris@84 | 1404     if (status == FolderExists) { | 
| Chris@237 | 1405         local = complainAboutCloneToExistingFolder(local, remote); | 
| Chris@237 | 1406         if (local == "") return false; | 
| Chris@84 | 1407     } | 
| Chris@84 | 1408 | 
| Chris@284 | 1409     m_workFolderPath = local; | 
| Chris@284 | 1410     m_remoteRepoPath = remote; | 
| Chris@84 | 1411     hgCloneFromRemote(); | 
| Chris@84 | 1412 | 
| Chris@79 | 1413     return true; | 
| Chris@79 | 1414 } | 
| Chris@79 | 1415 | 
| Chris@84 | 1416 bool MainWindow::openInit(QString local) | 
| Chris@79 | 1417 { | 
| Chris@84 | 1418     DEBUG << "openInit " << local << endl; | 
| Chris@84 | 1419 | 
| Chris@84 | 1420     FolderStatus status = getFolderStatus(local); | 
| Chris@84 | 1421     QString containing = getContainingRepoFolder(local); | 
| Chris@84 | 1422 | 
| Chris@84 | 1423     DEBUG << "status = " << status << ", containing = " << containing << endl; | 
| Chris@84 | 1424 | 
| Chris@84 | 1425     if (status == FolderHasRepo) { | 
| Chris@84 | 1426         if (!askToOpenInsteadOfInit(local)) return false; | 
| Chris@84 | 1427     } | 
| Chris@84 | 1428 | 
| Chris@84 | 1429     if (containing != "") { | 
| Chris@84 | 1430         return complainAboutInitInRepo(local); | 
| Chris@84 | 1431     } | 
| Chris@84 | 1432 | 
| Chris@84 | 1433     if (status == FolderIsFile) { | 
| Chris@84 | 1434         return complainAboutInitFile(local); | 
| Chris@84 | 1435     } | 
| Chris@84 | 1436 | 
| Chris@84 | 1437     if (status == FolderUnknown) { | 
| Chris@84 | 1438         return complainAboutUnknownFolder(local); | 
| Chris@84 | 1439     } | 
| Chris@84 | 1440 | 
| Chris@284 | 1441     m_workFolderPath = local; | 
| Chris@284 | 1442     m_remoteRepoPath = ""; | 
| Chris@84 | 1443     hgInit(); | 
| Chris@79 | 1444     return true; | 
| Chris@79 | 1445 } | 
| Chris@79 | 1446 | 
| jtkorhonen@0 | 1447 void MainWindow::settings() | 
| jtkorhonen@0 | 1448 { | 
| jtkorhonen@0 | 1449     SettingsDialog *settingsDlg = new SettingsDialog(this); | 
| jtkorhonen@0 | 1450     settingsDlg->exec(); | 
| Chris@230 | 1451 | 
| Chris@230 | 1452     if (settingsDlg->presentationChanged()) { | 
| Chris@284 | 1453         m_hgTabs->updateFileStates(); | 
| Chris@230 | 1454         updateToolBarStyle(); | 
| Chris@273 | 1455         hgRefresh(); | 
| Chris@230 | 1456     } | 
| jtkorhonen@0 | 1457 } | 
| jtkorhonen@0 | 1458 | 
| Chris@90 | 1459 void MainWindow::updateFileSystemWatcher() | 
| Chris@90 | 1460 { | 
| Chris@199 | 1461     bool justCreated = false; | 
| Chris@284 | 1462     if (!m_fsWatcher) { | 
| Chris@284 | 1463         m_fsWatcher = new QFileSystemWatcher(); | 
| Chris@199 | 1464         justCreated = true; | 
| Chris@199 | 1465     } | 
| Chris@199 | 1466 | 
| Chris@199 | 1467     // QFileSystemWatcher will refuse to add a file or directory to | 
| Chris@199 | 1468     // its watch list that it is already watching -- fine, that's what | 
| Chris@199 | 1469     // we want -- but it prints a warning when this happens, which is | 
| Chris@199 | 1470     // annoying because it would be the normal case for us.  So we'll | 
| Chris@199 | 1471     // check for duplicates ourselves. | 
| Chris@199 | 1472     QSet<QString> alreadyWatched; | 
| Chris@284 | 1473     QStringList dl(m_fsWatcher->directories()); | 
| Chris@199 | 1474     foreach (QString d, dl) alreadyWatched.insert(d); | 
| Chris@199 | 1475 | 
| Chris@90 | 1476     std::deque<QString> pending; | 
| Chris@284 | 1477     pending.push_back(m_workFolderPath); | 
| Chris@199 | 1478 | 
| Chris@90 | 1479     while (!pending.empty()) { | 
| Chris@199 | 1480 | 
| Chris@90 | 1481         QString path = pending.front(); | 
| Chris@90 | 1482         pending.pop_front(); | 
| Chris@199 | 1483         if (!alreadyWatched.contains(path)) { | 
| Chris@284 | 1484             m_fsWatcher->addPath(path); | 
| Chris@199 | 1485             DEBUG << "Added to file system watcher: " << path << endl; | 
| Chris@199 | 1486         } | 
| Chris@199 | 1487 | 
| Chris@90 | 1488         QDir d(path); | 
| Chris@90 | 1489         if (d.exists()) { | 
| Chris@199 | 1490             d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | | 
| Chris@199 | 1491                         QDir::Readable | QDir::NoSymLinks); | 
| Chris@90 | 1492             foreach (QString entry, d.entryList()) { | 
| Chris@199 | 1493                 if (entry.startsWith('.')) continue; | 
| Chris@90 | 1494                 QString entryPath = d.absoluteFilePath(entry); | 
| Chris@90 | 1495                 pending.push_back(entryPath); | 
| Chris@90 | 1496             } | 
| Chris@90 | 1497         } | 
| Chris@90 | 1498     } | 
| Chris@199 | 1499 | 
| Chris@238 | 1500     // The general timer isn't really related to the fs watcher | 
| Chris@238 | 1501     // object, it just does something similar -- every now and then we | 
| Chris@238 | 1502     // do a refresh just to update the history dates etc | 
| Chris@238 | 1503 | 
| Chris@238 | 1504     m_fsWatcherGeneralTimer = new QTimer(this); | 
| Chris@238 | 1505     connect(m_fsWatcherGeneralTimer, SIGNAL(timeout()), | 
| Chris@238 | 1506             this, SLOT(checkFilesystem())); | 
| Chris@238 | 1507     m_fsWatcherGeneralTimer->setInterval(30 * 60 * 1000); // half an hour | 
| Chris@238 | 1508     m_fsWatcherGeneralTimer->start(); | 
| Chris@238 | 1509 | 
| Chris@199 | 1510     if (justCreated) { | 
| Chris@284 | 1511         connect(m_fsWatcher, SIGNAL(directoryChanged(QString)), | 
| Chris@199 | 1512                 this, SLOT(fsDirectoryChanged(QString))); | 
| Chris@284 | 1513         connect(m_fsWatcher, SIGNAL(fileChanged(QString)), | 
| Chris@199 | 1514                 this, SLOT(fsFileChanged(QString))); | 
| Chris@199 | 1515     } | 
| Chris@90 | 1516 } | 
| Chris@90 | 1517 | 
| Chris@238 | 1518 void MainWindow::suspendFileSystemWatcher() | 
| Chris@238 | 1519 { | 
| Chris@238 | 1520     DEBUG << "MainWindow::suspendFileSystemWatcher" << endl; | 
| Chris@284 | 1521     if (m_fsWatcher) { | 
| Chris@241 | 1522         m_fsWatcherSuspended = true; | 
| Chris@241 | 1523         if (m_fsWatcherRestoreTimer) { | 
| Chris@241 | 1524             delete m_fsWatcherRestoreTimer; | 
| Chris@241 | 1525             m_fsWatcherRestoreTimer = 0; | 
| Chris@241 | 1526         } | 
| Chris@238 | 1527         m_fsWatcherGeneralTimer->stop(); | 
| Chris@238 | 1528     } | 
| Chris@238 | 1529 } | 
| Chris@238 | 1530 | 
| Chris@238 | 1531 void MainWindow::restoreFileSystemWatcher() | 
| Chris@238 | 1532 { | 
| Chris@238 | 1533     DEBUG << "MainWindow::restoreFileSystemWatcher" << endl; | 
| Chris@238 | 1534     if (m_fsWatcherRestoreTimer) delete m_fsWatcherRestoreTimer; | 
| Chris@238 | 1535 | 
| Chris@238 | 1536     // The restore timer is used to leave a polite interval between | 
| Chris@238 | 1537     // being asked to restore the watcher and actually doing so.  It's | 
| Chris@238 | 1538     // a single shot timer each time it's used, but we don't use | 
| Chris@238 | 1539     // QTimer::singleShot because we want to stop the previous one if | 
| Chris@238 | 1540     // it's running (via deleting it) | 
| Chris@238 | 1541 | 
| Chris@238 | 1542     m_fsWatcherRestoreTimer = new QTimer(this); | 
| Chris@238 | 1543     connect(m_fsWatcherRestoreTimer, SIGNAL(timeout()), | 
| Chris@238 | 1544             this, SLOT(actuallyRestoreFileSystemWatcher())); | 
| Chris@238 | 1545     m_fsWatcherRestoreTimer->setInterval(1000); | 
| Chris@238 | 1546     m_fsWatcherRestoreTimer->setSingleShot(true); | 
| Chris@238 | 1547     m_fsWatcherRestoreTimer->start(); | 
| Chris@238 | 1548 } | 
| Chris@238 | 1549 | 
| Chris@238 | 1550 void MainWindow::actuallyRestoreFileSystemWatcher() | 
| Chris@238 | 1551 { | 
| Chris@238 | 1552     DEBUG << "MainWindow::actuallyRestoreFileSystemWatcher" << endl; | 
| Chris@284 | 1553     if (m_fsWatcher) { | 
| Chris@241 | 1554         m_fsWatcherSuspended = false; | 
| Chris@241 | 1555         m_fsWatcherGeneralTimer->start(); | 
| Chris@241 | 1556     } | 
| Chris@238 | 1557 } | 
| Chris@238 | 1558 | 
| Chris@238 | 1559 void MainWindow::checkFilesystem() | 
| Chris@238 | 1560 { | 
| Chris@238 | 1561     DEBUG << "MainWindow::checkFilesystem" << endl; | 
| Chris@273 | 1562     hgRefresh(); | 
| Chris@238 | 1563 } | 
| Chris@238 | 1564 | 
| Chris@122 | 1565 void MainWindow::fsDirectoryChanged(QString d) | 
| Chris@90 | 1566 { | 
| Chris@122 | 1567     DEBUG << "MainWindow::fsDirectoryChanged " << d << endl; | 
| Chris@241 | 1568     if (!m_fsWatcherSuspended) { | 
| Chris@241 | 1569         hgStat(); | 
| Chris@241 | 1570     } | 
| Chris@90 | 1571 } | 
| Chris@90 | 1572 | 
| Chris@122 | 1573 void MainWindow::fsFileChanged(QString f) | 
| Chris@90 | 1574 { | 
| Chris@122 | 1575     DEBUG << "MainWindow::fsFileChanged " << f << endl; | 
| Chris@241 | 1576     if (!m_fsWatcherSuspended) { | 
| Chris@241 | 1577         hgStat(); | 
| Chris@241 | 1578     } | 
| Chris@90 | 1579 } | 
| Chris@90 | 1580 | 
| Chris@275 | 1581 QString MainWindow::format1(QString head) | 
| Chris@275 | 1582 { | 
| Chris@275 | 1583     return QString("<qt><h3>%1</h3></qt>").arg(head); | 
| Chris@275 | 1584 } | 
| Chris@275 | 1585 | 
| Chris@125 | 1586 QString MainWindow::format3(QString head, QString intro, QString code) | 
| Chris@125 | 1587 { | 
| Chris@196 | 1588     code = xmlEncode(code).replace("\n", "<br>") | 
| Chris@196 | 1589 #ifndef Q_OS_WIN32 | 
| Chris@196 | 1590            // The hard hyphen comes out funny on Windows | 
| Chris@196 | 1591            .replace("-", "‑") | 
| Chris@196 | 1592 #endif | 
| Chris@196 | 1593            .replace(" ", " "); | 
| Chris@125 | 1594     if (intro == "") { | 
| Chris@168 | 1595         return QString("<qt><h3>%1</h3><p><code>%2</code></p>") | 
| Chris@168 | 1596             .arg(head).arg(code); | 
| Chris@126 | 1597     } else if (code == "") { | 
| Chris@126 | 1598         return QString("<qt><h3>%1</h3><p>%2</p>") | 
| Chris@126 | 1599             .arg(head).arg(intro); | 
| Chris@125 | 1600     } else { | 
| Chris@168 | 1601         return QString("<qt><h3>%1</h3><p>%2</p><p><code>%3</code></p>") | 
| Chris@168 | 1602             .arg(head).arg(intro).arg(code); | 
| Chris@125 | 1603     } | 
| Chris@125 | 1604 } | 
| Chris@125 | 1605 | 
| Chris@120 | 1606 void MainWindow::showIncoming(QString output) | 
| Chris@120 | 1607 { | 
| Chris@284 | 1608     m_runner->hide(); | 
| Chris@125 | 1609     IncomingDialog *d = new IncomingDialog(this, output); | 
| Chris@125 | 1610     d->exec(); | 
| Chris@125 | 1611     delete d; | 
| Chris@125 | 1612 } | 
| Chris@125 | 1613 | 
| Chris@125 | 1614 int MainWindow::extractChangeCount(QString text) | 
| Chris@125 | 1615 { | 
| Chris@125 | 1616     QRegExp re("added (\\d+) ch\\w+ with (\\d+) ch\\w+ to (\\d+) f\\w+"); | 
| Chris@125 | 1617     if (re.indexIn(text) >= 0) { | 
| Chris@125 | 1618         return re.cap(1).toInt(); | 
| Chris@125 | 1619     } else if (text.contains("no changes")) { | 
| Chris@125 | 1620         return 0; | 
| Chris@125 | 1621     } else { | 
| Chris@125 | 1622         return -1; // unknown | 
| Chris@125 | 1623     } | 
| Chris@120 | 1624 } | 
| Chris@120 | 1625 | 
| Chris@120 | 1626 void MainWindow::showPushResult(QString output) | 
| Chris@120 | 1627 { | 
| Chris@291 | 1628     QString head; | 
| Chris@125 | 1629     QString report; | 
| Chris@125 | 1630     int n = extractChangeCount(output); | 
| Chris@125 | 1631     if (n > 0) { | 
| Chris@291 | 1632         head = tr("Pushed %n changeset(s)", "", n); | 
| Chris@300 | 1633         report = tr("<qt>Successfully pushed to the remote repository at <code>%1</code>.</qt>").arg(xmlEncode(m_remoteRepoPath)); | 
| Chris@125 | 1634     } else if (n == 0) { | 
| Chris@291 | 1635         head = tr("No changes to push"); | 
| Chris@291 | 1636         report = tr("The remote repository already contains all changes that have been committed locally."); | 
| Chris@294 | 1637         if (m_hgTabs->canCommit()) { | 
| Chris@291 | 1638             report = tr("%1<p>You do have some uncommitted changes. If you wish to push those to the remote repository, commit them locally first.").arg(report); | 
| Chris@291 | 1639         } | 
| Chris@125 | 1640     } else { | 
| Chris@291 | 1641         head = tr("Push complete"); | 
| Chris@125 | 1642     } | 
| Chris@284 | 1643     m_runner->hide(); | 
| Chris@291 | 1644 | 
| Chris@291 | 1645     MoreInformationDialog::information(this, tr("Push complete"), | 
| Chris@291 | 1646                                        head, report, output); | 
| Chris@120 | 1647 } | 
| Chris@120 | 1648 | 
| Chris@120 | 1649 void MainWindow::showPullResult(QString output) | 
| Chris@120 | 1650 { | 
| Chris@291 | 1651     QString head; | 
| Chris@125 | 1652     QString report; | 
| Chris@125 | 1653     int n = extractChangeCount(output); | 
| Chris@125 | 1654     if (n > 0) { | 
| Chris@291 | 1655         head = tr("Pulled %n changeset(s)", "", n); | 
| Chris@322 | 1656         report = tr("New changes will be highlighted in yellow in the history."); | 
| Chris@125 | 1657     } else if (n == 0) { | 
| Chris@291 | 1658         head = tr("No changes to pull"); | 
| Chris@291 | 1659         report = tr("Your local repository already contains all changes found in the remote repository."); | 
| Chris@125 | 1660     } else { | 
| Chris@291 | 1661         head = tr("Pull complete"); | 
| Chris@125 | 1662     } | 
| Chris@284 | 1663     m_runner->hide(); | 
| Chris@275 | 1664 | 
| Chris@275 | 1665     MoreInformationDialog::information(this, tr("Pull complete"), | 
| Chris@291 | 1666                                        head, report, output); | 
| Chris@120 | 1667 } | 
| Chris@120 | 1668 | 
| Chris@174 | 1669 void MainWindow::reportNewRemoteHeads(QString output) | 
| Chris@174 | 1670 { | 
| Chris@174 | 1671     bool headsAreLocal = false; | 
| Chris@174 | 1672 | 
| Chris@284 | 1673     if (m_currentParents.size() == 1) { | 
| Chris@284 | 1674         int m_currentBranchHeads = 0; | 
| Chris@174 | 1675         bool parentIsHead = false; | 
| Chris@284 | 1676         Changeset *parent = m_currentParents[0]; | 
| Chris@284 | 1677         foreach (Changeset *head, m_currentHeads) { | 
| Chris@284 | 1678             if (head->isOnBranch(m_currentBranch)) { | 
| Chris@284 | 1679                 ++m_currentBranchHeads; | 
| Chris@174 | 1680             } | 
| Chris@174 | 1681             if (parent->id() == head->id()) { | 
| Chris@174 | 1682                 parentIsHead = true; | 
| Chris@174 | 1683             } | 
| Chris@174 | 1684         } | 
| Chris@284 | 1685         if (m_currentBranchHeads == 2 && parentIsHead) { | 
| Chris@174 | 1686             headsAreLocal = true; | 
| Chris@174 | 1687         } | 
| Chris@174 | 1688     } | 
| Chris@174 | 1689 | 
| Chris@174 | 1690     if (headsAreLocal) { | 
| Chris@291 | 1691         MoreInformationDialog::warning | 
| Chris@291 | 1692             (this, | 
| Chris@291 | 1693              tr("Push failed"), | 
| Chris@291 | 1694              tr("Push failed"), | 
| Chris@291 | 1695              tr("Your local repository could not be pushed to the remote repository.<br><br>You may need to merge the changes locally first."), | 
| Chris@291 | 1696              output); | 
| Chris@318 | 1697     } else if (m_hgTabs->canCommit() && m_currentParents.size() > 1) { | 
| Chris@318 | 1698         MoreInformationDialog::warning | 
| Chris@318 | 1699             (this, | 
| Chris@318 | 1700              tr("Push failed"), | 
| Chris@318 | 1701              tr("Push failed"), | 
| Chris@318 | 1702              tr("Your local repository could not be pushed to the remote repository.<br><br>You have an uncommitted merge in your local folder.  You probably need to commit it before you push."), | 
| Chris@318 | 1703              output); | 
| Chris@174 | 1704     } else { | 
| Chris@291 | 1705         MoreInformationDialog::warning | 
| Chris@291 | 1706             (this, | 
| Chris@291 | 1707              tr("Push failed"), | 
| Chris@291 | 1708              tr("Push failed"), | 
| Chris@291 | 1709              tr("Your local repository could not be pushed to the remote repository.<br><br>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 | 1710              output); | 
| Chris@174 | 1711     } | 
| Chris@174 | 1712 } | 
| Chris@174 | 1713 | 
| Chris@238 | 1714 void MainWindow::commandStarting(HgAction action) | 
| Chris@238 | 1715 { | 
| Chris@238 | 1716     // Annoyingly, hg stat actually modifies the working directory -- | 
| Chris@238 | 1717     // it creates files called hg-checklink and hg-checkexec to test | 
| Chris@238 | 1718     // properties of the filesystem.  For safety's sake, suspend the | 
| Chris@238 | 1719     // fs watcher while running commands, and restore it shortly after | 
| Chris@238 | 1720     // a command has finished. | 
| Chris@238 | 1721 | 
| Chris@246 | 1722     if (action.action == ACT_STAT) { | 
| Chris@246 | 1723         suspendFileSystemWatcher(); | 
| Chris@246 | 1724     } | 
| Chris@238 | 1725 } | 
| Chris@238 | 1726 | 
| Chris@143 | 1727 void MainWindow::commandFailed(HgAction action, QString output) | 
| Chris@62 | 1728 { | 
| Chris@62 | 1729     DEBUG << "MainWindow::commandFailed" << endl; | 
| Chris@238 | 1730     restoreFileSystemWatcher(); | 
| Chris@74 | 1731 | 
| Chris@210 | 1732     QString setstr; | 
| Chris@210 | 1733 #ifdef Q_OS_MAC | 
| Chris@210 | 1734     setstr = tr("Preferences"); | 
| Chris@210 | 1735 #else | 
| Chris@210 | 1736     setstr = tr("Settings"); | 
| Chris@210 | 1737 #endif | 
| Chris@210 | 1738 | 
| Chris@113 | 1739     // Some commands we just have to ignore bad return values from: | 
| Chris@113 | 1740 | 
| Chris@113 | 1741     switch(action.action) { | 
| Chris@113 | 1742     case ACT_NONE: | 
| Chris@113 | 1743         // uh huh | 
| Chris@113 | 1744         return; | 
| Chris@175 | 1745     case ACT_TEST_HG: | 
| Chris@291 | 1746         MoreInformationDialog::warning | 
| Chris@291 | 1747             (this, | 
| Chris@291 | 1748              tr("Failed to run Mercurial"), | 
| Chris@291 | 1749              tr("Failed to run Mercurial"), | 
| Chris@291 | 1750              tr("The Mercurial program either could not be found or failed to run.<br>Check that the Mercurial program path is correct in %1.").arg(setstr), | 
| Chris@291 | 1751              output); | 
| Chris@200 | 1752         settings(); | 
| Chris@200 | 1753         return; | 
| Chris@200 | 1754     case ACT_TEST_HG_EXT: | 
| Chris@309 | 1755         MoreInformationDialog::warning | 
| Chris@291 | 1756             (this, | 
| Chris@291 | 1757              tr("Failed to run Mercurial"), | 
| Chris@291 | 1758              tr("Failed to run Mercurial with extension enabled"), | 
| Chris@310 | 1759              tr("The Mercurial program failed to run with the EasyMercurial interaction extension enabled.<br>This may indicate an installation problem.<br><br>You may be able to continue working if you switch off “Use EasyHg Mercurial Extension” in %1.  Note that remote repositories that require authentication might not work if you do this.").arg(setstr), | 
| Chris@291 | 1760              output); | 
| Chris@175 | 1761         settings(); | 
| Chris@175 | 1762         return; | 
| Chris@252 | 1763     case ACT_CLONEFROMREMOTE: | 
| Chris@252 | 1764         // if clone fails, we have no repo | 
| Chris@284 | 1765         m_workFolderPath = ""; | 
| Chris@252 | 1766         enableDisableActions(); | 
| Chris@330 | 1767         break; // go on to default report | 
| Chris@113 | 1768     case ACT_INCOMING: | 
| Chris@211 | 1769         // returns non-zero code and no output if the check was | 
| Chris@211 | 1770         // successful but there are no changes pending | 
| Chris@280 | 1771         if (output.replace(QRegExp("(^|\\n)warning: [^\\n]*\\n"), "").trimmed() == "") { | 
| Chris@211 | 1772             showIncoming(""); | 
| Chris@211 | 1773             return; | 
| Chris@211 | 1774         } | 
| Chris@330 | 1775         break; // go on to default report | 
| Chris@162 | 1776     case ACT_QUERY_HEADS: | 
| Chris@162 | 1777         // fails if repo is empty; we don't care (if there's a genuine | 
| Chris@202 | 1778         // problem, something else will fail too).  Pretend it | 
| Chris@202 | 1779         // succeeded, so that any further actions that are contingent | 
| Chris@202 | 1780         // on the success of the heads query get carried out properly. | 
| Chris@202 | 1781         commandCompleted(action, ""); | 
| Chris@162 | 1782         return; | 
| Chris@113 | 1783     case ACT_FOLDERDIFF: | 
| Chris@113 | 1784     case ACT_CHGSETDIFF: | 
| Chris@113 | 1785         // external program, unlikely to be anything useful in stderr | 
| Chris@113 | 1786         // and some return with failure codes when something as basic | 
| Chris@113 | 1787         // as the user closing the window via the wm happens | 
| Chris@113 | 1788         return; | 
| Chris@174 | 1789     case ACT_PUSH: | 
| Chris@174 | 1790         if (output.contains("creates new remote heads")) { | 
| Chris@174 | 1791             reportNewRemoteHeads(output); | 
| Chris@174 | 1792             return; | 
| Chris@174 | 1793         } | 
| Chris@330 | 1794         break; // go on to default report | 
| Chris@328 | 1795     case ACT_MERGE: | 
| Chris@328 | 1796     case ACT_RETRY_MERGE: | 
| Chris@328 | 1797         MoreInformationDialog::information | 
| Chris@328 | 1798             (this, tr("Merge"), tr("Merge failed"), | 
| Chris@328 | 1799              tr("Some files were not merged successfully.<p>You can Merge again to repeat the interactive merge; use Revert to abandon the merge entirely; or edit the files that are in conflict in an editor and, when you are happy with them, choose Mark Resolved in each file's right-button menu."), | 
| Chris@328 | 1800              output); | 
| Chris@328 | 1801         return; | 
| Chris@199 | 1802     case ACT_STAT: | 
| Chris@330 | 1803         break; // go on to default report | 
| Chris@113 | 1804     default: | 
| Chris@114 | 1805         break; | 
| Chris@113 | 1806     } | 
| Chris@113 | 1807 | 
| Chris@113 | 1808     QString command = action.executable; | 
| Chris@113 | 1809     if (command == "") command = "hg"; | 
| Chris@113 | 1810     foreach (QString arg, action.params) { | 
| Chris@113 | 1811         command += " " + arg; | 
| Chris@113 | 1812     } | 
| Chris@113 | 1813 | 
| Chris@309 | 1814     MoreInformationDialog::warning | 
| Chris@309 | 1815         (this, | 
| Chris@309 | 1816          tr("Command failed"), | 
| Chris@309 | 1817          tr("Command failed"), | 
| Chris@309 | 1818          tr("A Mercurial command failed to run correctly.  This may indicate an installation problem or some other problem with EasyMercurial.<br><br>See “More Details” for the command output."), | 
| Chris@309 | 1819          output); | 
| Chris@62 | 1820 } | 
| Chris@62 | 1821 | 
| Chris@109 | 1822 void MainWindow::commandCompleted(HgAction completedAction, QString output) | 
| jtkorhonen@0 | 1823 { | 
| Chris@238 | 1824     restoreFileSystemWatcher(); | 
| Chris@109 | 1825     HGACTIONS action = completedAction.action; | 
| Chris@109 | 1826 | 
| Chris@109 | 1827     if (action == ACT_NONE) return; | 
| Chris@109 | 1828 | 
| Chris@150 | 1829     bool headsChanged = false; | 
| Chris@150 | 1830     QStringList oldHeadIds; | 
| Chris@150 | 1831 | 
| Chris@150 | 1832     switch (action) { | 
| Chris@109 | 1833 | 
| Chris@175 | 1834     case ACT_TEST_HG: | 
| Chris@175 | 1835         break; | 
| Chris@175 | 1836 | 
| Chris@200 | 1837     case ACT_TEST_HG_EXT: | 
| Chris@200 | 1838         break; | 
| Chris@200 | 1839 | 
| Chris@109 | 1840     case ACT_QUERY_PATHS: | 
| jtkorhonen@0 | 1841     { | 
| Chris@109 | 1842         DEBUG << "stdout is " << output << endl; | 
| Chris@109 | 1843         LogParser lp(output, "="); | 
| Chris@109 | 1844         LogList ll = lp.parse(); | 
| Chris@109 | 1845         DEBUG << ll.size() << " results" << endl; | 
| Chris@109 | 1846         if (!ll.empty()) { | 
| Chris@284 | 1847             m_remoteRepoPath = lp.parse()[0]["default"].trimmed(); | 
| Chris@284 | 1848             DEBUG << "Set remote path to " << m_remoteRepoPath << endl; | 
| Chris@210 | 1849         } else { | 
| Chris@284 | 1850             m_remoteRepoPath = ""; | 
| Chris@109 | 1851         } | 
| Chris@284 | 1852         MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); | 
| Chris@284 | 1853         MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); | 
| Chris@287 | 1854         updateWorkFolderAndRepoNames(); | 
| Chris@109 | 1855         break; | 
| Chris@109 | 1856     } | 
| jtkorhonen@0 | 1857 | 
| Chris@109 | 1858     case ACT_QUERY_BRANCH: | 
| Chris@284 | 1859         m_currentBranch = output.trimmed(); | 
| Chris@109 | 1860         break; | 
| jtkorhonen@0 | 1861 | 
| Chris@109 | 1862     case ACT_STAT: | 
| Chris@284 | 1863         m_lastStatOutput = output; | 
| Chris@109 | 1864         updateFileSystemWatcher(); | 
| Chris@109 | 1865         break; | 
| Chris@163 | 1866 | 
| Chris@163 | 1867     case ACT_RESOLVE_LIST: | 
| Chris@163 | 1868         if (output != "") { | 
| Chris@163 | 1869             // Remove lines beginning with R (they are resolved, | 
| Chris@163 | 1870             // and the file stat parser treats R as removed) | 
| Chris@163 | 1871             QStringList outList = output.split('\n'); | 
| Chris@163 | 1872             QStringList winnowed; | 
| Chris@163 | 1873             foreach (QString line, outList) { | 
| Chris@163 | 1874                 if (!line.startsWith("R ")) winnowed.push_back(line); | 
| Chris@163 | 1875             } | 
| Chris@163 | 1876             output = winnowed.join("\n"); | 
| Chris@163 | 1877         } | 
| Chris@284 | 1878         DEBUG << "m_lastStatOutput = " << m_lastStatOutput << endl; | 
| Chris@199 | 1879         DEBUG << "resolve output = " << output << endl; | 
| Chris@284 | 1880         m_hgTabs->updateWorkFolderFileList(m_lastStatOutput + output); | 
| Chris@163 | 1881         break; | 
| Chris@163 | 1882 | 
| Chris@163 | 1883     case ACT_RESOLVE_MARK: | 
| Chris@284 | 1884         m_shouldHgStat = true; | 
| Chris@163 | 1885         break; | 
| Chris@109 | 1886 | 
| Chris@109 | 1887     case ACT_INCOMING: | 
| Chris@120 | 1888         showIncoming(output); | 
| Chris@120 | 1889         break; | 
| Chris@120 | 1890 | 
| Chris@109 | 1891     case ACT_ANNOTATE: | 
| Chris@331 | 1892     { | 
| Chris@331 | 1893         AnnotateDialog dialog(this, output); | 
| Chris@331 | 1894         dialog.exec(); | 
| Chris@284 | 1895         m_shouldHgStat = true; | 
| Chris@109 | 1896         break; | 
| Chris@331 | 1897     } | 
| Chris@109 | 1898 | 
| Chris@109 | 1899     case ACT_PULL: | 
| Chris@120 | 1900         showPullResult(output); | 
| Chris@284 | 1901         m_shouldHgStat = true; | 
| Chris@109 | 1902         break; | 
| Chris@109 | 1903 | 
| Chris@109 | 1904     case ACT_PUSH: | 
| Chris@120 | 1905         showPushResult(output); | 
| Chris@109 | 1906         break; | 
| Chris@109 | 1907 | 
| Chris@109 | 1908     case ACT_INIT: | 
| Chris@284 | 1909         MultiChoiceDialog::addRecentArgument("init", m_workFolderPath); | 
| Chris@284 | 1910         MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); | 
| Chris@109 | 1911         enableDisableActions(); | 
| Chris@284 | 1912         m_shouldHgStat = true; | 
| Chris@109 | 1913         break; | 
| Chris@109 | 1914 | 
| Chris@109 | 1915     case ACT_CLONEFROMREMOTE: | 
| Chris@284 | 1916         MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); | 
| Chris@284 | 1917         MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); | 
| Chris@284 | 1918         MultiChoiceDialog::addRecentArgument("remote", m_workFolderPath, true); | 
| Chris@291 | 1919         MoreInformationDialog::information | 
| Chris@291 | 1920             (this, | 
| Chris@291 | 1921              tr("Clone"), | 
| Chris@291 | 1922              tr("Clone successful"), | 
| Chris@295 | 1923              tr("The remote repository was successfully cloned to the local folder <code>%1</code>.").arg(xmlEncode(m_workFolderPath)), | 
| Chris@291 | 1924              output); | 
| Chris@109 | 1925         enableDisableActions(); | 
| Chris@284 | 1926         m_shouldHgStat = true; | 
| Chris@109 | 1927         break; | 
| Chris@109 | 1928 | 
| Chris@109 | 1929     case ACT_LOG: | 
| Chris@284 | 1930         m_hgTabs->setNewLog(output); | 
| Chris@284 | 1931         m_needNewLog = false; | 
| Chris@120 | 1932         break; | 
| Chris@120 | 1933 | 
| Chris@120 | 1934     case ACT_LOG_INCREMENTAL: | 
| Chris@284 | 1935         m_hgTabs->addIncrementalLog(output); | 
| Chris@109 | 1936         break; | 
| Chris@109 | 1937 | 
| Chris@109 | 1938     case ACT_QUERY_PARENTS: | 
| Chris@152 | 1939     { | 
| Chris@284 | 1940         foreach (Changeset *cs, m_currentParents) delete cs; | 
| Chris@284 | 1941         m_currentParents = Changeset::parseChangesets(output); | 
| Chris@284 | 1942         QStringList parentIds = Changeset::getIds(m_currentParents); | 
| Chris@284 | 1943         m_hgTabs->setCurrent(parentIds, m_currentBranch); | 
| Chris@152 | 1944     } | 
| Chris@109 | 1945         break; | 
| Chris@109 | 1946 | 
| Chris@109 | 1947     case ACT_QUERY_HEADS: | 
| Chris@150 | 1948     { | 
| Chris@284 | 1949         oldHeadIds = Changeset::getIds(m_currentHeads); | 
| Chris@150 | 1950         Changesets newHeads = Changeset::parseChangesets(output); | 
| Chris@150 | 1951         QStringList newHeadIds = Changeset::getIds(newHeads); | 
| Chris@150 | 1952         if (oldHeadIds != newHeadIds) { | 
| Chris@150 | 1953             DEBUG << "Heads changed, will prompt an incremental log if appropriate" << endl; | 
| Chris@305 | 1954             DEBUG << "Old heads: " << oldHeadIds.join(",") << endl; | 
| Chris@305 | 1955             DEBUG << "New heads: " << newHeadIds.join(",") << endl; | 
| Chris@150 | 1956             headsChanged = true; | 
| Chris@284 | 1957             foreach (Changeset *cs, m_currentHeads) delete cs; | 
| Chris@284 | 1958             m_currentHeads = newHeads; | 
| Chris@150 | 1959         } | 
| Chris@150 | 1960     } | 
| Chris@109 | 1961         break; | 
| Chris@130 | 1962 | 
| Chris@130 | 1963     case ACT_COMMIT: | 
| Chris@347 | 1964         if (m_currentParents.empty()) { | 
| Chris@347 | 1965             // first commit to empty repo | 
| Chris@347 | 1966             m_needNewLog = true; | 
| Chris@347 | 1967         } | 
| Chris@284 | 1968         m_hgTabs->clearSelections(); | 
| Chris@284 | 1969         m_justMerged = false; | 
| Chris@284 | 1970         m_shouldHgStat = true; | 
| Chris@130 | 1971         break; | 
| Chris@163 | 1972 | 
| Chris@163 | 1973     case ACT_REVERT: | 
| Chris@326 | 1974         hgMarkFilesResolved(m_lastRevertedFiles); | 
| Chris@284 | 1975         m_justMerged = false; | 
| Chris@163 | 1976         break; | 
| Chris@109 | 1977 | 
| Chris@109 | 1978     case ACT_REMOVE: | 
| Chris@109 | 1979     case ACT_ADD: | 
| Chris@284 | 1980         m_hgTabs->clearSelections(); | 
| Chris@284 | 1981         m_shouldHgStat = true; | 
| Chris@116 | 1982         break; | 
| Chris@116 | 1983 | 
| Chris@164 | 1984     case ACT_TAG: | 
| Chris@284 | 1985         m_needNewLog = true; | 
| Chris@284 | 1986         m_shouldHgStat = true; | 
| Chris@164 | 1987         break; | 
| Chris@164 | 1988 | 
| Chris@278 | 1989     case ACT_NEW_BRANCH: | 
| Chris@307 | 1990         m_shouldHgStat = true; | 
| Chris@278 | 1991         break; | 
| Chris@278 | 1992 | 
| Chris@288 | 1993     case ACT_UNCOMMITTED_SUMMARY: | 
| Chris@168 | 1994         QMessageBox::information(this, tr("Change summary"), | 
| Chris@168 | 1995                                  format3(tr("Summary of uncommitted changes"), | 
| Chris@168 | 1996                                          "", | 
| Chris@168 | 1997                                          output)); | 
| Chris@168 | 1998         break; | 
| Chris@168 | 1999 | 
| Chris@288 | 2000     case ACT_DIFF_SUMMARY: | 
| Chris@289 | 2001     { | 
| Chris@289 | 2002         // Output has log info first, diff following after a blank line | 
| Chris@289 | 2003         output.replace("\r\n", "\n"); | 
| Chris@289 | 2004         QStringList olist = output.split("\n\n", QString::SkipEmptyParts); | 
| Chris@289 | 2005         if (olist.size() > 1) output = olist[1]; | 
| Chris@289 | 2006 | 
| Chris@289 | 2007         Changeset *cs = (Changeset *)completedAction.extraData; | 
| Chris@289 | 2008         if (cs) { | 
| Chris@289 | 2009             QMessageBox::information | 
| Chris@289 | 2010                 (this, tr("Change summary"), | 
| Chris@289 | 2011                  format3(tr("Summary of changes"), | 
| Chris@289 | 2012                          cs->formatHtml(), | 
| Chris@289 | 2013                          output)); | 
| Chris@289 | 2014         } else if (output == "") { | 
| Chris@289 | 2015             // Can happen, for a merge commit (depending on parent) | 
| Chris@288 | 2016             QMessageBox::information(this, tr("Change summary"), | 
| Chris@288 | 2017                                      format3(tr("Summary of changes"), | 
| Chris@288 | 2018                                              tr("No changes"), | 
| Chris@288 | 2019                                              output)); | 
| Chris@288 | 2020         } else { | 
| Chris@288 | 2021             QMessageBox::information(this, tr("Change summary"), | 
| Chris@288 | 2022                                      format3(tr("Summary of changes"), | 
| Chris@288 | 2023                                              "", | 
| Chris@288 | 2024                                              output)); | 
| Chris@288 | 2025         } | 
| Chris@288 | 2026         break; | 
| Chris@289 | 2027     } | 
| Chris@288 | 2028 | 
| Chris@109 | 2029     case ACT_FOLDERDIFF: | 
| Chris@109 | 2030     case ACT_CHGSETDIFF: | 
| Chris@109 | 2031     case ACT_SERVE: | 
| Chris@109 | 2032     case ACT_HG_IGNORE: | 
| Chris@284 | 2033         m_shouldHgStat = true; | 
| Chris@109 | 2034         break; | 
| Chris@109 | 2035 | 
| Chris@109 | 2036     case ACT_UPDATE: | 
| Chris@162 | 2037         QMessageBox::information(this, tr("Update"), tr("<qt><h3>Update successful</h3><p>%1</p>").arg(xmlEncode(output))); | 
| Chris@284 | 2038         m_shouldHgStat = true; | 
| Chris@109 | 2039         break; | 
| Chris@109 | 2040 | 
| Chris@109 | 2041     case ACT_MERGE: | 
| Chris@294 | 2042         MoreInformationDialog::information | 
| Chris@294 | 2043             (this, tr("Merge"), tr("Merge successful"), | 
| Chris@302 | 2044              tr("Remember to test and commit the result before making any further changes."), | 
| Chris@294 | 2045              output); | 
| Chris@284 | 2046         m_shouldHgStat = true; | 
| Chris@284 | 2047         m_justMerged = true; | 
| Chris@109 | 2048         break; | 
| Chris@109 | 2049 | 
| Chris@109 | 2050     case ACT_RETRY_MERGE: | 
| Chris@163 | 2051         QMessageBox::information(this, tr("Resolved"), | 
| Chris@302 | 2052                                  tr("<qt><h3>Merge resolved</h3><p>Merge resolved successfully.<br>Remember to test and commit the result before making any further changes.</p>")); | 
| Chris@284 | 2053         m_shouldHgStat = true; | 
| Chris@284 | 2054         m_justMerged = true; | 
| Chris@109 | 2055         break; | 
| Chris@109 | 2056 | 
| Chris@109 | 2057     default: | 
| Chris@109 | 2058         break; | 
| Chris@109 | 2059     } | 
| Chris@108 | 2060 | 
| Chris@121 | 2061     // Sequence when no full log required: | 
| Chris@163 | 2062     //   paths -> branch -> stat -> resolve-list -> heads -> | 
| Chris@150 | 2063     //     incremental-log (only if heads changed) -> parents | 
| Chris@150 | 2064     // | 
| Chris@121 | 2065     // Sequence when full log required: | 
| Chris@163 | 2066     //   paths -> branch -> stat -> resolve-list -> heads -> parents -> log | 
| Chris@150 | 2067     // | 
| Chris@150 | 2068     // Note we want to call enableDisableActions only once, at the end | 
| Chris@150 | 2069     // of whichever sequence is in use. | 
| Chris@150 | 2070 | 
| Chris@156 | 2071     bool noMore = false; | 
| Chris@156 | 2072 | 
| Chris@150 | 2073     switch (action) { | 
| Chris@175 | 2074 | 
| Chris@175 | 2075     case ACT_TEST_HG: | 
| Chris@248 | 2076     { | 
| Chris@248 | 2077         QSettings settings; | 
| Chris@248 | 2078         settings.beginGroup("General"); | 
| Chris@248 | 2079         if (settings.value("useextension", true).toBool()) { | 
| Chris@248 | 2080             hgTestExtension(); | 
| Chris@284 | 2081         } else if (m_workFolderPath == "") { | 
| Chris@248 | 2082             open(); | 
| Chris@248 | 2083         } else { | 
| Chris@248 | 2084             hgQueryPaths(); | 
| Chris@248 | 2085         } | 
| Chris@200 | 2086         break; | 
| Chris@248 | 2087     } | 
| Chris@200 | 2088 | 
| Chris@200 | 2089     case ACT_TEST_HG_EXT: | 
| Chris@284 | 2090         if (m_workFolderPath == "") { | 
| Chris@248 | 2091             open(); | 
| Chris@248 | 2092         } else{ | 
| Chris@248 | 2093             hgQueryPaths(); | 
| Chris@248 | 2094         } | 
| Chris@175 | 2095         break; | 
| Chris@150 | 2096 | 
| Chris@150 | 2097     case ACT_QUERY_PATHS: | 
| Chris@109 | 2098         hgQueryBranch(); | 
| Chris@150 | 2099         break; | 
| Chris@150 | 2100 | 
| Chris@150 | 2101     case ACT_QUERY_BRANCH: | 
| Chris@109 | 2102         hgStat(); | 
| Chris@150 | 2103         break; | 
| Chris@150 | 2104 | 
| Chris@150 | 2105     case ACT_STAT: | 
| Chris@163 | 2106         hgResolveList(); | 
| Chris@163 | 2107         break; | 
| Chris@163 | 2108 | 
| Chris@163 | 2109     case ACT_RESOLVE_LIST: | 
| Chris@150 | 2110         hgQueryHeads(); | 
| Chris@150 | 2111         break; | 
| Chris@150 | 2112 | 
| Chris@150 | 2113     case ACT_QUERY_HEADS: | 
| Chris@284 | 2114         if (headsChanged && !m_needNewLog) { | 
| Chris@150 | 2115             hgLogIncremental(oldHeadIds); | 
| Chris@121 | 2116         } else { | 
| Chris@150 | 2117             hgQueryParents(); | 
| Chris@121 | 2118         } | 
| Chris@150 | 2119         break; | 
| Chris@150 | 2120 | 
| Chris@150 | 2121     case ACT_LOG_INCREMENTAL: | 
| Chris@109 | 2122         hgQueryParents(); | 
| Chris@150 | 2123         break; | 
| Chris@150 | 2124 | 
| Chris@150 | 2125     case ACT_QUERY_PARENTS: | 
| Chris@284 | 2126         if (m_needNewLog) { | 
| Chris@120 | 2127             hgLog(); | 
| Chris@150 | 2128         } else { | 
| Chris@150 | 2129             // we're done | 
| Chris@156 | 2130             noMore = true; | 
| Chris@120 | 2131         } | 
| Chris@150 | 2132         break; | 
| Chris@150 | 2133 | 
| Chris@150 | 2134     case ACT_LOG: | 
| Chris@150 | 2135         // we're done | 
| Chris@156 | 2136         noMore = true; | 
| Chris@198 | 2137         break; | 
| Chris@150 | 2138 | 
| Chris@150 | 2139     default: | 
| Chris@284 | 2140         if (m_shouldHgStat) { | 
| Chris@284 | 2141             m_shouldHgStat = false; | 
| Chris@109 | 2142             hgQueryPaths(); | 
| Chris@150 | 2143         } else { | 
| Chris@156 | 2144             noMore = true; | 
| jtkorhonen@0 | 2145         } | 
| Chris@150 | 2146         break; | 
| Chris@150 | 2147     } | 
| Chris@156 | 2148 | 
| Chris@156 | 2149     if (noMore) { | 
| Chris@284 | 2150         m_stateUnknown = false; | 
| Chris@156 | 2151         enableDisableActions(); | 
| Chris@284 | 2152         m_hgTabs->updateHistory(); | 
| Chris@156 | 2153     } | 
| jtkorhonen@0 | 2154 } | 
| jtkorhonen@0 | 2155 | 
| jtkorhonen@0 | 2156 void MainWindow::connectActions() | 
| jtkorhonen@0 | 2157 { | 
| Chris@284 | 2158     connect(m_exitAct, SIGNAL(triggered()), this, SLOT(close())); | 
| Chris@284 | 2159     connect(m_aboutAct, SIGNAL(triggered()), this, SLOT(about())); | 
| Chris@273 | 2160 | 
| Chris@284 | 2161     connect(m_hgRefreshAct, SIGNAL(triggered()), this, SLOT(hgRefresh())); | 
| Chris@284 | 2162     connect(m_hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove())); | 
| Chris@284 | 2163     connect(m_hgAddAct, SIGNAL(triggered()), this, SLOT(hgAdd())); | 
| Chris@284 | 2164     connect(m_hgCommitAct, SIGNAL(triggered()), this, SLOT(hgCommit())); | 
| Chris@284 | 2165     connect(m_hgFolderDiffAct, SIGNAL(triggered()), this, SLOT(hgFolderDiff())); | 
| Chris@284 | 2166     connect(m_hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate())); | 
| Chris@284 | 2167     connect(m_hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert())); | 
| Chris@284 | 2168     connect(m_hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge())); | 
| Chris@284 | 2169     connect(m_hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore())); | 
| Chris@273 | 2170 | 
| Chris@284 | 2171     connect(m_settingsAct, SIGNAL(triggered()), this, SLOT(settings())); | 
| Chris@284 | 2172     connect(m_openAct, SIGNAL(triggered()), this, SLOT(open())); | 
| Chris@284 | 2173     connect(m_changeRemoteRepoAct, SIGNAL(triggered()), this, SLOT(changeRemoteRepo())); | 
| Chris@273 | 2174 | 
| Chris@284 | 2175     connect(m_hgIncomingAct, SIGNAL(triggered()), this, SLOT(hgIncoming())); | 
| Chris@284 | 2176     connect(m_hgPullAct, SIGNAL(triggered()), this, SLOT(hgPull())); | 
| Chris@284 | 2177     connect(m_hgPushAct, SIGNAL(triggered()), this, SLOT(hgPush())); | 
| Chris@273 | 2178 | 
| Chris@284 | 2179     connect(m_hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe())); | 
| jtkorhonen@0 | 2180 } | 
| Chris@141 | 2181 | 
| Chris@141 | 2182 void MainWindow::connectTabsSignals() | 
| Chris@141 | 2183 { | 
| Chris@327 | 2184     connect(m_hgTabs, SIGNAL(currentChanged(int)), | 
| Chris@327 | 2185             this, SLOT(enableDisableActions())); | 
| Chris@327 | 2186 | 
| Chris@284 | 2187     connect(m_hgTabs, SIGNAL(commit()), | 
| Chris@141 | 2188             this, SLOT(hgCommit())); | 
| Chris@141 | 2189 | 
| Chris@284 | 2190     connect(m_hgTabs, SIGNAL(revert()), | 
| Chris@141 | 2191             this, SLOT(hgRevert())); | 
| Chris@141 | 2192 | 
| Chris@284 | 2193     connect(m_hgTabs, SIGNAL(diffWorkingFolder()), | 
| Chris@141 | 2194             this, SLOT(hgFolderDiff())); | 
| Chris@168 | 2195 | 
| Chris@284 | 2196     connect(m_hgTabs, SIGNAL(showSummary()), | 
| Chris@168 | 2197             this, SLOT(hgShowSummary())); | 
| Chris@311 | 2198 | 
| Chris@311 | 2199     connect(m_hgTabs, SIGNAL(newBranch()), | 
| Chris@311 | 2200             this, SLOT(hgNewBranch())); | 
| Chris@311 | 2201 | 
| Chris@311 | 2202     connect(m_hgTabs, SIGNAL(noBranch()), | 
| Chris@311 | 2203             this, SLOT(hgNoBranch())); | 
| Chris@148 | 2204 | 
| Chris@284 | 2205     connect(m_hgTabs, SIGNAL(updateTo(QString)), | 
| Chris@148 | 2206             this, SLOT(hgUpdateToRev(QString))); | 
| Chris@141 | 2207 | 
| Chris@284 | 2208     connect(m_hgTabs, SIGNAL(diffToCurrent(QString)), | 
| Chris@148 | 2209             this, SLOT(hgDiffToCurrent(QString))); | 
| Chris@141 | 2210 | 
| Chris@284 | 2211     connect(m_hgTabs, SIGNAL(diffToParent(QString, QString)), | 
| Chris@148 | 2212             this, SLOT(hgDiffToParent(QString, QString))); | 
| Chris@141 | 2213 | 
| Chris@289 | 2214     connect(m_hgTabs, SIGNAL(showSummary(Changeset *)), | 
| Chris@289 | 2215             this, SLOT(hgShowSummaryFor(Changeset *))); | 
| Chris@288 | 2216 | 
| Chris@284 | 2217     connect(m_hgTabs, SIGNAL(mergeFrom(QString)), | 
| Chris@148 | 2218             this, SLOT(hgMergeFrom(QString))); | 
| Chris@164 | 2219 | 
| Chris@307 | 2220     connect(m_hgTabs, SIGNAL(newBranch(QString)), | 
| Chris@311 | 2221             this, SLOT(hgNewBranch())); | 
| Chris@278 | 2222 | 
| Chris@284 | 2223     connect(m_hgTabs, SIGNAL(tag(QString)), | 
| Chris@148 | 2224             this, SLOT(hgTag(QString))); | 
| Chris@326 | 2225 | 
| Chris@326 | 2226     connect(m_hgTabs, SIGNAL(annotateFiles(QStringList)), | 
| Chris@326 | 2227             this, SLOT(hgAnnotateFiles(QStringList))); | 
| Chris@326 | 2228 | 
| Chris@326 | 2229     connect(m_hgTabs, SIGNAL(diffFiles(QStringList)), | 
| Chris@326 | 2230             this, SLOT(hgDiffFiles(QStringList))); | 
| Chris@326 | 2231 | 
| Chris@326 | 2232     connect(m_hgTabs, SIGNAL(commitFiles(QStringList)), | 
| Chris@326 | 2233             this, SLOT(hgCommitFiles(QStringList))); | 
| Chris@326 | 2234 | 
| Chris@326 | 2235     connect(m_hgTabs, SIGNAL(revertFiles(QStringList)), | 
| Chris@326 | 2236             this, SLOT(hgRevertFiles(QStringList))); | 
| Chris@326 | 2237 | 
| Chris@326 | 2238     connect(m_hgTabs, SIGNAL(addFiles(QStringList)), | 
| Chris@326 | 2239             this, SLOT(hgAddFiles(QStringList))); | 
| Chris@326 | 2240 | 
| Chris@326 | 2241     connect(m_hgTabs, SIGNAL(removeFiles(QStringList)), | 
| Chris@326 | 2242             this, SLOT(hgRemoveFiles(QStringList))); | 
| Chris@326 | 2243 | 
| Chris@326 | 2244     connect(m_hgTabs, SIGNAL(redoFileMerges(QStringList)), | 
| Chris@326 | 2245             this, SLOT(hgRedoFileMerges(QStringList))); | 
| Chris@326 | 2246 | 
| Chris@326 | 2247     connect(m_hgTabs, SIGNAL(markFilesResolved(QStringList)), | 
| Chris@326 | 2248             this, SLOT(hgMarkFilesResolved(QStringList))); | 
| Chris@326 | 2249 | 
| Chris@326 | 2250     connect(m_hgTabs, SIGNAL(ignoreFiles(QStringList)), | 
| Chris@326 | 2251             this, SLOT(hgIgnoreFiles(QStringList))); | 
| Chris@326 | 2252 | 
| Chris@326 | 2253     connect(m_hgTabs, SIGNAL(unIgnoreFiles(QStringList)), | 
| Chris@326 | 2254             this, SLOT(hgUnIgnoreFiles(QStringList))); | 
| Chris@141 | 2255 } | 
| Chris@141 | 2256 | 
| jtkorhonen@0 | 2257 void MainWindow::enableDisableActions() | 
| jtkorhonen@0 | 2258 { | 
| Chris@90 | 2259     DEBUG << "MainWindow::enableDisableActions" << endl; | 
| Chris@90 | 2260 | 
| Chris@284 | 2261     QString dirname = QDir(m_workFolderPath).dirName(); | 
| Chris@340 | 2262 | 
| Chris@340 | 2263     if (m_workFolderPath != "") { // dirname of "" is ".", so test path instead | 
| Chris@202 | 2264         setWindowTitle(tr("EasyMercurial: %1").arg(dirname)); | 
| Chris@202 | 2265     } else { | 
| Chris@202 | 2266         setWindowTitle(tr("EasyMercurial")); | 
| Chris@202 | 2267     } | 
| Chris@202 | 2268 | 
| Chris@115 | 2269     //!!! should also do things like set the status texts for the | 
| Chris@115 | 2270     //!!! actions appropriately by context | 
| Chris@115 | 2271 | 
| jtkorhonen@0 | 2272     QDir localRepoDir; | 
| jtkorhonen@0 | 2273     QDir workFolderDir; | 
| Chris@145 | 2274     bool workFolderExist = true; | 
| Chris@145 | 2275     bool localRepoExist = true; | 
| jtkorhonen@0 | 2276 | 
| Chris@284 | 2277     m_remoteRepoActionsEnabled = true; | 
| Chris@284 | 2278     if (m_remoteRepoPath.isEmpty()) { | 
| Chris@284 | 2279         m_remoteRepoActionsEnabled = false; | 
| jtkorhonen@0 | 2280     } | 
| jtkorhonen@0 | 2281 | 
| Chris@284 | 2282     m_localRepoActionsEnabled = true; | 
| Chris@284 | 2283     if (m_workFolderPath.isEmpty()) { | 
| Chris@284 | 2284         m_localRepoActionsEnabled = false; | 
| jtkorhonen@0 | 2285         workFolderExist = false; | 
| jtkorhonen@0 | 2286     } | 
| jtkorhonen@0 | 2287 | 
| Chris@284 | 2288     if (m_workFolderPath == "" || !workFolderDir.exists(m_workFolderPath)) { | 
| Chris@284 | 2289         m_localRepoActionsEnabled = false; | 
| jtkorhonen@0 | 2290         workFolderExist = false; | 
| Chris@90 | 2291     } else { | 
| jtkorhonen@0 | 2292         workFolderExist = true; | 
| jtkorhonen@0 | 2293     } | 
| jtkorhonen@0 | 2294 | 
| Chris@284 | 2295     if (!localRepoDir.exists(m_workFolderPath + "/.hg")) { | 
| Chris@284 | 2296         m_localRepoActionsEnabled = false; | 
| jtkorhonen@0 | 2297         localRepoExist = false; | 
| jtkorhonen@0 | 2298     } | 
| jtkorhonen@0 | 2299 | 
| Chris@284 | 2300     m_hgIncomingAct -> setEnabled(m_remoteRepoActionsEnabled && m_remoteRepoActionsEnabled); | 
| Chris@284 | 2301     m_hgPullAct -> setEnabled(m_remoteRepoActionsEnabled && m_remoteRepoActionsEnabled); | 
| Chris@284 | 2302     m_hgPushAct -> setEnabled(m_remoteRepoActionsEnabled && m_remoteRepoActionsEnabled); | 
| Chris@169 | 2303 | 
| Chris@179 | 2304     bool haveDiff = false; | 
| Chris@179 | 2305     QSettings settings; | 
| Chris@179 | 2306     settings.beginGroup("Locations"); | 
| Chris@179 | 2307     if (settings.value("extdiffbinary", "").toString() != "") { | 
| Chris@179 | 2308         haveDiff = true; | 
| Chris@179 | 2309     } | 
| Chris@179 | 2310     settings.endGroup(); | 
| Chris@112 | 2311 | 
| Chris@284 | 2312     m_hgRefreshAct -> setEnabled(m_localRepoActionsEnabled); | 
| Chris@284 | 2313     m_hgFolderDiffAct -> setEnabled(m_localRepoActionsEnabled && haveDiff); | 
| Chris@284 | 2314     m_hgRevertAct -> setEnabled(m_localRepoActionsEnabled); | 
| Chris@284 | 2315     m_hgAddAct -> setEnabled(m_localRepoActionsEnabled); | 
| Chris@284 | 2316     m_hgRemoveAct -> setEnabled(m_localRepoActionsEnabled); | 
| Chris@284 | 2317     m_hgUpdateAct -> setEnabled(m_localRepoActionsEnabled); | 
| Chris@284 | 2318     m_hgCommitAct -> setEnabled(m_localRepoActionsEnabled); | 
| Chris@284 | 2319     m_hgMergeAct -> setEnabled(m_localRepoActionsEnabled); | 
| Chris@284 | 2320     m_hgServeAct -> setEnabled(m_localRepoActionsEnabled); | 
| Chris@284 | 2321     m_hgIgnoreAct -> setEnabled(m_localRepoActionsEnabled); | 
| Chris@273 | 2322 | 
| Chris@284 | 2323     DEBUG << "m_localRepoActionsEnabled = " << m_localRepoActionsEnabled << endl; | 
| Chris@284 | 2324     DEBUG << "canCommit = " << m_hgTabs->canCommit() << endl; | 
| Chris@273 | 2325 | 
| Chris@284 | 2326     m_hgAddAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canAdd()); | 
| Chris@284 | 2327     m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRemove()); | 
| Chris@284 | 2328     m_hgCommitAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canCommit()); | 
| Chris@284 | 2329     m_hgRevertAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRevert()); | 
| Chris@284 | 2330     m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canDiff()); | 
| Chris@90 | 2331 | 
| Chris@108 | 2332     // A default merge makes sense if: | 
| Chris@108 | 2333     //  * there is only one parent (if there are two, we have an uncommitted merge) and | 
| Chris@108 | 2334     //  * there are exactly two heads that have the same branch as the current branch and | 
| Chris@108 | 2335     //  * our parent is one of those heads | 
| Chris@108 | 2336     // | 
| Chris@108 | 2337     // A default update makes sense if: | 
| Chris@108 | 2338     //  * there is only one parent and | 
| Chris@108 | 2339     //  * the parent is not one of the current heads | 
| Chris@156 | 2340 | 
| Chris@108 | 2341     bool canMerge = false; | 
| Chris@108 | 2342     bool canUpdate = false; | 
| Chris@156 | 2343     bool haveMerge = false; | 
| Chris@162 | 2344     bool emptyRepo = false; | 
| Chris@225 | 2345     bool noWorkingCopy = false; | 
| Chris@235 | 2346     bool newBranch = false; | 
| Chris@284 | 2347     int m_currentBranchHeads = 0; | 
| Chris@273 | 2348 | 
| Chris@284 | 2349     if (m_currentParents.size() == 1) { | 
| Chris@156 | 2350         bool parentIsHead = false; | 
| Chris@284 | 2351         Changeset *parent = m_currentParents[0]; | 
| Chris@284 | 2352         foreach (Changeset *head, m_currentHeads) { | 
| Chris@284 | 2353             DEBUG << "head branch " << head->branch() << ", current branch " << m_currentBranch << endl; | 
| Chris@284 | 2354             if (head->isOnBranch(m_currentBranch)) { | 
| Chris@284 | 2355                 ++m_currentBranchHeads; | 
| Chris@235 | 2356             } | 
| Chris@235 | 2357             if (parent->id() == head->id()) { | 
| Chris@235 | 2358                 parentIsHead = true; | 
| Chris@108 | 2359             } | 
| Chris@108 | 2360         } | 
| Chris@284 | 2361         if (m_currentBranchHeads == 2 && parentIsHead) { | 
| Chris@108 | 2362             canMerge = true; | 
| Chris@108 | 2363         } | 
| Chris@284 | 2364         if (m_currentBranchHeads == 0 && parentIsHead) { | 
| Chris@235 | 2365             // Just created a new branch | 
| Chris@235 | 2366             newBranch = true; | 
| Chris@235 | 2367         } | 
| Chris@108 | 2368         if (!parentIsHead) { | 
| Chris@108 | 2369             canUpdate = true; | 
| Chris@108 | 2370             DEBUG << "parent id = " << parent->id() << endl; | 
| Chris@108 | 2371             DEBUG << " head ids "<<endl; | 
| Chris@284 | 2372             foreach (Changeset *h, m_currentHeads) { | 
| Chris@108 | 2373                 DEBUG << "head id = " << h->id() << endl; | 
| Chris@108 | 2374             } | 
| Chris@108 | 2375         } | 
| Chris@284 | 2376         m_justMerged = false; | 
| Chris@284 | 2377     } else if (m_currentParents.size() == 0) { | 
| Chris@284 | 2378         if (m_currentHeads.size() == 0) { | 
| Chris@225 | 2379             // No heads -> empty repo | 
| Chris@225 | 2380             emptyRepo = true; | 
| Chris@225 | 2381         } else { | 
| Chris@225 | 2382             // Heads, but no parents -> no working copy, e.g. we have | 
| Chris@225 | 2383             // just converted this repo but haven't updated in it yet. | 
| Chris@225 | 2384             // Uncommon but confusing; probably merits a special case | 
| Chris@225 | 2385             noWorkingCopy = true; | 
| Chris@225 | 2386             canUpdate = true; | 
| Chris@225 | 2387         } | 
| Chris@284 | 2388         m_justMerged = false; | 
| Chris@156 | 2389     } else { | 
| Chris@156 | 2390         haveMerge = true; | 
| Chris@284 | 2391         m_justMerged = true; | 
| Chris@108 | 2392     } | 
| Chris@156 | 2393 | 
| Chris@284 | 2394     m_hgMergeAct->setEnabled(m_localRepoActionsEnabled && | 
| Chris@284 | 2395                            (canMerge || m_hgTabs->canResolve())); | 
| Chris@284 | 2396     m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled && | 
| Chris@284 | 2397                             (canUpdate && !m_hgTabs->haveChangesToCommit())); | 
| Chris@115 | 2398 | 
| Chris@115 | 2399     // Set the state field on the file status widget | 
| Chris@115 | 2400 | 
| Chris@115 | 2401     QString branchText; | 
| Chris@284 | 2402     if (m_currentBranch == "" || m_currentBranch == "default") { | 
| Chris@115 | 2403         branchText = tr("the default branch"); | 
| Chris@115 | 2404     } else { | 
| Chris@284 | 2405         branchText = tr("branch \"%1\"").arg(m_currentBranch); | 
| Chris@115 | 2406     } | 
| Chris@156 | 2407 | 
| Chris@284 | 2408     if (m_stateUnknown) { | 
| Chris@284 | 2409         if (m_workFolderPath == "") { | 
| Chris@287 | 2410             m_workStatus->setState(tr("No repository open")); | 
| Chris@248 | 2411         } else { | 
| Chris@287 | 2412             m_workStatus->setState(tr("(Examining repository)")); | 
| Chris@248 | 2413         } | 
| Chris@173 | 2414     } else if (emptyRepo) { | 
| Chris@287 | 2415         m_workStatus->setState(tr("Nothing committed to this repository yet")); | 
| Chris@225 | 2416     } else if (noWorkingCopy) { | 
| Chris@287 | 2417         m_workStatus->setState(tr("No working copy yet: consider updating")); | 
| Chris@162 | 2418     } else if (canMerge) { | 
| Chris@287 | 2419         m_workStatus->setState(tr("<b>Awaiting merge</b> on %1").arg(branchText)); | 
| Chris@284 | 2420     } else if (!m_hgTabs->getAllUnresolvedFiles().empty()) { | 
| Chris@287 | 2421         m_workStatus->setState(tr("Have unresolved files following merge on %1").arg(branchText)); | 
| Chris@156 | 2422     } else if (haveMerge) { | 
| Chris@287 | 2423         m_workStatus->setState(tr("Have merged but not yet committed on %1").arg(branchText)); | 
| Chris@235 | 2424     } else if (newBranch) { | 
| Chris@287 | 2425         m_workStatus->setState(tr("On %1.  New branch: has not yet been committed").arg(branchText)); | 
| Chris@156 | 2426     } else if (canUpdate) { | 
| Chris@284 | 2427         if (m_hgTabs->haveChangesToCommit()) { | 
| Chris@163 | 2428             // have uncommitted changes | 
| Chris@287 | 2429             m_workStatus->setState(tr("On %1. Not at the head of the branch").arg(branchText)); | 
| Chris@163 | 2430         } else { | 
| Chris@163 | 2431             // no uncommitted changes | 
| Chris@287 | 2432             m_workStatus->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText)); | 
| Chris@163 | 2433         } | 
| Chris@284 | 2434     } else if (m_currentBranchHeads > 1) { | 
| Chris@287 | 2435         m_workStatus->setState(tr("At one of %n heads of %1", "", m_currentBranchHeads).arg(branchText)); | 
| Chris@115 | 2436     } else { | 
| Chris@287 | 2437         m_workStatus->setState(tr("At the head of %1").arg(branchText)); | 
| Chris@115 | 2438     } | 
| jtkorhonen@0 | 2439 } | 
| jtkorhonen@0 | 2440 | 
| jtkorhonen@0 | 2441 void MainWindow::createActions() | 
| jtkorhonen@0 | 2442 { | 
| jtkorhonen@0 | 2443     //File actions | 
| Chris@284 | 2444     m_openAct = new QAction(QIcon(":/images/fileopen.png"), tr("Open..."), this); | 
| Chris@284 | 2445     m_openAct -> setStatusTip(tr("Open an existing repository or working folder")); | 
| Chris@273 | 2446 | 
| Chris@284 | 2447     m_changeRemoteRepoAct = new QAction(tr("Change Remote Location..."), this); | 
| Chris@284 | 2448     m_changeRemoteRepoAct->setStatusTip(tr("Change the default remote repository for pull and push actions")); | 
| Chris@273 | 2449 | 
| Chris@284 | 2450     m_settingsAct = new QAction(QIcon(":/images/settings.png"), tr("Settings..."), this); | 
| Chris@284 | 2451     m_settingsAct -> setStatusTip(tr("View and change application settings")); | 
| Chris@273 | 2452 | 
| Chris@343 | 2453 #ifdef Q_OS_WIN32 | 
| Chris@343 | 2454     m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("Exit"), this); | 
| Chris@343 | 2455 #else | 
| Chris@284 | 2456     m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("Quit"), this); | 
| Chris@343 | 2457 #endif | 
| Chris@284 | 2458     m_exitAct->setShortcuts(QKeySequence::Quit); | 
| Chris@284 | 2459     m_exitAct->setStatusTip(tr("Quit EasyMercurial")); | 
| jtkorhonen@0 | 2460 | 
| jtkorhonen@0 | 2461     //Repository actions | 
| Chris@284 | 2462     m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("Refresh"), this); | 
| Chris@284 | 2463     m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder")); | 
| Chris@273 | 2464 | 
| Chris@284 | 2465     m_hgIncomingAct = new QAction(QIcon(":/images/incoming.png"), tr("Preview"), this); | 
| Chris@284 | 2466     m_hgIncomingAct -> setStatusTip(tr("See what changes are available in the remote repository waiting to be pulled")); | 
| Chris@273 | 2467 | 
| Chris@284 | 2468     m_hgPullAct = new QAction(QIcon(":/images/pull.png"), tr("Pull"), this); | 
| Chris@284 | 2469     m_hgPullAct -> setStatusTip(tr("Pull changes from the remote repository to the local repository")); | 
| Chris@273 | 2470 | 
| Chris@284 | 2471     m_hgPushAct = new QAction(QIcon(":/images/push.png"), tr("Push"), this); | 
| Chris@284 | 2472     m_hgPushAct->setStatusTip(tr("Push changes from the local repository to the remote repository")); | 
| jtkorhonen@0 | 2473 | 
| jtkorhonen@0 | 2474     //Workfolder actions | 
| Chris@284 | 2475     m_hgFolderDiffAct   = new QAction(QIcon(":/images/folderdiff.png"), tr("Diff"), this); | 
| Chris@284 | 2476     m_hgFolderDiffAct->setStatusTip(tr("See what has changed in the working folder compared with the last committed state")); | 
| Chris@273 | 2477 | 
| Chris@284 | 2478     m_hgRevertAct = new QAction(QIcon(":/images/undo.png"), tr("Revert"), this); | 
| Chris@284 | 2479     m_hgRevertAct->setStatusTip(tr("Throw away your changes and return to the last committed state")); | 
| Chris@273 | 2480 | 
| Chris@284 | 2481     m_hgAddAct = new QAction(QIcon(":/images/add.png"), tr("Add"), this); | 
| Chris@284 | 2482     m_hgAddAct -> setStatusTip(tr("Mark the selected file(s) to be added on the next commit")); | 
| jtkorhonen@0 | 2483 | 
| Chris@169 | 2484     //!!! needs to be modified for number | 
| Chris@284 | 2485     m_hgRemoveAct = new QAction(QIcon(":/images/remove.png"), tr("Remove"), this); | 
| Chris@284 | 2486     m_hgRemoveAct -> setStatusTip(tr("Mark the selected file(s) to be removed from version control on the next commit")); | 
| Chris@273 | 2487 | 
| Chris@284 | 2488     m_hgUpdateAct = new QAction(QIcon(":/images/update.png"), tr("Update"), this); | 
| Chris@284 | 2489     m_hgUpdateAct->setStatusTip(tr("Update the working folder to the head of the current repository branch")); | 
| jtkorhonen@0 | 2490 | 
| Chris@169 | 2491     //!!! needs to be modified when files selected | 
| Chris@284 | 2492     m_hgCommitAct = new QAction(QIcon(":/images/commit.png"), tr("Commit"), this); | 
| Chris@284 | 2493     m_hgCommitAct->setStatusTip(tr("Commit your changes to the local repository")); | 
| Chris@273 | 2494 | 
| Chris@284 | 2495     m_hgMergeAct = new QAction(QIcon(":/images/merge.png"), tr("Merge"), this); | 
| Chris@284 | 2496     m_hgMergeAct->setStatusTip(tr("Merge the two independent sets of changes in the local repository into the working folder")); | 
| jtkorhonen@0 | 2497 | 
| jtkorhonen@0 | 2498     //Advanced actions | 
| Chris@273 | 2499 | 
| Chris@284 | 2500     m_hgIgnoreAct = new QAction(tr("Edit .hgignore File"), this); | 
| Chris@284 | 2501     m_hgIgnoreAct -> setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial")); | 
| Chris@273 | 2502 | 
| Chris@284 | 2503     m_hgServeAct = new QAction(tr("Serve via HTTP"), this); | 
| Chris@284 | 2504     m_hgServeAct -> setStatusTip(tr("Serve local repository via http for workgroup access")); | 
| jtkorhonen@11 | 2505 | 
| jtkorhonen@0 | 2506     //Help actions | 
| Chris@284 | 2507     m_aboutAct = new QAction(tr("About EasyMercurial"), this); | 
| Chris@94 | 2508 | 
| Chris@94 | 2509     // Miscellaneous | 
| Chris@199 | 2510     QShortcut *clearSelectionsShortcut = new QShortcut(Qt::Key_Escape, this); | 
| Chris@199 | 2511     connect(clearSelectionsShortcut, SIGNAL(activated()), | 
| Chris@199 | 2512             this, SLOT(clearSelections())); | 
| jtkorhonen@0 | 2513 } | 
| jtkorhonen@0 | 2514 | 
| jtkorhonen@0 | 2515 void MainWindow::createMenus() | 
| jtkorhonen@0 | 2516 { | 
| Chris@284 | 2517     m_fileMenu = menuBar()->addMenu(tr("File")); | 
| Chris@273 | 2518 | 
| Chris@284 | 2519     m_fileMenu -> addAction(m_openAct); | 
| Chris@284 | 2520     m_fileMenu -> addAction(m_changeRemoteRepoAct); | 
| Chris@284 | 2521     m_fileMenu -> addSeparator(); | 
| Chris@273 | 2522 | 
| Chris@284 | 2523     m_advancedMenu = m_fileMenu->addMenu(tr("Advanced")); | 
| Chris@273 | 2524 | 
| Chris@284 | 2525     m_fileMenu -> addAction(m_settingsAct); | 
| Chris@273 | 2526 | 
| Chris@284 | 2527     m_fileMenu -> addSeparator(); | 
| Chris@284 | 2528     m_fileMenu -> addAction(m_exitAct); | 
| Chris@273 | 2529 | 
| Chris@284 | 2530     m_advancedMenu -> addAction(m_hgIgnoreAct); | 
| Chris@284 | 2531     m_advancedMenu -> addSeparator(); | 
| Chris@284 | 2532     m_advancedMenu -> addAction(m_hgServeAct); | 
| Chris@273 | 2533 | 
| Chris@284 | 2534     m_helpMenu = menuBar()->addMenu(tr("Help")); | 
| Chris@284 | 2535     m_helpMenu->addAction(m_aboutAct); | 
| jtkorhonen@0 | 2536 } | 
| jtkorhonen@0 | 2537 | 
| jtkorhonen@0 | 2538 void MainWindow::createToolBars() | 
| jtkorhonen@0 | 2539 { | 
| Chris@284 | 2540     m_fileToolBar = addToolBar(tr("File")); | 
| Chris@284 | 2541     m_fileToolBar -> setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); | 
| Chris@284 | 2542     m_fileToolBar -> addAction(m_openAct); | 
| Chris@284 | 2543     m_fileToolBar -> addAction(m_hgRefreshAct); | 
| Chris@284 | 2544     m_fileToolBar -> setMovable(false); | 
| Chris@273 | 2545 | 
| Chris@284 | 2546     m_repoToolBar = addToolBar(tr(REPOMENU_TITLE)); | 
| Chris@284 | 2547     m_repoToolBar -> setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); | 
| Chris@284 | 2548     m_repoToolBar->addAction(m_hgIncomingAct); | 
| Chris@284 | 2549     m_repoToolBar->addAction(m_hgPullAct); | 
| Chris@284 | 2550     m_repoToolBar->addAction(m_hgPushAct); | 
| Chris@284 | 2551     m_repoToolBar -> setMovable(false); | 
| Chris@273 | 2552 | 
| Chris@284 | 2553     m_workFolderToolBar = addToolBar(tr(WORKFOLDERMENU_TITLE)); | 
| Chris@284 | 2554     addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar); | 
| Chris@284 | 2555     m_workFolderToolBar -> setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); | 
| Chris@284 | 2556     m_workFolderToolBar->addAction(m_hgFolderDiffAct); | 
| Chris@284 | 2557     m_workFolderToolBar->addSeparator(); | 
| Chris@284 | 2558     m_workFolderToolBar->addAction(m_hgRevertAct); | 
| Chris@284 | 2559     m_workFolderToolBar->addAction(m_hgUpdateAct); | 
| Chris@284 | 2560     m_workFolderToolBar->addAction(m_hgCommitAct); | 
| Chris@284 | 2561     m_workFolderToolBar->addAction(m_hgMergeAct); | 
| Chris@284 | 2562     m_workFolderToolBar->addSeparator(); | 
| Chris@284 | 2563     m_workFolderToolBar->addAction(m_hgAddAct); | 
| Chris@284 | 2564     m_workFolderToolBar->addAction(m_hgRemoveAct); | 
| Chris@284 | 2565     m_workFolderToolBar -> setMovable(false); | 
| Chris@61 | 2566 | 
| Chris@230 | 2567     updateToolBarStyle(); | 
| jtkorhonen@0 | 2568 } | 
| jtkorhonen@0 | 2569 | 
| Chris@230 | 2570 void MainWindow::updateToolBarStyle() | 
| Chris@230 | 2571 { | 
| Chris@230 | 2572     QSettings settings; | 
| Chris@230 | 2573     settings.beginGroup("Presentation"); | 
| Chris@230 | 2574     bool showText = settings.value("showiconlabels", true).toBool(); | 
| Chris@230 | 2575     settings.endGroup(); | 
| Chris@230 | 2576 | 
| Chris@230 | 2577     foreach (QToolButton *tb, findChildren<QToolButton *>()) { | 
| Chris@230 | 2578         tb->setToolButtonStyle(showText ? | 
| Chris@230 | 2579                                Qt::ToolButtonTextUnderIcon : | 
| Chris@230 | 2580                                Qt::ToolButtonIconOnly); | 
| Chris@230 | 2581     } | 
| Chris@230 | 2582 } | 
| jtkorhonen@0 | 2583 | 
| Chris@287 | 2584 void MainWindow::updateWorkFolderAndRepoNames() | 
| Chris@287 | 2585 { | 
| Chris@287 | 2586     m_hgTabs->setLocalPath(m_workFolderPath); | 
| Chris@287 | 2587 | 
| Chris@287 | 2588     m_workStatus->setLocalPath(m_workFolderPath); | 
| Chris@287 | 2589     m_workStatus->setRemoteURL(m_remoteRepoPath); | 
| Chris@287 | 2590 } | 
| Chris@287 | 2591 | 
| jtkorhonen@0 | 2592 void MainWindow::createStatusBar() | 
| jtkorhonen@0 | 2593 { | 
| jtkorhonen@0 | 2594     statusBar()->showMessage(tr("Ready")); | 
| jtkorhonen@0 | 2595 } | 
| jtkorhonen@0 | 2596 | 
| jtkorhonen@0 | 2597 void MainWindow::readSettings() | 
| jtkorhonen@0 | 2598 { | 
| jtkorhonen@0 | 2599     QDir workFolder; | 
| jtkorhonen@0 | 2600 | 
| Chris@61 | 2601     QSettings settings; | 
| jtkorhonen@0 | 2602 | 
| Chris@284 | 2603     m_remoteRepoPath = settings.value("remoterepopath", "").toString(); | 
| Chris@284 | 2604     m_workFolderPath = settings.value("workfolderpath", "").toString(); | 
| Chris@284 | 2605     if (!workFolder.exists(m_workFolderPath)) | 
| jtkorhonen@0 | 2606     { | 
| Chris@284 | 2607         m_workFolderPath = ""; | 
| jtkorhonen@0 | 2608     } | 
| jtkorhonen@0 | 2609 | 
| jtkorhonen@0 | 2610     QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint(); | 
| Chris@340 | 2611     QSize size = settings.value("size", QSize(550, 550)).toSize(); | 
| Chris@284 | 2612     m_firstStart = settings.value("firststart", QVariant(true)).toBool(); | 
| jtkorhonen@0 | 2613 | 
| jtkorhonen@0 | 2614     resize(size); | 
| jtkorhonen@0 | 2615     move(pos); | 
| jtkorhonen@0 | 2616 } | 
| jtkorhonen@0 | 2617 | 
| jtkorhonen@0 | 2618 void MainWindow::writeSettings() | 
| jtkorhonen@0 | 2619 { | 
| Chris@61 | 2620     QSettings settings; | 
| jtkorhonen@0 | 2621     settings.setValue("pos", pos()); | 
| jtkorhonen@0 | 2622     settings.setValue("size", size()); | 
| Chris@284 | 2623     settings.setValue("remoterepopath", m_remoteRepoPath); | 
| Chris@284 | 2624     settings.setValue("workfolderpath", m_workFolderPath); | 
| Chris@284 | 2625     settings.setValue("firststart", m_firstStart); | 
| jtkorhonen@0 | 2626 } | 
| jtkorhonen@0 | 2627 | 
| jtkorhonen@0 | 2628 | 
| jtkorhonen@0 | 2629 | 
| jtkorhonen@0 | 2630 |