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