annotate src/mainwindow.cpp @ 558:d932ce55c364 find

Remove the single find widget from top, add one to each tab at the bottom instead. (Turns out you don't usually want to search for the same text in both types of widget.) Also provide sensible no-results text.
author Chris Cannam
date Mon, 27 Feb 2012 17:08:26 +0000
parents a1d210c767ab
children 533519ebc0cb
rev   line source
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@359 32 #include <QWidgetAction>
Chris@125 33 #include <QRegExp>
Chris@199 34 #include <QShortcut>
Chris@237 35 #include <QUrl>
Chris@476 36 #include <QDialogButtonBox>
Chris@238 37 #include <QTimer>
Chris@494 38 #include <QTextBrowser>
jtkorhonen@0 39
Chris@53 40 #include "mainwindow.h"
Chris@69 41 #include "multichoicedialog.h"
Chris@64 42 #include "startupdialog.h"
Chris@53 43 #include "colourset.h"
Chris@62 44 #include "debug.h"
Chris@74 45 #include "logparser.h"
Chris@103 46 #include "confirmcommentdialog.h"
Chris@125 47 #include "incomingdialog.h"
Chris@175 48 #include "settingsdialog.h"
Chris@275 49 #include "moreinformationdialog.h"
Chris@331 50 #include "annotatedialog.h"
Chris@229 51 #include "version.h"
Chris@287 52 #include "workstatuswidget.h"
Chris@414 53 #include "hgignoredialog.h"
Chris@491 54 #include "versiontester.h"
Chris@540 55 #include "fswatcher.h"
Chris@53 56
jtkorhonen@0 57
Chris@172 58 MainWindow::MainWindow(QString myDirPath) :
Chris@238 59 m_myDirPath(myDirPath),
Chris@540 60 m_helpDialog(0)
jtkorhonen@0 61 {
Chris@197 62 setWindowIcon(QIcon(":images/easyhg-icon.png"));
Chris@197 63
jtkorhonen@0 64 QString wndTitle;
jtkorhonen@0 65
Chris@284 66 m_showAllFiles = false;
Chris@273 67
Chris@540 68 m_fsWatcher = new FsWatcher();
Chris@540 69 m_fsWatcherToken = m_fsWatcher->getNewToken();
Chris@540 70 m_commandSequenceInProgress = false;
Chris@541 71 connect(m_fsWatcher, SIGNAL(changed()), this, SLOT(checkFilesystem()));
Chris@540 72
Chris@284 73 m_commitsSincePush = 0;
Chris@284 74 m_shouldHgStat = true;
Chris@90 75
jtkorhonen@0 76 createActions();
jtkorhonen@0 77 createMenus();
jtkorhonen@0 78 createToolBars();
jtkorhonen@0 79 createStatusBar();
jtkorhonen@0 80
Chris@284 81 m_runner = new HgRunner(m_myDirPath, this);
Chris@284 82 connect(m_runner, SIGNAL(commandStarting(HgAction)),
Chris@241 83 this, SLOT(commandStarting(HgAction)));
Chris@284 84 connect(m_runner, SIGNAL(commandCompleted(HgAction, QString)),
Chris@109 85 this, SLOT(commandCompleted(HgAction, QString)));
Chris@537 86 connect(m_runner, SIGNAL(commandFailed(HgAction, QString, QString)),
Chris@537 87 this, SLOT(commandFailed(HgAction, QString, QString)));
Chris@284 88 statusBar()->addPermanentWidget(m_runner);
jtkorhonen@0 89
Chris@61 90 setWindowTitle(tr("EasyMercurial"));
jtkorhonen@0 91
Chris@284 92 m_remoteRepoPath = "";
Chris@284 93 m_workFolderPath = "";
jtkorhonen@0 94
jtkorhonen@0 95 readSettings();
jtkorhonen@0 96
Chris@284 97 m_justMerged = false;
Chris@210 98
Chris@210 99 QWidget *central = new QWidget(this);
Chris@210 100 setCentralWidget(central);
Chris@210 101
Chris@210 102 QGridLayout *cl = new QGridLayout(central);
Chris@287 103 int row = 0;
Chris@210 104
Chris@210 105 #ifndef Q_OS_MAC
Chris@210 106 cl->setMargin(0);
Chris@210 107 #endif
jtkorhonen@0 108
Chris@287 109 m_workStatus = new WorkStatusWidget(this);
Chris@558 110 cl->addWidget(m_workStatus, row++, 0);
Chris@287 111
Chris@287 112 m_hgTabs = new HgTabWidget(central, m_workFolderPath);
Chris@287 113 connectTabsSignals();
Chris@287 114
Chris@554 115 cl->addWidget(m_hgTabs, row++, 0, 1, 2);
Chris@287 116
Chris@284 117 connect(m_hgTabs, SIGNAL(selectionChanged()),
Chris@95 118 this, SLOT(enableDisableActions()));
Chris@484 119 connect(m_hgTabs, SIGNAL(showAllChanged()),
Chris@484 120 this, SLOT(showAllChanged()));
Chris@95 121
jtkorhonen@0 122 setUnifiedTitleAndToolBarOnMac(true);
jtkorhonen@0 123 connectActions();
Chris@120 124 clearState();
jtkorhonen@0 125 enableDisableActions();
jtkorhonen@0 126
Chris@284 127 if (m_firstStart) {
Chris@64 128 startupDialog();
jtkorhonen@0 129 }
jtkorhonen@0 130
Chris@239 131 SettingsDialog::findDefaultLocations(m_myDirPath);
Chris@112 132
Chris@64 133 ColourSet *cs = ColourSet::instance();
Chris@64 134 cs->clearDefaultNames();
Chris@64 135 cs->addDefaultName("");
Chris@153 136 cs->addDefaultName("default");
Chris@64 137 cs->addDefaultName(getUserInfo());
Chris@62 138
Chris@491 139 VersionTester *vt = new VersionTester
Chris@507 140 ("easyhg.org", "/latest-version.txt", EASYHG_VERSION);
Chris@491 141 connect(vt, SIGNAL(newerVersionAvailable(QString)),
Chris@491 142 this, SLOT(newerVersionAvailable(QString)));
Chris@491 143
Chris@175 144 hgTest();
Chris@359 145 updateRecentMenu();
jtkorhonen@0 146 }
jtkorhonen@0 147
jtkorhonen@0 148
jtkorhonen@0 149 void MainWindow::closeEvent(QCloseEvent *)
jtkorhonen@0 150 {
jtkorhonen@0 151 writeSettings();
Chris@284 152 delete m_fsWatcher;
jtkorhonen@0 153 }
jtkorhonen@0 154
jtkorhonen@0 155
Chris@554 156 void MainWindow::resizeEvent(QResizeEvent *)
Chris@554 157 {
Chris@554 158 }
Chris@554 159
Chris@554 160
Chris@64 161 QString MainWindow::getUserInfo() const
Chris@64 162 {
Chris@64 163 QSettings settings;
Chris@64 164 settings.beginGroup("User Information");
Chris@64 165 QString name = settings.value("name", getUserRealName()).toString();
Chris@64 166 QString email = settings.value("email", "").toString();
Chris@64 167
Chris@64 168 QString identifier;
Chris@64 169
Chris@64 170 if (email != "") {
Chris@64 171 identifier = QString("%1 <%2>").arg(name).arg(email);
Chris@64 172 } else {
Chris@64 173 identifier = name;
Chris@64 174 }
Chris@64 175
Chris@64 176 return identifier;
Chris@64 177 }
Chris@64 178
jtkorhonen@0 179 void MainWindow::about()
jtkorhonen@0 180 {
Chris@97 181 QMessageBox::about(this, tr("About EasyMercurial"),
Chris@229 182 tr("<qt><h2>EasyMercurial v%1</h2>"
Chris@228 183 #ifdef Q_OS_MAC
Chris@228 184 "<font size=-1>"
Chris@228 185 #endif
Chris@97 186 "<p>EasyMercurial is a simple user interface for the "
Chris@186 187 "Mercurial</a> version control system.</p>"
Chris@186 188 "<h4>Credits and Copyright</h4>"
Chris@186 189 "<p>Development carried out by Chris Cannam for "
Chris@186 190 "SoundSoftware.ac.uk at the Centre for Digital Music, "
Chris@186 191 "Queen Mary, University of London.</p>"
Chris@186 192 "<p>EasyMercurial is based on HgExplorer by "
Chris@186 193 "Jari Korhonen, with thanks.</p>"
Chris@186 194 "<p style=\"margin-left: 2em;\">"
Chris@244 195 "Copyright &copy; 2011 Queen Mary, University of London.<br>"
Chris@186 196 "Copyright &copy; 2010 Jari Korhonen.<br>"
Chris@244 197 "Copyright &copy; 2011 Chris Cannam."
Chris@186 198 "</p>"
Chris@186 199 "<p style=\"margin-left: 2em;\">"
Chris@186 200 "This program requires Mercurial, by Matt Mackall and others.<br>"
Chris@186 201 "This program uses Qt by Nokia.<br>"
Chris@186 202 "This program uses Nuvola icons by David Vignoni.<br>"
Chris@186 203 "This program may use KDiff3 by Joachim Eibl.<br>"
Chris@186 204 "This program may use PyQt by River Bank Computing.<br>"
Chris@186 205 "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others."
Chris@186 206 "</p>"
Chris@186 207 "<h4>License</h4>"
Chris@186 208 "<p>This program is free software; you can redistribute it and/or "
Chris@97 209 "modify it under the terms of the GNU General Public License as "
Chris@97 210 "published by the Free Software Foundation; either version 2 of the "
Chris@97 211 "License, or (at your option) any later version. See the file "
Chris@223 212 "COPYING included with this distribution for more information.</p>"
Chris@228 213 #ifdef Q_OS_MAC
Chris@228 214 "</font>"
Chris@228 215 #endif
Chris@229 216 ).arg(EASYHG_VERSION));
jtkorhonen@0 217 }
jtkorhonen@0 218
Chris@94 219 void MainWindow::clearSelections()
Chris@94 220 {
Chris@284 221 m_hgTabs->clearSelections();
Chris@94 222 }
jtkorhonen@0 223
Chris@484 224 void MainWindow::showAllChanged()
Chris@199 225 {
Chris@199 226 hgQueryPaths();
Chris@199 227 }
Chris@199 228
Chris@120 229 void MainWindow::hgRefresh()
Chris@120 230 {
Chris@120 231 clearState();
Chris@120 232 hgQueryPaths();
Chris@120 233 }
Chris@120 234
Chris@175 235 void MainWindow::hgTest()
Chris@175 236 {
Chris@175 237 QStringList params;
Chris@175 238 params << "--version";
Chris@382 239 // The path is not necessarily set here, but we need something for
Chris@382 240 // this test or else HgRunner will (cautiously) refuse to run
Chris@382 241 QString path = m_myDirPath;
Chris@382 242 if (path == "") path = QDir::homePath();
Chris@382 243 m_runner->requestAction(HgAction(ACT_TEST_HG, path, params));
Chris@175 244 }
Chris@175 245
Chris@200 246 void MainWindow::hgTestExtension()
Chris@200 247 {
Chris@200 248 QStringList params;
Chris@200 249 params << "--version";
Chris@382 250 // The path is not necessarily set here, but we need something for
Chris@382 251 // this test or else HgRunner will (cautiously) refuse to run
Chris@382 252 QString path = m_myDirPath;
Chris@382 253 if (path == "") path = QDir::homePath();
Chris@382 254 m_runner->requestAction(HgAction(ACT_TEST_HG_EXT, path, params));
Chris@200 255 }
Chris@200 256
jtkorhonen@0 257 void MainWindow::hgStat()
jtkorhonen@0 258 {
Chris@109 259 QStringList params;
Chris@199 260
Chris@542 261 // We always stat all files, regardless of whether we're showing
Chris@542 262 // them all, because we need them for the filesystem monitor
Chris@542 263 params << "stat" << "-A";
Chris@153 264
Chris@284 265 m_lastStatOutput = "";
Chris@273 266
Chris@541 267 // We're about to do a stat, so we can silently bring ourselves
Chris@541 268 // up-to-date on any file changes to this point
Chris@541 269 (void)m_fsWatcher->getChangedPaths(m_fsWatcherToken);
Chris@541 270
Chris@284 271 m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params));
jtkorhonen@0 272 }
jtkorhonen@0 273
Chris@109 274 void MainWindow::hgQueryPaths()
Chris@74 275 {
Chris@484 276 m_showAllFiles = m_hgTabs->shouldShowAll();
Chris@484 277
Chris@210 278 // Quickest is to just read the file
Chris@198 279
Chris@284 280 QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc");
Chris@198 281
Chris@210 282 QString path;
Chris@210 283
Chris@198 284 if (hgrc.exists()) {
Chris@198 285 QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat);
Chris@198 286 s.beginGroup("paths");
Chris@210 287 path = s.value("default").toString();
Chris@210 288 }
Chris@198 289
Chris@553 290 std::cerr << "hgQueryPaths: setting m_remoteRepoPath to " << m_remoteRepoPath << " from file " << hgrc.absoluteFilePath() << std::endl;
Chris@553 291
Chris@284 292 m_remoteRepoPath = path;
Chris@198 293
Chris@210 294 // We have to do this here, because commandCompleted won't be called
Chris@284 295 MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
Chris@284 296 MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
Chris@287 297 updateWorkFolderAndRepoNames();
Chris@210 298
Chris@210 299 hgQueryBranch();
Chris@210 300 return;
Chris@210 301
Chris@210 302 /* The classic method!
Chris@198 303
Chris@109 304 QStringList params;
Chris@109 305 params << "paths";
Chris@284 306 m_runner->requestAction(HgAction(ACT_QUERY_PATHS, m_workFolderPath, params));
Chris@210 307 */
Chris@74 308 }
Chris@74 309
Chris@109 310 void MainWindow::hgQueryBranch()
Chris@106 311 {
Chris@210 312 // Quickest is to just read the file
Chris@198 313
Chris@284 314 QFile hgbr(m_workFolderPath + "/.hg/branch");
Chris@198 315
Chris@210 316 QString br = "default";
Chris@210 317
Chris@198 318 if (hgbr.exists() && hgbr.open(QFile::ReadOnly)) {
Chris@210 319 QByteArray ba = hgbr.readLine();
Chris@210 320 br = QString::fromUtf8(ba).trimmed();
Chris@210 321 }
Chris@210 322
Chris@284 323 m_currentBranch = br;
Chris@210 324
Chris@210 325 // We have to do this here, because commandCompleted won't be called
Chris@210 326 hgStat();
Chris@210 327 return;
Chris@198 328
Chris@210 329 /* The classic method!
Chris@198 330
Chris@109 331 QStringList params;
Chris@109 332 params << "branch";
Chris@284 333 m_runner->requestAction(HgAction(ACT_QUERY_BRANCH, m_workFolderPath, params));
Chris@210 334 */
Chris@106 335 }
Chris@106 336
Chris@506 337 void MainWindow::hgQueryHeadsActive()
Chris@506 338 {
Chris@506 339 QStringList params;
Chris@514 340 params << "heads";
Chris@506 341 m_runner->requestAction(HgAction(ACT_QUERY_HEADS_ACTIVE, m_workFolderPath, params));
Chris@506 342 }
Chris@506 343
Chris@109 344 void MainWindow::hgQueryHeads()
jtkorhonen@0 345 {
Chris@109 346 QStringList params;
Chris@137 347 // On empty repos, "hg heads" will fail -- we don't care about
Chris@137 348 // that. Use --closed option so as to include closed branches;
Chris@137 349 // otherwise we'll be stuck if the user updates into one, and our
Chris@137 350 // incremental log will end up with spurious stuff in it because
Chris@137 351 // we won't be pruning at the ends of closed branches
Chris@137 352 params << "heads" << "--closed";
Chris@284 353 m_runner->requestAction(HgAction(ACT_QUERY_HEADS, m_workFolderPath, params));
jtkorhonen@0 354 }
jtkorhonen@0 355
jtkorhonen@0 356 void MainWindow::hgLog()
jtkorhonen@0 357 {
Chris@109 358 QStringList params;
Chris@109 359 params << "log";
Chris@109 360 params << "--template";
Chris@125 361 params << Changeset::getLogTemplate();
Chris@109 362
Chris@284 363 m_runner->requestAction(HgAction(ACT_LOG, m_workFolderPath, params));
Chris@109 364 }
Chris@109 365
Chris@150 366 void MainWindow::hgLogIncremental(QStringList prune)
Chris@120 367 {
Chris@305 368 // Sometimes we can be called with prune empty -- it represents
Chris@305 369 // the current heads, but if we have none already and for some
Chris@305 370 // reason are being prompted for an incremental update, we may run
Chris@305 371 // into trouble. In that case, make this a full log instead
Chris@305 372
Chris@305 373 if (prune.empty()) {
Chris@305 374 hgLog();
Chris@305 375 return;
Chris@305 376 }
Chris@305 377
Chris@120 378 QStringList params;
Chris@120 379 params << "log";
Chris@120 380
Chris@150 381 foreach (QString p, prune) {
Chris@153 382 params << "--prune" << Changeset::hashOf(p);
Chris@120 383 }
Chris@120 384
Chris@120 385 params << "--template";
Chris@125 386 params << Changeset::getLogTemplate();
Chris@120 387
Chris@284 388 m_runner->requestAction(HgAction(ACT_LOG_INCREMENTAL, m_workFolderPath, params));
Chris@120 389 }
Chris@109 390
Chris@109 391 void MainWindow::hgQueryParents()
Chris@109 392 {
Chris@109 393 QStringList params;
Chris@109 394 params << "parents";
Chris@284 395 m_runner->requestAction(HgAction(ACT_QUERY_PARENTS, m_workFolderPath, params));
Chris@109 396 }
Chris@109 397
Chris@326 398 void MainWindow::hgAnnotateFiles(QStringList files)
Chris@326 399 {
Chris@326 400 QStringList params;
Chris@326 401
Chris@326 402 if (!files.isEmpty()) {
Chris@332 403 params << "annotate" << "-udqc" << "--" << files;
Chris@326 404 m_runner->requestAction(HgAction(ACT_ANNOTATE, m_workFolderPath, params));
Chris@326 405 }
Chris@326 406 }
Chris@326 407
Chris@109 408 void MainWindow::hgResolveList()
Chris@109 409 {
Chris@109 410 QStringList params;
jtkorhonen@0 411
Chris@109 412 params << "resolve" << "--list";
Chris@284 413 m_runner->requestAction(HgAction(ACT_RESOLVE_LIST, m_workFolderPath, params));
Chris@109 414 }
Chris@109 415
Chris@109 416 void MainWindow::hgAdd()
jtkorhonen@0 417 {
Chris@109 418 // hgExplorer permitted adding "all" files -- I'm not sure
Chris@109 419 // that one is a good idea, let's require the user to select
jtkorhonen@0 420
Chris@326 421 hgAddFiles(m_hgTabs->getSelectedAddableFiles());
Chris@326 422 }
Chris@326 423
Chris@326 424 void MainWindow::hgAddFiles(QStringList files)
Chris@326 425 {
Chris@326 426 QStringList params;
Chris@109 427
Chris@109 428 if (!files.empty()) {
Chris@109 429 params << "add" << "--" << files;
Chris@284 430 m_runner->requestAction(HgAction(ACT_ADD, m_workFolderPath, params));
jtkorhonen@0 431 }
jtkorhonen@0 432 }
jtkorhonen@0 433
Chris@98 434 void MainWindow::hgRemove()
Chris@98 435 {
Chris@326 436 hgRemoveFiles(m_hgTabs->getSelectedRemovableFiles());
Chris@326 437 }
Chris@326 438
Chris@326 439 void MainWindow::hgRemoveFiles(QStringList files)
Chris@326 440 {
Chris@109 441 QStringList params;
Chris@98 442
Chris@109 443 if (!files.empty()) {
Chris@109 444 params << "remove" << "--after" << "--force" << "--" << files;
Chris@284 445 m_runner->requestAction(HgAction(ACT_REMOVE, m_workFolderPath, params));
Chris@109 446 }
jtkorhonen@0 447 }
jtkorhonen@0 448
jtkorhonen@0 449 void MainWindow::hgCommit()
jtkorhonen@0 450 {
Chris@326 451 hgCommitFiles(QStringList());
Chris@326 452 }
Chris@326 453
Chris@326 454 void MainWindow::hgCommitFiles(QStringList files)
Chris@326 455 {
Chris@109 456 QStringList params;
Chris@109 457 QString comment;
Chris@94 458
Chris@284 459 if (m_justMerged) {
Chris@284 460 comment = m_mergeCommitComment;
Chris@157 461 }
Chris@157 462
Chris@284 463 QStringList allFiles = m_hgTabs->getAllCommittableFiles();
Chris@127 464 QStringList reportFiles = files;
Chris@237 465 if (reportFiles.empty()) {
Chris@237 466 reportFiles = allFiles;
Chris@237 467 }
Chris@103 468
Chris@237 469 QString subsetNote;
Chris@237 470 if (reportFiles != allFiles) {
Chris@237 471 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 472 }
Chris@237 473
Chris@155 474 QString cf(tr("Commit files"));
Chris@155 475
Chris@311 476 QString branchText;
Chris@311 477 if (m_currentBranch == "" || m_currentBranch == "default") {
Chris@311 478 branchText = tr("the default branch");
Chris@311 479 } else {
Chris@311 480 branchText = tr("branch \"%1\"").arg(m_currentBranch);
Chris@311 481 }
Chris@311 482
Chris@109 483 if (ConfirmCommentDialog::confirmAndGetLongComment
Chris@109 484 (this,
Chris@155 485 cf,
Chris@237 486 tr("<h3>%1</h3><p>%2%3").arg(cf)
Chris@473 487 .arg(tr("You are about to commit changes to the following files in %1:").arg(branchText))
Chris@237 488 .arg(subsetNote),
Chris@237 489 tr("<h3>%1</h3><p>%2%3").arg(cf)
Chris@473 490 .arg(tr("You are about to commit changes to %n file(s) in %1.", "", reportFiles.size()).arg(branchText))
Chris@237 491 .arg(subsetNote),
Chris@127 492 reportFiles,
Chris@193 493 comment,
Chris@365 494 tr("Co&mmit"))) {
Chris@103 495
Chris@284 496 if (!m_justMerged && !files.empty()) {
Chris@157 497 // User wants to commit selected file(s) (and this is not
Chris@157 498 // merge commit, which would fail if we selected files)
Chris@157 499 params << "commit" << "--message" << comment
Chris@157 500 << "--user" << getUserInfo() << "--" << files;
Chris@109 501 } else {
Chris@109 502 // Commit all changes
Chris@157 503 params << "commit" << "--message" << comment
Chris@157 504 << "--user" << getUserInfo();
jtkorhonen@0 505 }
Chris@109 506
Chris@284 507 m_runner->requestAction(HgAction(ACT_COMMIT, m_workFolderPath, params));
Chris@284 508 m_mergeCommitComment = "";
jtkorhonen@0 509 }
jtkorhonen@0 510 }
jtkorhonen@0 511
jtkorhonen@34 512 QString MainWindow::filterTag(QString tag)
jtkorhonen@34 513 {
Chris@278 514 for(int i = 0; i < tag.size(); i++) {
Chris@278 515 if (tag[i].isLower() || tag[i].isUpper() ||
Chris@278 516 tag[i].isDigit() || (tag[i] == QChar('.'))) {
jtkorhonen@34 517 //ok
Chris@278 518 } else {
jtkorhonen@34 519 tag[i] = QChar('_');
jtkorhonen@34 520 }
jtkorhonen@34 521 }
jtkorhonen@34 522 return tag;
jtkorhonen@34 523 }
jtkorhonen@34 524
jtkorhonen@34 525
Chris@311 526 void MainWindow::hgNewBranch()
Chris@278 527 {
Chris@278 528 QStringList params;
Chris@278 529 QString branch;
Chris@278 530
Chris@278 531 if (ConfirmCommentDialog::confirmAndGetShortComment
Chris@278 532 (this,
Chris@278 533 tr("New Branch"),
Chris@278 534 tr("Enter new branch name:"),
Chris@278 535 branch,
Chris@365 536 tr("Start &Branch"))) {
Chris@278 537 if (!branch.isEmpty()) {//!!! do something better if it is empty
Chris@278 538
Chris@278 539 params << "branch" << filterTag(branch);
Chris@307 540 m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params));
Chris@278 541 }
Chris@278 542 }
Chris@278 543 }
Chris@278 544
Chris@311 545 void MainWindow::hgNoBranch()
Chris@311 546 {
Chris@311 547 if (m_currentParents.empty()) return;
Chris@311 548
Chris@311 549 QString parentBranch = m_currentParents[0]->branch();
Chris@311 550 if (parentBranch == "") parentBranch = "default";
Chris@311 551
Chris@311 552 QStringList params;
Chris@311 553 params << "branch" << parentBranch;
Chris@311 554 m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params));
Chris@311 555 }
Chris@311 556
Chris@514 557 void MainWindow::hgCloseBranch()
Chris@514 558 {
Chris@514 559 QStringList params;
Chris@514 560
Chris@514 561 //!!! how to ensure this doesn't happen when uncommitted changes present?
Chris@514 562
Chris@514 563 QString cf(tr("Close branch"));
Chris@514 564 QString comment;
Chris@514 565
Chris@514 566 QString defaultWarning;
Chris@514 567
Chris@514 568 QString branchText;
Chris@514 569 if (m_currentBranch == "" || m_currentBranch == "default") {
Chris@514 570 branchText = tr("the default branch");
Chris@514 571 defaultWarning = tr("<p><b>Warning:</b> you are asking to close the default branch. This is not usually a good idea!</p>");
Chris@514 572 } else {
Chris@514 573 branchText = tr("branch \"%1\"").arg(m_currentBranch);
Chris@514 574 }
Chris@514 575
Chris@514 576 if (ConfirmCommentDialog::confirmAndGetLongComment
Chris@514 577 (this,
Chris@514 578 cf,
Chris@514 579 tr("<h3>%1</h3><p>%2%3").arg(cf)
Chris@518 580 .arg(tr("You are about to close %1.<p>This branch will be marked as closed and hidden from the history view.<p>You will still be able to see if it you select \"Show closed branches\" in the history view, and it will be reopened if you commit to it.<p>Please enter your comment for the commit log:").arg(branchText))
Chris@514 581 .arg(defaultWarning),
Chris@514 582 comment,
Chris@514 583 tr("C&lose branch"))) {
Chris@514 584
Chris@514 585 params << "commit" << "--message" << comment
Chris@514 586 << "--user" << getUserInfo() << "--close-branch";
Chris@514 587
Chris@514 588 m_runner->requestAction(HgAction(ACT_CLOSE_BRANCH, m_workFolderPath, params));
Chris@514 589 }
Chris@514 590 }
Chris@514 591
Chris@164 592 void MainWindow::hgTag(QString id)
jtkorhonen@34 593 {
Chris@109 594 QStringList params;
Chris@109 595 QString tag;
jtkorhonen@34 596
Chris@109 597 if (ConfirmCommentDialog::confirmAndGetShortComment
Chris@109 598 (this,
Chris@109 599 tr("Tag"),
Chris@109 600 tr("Enter tag:"),
Chris@193 601 tag,
Chris@365 602 tr("Add &Tag"))) {
Chris@164 603 if (!tag.isEmpty()) {//!!! do something better if it is empty
Chris@164 604
Chris@164 605 params << "tag" << "--user" << getUserInfo();
Chris@164 606 params << "--rev" << Changeset::hashOf(id) << filterTag(tag);
Chris@109 607
Chris@284 608 m_runner->requestAction(HgAction(ACT_TAG, m_workFolderPath, params));
jtkorhonen@34 609 }
jtkorhonen@34 610 }
jtkorhonen@34 611 }
jtkorhonen@34 612
Chris@417 613 void MainWindow::initHgIgnore()
jtkorhonen@34 614 {
Chris@284 615 if (!QDir(m_workFolderPath).exists()) return;
Chris@417 616 QString hgIgnorePath = m_workFolderPath + "/.hgignore";
Chris@417 617
Chris@249 618 QFile f(hgIgnorePath);
Chris@249 619 if (!f.exists()) {
Chris@249 620 f.open(QFile::WriteOnly);
Chris@249 621 QTextStream *ts = new QTextStream(&f);
Chris@249 622 *ts << "syntax: glob\n";
Chris@249 623 delete ts;
Chris@249 624 f.close();
Chris@249 625 }
Chris@417 626 }
Chris@417 627
Chris@425 628 void MainWindow::hgIgnore()
Chris@425 629 {
Chris@425 630 // hgExplorer permitted adding "all" files -- I'm not sure
Chris@425 631 // that one is a good idea, let's require the user to select
Chris@425 632
Chris@425 633 hgIgnoreFiles(m_hgTabs->getSelectedAddableFiles());
Chris@425 634 }
Chris@425 635
Chris@417 636 void MainWindow::hgEditIgnore()
Chris@417 637 {
Chris@417 638 if (!QDir(m_workFolderPath).exists()) return;
Chris@417 639
Chris@417 640 initHgIgnore();
Chris@417 641
Chris@417 642 QString hgIgnorePath = m_workFolderPath + "/.hgignore";
Chris@476 643
Chris@476 644 QFile f(hgIgnorePath);
Chris@476 645 if (!f.exists()) return; // shouldn't happen (after initHgIgnore called)
Chris@476 646
Chris@476 647 if (!f.open(QFile::ReadOnly)) return;
Chris@476 648 QTextStream sin(&f);
Chris@476 649 QString all = sin.readAll();
Chris@476 650 f.close();
Chris@476 651
Chris@476 652 QDialog d;
Chris@476 653 QGridLayout layout;
Chris@476 654 d.setLayout(&layout);
Chris@476 655
Chris@476 656 int row = 0;
Chris@476 657 layout.addWidget(new QLabel(tr("<qt><h3>Ignored File Patterns</h3></qt>")), row++, 0);//!!! todo: link to Hg docs?
Chris@476 658
Chris@476 659 QTextEdit ed;
Chris@476 660 ed.setAcceptRichText(false);
Chris@476 661 ed.setLineWrapMode(QTextEdit::NoWrap);
Chris@476 662 layout.setRowStretch(row, 10);
Chris@476 663 layout.addWidget(&ed, row++, 0);
Chris@179 664
Chris@476 665 QDialogButtonBox bb(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
Chris@476 666 connect(bb.button(QDialogButtonBox::Save), SIGNAL(clicked()),
Chris@476 667 &d, SLOT(accept()));
Chris@476 668 connect(bb.button(QDialogButtonBox::Cancel), SIGNAL(clicked()),
Chris@476 669 &d, SLOT(reject()));
Chris@476 670 layout.addWidget(&bb, row++, 0);
Chris@476 671
Chris@476 672 ed.document()->setPlainText(all);
Chris@476 673
Chris@476 674 d.resize(QSize(300, 400));
Chris@476 675
Chris@476 676 if (d.exec() == QDialog::Accepted) {
Chris@476 677 if (!f.open(QFile::WriteOnly | QFile::Truncate)) {
Chris@476 678 QMessageBox::critical(this, tr("Write failed"),
Chris@476 679 tr("Failed to open file %1 for writing")
Chris@476 680 .arg(f.fileName()));
Chris@476 681 return;
Chris@476 682 }
Chris@476 683 QTextStream sout(&f);
Chris@476 684 sout << ed.document()->toPlainText();
Chris@476 685 f.close();
Chris@179 686 }
Chris@179 687 }
Chris@179 688
Chris@421 689 static QString regexEscape(QString filename)
Chris@421 690 {
Chris@421 691 return filename
Chris@421 692 .replace(".", "\\.")
Chris@421 693 .replace("[", "\\[")
Chris@421 694 .replace("]", "\\]")
Chris@421 695 .replace("(", "\\(")
Chris@421 696 .replace(")", "\\)")
Chris@421 697 .replace("?", "\\?");
Chris@421 698 }
Chris@421 699
Chris@326 700 void MainWindow::hgIgnoreFiles(QStringList files)
Chris@326 701 {
Chris@417 702 if (!QDir(m_workFolderPath).exists() || files.empty()) return;
Chris@413 703
Chris@413 704 // we should:
Chris@413 705 //
Chris@413 706 // * show the user the list of file names selected
Chris@413 707 //
Chris@413 708 // * offer a choice (depending on the files selected?)
Chris@413 709 //
Chris@413 710 // - ignore only these files
Chris@413 711 //
Chris@413 712 // - ignore files with these names, in any subdirectories?
Chris@413 713 //
Chris@413 714 // - ignore all files with this extension (if they have a common
Chris@413 715 // extension)?
Chris@413 716 //
Chris@413 717 // - ignore all files with these extensions (if they have any
Chris@413 718 // extensions?)
Chris@413 719
Chris@414 720 DEBUG << "MainWindow::hgIgnoreFiles: File names are:" << endl;
Chris@414 721 foreach (QString file, files) DEBUG << file << endl;
Chris@414 722
Chris@414 723 QSet<QString> suffixes;
Chris@414 724 foreach (QString file, files) {
Chris@414 725 QString s = QFileInfo(file).suffix();
Chris@414 726 if (s != "") suffixes.insert(s);
Chris@414 727 }
Chris@414 728
Chris@419 729 QString directory;
Chris@429 730 int dirCount = 0;
Chris@419 731 foreach (QString file, files) {
Chris@419 732 QString d = QFileInfo(file).path();
Chris@419 733 if (d != directory) {
Chris@419 734 ++dirCount;
Chris@419 735 directory = d;
Chris@419 736 }
Chris@419 737 }
Chris@421 738 if (dirCount != 1 || directory == ".") directory = "";
Chris@419 739
Chris@414 740 HgIgnoreDialog::IgnoreType itype =
Chris@415 741 HgIgnoreDialog::confirmIgnore
Chris@419 742 (this, files, QStringList::fromSet(suffixes), directory);
Chris@414 743
Chris@416 744 DEBUG << "hgIgnoreFiles: Ignore type is " << itype << endl;
Chris@417 745
Chris@421 746 if (itype == HgIgnoreDialog::IgnoreNothing) return;
Chris@421 747
Chris@417 748 // Now, .hgignore can be switched from regex to glob syntax
Chris@417 749 // part-way through -- and glob is much simpler for us, so we
Chris@417 750 // should do that if it's in regex mode at the end of the file.
Chris@417 751
Chris@417 752 initHgIgnore();
Chris@417 753
Chris@417 754 QString hgIgnorePath = m_workFolderPath + "/.hgignore";
Chris@417 755
Chris@417 756 // hgignore file should now exist (initHgIgnore should have
Chris@417 757 // created it if it didn't). Check for glob status first
Chris@417 758
Chris@417 759 QFile f(hgIgnorePath);
Chris@417 760 if (!f.exists()) {
Chris@417 761 std::cerr << "MainWindow::ignoreFiles: Internal error: .hgignore file not found (even though we were supposed to have created it)" << std::endl;
Chris@417 762 return;
Chris@417 763 }
Chris@417 764
Chris@417 765 f.open(QFile::ReadOnly);
Chris@417 766 bool glob = false;
Chris@417 767 while (!f.atEnd()) {
Chris@417 768 QByteArray ba = f.readLine();
Chris@417 769 QString s = QString::fromLocal8Bit(ba).trimmed();
Chris@417 770 if (s.startsWith("syntax:")) {
Chris@417 771 if (s.endsWith("glob")) {
Chris@417 772 glob = true;
Chris@417 773 } else {
Chris@417 774 glob = false;
Chris@417 775 }
Chris@417 776 }
Chris@417 777 }
Chris@417 778 f.close();
Chris@417 779
Chris@417 780 f.open(QFile::Append);
Chris@417 781 QTextStream out(&f);
Chris@417 782
Chris@417 783 if (!glob) {
Chris@417 784 out << "syntax: glob" << endl;
Chris@417 785 }
Chris@417 786
Chris@421 787 QString info = "<qt><h3>" + tr("Ignored files") + "</h3><p>";
Chris@421 788 info += tr("The following lines have been added to the .hgignore file for this working copy:");
Chris@421 789 info += "</p><code>";
Chris@421 790
Chris@421 791 QStringList args;
Chris@417 792 if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenSuffixes) {
Chris@421 793 args = QStringList::fromSet(suffixes);
Chris@421 794 } else if (itype == HgIgnoreDialog::IgnoreGivenFilesOnly) {
Chris@421 795 args = files;
Chris@421 796 } else if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenNames) {
Chris@421 797 QSet<QString> names;
Chris@421 798 foreach (QString f, files) {
Chris@421 799 names << QFileInfo(f).fileName();
Chris@417 800 }
Chris@421 801 args = QStringList::fromSet(names);
Chris@421 802 } else if (itype == HgIgnoreDialog::IgnoreWholeDirectory) {
Chris@421 803 args << directory;
Chris@421 804 }
Chris@421 805
Chris@421 806 bool first = true;
Chris@421 807
Chris@421 808 foreach (QString a, args) {
Chris@421 809 QString line;
Chris@421 810 if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenSuffixes) {
Chris@421 811 line = "*." + a;
Chris@421 812 } else if (itype == HgIgnoreDialog::IgnoreGivenFilesOnly) {
Chris@421 813 // Doesn't seem to be possible to do this with a glob,
Chris@421 814 // because the glob is always unanchored and there is no
Chris@421 815 // equivalent of ^ to anchor it
chris@423 816 line = "re:^" + regexEscape(a) + "$";
Chris@421 817 } else if (itype == HgIgnoreDialog::IgnoreAllFilesOfGivenNames) {
Chris@421 818 line = a;
Chris@421 819 } else if (itype == HgIgnoreDialog::IgnoreWholeDirectory) {
Chris@421 820 line = "re:^" + regexEscape(a) + "/";
Chris@418 821 }
Chris@421 822 if (line != "") {
Chris@421 823 out << line << endl;
Chris@421 824 if (!first) info += "<br>";
Chris@421 825 first = false;
Chris@421 826 info += xmlEncode(line);
Chris@417 827 }
Chris@417 828 }
Chris@417 829
Chris@419 830 f.close();
Chris@419 831
Chris@421 832 info += "</code></qt>";
Chris@421 833
Chris@421 834 QMessageBox::information(this, tr("Ignored files"),
Chris@421 835 info);
Chris@421 836
Chris@417 837 hgRefresh();
Chris@326 838 }
Chris@326 839
Chris@326 840 void MainWindow::hgUnIgnoreFiles(QStringList files)
Chris@326 841 {
Chris@421 842 // Not implemented: edit the .hgignore instead
Chris@421 843 hgEditIgnore();
Chris@326 844 }
Chris@326 845
Chris@239 846 QString MainWindow::getDiffBinaryName()
Chris@179 847 {
Chris@179 848 QSettings settings;
Chris@179 849 settings.beginGroup("Locations");
Chris@239 850 return settings.value("extdiffbinary", "").toString();
Chris@179 851 }
Chris@179 852
Chris@239 853 QString MainWindow::getMergeBinaryName()
Chris@179 854 {
Chris@179 855 QSettings settings;
Chris@179 856 settings.beginGroup("Locations");
Chris@239 857 return settings.value("mergebinary", "").toString();
Chris@179 858 }
Chris@179 859
Chris@168 860 void MainWindow::hgShowSummary()
Chris@168 861 {
Chris@168 862 QStringList params;
Chris@168 863
Chris@168 864 params << "diff" << "--stat";
Chris@168 865
Chris@288 866 m_runner->requestAction(HgAction(ACT_UNCOMMITTED_SUMMARY, m_workFolderPath, params));
Chris@168 867 }
Chris@168 868
jtkorhonen@0 869 void MainWindow::hgFolderDiff()
jtkorhonen@0 870 {
Chris@326 871 hgDiffFiles(QStringList());
Chris@326 872 }
Chris@326 873
Chris@326 874 void MainWindow::hgDiffFiles(QStringList files)
Chris@326 875 {
Chris@239 876 QString diff = getDiffBinaryName();
Chris@179 877 if (diff == "") return;
Chris@112 878
Chris@109 879 QStringList params;
jtkorhonen@0 880
Chris@112 881 // Diff parent against working folder (folder diff)
Chris@112 882
Chris@148 883 params << "--config" << "extensions.extdiff=" << "extdiff";
Chris@179 884 params << "--program" << diff;
Chris@109 885
Chris@331 886 params << "--" << files; // may be none: whole dir
Chris@273 887
Chris@284 888 m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params));
jtkorhonen@0 889 }
jtkorhonen@0 890
Chris@148 891 void MainWindow::hgDiffToCurrent(QString id)
jtkorhonen@0 892 {
Chris@239 893 QString diff = getDiffBinaryName();
Chris@179 894 if (diff == "") return;
Chris@163 895
Chris@148 896 QStringList params;
jtkorhonen@0 897
Chris@148 898 // Diff given revision against working folder
jtkorhonen@0 899
Chris@148 900 params << "--config" << "extensions.extdiff=" << "extdiff";
Chris@179 901 params << "--program" << diff;
Chris@153 902 params << "--rev" << Changeset::hashOf(id);
Chris@148 903
Chris@284 904 m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params));
jtkorhonen@0 905 }
jtkorhonen@0 906
Chris@148 907 void MainWindow::hgDiffToParent(QString child, QString parent)
Chris@148 908 {
Chris@239 909 QString diff = getDiffBinaryName();
Chris@179 910 if (diff == "") return;
Chris@163 911
Chris@148 912 QStringList params;
Chris@148 913
Chris@281 914 // Diff given revision against parent revision
Chris@148 915
Chris@148 916 params << "--config" << "extensions.extdiff=" << "extdiff";
Chris@179 917 params << "--program" << diff;
Chris@153 918 params << "--rev" << Changeset::hashOf(parent)
Chris@153 919 << "--rev" << Changeset::hashOf(child);
Chris@148 920
Chris@284 921 m_runner->requestAction(HgAction(ACT_CHGSETDIFF, m_workFolderPath, params));
Chris@273 922 }
Chris@326 923
Chris@273 924
Chris@289 925 void MainWindow::hgShowSummaryFor(Changeset *cs)
Chris@288 926 {
Chris@288 927 QStringList params;
Chris@288 928
Chris@289 929 // This will pick a default parent if there is more than one
Chris@289 930 // (whereas with diff we need to supply one). But it does need a
Chris@289 931 // bit more parsing
Chris@289 932 params << "log" << "--stat" << "--rev" << Changeset::hashOf(cs->id());
Chris@289 933
Chris@289 934 m_runner->requestAction(HgAction(ACT_DIFF_SUMMARY, m_workFolderPath,
Chris@289 935 params, cs));
Chris@148 936 }
Chris@148 937
jtkorhonen@0 938
jtkorhonen@0 939 void MainWindow::hgUpdate()
jtkorhonen@0 940 {
Chris@109 941 QStringList params;
jtkorhonen@0 942
Chris@109 943 params << "update";
Chris@109 944
Chris@284 945 m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params));
jtkorhonen@0 946 }
jtkorhonen@0 947
jtkorhonen@0 948
Chris@148 949 void MainWindow::hgUpdateToRev(QString id)
jtkorhonen@0 950 {
Chris@148 951 QStringList params;
jtkorhonen@0 952
Chris@153 953 params << "update" << "--rev" << Changeset::hashOf(id) << "--check";
jtkorhonen@0 954
Chris@284 955 m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params));
jtkorhonen@0 956 }
jtkorhonen@0 957
jtkorhonen@0 958
jtkorhonen@0 959 void MainWindow::hgRevert()
jtkorhonen@0 960 {
Chris@326 961 hgRevertFiles(QStringList());
Chris@326 962 }
Chris@326 963
Chris@326 964 void MainWindow::hgRevertFiles(QStringList files)
Chris@326 965 {
Chris@109 966 QStringList params;
Chris@109 967 QString comment;
Chris@237 968 bool all = false;
Chris@98 969
Chris@284 970 QStringList allFiles = m_hgTabs->getAllRevertableFiles();
Chris@237 971 if (files.empty() || files == allFiles) {
Chris@237 972 files = allFiles;
Chris@237 973 all = true;
Chris@237 974 }
Chris@237 975
Chris@237 976 QString subsetNote;
Chris@237 977 if (!all) {
Chris@237 978 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 979 }
Chris@155 980
Chris@155 981 QString rf(tr("Revert files"));
Chris@237 982
Chris@237 983 // Set up params before asking for confirmation, because there is
Chris@237 984 // a failure case here that we would need to report on early
Chris@237 985
Chris@284 986 DEBUG << "hgRevert: m_justMerged = " << m_justMerged << ", m_mergeTargetRevision = " << m_mergeTargetRevision << endl;
Chris@273 987
Chris@284 988 if (m_justMerged) {
Chris@237 989
Chris@237 990 // This is a little fiddly. The proper way to "revert" the
Chris@237 991 // whole of an uncommitted merge is with "hg update --clean ."
Chris@237 992 // But if the user has selected only some files, we're sort of
Chris@237 993 // promising to revert only those, which means we need to
Chris@237 994 // specify which parent to revert to. We can only do that if
Chris@237 995 // we have a record of it, which we do if you just did the
Chris@237 996 // merge from within easyhg but don't if you've exited and
Chris@237 997 // restarted, or changed repository, since then. Hmmm.
Chris@237 998
Chris@237 999 if (all) {
Chris@237 1000 params << "update" << "--clean" << ".";
Chris@237 1001 } else {
Chris@284 1002 if (m_mergeTargetRevision != "") {
Chris@237 1003 params << "revert" << "--rev"
Chris@284 1004 << Changeset::hashOf(m_mergeTargetRevision)
Chris@237 1005 << "--" << files;
Chris@237 1006 } else {
Chris@237 1007 QMessageBox::information
Chris@237 1008 (this, tr("Unable to revert"),
Chris@237 1009 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 1010 return;
Chris@237 1011 }
Chris@237 1012 }
Chris@237 1013 } else {
Chris@237 1014 params << "revert" << "--" << files;
Chris@237 1015 }
Chris@237 1016
Chris@109 1017 if (ConfirmCommentDialog::confirmDangerousFilesAction
Chris@109 1018 (this,
Chris@155 1019 rf,
Chris@237 1020 tr("<h3>%1</h3><p>%2%3").arg(rf)
Chris@237 1021 .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 1022 .arg(subsetNote),
Chris@237 1023 tr("<h3>%1</h3><p>%2%3").arg(rf)
Chris@237 1024 .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 1025 .arg(subsetNote),
Chris@193 1026 files,
Chris@365 1027 tr("Re&vert"))) {
Chris@163 1028
Chris@284 1029 m_lastRevertedFiles = files;
Chris@109 1030
Chris@284 1031 m_runner->requestAction(HgAction(ACT_REVERT, m_workFolderPath, params));
jtkorhonen@0 1032 }
jtkorhonen@0 1033 }
jtkorhonen@0 1034
Chris@163 1035
Chris@361 1036 void MainWindow::hgRenameFiles(QStringList files)
Chris@361 1037 {
Chris@361 1038 QString renameTo;
Chris@361 1039
Chris@361 1040 QString file;
Chris@361 1041 if (files.empty()) return;
Chris@361 1042 file = files[0];
Chris@361 1043
Chris@361 1044 if (ConfirmCommentDialog::confirmAndGetShortComment
Chris@361 1045 (this,
Chris@361 1046 tr("Rename"),
Chris@361 1047 tr("Rename <code>%1</code> to:").arg(xmlEncode(file)),
Chris@361 1048 renameTo,
Chris@365 1049 tr("Re&name"))) {
Chris@361 1050
Chris@361 1051 if (renameTo != "" && renameTo != file) {
Chris@361 1052
Chris@361 1053 QStringList params;
Chris@361 1054
Chris@361 1055 params << "rename" << "--" << file << renameTo;
Chris@361 1056
Chris@361 1057 m_runner->requestAction(HgAction(ACT_RENAME_FILE, m_workFolderPath, params));
Chris@361 1058 }
Chris@361 1059 }
Chris@361 1060 }
Chris@361 1061
Chris@361 1062
Chris@361 1063 void MainWindow::hgCopyFiles(QStringList files)
Chris@361 1064 {
Chris@361 1065 QString copyTo;
Chris@361 1066
Chris@361 1067 QString file;
Chris@361 1068 if (files.empty()) return;
Chris@361 1069 file = files[0];
Chris@361 1070
Chris@361 1071 if (ConfirmCommentDialog::confirmAndGetShortComment
Chris@361 1072 (this,
Chris@361 1073 tr("Copy"),
Chris@361 1074 tr("Copy <code>%1</code> to:").arg(xmlEncode(file)),
Chris@361 1075 copyTo,
Chris@365 1076 tr("Co&py"))) {
Chris@361 1077
Chris@361 1078 if (copyTo != "" && copyTo != file) {
Chris@361 1079
Chris@361 1080 QStringList params;
Chris@361 1081
Chris@361 1082 params << "copy" << "--" << file << copyTo;
Chris@361 1083
Chris@361 1084 m_runner->requestAction(HgAction(ACT_COPY_FILE, m_workFolderPath, params));
Chris@361 1085 }
Chris@361 1086 }
Chris@361 1087 }
Chris@361 1088
Chris@361 1089
Chris@326 1090 void MainWindow::hgMarkFilesResolved(QStringList files)
Chris@163 1091 {
Chris@163 1092 QStringList params;
Chris@163 1093
Chris@163 1094 params << "resolve" << "--mark";
Chris@163 1095
Chris@163 1096 if (files.empty()) {
Chris@163 1097 params << "--all";
Chris@163 1098 } else {
Chris@164 1099 params << "--" << files;
Chris@163 1100 }
Chris@163 1101
Chris@284 1102 m_runner->requestAction(HgAction(ACT_RESOLVE_MARK, m_workFolderPath, params));
Chris@163 1103 }
Chris@163 1104
Chris@163 1105
Chris@326 1106 void MainWindow::hgRedoMerge()
Chris@326 1107 {
Chris@326 1108 hgRedoFileMerges(QStringList());
Chris@326 1109 }
Chris@326 1110
Chris@326 1111
Chris@326 1112 void MainWindow::hgRedoFileMerges(QStringList files)
jtkorhonen@33 1113 {
Chris@109 1114 QStringList params;
jtkorhonen@33 1115
Chris@163 1116 params << "resolve";
Chris@163 1117
Chris@239 1118 QString merge = getMergeBinaryName();
Chris@179 1119 if (merge != "") {
Chris@179 1120 params << "--tool" << merge;
Chris@163 1121 }
Chris@163 1122
Chris@163 1123 if (files.empty()) {
Chris@163 1124 params << "--all";
Chris@163 1125 } else {
Chris@164 1126 params << "--" << files;
Chris@163 1127 }
Chris@163 1128
Chris@284 1129 if (m_currentParents.size() == 1) {
Chris@284 1130 m_mergeTargetRevision = m_currentParents[0]->id();
Chris@237 1131 }
Chris@237 1132
Chris@284 1133 m_runner->requestAction(HgAction(ACT_RETRY_MERGE, m_workFolderPath, params));
Chris@273 1134
Chris@284 1135 m_mergeCommitComment = tr("Merge");
jtkorhonen@33 1136 }
Chris@326 1137
jtkorhonen@33 1138
jtkorhonen@0 1139 void MainWindow::hgMerge()
jtkorhonen@0 1140 {
Chris@284 1141 if (m_hgTabs->canResolve()) {
Chris@326 1142 hgRedoMerge();
Chris@163 1143 return;
Chris@163 1144 }
Chris@163 1145
Chris@109 1146 QStringList params;
jtkorhonen@0 1147
Chris@109 1148 params << "merge";
Chris@163 1149
Chris@239 1150 QString merge = getMergeBinaryName();
Chris@179 1151 if (merge != "") {
Chris@179 1152 params << "--tool" << merge;
Chris@163 1153 }
Chris@163 1154
Chris@284 1155 if (m_currentParents.size() == 1) {
Chris@284 1156 m_mergeTargetRevision = m_currentParents[0]->id();
Chris@163 1157 }
Chris@163 1158
Chris@284 1159 m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params));
Chris@273 1160
Chris@284 1161 m_mergeCommitComment = tr("Merge");
jtkorhonen@0 1162 }
jtkorhonen@0 1163
jtkorhonen@0 1164
Chris@148 1165 void MainWindow::hgMergeFrom(QString id)
Chris@148 1166 {
Chris@148 1167 QStringList params;
Chris@148 1168
Chris@148 1169 params << "merge";
Chris@153 1170 params << "--rev" << Changeset::hashOf(id);
Chris@163 1171
Chris@239 1172 QString merge = getMergeBinaryName();
Chris@179 1173 if (merge != "") {
Chris@179 1174 params << "--tool" << merge;
Chris@163 1175 }
Chris@148 1176
Chris@284 1177 if (m_currentParents.size() == 1) {
Chris@284 1178 m_mergeTargetRevision = m_currentParents[0]->id();
Chris@237 1179 }
Chris@237 1180
Chris@284 1181 m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params));
Chris@273 1182
Chris@284 1183 m_mergeCommitComment = "";
Chris@273 1184
Chris@284 1185 foreach (Changeset *cs, m_currentHeads) {
Chris@284 1186 if (cs->id() == id && !cs->isOnBranch(m_currentBranch)) {
Chris@157 1187 if (cs->branch() == "" || cs->branch() == "default") {
Chris@284 1188 m_mergeCommitComment = tr("Merge from the default branch");
Chris@157 1189 } else {
Chris@284 1190 m_mergeCommitComment = tr("Merge from branch \"%1\"").arg(cs->branch());
Chris@157 1191 }
Chris@157 1192 }
Chris@157 1193 }
Chris@157 1194
Chris@284 1195 if (m_mergeCommitComment == "") {
Chris@284 1196 m_mergeCommitComment = tr("Merge from %1").arg(id);
Chris@157 1197 }
Chris@148 1198 }
Chris@148 1199
Chris@148 1200
jtkorhonen@0 1201 void MainWindow::hgCloneFromRemote()
jtkorhonen@0 1202 {
Chris@109 1203 QStringList params;
jtkorhonen@0 1204
Chris@284 1205 if (!QDir(m_workFolderPath).exists()) {
Chris@284 1206 if (!QDir().mkpath(m_workFolderPath)) {
Chris@109 1207 DEBUG << "hgCloneFromRemote: Failed to create target path "
Chris@284 1208 << m_workFolderPath << endl;
Chris@109 1209 //!!! report error
Chris@109 1210 return;
Chris@104 1211 }
Chris@109 1212 }
Chris@104 1213
Chris@284 1214 params << "clone" << m_remoteRepoPath << m_workFolderPath;
Chris@109 1215
Chris@287 1216 updateWorkFolderAndRepoNames();
Chris@284 1217 m_hgTabs->updateWorkFolderFileList("");
Chris@273 1218
Chris@284 1219 m_runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, m_workFolderPath, params));
jtkorhonen@0 1220 }
jtkorhonen@0 1221
jtkorhonen@0 1222 void MainWindow::hgInit()
jtkorhonen@0 1223 {
Chris@109 1224 QStringList params;
jtkorhonen@0 1225
Chris@109 1226 params << "init";
Chris@284 1227 params << m_workFolderPath;
Chris@273 1228
Chris@284 1229 m_runner->requestAction(HgAction(ACT_INIT, m_workFolderPath, params));
jtkorhonen@0 1230 }
jtkorhonen@0 1231
jtkorhonen@0 1232 void MainWindow::hgIncoming()
jtkorhonen@0 1233 {
Chris@109 1234 QStringList params;
jtkorhonen@0 1235
Chris@284 1236 params << "incoming" << "--newest-first" << m_remoteRepoPath;
Chris@125 1237 params << "--template" << Changeset::getLogTemplate();
jtkorhonen@0 1238
Chris@284 1239 m_runner->requestAction(HgAction(ACT_INCOMING, m_workFolderPath, params));
jtkorhonen@0 1240 }
jtkorhonen@0 1241
jtkorhonen@0 1242 void MainWindow::hgPull()
jtkorhonen@0 1243 {
Chris@193 1244 if (ConfirmCommentDialog::confirm
Chris@126 1245 (this, tr("Confirm pull"),
Chris@299 1246 tr("<qt><h3>Pull from remote repository?</h3></qt>"),
Chris@299 1247 tr("<qt><p>You are about to pull changes from the remote repository at <code>%1</code>.</p></qt>").arg(xmlEncode(m_remoteRepoPath)),
Chris@365 1248 tr("&Pull"))) {
jtkorhonen@0 1249
Chris@126 1250 QStringList params;
Chris@284 1251 params << "pull" << m_remoteRepoPath;
Chris@284 1252 m_runner->requestAction(HgAction(ACT_PULL, m_workFolderPath, params));
Chris@126 1253 }
jtkorhonen@0 1254 }
jtkorhonen@0 1255
jtkorhonen@0 1256 void MainWindow::hgPush()
jtkorhonen@0 1257 {
Chris@363 1258 if (m_remoteRepoPath.isEmpty()) {
Chris@363 1259 changeRemoteRepo(true);
Chris@363 1260 if (m_remoteRepoPath.isEmpty()) return;
Chris@363 1261 }
Chris@363 1262
Chris@356 1263 QString uncommittedNote;
Chris@356 1264 if (m_hgTabs->canCommit()) {
Chris@356 1265 uncommittedNote = tr("<p><b>Note:</b> You have uncommitted changes. If you want to push these changes to the remote repository, you need to commit them first.");
Chris@356 1266 }
Chris@356 1267
Chris@193 1268 if (ConfirmCommentDialog::confirm
Chris@126 1269 (this, tr("Confirm push"),
Chris@299 1270 tr("<qt><h3>Push to remote repository?</h3></qt>"),
Chris@356 1271 tr("<qt><p>You are about to push your commits to the remote repository at <code>%1</code>.</p>%2</qt>").arg(xmlEncode(m_remoteRepoPath)).arg(uncommittedNote),
Chris@365 1272 tr("&Push"))) {
jtkorhonen@0 1273
Chris@126 1274 QStringList params;
Chris@284 1275 params << "push" << "--new-branch" << m_remoteRepoPath;
Chris@284 1276 m_runner->requestAction(HgAction(ACT_PUSH, m_workFolderPath, params));
Chris@126 1277 }
jtkorhonen@0 1278 }
jtkorhonen@0 1279
Chris@182 1280 QStringList MainWindow::listAllUpIpV4Addresses()
jtkorhonen@26 1281 {
Chris@182 1282 QStringList ret;
jtkorhonen@26 1283 QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
jtkorhonen@26 1284
Chris@182 1285 for (int i = 0; i < ifaces.count(); i++) {
jtkorhonen@26 1286 QNetworkInterface iface = ifaces.at(i);
Chris@182 1287 if (iface.flags().testFlag(QNetworkInterface::IsUp)
Chris@182 1288 && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) {
Chris@182 1289 for (int j=0; j<iface.addressEntries().count(); j++) {
jtkorhonen@28 1290 QHostAddress tmp = iface.addressEntries().at(j).ip();
Chris@182 1291 if (QAbstractSocket::IPv4Protocol == tmp.protocol()) {
Chris@182 1292 ret.push_back(tmp.toString());
jtkorhonen@24 1293 }
jtkorhonen@24 1294 }
jtkorhonen@22 1295 }
jtkorhonen@17 1296 }
jtkorhonen@28 1297 return ret;
jtkorhonen@28 1298 }
jtkorhonen@17 1299
Chris@120 1300 void MainWindow::clearState()
Chris@120 1301 {
Chris@305 1302 DEBUG << "MainWindow::clearState" << endl;
Chris@284 1303 foreach (Changeset *cs, m_currentParents) delete cs;
Chris@284 1304 m_currentParents.clear();
Chris@284 1305 foreach (Changeset *cs, m_currentHeads) delete cs;
Chris@284 1306 m_currentHeads.clear();
Chris@506 1307 m_closedHeadIds.clear();
Chris@284 1308 m_currentBranch = "";
Chris@284 1309 m_lastStatOutput = "";
Chris@284 1310 m_lastRevertedFiles.clear();
Chris@284 1311 m_mergeTargetRevision = "";
Chris@284 1312 m_mergeCommitComment = "";
Chris@284 1313 m_stateUnknown = true;
Chris@284 1314 m_needNewLog = true;
Chris@120 1315 }
jtkorhonen@17 1316
jtkorhonen@11 1317 void MainWindow::hgServe()
jtkorhonen@11 1318 {
Chris@109 1319 QStringList params;
Chris@109 1320 QString msg;
jtkorhonen@11 1321
Chris@182 1322 QStringList addrs = listAllUpIpV4Addresses();
Chris@182 1323
Chris@182 1324 if (addrs.empty()) {
Chris@182 1325 QMessageBox::critical
Chris@182 1326 (this, tr("Serve"), tr("Failed to identify an active IPv4 address"));
Chris@182 1327 return;
Chris@182 1328 }
Chris@182 1329
Chris@526 1330 // See #202. We really want to display the port that the server
Chris@526 1331 // ends up using -- but we don't get that information until after
Chris@526 1332 // it has exited! However, we can improve the likelihood of
Chris@526 1333 // showing the right port by at least checking whether a port is
Chris@526 1334 // defined in the hgrc file.
Chris@526 1335
Chris@526 1336 QFileInfo hgrc(QDir::homePath() + "/.hgrc");
Chris@526 1337 QString path;
Chris@526 1338 int port = 8000; // the default
Chris@526 1339 if (hgrc.exists()) {
Chris@526 1340 QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat);
Chris@526 1341 s.beginGroup("web");
Chris@527 1342 int p = s.value("port").toInt();
Chris@527 1343 if (p) port = p;
Chris@526 1344 }
Chris@182 1345
Chris@182 1346 QTextStream ts(&msg);
Chris@429 1347 ts << QString("<qt><h3>%1</h3><p>%2</p>")
Chris@425 1348 .arg(tr("Sharing Repository"))
Chris@429 1349 .arg(tr("Your local repository is now being made temporarily available via HTTP for workgroup access."));
Chris@429 1350 if (addrs.size() > 1) {
Chris@429 1351 ts << QString("<p>%3</p>")
Chris@429 1352 .arg(tr("Users who have network access to your computer can now clone your repository, by using one of the following URLs as a remote location:"));
Chris@429 1353 } else {
Chris@429 1354 ts << QString("<p>%3</p>")
Chris@429 1355 .arg(tr("Users who have network access to your computer can now clone your repository, by using the following URL as a remote location:"));
Chris@429 1356 }
Chris@182 1357 foreach (QString addr, addrs) {
Chris@526 1358 ts << QString("<pre>&nbsp;&nbsp;http://%1:%2</pre>").arg(xmlEncode(addr)).arg(port);
Chris@182 1359 }
Chris@425 1360 ts << tr("<p>Press Close to terminate this server, end remote access, and return.</p>");
Chris@182 1361 ts.flush();
Chris@182 1362
Chris@109 1363 params << "serve";
jtkorhonen@11 1364
Chris@284 1365 m_runner->requestAction(HgAction(ACT_SERVE, m_workFolderPath, params));
Chris@109 1366
Chris@425 1367 QMessageBox::information(this, tr("Share Repository"), msg, QMessageBox::Close);
Chris@182 1368
Chris@284 1369 m_runner->killCurrentActions();
jtkorhonen@11 1370 }
jtkorhonen@11 1371
Chris@64 1372 void MainWindow::startupDialog()
Chris@64 1373 {
Chris@64 1374 StartupDialog *dlg = new StartupDialog(this);
Chris@284 1375 if (dlg->exec()) m_firstStart = false;
Chris@344 1376 else exit(0);
Chris@64 1377 }
jtkorhonen@11 1378
Chris@69 1379 void MainWindow::open()
Chris@69 1380 {
Chris@86 1381 bool done = false;
Chris@69 1382
Chris@86 1383 while (!done) {
Chris@69 1384
Chris@86 1385 MultiChoiceDialog *d = new MultiChoiceDialog
Chris@86 1386 (tr("Open Repository"),
Chris@86 1387 tr("<qt><big>What would you like to open?</big></qt>"),
Chris@86 1388 this);
Chris@69 1389
Chris@345 1390 d->addChoice("remote",
Chris@345 1391 tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"),
Chris@345 1392 tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."),
Chris@345 1393 MultiChoiceDialog::UrlToDirectoryArg);
Chris@345 1394
Chris@339 1395 d->addChoice("local",
Chris@339 1396 tr("<qt><center><img src=\":images/hglogo-64.png\"><br>Local repository</center></qt>"),
Chris@339 1397 tr("Open an existing local Mercurial repository."),
Chris@339 1398 MultiChoiceDialog::DirectoryArg);
Chris@339 1399
Chris@339 1400 d->addChoice("init",
Chris@339 1401 tr("<qt><center><img src=\":images/hdd_unmount-64.png\"><br>File folder</center></qt>"),
Chris@339 1402 tr("Open a local folder, by creating a Mercurial repository in it."),
Chris@339 1403 MultiChoiceDialog::DirectoryArg);
Chris@339 1404
Chris@248 1405 QSettings settings;
Chris@484 1406 settings.beginGroup("");
Chris@359 1407 QString lastChoice = settings.value("lastopentype", "remote").toString();
Chris@248 1408 if (lastChoice != "local" &&
Chris@248 1409 lastChoice != "remote" &&
Chris@248 1410 lastChoice != "init") {
Chris@359 1411 lastChoice = "remote";
Chris@248 1412 }
Chris@248 1413
Chris@248 1414 d->setCurrentChoice(lastChoice);
Chris@86 1415
Chris@86 1416 if (d->exec() == QDialog::Accepted) {
Chris@86 1417
Chris@86 1418 QString choice = d->getCurrentChoice();
Chris@248 1419 settings.setValue("lastopentype", choice);
Chris@248 1420
Chris@86 1421 QString arg = d->getArgument().trimmed();
Chris@86 1422
Chris@86 1423 bool result = false;
Chris@86 1424
Chris@86 1425 if (choice == "local") {
Chris@86 1426 result = openLocal(arg);
Chris@86 1427 } else if (choice == "remote") {
Chris@86 1428 result = openRemote(arg, d->getAdditionalArgument().trimmed());
Chris@86 1429 } else if (choice == "init") {
Chris@86 1430 result = openInit(arg);
Chris@86 1431 }
Chris@86 1432
Chris@86 1433 if (result) {
Chris@86 1434 enableDisableActions();
Chris@120 1435 clearState();
Chris@109 1436 hgQueryPaths();
Chris@91 1437 done = true;
Chris@91 1438 }
Chris@86 1439
Chris@86 1440 } else {
Chris@86 1441
Chris@86 1442 // cancelled
Chris@86 1443 done = true;
Chris@69 1444 }
Chris@79 1445
Chris@86 1446 delete d;
Chris@69 1447 }
Chris@69 1448 }
Chris@69 1449
Chris@359 1450 void MainWindow::recentMenuActivated()
Chris@359 1451 {
Chris@359 1452 QAction *a = qobject_cast<QAction *>(sender());
Chris@359 1453 if (!a) return;
Chris@359 1454 QString local = a->text();
Chris@359 1455 open(local);
Chris@359 1456 }
Chris@359 1457
Chris@182 1458 void MainWindow::changeRemoteRepo()
Chris@182 1459 {
Chris@363 1460 changeRemoteRepo(false);
Chris@363 1461 }
Chris@363 1462
Chris@363 1463 void MainWindow::changeRemoteRepo(bool initial)
Chris@363 1464 {
Chris@183 1465 // This will involve rewriting the local .hgrc
Chris@183 1466
Chris@284 1467 QDir hgDir(m_workFolderPath + "/.hg");
Chris@184 1468 if (!hgDir.exists()) {
Chris@184 1469 //!!! visible error!
Chris@184 1470 return;
Chris@184 1471 }
Chris@184 1472
Chris@284 1473 QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc");
Chris@184 1474 if (hgrc.exists() && !hgrc.isWritable()) {
Chris@183 1475 //!!! visible error!
Chris@183 1476 return;
Chris@183 1477 }
Chris@183 1478
Chris@183 1479 MultiChoiceDialog *d = new MultiChoiceDialog
Chris@363 1480 (tr("Set Remote Location"),
Chris@363 1481 tr("<qt><big>Set the remote location</big></qt>"),
Chris@183 1482 this);
Chris@183 1483
Chris@363 1484 QString explanation;
Chris@363 1485 if (initial) {
Chris@363 1486 explanation = tr("Provide a URL to use for push and pull actions from the current local repository.<br>This will be the default for subsequent pushes and pulls.<br>You can change it using &ldquo;Set Remote Location&rdquo; on the File menu.");
Chris@363 1487 } else {
Chris@363 1488 explanation = tr("Provide a new URL to use for push and pull actions from the current local repository.");
Chris@363 1489 }
Chris@363 1490
Chris@183 1491 d->addChoice("remote",
Chris@183 1492 tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"),
Chris@363 1493 explanation,
Chris@553 1494 MultiChoiceDialog::UrlArg,
Chris@553 1495 true); // default empty
Chris@183 1496
Chris@183 1497 if (d->exec() == QDialog::Accepted) {
Chris@210 1498
Chris@210 1499 // New block to ensure QSettings is deleted before
Chris@210 1500 // hgQueryPaths called. NB use of absoluteFilePath instead of
Chris@210 1501 // canonicalFilePath, which would fail if the file did not yet
Chris@210 1502 // exist
Chris@210 1503
Chris@210 1504 {
Chris@210 1505 QSettings s(hgrc.absoluteFilePath(), QSettings::IniFormat);
Chris@210 1506 s.beginGroup("paths");
Chris@210 1507 s.setValue("default", d->getArgument());
Chris@210 1508 }
Chris@210 1509
Chris@284 1510 m_stateUnknown = true;
Chris@183 1511 hgQueryPaths();
Chris@183 1512 }
Chris@183 1513
Chris@183 1514 delete d;
Chris@182 1515 }
Chris@182 1516
Chris@145 1517 void MainWindow::open(QString local)
Chris@145 1518 {
Chris@145 1519 if (openLocal(local)) {
Chris@145 1520 enableDisableActions();
Chris@145 1521 clearState();
Chris@145 1522 hgQueryPaths();
Chris@145 1523 }
Chris@145 1524 }
Chris@145 1525
Chris@79 1526 bool MainWindow::complainAboutFilePath(QString arg)
Chris@79 1527 {
Chris@79 1528 QMessageBox::critical
Chris@79 1529 (this, tr("File chosen"),
Chris@84 1530 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 1531 return false;
Chris@79 1532 }
Chris@79 1533
Chris@248 1534 bool MainWindow::askAboutUnknownFolder(QString arg)
Chris@248 1535 {
Chris@248 1536 bool result = (QMessageBox::question
Chris@248 1537 (this, tr("Path does not exist"),
Chris@248 1538 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 1539 QMessageBox::Ok | QMessageBox::Cancel,
Chris@248 1540 QMessageBox::Cancel)
Chris@248 1541 == QMessageBox::Ok);
Chris@248 1542 if (result) {
Chris@248 1543 QDir dir(arg);
Chris@248 1544 dir.cdUp();
Chris@248 1545 if (!dir.mkpath(dir.absolutePath())) {
Chris@248 1546 QMessageBox::critical
Chris@248 1547 (this, tr("Failed to create folder"),
Chris@248 1548 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 1549 return false;
Chris@248 1550 }
Chris@248 1551 return true;
Chris@248 1552 }
Chris@248 1553 return false;
Chris@248 1554 }
Chris@248 1555
Chris@79 1556 bool MainWindow::complainAboutUnknownFolder(QString arg)
Chris@79 1557 {
Chris@79 1558 QMessageBox::critical
Chris@79 1559 (this, tr("Folder does not exist"),
Chris@84 1560 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 1561 return false;
Chris@84 1562 }
Chris@84 1563
Chris@84 1564 bool MainWindow::complainAboutInitInRepo(QString arg)
Chris@84 1565 {
Chris@84 1566 QMessageBox::critical
Chris@84 1567 (this, tr("Path is in existing repository"),
Chris@84 1568 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 1569 return false;
Chris@84 1570 }
Chris@84 1571
Chris@84 1572 bool MainWindow::complainAboutInitFile(QString arg)
Chris@84 1573 {
Chris@84 1574 QMessageBox::critical
Chris@84 1575 (this, tr("Path is a file"),
Chris@84 1576 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 1577 return false;
Chris@84 1578 }
Chris@84 1579
Chris@84 1580 bool MainWindow::complainAboutCloneToExisting(QString arg)
Chris@84 1581 {
Chris@84 1582 QMessageBox::critical
Chris@84 1583 (this, tr("Path is in existing repository"),
Chris@237 1584 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 1585 return false;
Chris@84 1586 }
Chris@84 1587
Chris@84 1588 bool MainWindow::complainAboutCloneToFile(QString arg)
Chris@84 1589 {
Chris@84 1590 QMessageBox::critical
Chris@84 1591 (this, tr("Path is a file"),
Chris@84 1592 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 1593 return false;
Chris@84 1594 }
Chris@84 1595
Chris@237 1596 QString MainWindow::complainAboutCloneToExistingFolder(QString arg, QString remote)
Chris@84 1597 {
Chris@348 1598 // If the directory "arg" exists but is empty, then we accept it.
Chris@348 1599
Chris@348 1600 // If the directory "arg" exists and is non-empty, but "arg" plus
Chris@348 1601 // the last path component of "remote" does not exist, then offer
Chris@348 1602 // the latter as an alternative path.
Chris@237 1603
Chris@237 1604 QString offer;
Chris@237 1605
Chris@237 1606 QDir d(arg);
Chris@348 1607
Chris@237 1608 if (d.exists()) {
Chris@348 1609
Chris@348 1610 if (d.entryList(QDir::Dirs | QDir::Files |
Chris@348 1611 QDir::NoDotAndDotDot |
Chris@348 1612 QDir::Hidden | QDir::System).empty()) {
Chris@348 1613 // directory is empty; accept it
Chris@348 1614 return arg;
Chris@348 1615 }
Chris@348 1616
Chris@237 1617 if (QRegExp("^\\w+://").indexIn(remote) >= 0) {
Chris@237 1618 QString rpath = QUrl(remote).path();
Chris@237 1619 if (rpath != "") {
Chris@237 1620 rpath = QDir(rpath).dirName();
Chris@237 1621 if (rpath != "" && !d.exists(rpath)) {
Chris@237 1622 offer = d.filePath(rpath);
Chris@237 1623 }
Chris@237 1624 }
Chris@237 1625 }
Chris@237 1626 }
Chris@237 1627
Chris@237 1628 if (offer != "") {
Chris@237 1629 bool result = (QMessageBox::question
Chris@237 1630 (this, tr("Folder exists"),
Chris@237 1631 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 1632 .arg(xmlEncode(arg)).arg(xmlEncode(offer)),
Chris@237 1633 QMessageBox::Ok | QMessageBox::Cancel,
Chris@237 1634 QMessageBox::Cancel)
Chris@237 1635 == QMessageBox::Ok);
Chris@237 1636 if (result) return offer;
Chris@237 1637 else return "";
Chris@237 1638 }
Chris@237 1639
Chris@84 1640 QMessageBox::critical
Chris@84 1641 (this, tr("Folder exists"),
Chris@237 1642 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 1643 return "";
Chris@79 1644 }
Chris@79 1645
Chris@79 1646 bool MainWindow::askToOpenParentRepo(QString arg, QString parent)
Chris@79 1647 {
Chris@79 1648 return (QMessageBox::question
Chris@84 1649 (this, tr("Path is inside a repository"),
Chris@86 1650 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 1651 .arg(xmlEncode(arg)).arg(xmlEncode(parent)),
Chris@79 1652 QMessageBox::Ok | QMessageBox::Cancel,
Chris@79 1653 QMessageBox::Ok)
Chris@79 1654 == QMessageBox::Ok);
Chris@79 1655 }
Chris@79 1656
Chris@79 1657 bool MainWindow::askToInitExisting(QString arg)
Chris@79 1658 {
Chris@79 1659 return (QMessageBox::question
Chris@84 1660 (this, tr("Folder has no repository"),
Chris@359 1661 tr("<qt><b>Initialise a repository here?</b><br><br>You asked to open \"%1\".<br>This folder is not a Mercurial working copy.<br><br>Would you like to initialise a repository here?</qt>")
Chris@79 1662 .arg(xmlEncode(arg)),
Chris@79 1663 QMessageBox::Ok | QMessageBox::Cancel,
Chris@79 1664 QMessageBox::Ok)
Chris@79 1665 == QMessageBox::Ok);
Chris@79 1666 }
Chris@79 1667
Chris@79 1668 bool MainWindow::askToInitNew(QString arg)
Chris@79 1669 {
Chris@79 1670 return (QMessageBox::question
Chris@84 1671 (this, tr("Folder does not exist"),
Chris@84 1672 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 1673 .arg(xmlEncode(arg)),
Chris@84 1674 QMessageBox::Ok | QMessageBox::Cancel,
Chris@84 1675 QMessageBox::Ok)
Chris@84 1676 == QMessageBox::Ok);
Chris@84 1677 }
Chris@84 1678
Chris@84 1679 bool MainWindow::askToOpenInsteadOfInit(QString arg)
Chris@84 1680 {
Chris@84 1681 return (QMessageBox::question
Chris@84 1682 (this, tr("Repository exists"),
Chris@84 1683 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 1684 .arg(xmlEncode(arg)),
Chris@79 1685 QMessageBox::Ok | QMessageBox::Cancel,
Chris@79 1686 QMessageBox::Ok)
Chris@79 1687 == QMessageBox::Ok);
Chris@79 1688 }
Chris@79 1689
Chris@79 1690 bool MainWindow::openLocal(QString local)
Chris@79 1691 {
Chris@79 1692 DEBUG << "open " << local << endl;
Chris@79 1693
Chris@79 1694 FolderStatus status = getFolderStatus(local);
Chris@79 1695 QString containing = getContainingRepoFolder(local);
Chris@79 1696
Chris@79 1697 switch (status) {
Chris@79 1698
Chris@79 1699 case FolderHasRepo:
Chris@79 1700 // fine
Chris@79 1701 break;
Chris@79 1702
Chris@79 1703 case FolderExists:
Chris@79 1704 if (containing != "") {
Chris@79 1705 if (!askToOpenParentRepo(local, containing)) return false;
Chris@84 1706 local = containing;
Chris@79 1707 } else {
Chris@86 1708 //!!! No -- this is likely to happen far more by accident
Chris@86 1709 // than because the user actually wanted to init something.
Chris@86 1710 // Don't ask, just politely reject.
Chris@79 1711 if (!askToInitExisting(local)) return false;
Chris@79 1712 return openInit(local);
Chris@79 1713 }
Chris@79 1714 break;
Chris@79 1715
Chris@79 1716 case FolderParentExists:
Chris@79 1717 if (containing != "") {
Chris@79 1718 if (!askToOpenParentRepo(local, containing)) return false;
Chris@84 1719 local = containing;
Chris@79 1720 } else {
Chris@79 1721 if (!askToInitNew(local)) return false;
Chris@79 1722 return openInit(local);
Chris@79 1723 }
Chris@79 1724 break;
Chris@79 1725
Chris@79 1726 case FolderUnknown:
Chris@84 1727 if (containing != "") {
Chris@84 1728 if (!askToOpenParentRepo(local, containing)) return false;
Chris@84 1729 local = containing;
Chris@84 1730 } else {
Chris@84 1731 return complainAboutUnknownFolder(local);
Chris@84 1732 }
Chris@84 1733 break;
Chris@79 1734
Chris@79 1735 case FolderIsFile:
Chris@79 1736 return complainAboutFilePath(local);
Chris@79 1737 }
Chris@79 1738
Chris@284 1739 m_workFolderPath = local;
Chris@284 1740 m_remoteRepoPath = "";
Chris@79 1741 return true;
Chris@79 1742 }
Chris@79 1743
Chris@79 1744 bool MainWindow::openRemote(QString remote, QString local)
Chris@79 1745 {
Chris@79 1746 DEBUG << "clone " << remote << " to " << local << endl;
Chris@84 1747
Chris@84 1748 FolderStatus status = getFolderStatus(local);
Chris@84 1749 QString containing = getContainingRepoFolder(local);
Chris@84 1750
Chris@84 1751 DEBUG << "status = " << status << ", containing = " << containing << endl;
Chris@84 1752
Chris@84 1753 if (status == FolderHasRepo || containing != "") {
Chris@84 1754 return complainAboutCloneToExisting(local);
Chris@84 1755 }
Chris@84 1756
Chris@84 1757 if (status == FolderIsFile) {
Chris@84 1758 return complainAboutCloneToFile(local);
Chris@84 1759 }
Chris@84 1760
Chris@84 1761 if (status == FolderUnknown) {
Chris@248 1762 if (!askAboutUnknownFolder(local)) {
Chris@248 1763 return false;
Chris@248 1764 }
Chris@84 1765 }
Chris@84 1766
Chris@84 1767 if (status == FolderExists) {
Chris@237 1768 local = complainAboutCloneToExistingFolder(local, remote);
Chris@237 1769 if (local == "") return false;
Chris@84 1770 }
Chris@84 1771
Chris@284 1772 m_workFolderPath = local;
Chris@284 1773 m_remoteRepoPath = remote;
Chris@84 1774 hgCloneFromRemote();
Chris@84 1775
Chris@79 1776 return true;
Chris@79 1777 }
Chris@79 1778
Chris@84 1779 bool MainWindow::openInit(QString local)
Chris@79 1780 {
Chris@84 1781 DEBUG << "openInit " << local << endl;
Chris@84 1782
Chris@84 1783 FolderStatus status = getFolderStatus(local);
Chris@84 1784 QString containing = getContainingRepoFolder(local);
Chris@84 1785
Chris@84 1786 DEBUG << "status = " << status << ", containing = " << containing << endl;
Chris@84 1787
Chris@84 1788 if (status == FolderHasRepo) {
Chris@84 1789 if (!askToOpenInsteadOfInit(local)) return false;
Chris@552 1790 return openLocal(local);
Chris@84 1791 }
Chris@84 1792
Chris@84 1793 if (containing != "") {
Chris@84 1794 return complainAboutInitInRepo(local);
Chris@84 1795 }
Chris@84 1796
Chris@84 1797 if (status == FolderIsFile) {
Chris@84 1798 return complainAboutInitFile(local);
Chris@84 1799 }
Chris@84 1800
Chris@84 1801 if (status == FolderUnknown) {
Chris@84 1802 return complainAboutUnknownFolder(local);
Chris@84 1803 }
Chris@84 1804
Chris@284 1805 m_workFolderPath = local;
Chris@284 1806 m_remoteRepoPath = "";
Chris@84 1807 hgInit();
Chris@79 1808 return true;
Chris@79 1809 }
Chris@79 1810
jtkorhonen@0 1811 void MainWindow::settings()
jtkorhonen@0 1812 {
Chris@472 1813 settings(SettingsDialog::PersonalDetailsTab);
Chris@472 1814 }
Chris@472 1815
Chris@472 1816 void MainWindow::settings(SettingsDialog::Tab tab)
Chris@472 1817 {
jtkorhonen@0 1818 SettingsDialog *settingsDlg = new SettingsDialog(this);
Chris@472 1819 settingsDlg->setCurrentTab(tab);
jtkorhonen@0 1820 settingsDlg->exec();
Chris@230 1821
Chris@230 1822 if (settingsDlg->presentationChanged()) {
Chris@284 1823 m_hgTabs->updateFileStates();
Chris@230 1824 updateToolBarStyle();
Chris@273 1825 hgRefresh();
Chris@230 1826 }
jtkorhonen@0 1827 }
jtkorhonen@0 1828
Chris@541 1829 void MainWindow::updateFsWatcher()
Chris@541 1830 {
Chris@541 1831 m_fsWatcher->setWorkDirPath(m_workFolderPath);
Chris@541 1832 m_fsWatcher->setTrackedFilePaths(m_hgTabs->getFileStates().trackedFiles());
Chris@541 1833 }
Chris@541 1834
Chris@238 1835 void MainWindow::checkFilesystem()
Chris@238 1836 {
Chris@238 1837 DEBUG << "MainWindow::checkFilesystem" << endl;
Chris@541 1838 if (!m_commandSequenceInProgress) {
Chris@541 1839 if (!m_fsWatcher->getChangedPaths(m_fsWatcherToken).empty()) {
Chris@541 1840 hgRefresh();
Chris@541 1841 return;
Chris@541 1842 }
Chris@549 1843 updateFsWatcher();
Chris@541 1844 }
Chris@90 1845 }
Chris@90 1846
Chris@275 1847 QString MainWindow::format1(QString head)
Chris@275 1848 {
Chris@275 1849 return QString("<qt><h3>%1</h3></qt>").arg(head);
Chris@275 1850 }
Chris@275 1851
Chris@125 1852 QString MainWindow::format3(QString head, QString intro, QString code)
Chris@125 1853 {
Chris@196 1854 code = xmlEncode(code).replace("\n", "<br>")
Chris@196 1855 #ifndef Q_OS_WIN32
Chris@196 1856 // The hard hyphen comes out funny on Windows
Chris@196 1857 .replace("-", "&#8209;")
Chris@196 1858 #endif
Chris@196 1859 .replace(" ", "&nbsp;");
Chris@125 1860 if (intro == "") {
Chris@168 1861 return QString("<qt><h3>%1</h3><p><code>%2</code></p>")
Chris@168 1862 .arg(head).arg(code);
Chris@126 1863 } else if (code == "") {
Chris@126 1864 return QString("<qt><h3>%1</h3><p>%2</p>")
Chris@126 1865 .arg(head).arg(intro);
Chris@125 1866 } else {
Chris@168 1867 return QString("<qt><h3>%1</h3><p>%2</p><p><code>%3</code></p>")
Chris@168 1868 .arg(head).arg(intro).arg(code);
Chris@125 1869 }
Chris@125 1870 }
Chris@125 1871
Chris@120 1872 void MainWindow::showIncoming(QString output)
Chris@120 1873 {
Chris@284 1874 m_runner->hide();
Chris@125 1875 IncomingDialog *d = new IncomingDialog(this, output);
Chris@125 1876 d->exec();
Chris@125 1877 delete d;
Chris@125 1878 }
Chris@125 1879
Chris@125 1880 int MainWindow::extractChangeCount(QString text)
Chris@125 1881 {
Chris@125 1882 QRegExp re("added (\\d+) ch\\w+ with (\\d+) ch\\w+ to (\\d+) f\\w+");
Chris@125 1883 if (re.indexIn(text) >= 0) {
Chris@125 1884 return re.cap(1).toInt();
Chris@125 1885 } else if (text.contains("no changes")) {
Chris@125 1886 return 0;
Chris@125 1887 } else {
Chris@125 1888 return -1; // unknown
Chris@125 1889 }
Chris@120 1890 }
Chris@120 1891
Chris@120 1892 void MainWindow::showPushResult(QString output)
Chris@120 1893 {
Chris@291 1894 QString head;
Chris@125 1895 QString report;
Chris@125 1896 int n = extractChangeCount(output);
Chris@125 1897 if (n > 0) {
Chris@291 1898 head = tr("Pushed %n changeset(s)", "", n);
Chris@300 1899 report = tr("<qt>Successfully pushed to the remote repository at <code>%1</code>.</qt>").arg(xmlEncode(m_remoteRepoPath));
Chris@125 1900 } else if (n == 0) {
Chris@291 1901 head = tr("No changes to push");
Chris@291 1902 report = tr("The remote repository already contains all changes that have been committed locally.");
Chris@294 1903 if (m_hgTabs->canCommit()) {
Chris@291 1904 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 1905 }
Chris@125 1906 } else {
Chris@291 1907 head = tr("Push complete");
Chris@125 1908 }
Chris@284 1909 m_runner->hide();
Chris@291 1910
Chris@291 1911 MoreInformationDialog::information(this, tr("Push complete"),
Chris@291 1912 head, report, output);
Chris@120 1913 }
Chris@120 1914
Chris@120 1915 void MainWindow::showPullResult(QString output)
Chris@120 1916 {
Chris@291 1917 QString head;
Chris@125 1918 QString report;
Chris@125 1919 int n = extractChangeCount(output);
Chris@125 1920 if (n > 0) {
Chris@291 1921 head = tr("Pulled %n changeset(s)", "", n);
Chris@322 1922 report = tr("New changes will be highlighted in yellow in the history.");
Chris@125 1923 } else if (n == 0) {
Chris@291 1924 head = tr("No changes to pull");
Chris@291 1925 report = tr("Your local repository already contains all changes found in the remote repository.");
Chris@125 1926 } else {
Chris@291 1927 head = tr("Pull complete");
Chris@125 1928 }
Chris@284 1929 m_runner->hide();
Chris@275 1930
Chris@275 1931 MoreInformationDialog::information(this, tr("Pull complete"),
Chris@291 1932 head, report, output);
Chris@120 1933 }
Chris@120 1934
Chris@174 1935 void MainWindow::reportNewRemoteHeads(QString output)
Chris@174 1936 {
Chris@174 1937 bool headsAreLocal = false;
Chris@174 1938
Chris@284 1939 if (m_currentParents.size() == 1) {
Chris@506 1940 int currentBranchActiveHeads = 0;
Chris@174 1941 bool parentIsHead = false;
Chris@284 1942 Changeset *parent = m_currentParents[0];
Chris@506 1943 foreach (Changeset *head, m_activeHeads) {
Chris@284 1944 if (head->isOnBranch(m_currentBranch)) {
Chris@506 1945 ++currentBranchActiveHeads;
Chris@174 1946 }
Chris@174 1947 if (parent->id() == head->id()) {
Chris@174 1948 parentIsHead = true;
Chris@174 1949 }
Chris@174 1950 }
Chris@506 1951 if (currentBranchActiveHeads == 2 && parentIsHead) {
Chris@174 1952 headsAreLocal = true;
Chris@174 1953 }
Chris@174 1954 }
Chris@174 1955
Chris@174 1956 if (headsAreLocal) {
Chris@291 1957 MoreInformationDialog::warning
Chris@291 1958 (this,
Chris@291 1959 tr("Push failed"),
Chris@291 1960 tr("Push failed"),
Chris@291 1961 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 1962 output);
Chris@318 1963 } else if (m_hgTabs->canCommit() && m_currentParents.size() > 1) {
Chris@318 1964 MoreInformationDialog::warning
Chris@318 1965 (this,
Chris@318 1966 tr("Push failed"),
Chris@318 1967 tr("Push failed"),
Chris@318 1968 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 1969 output);
Chris@174 1970 } else {
Chris@291 1971 MoreInformationDialog::warning
Chris@291 1972 (this,
Chris@291 1973 tr("Push failed"),
Chris@291 1974 tr("Push failed"),
Chris@291 1975 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 1976 output);
Chris@174 1977 }
Chris@174 1978 }
Chris@174 1979
Chris@353 1980 void MainWindow::reportAuthFailed(QString output)
Chris@353 1981 {
Chris@353 1982 MoreInformationDialog::warning
Chris@353 1983 (this,
Chris@353 1984 tr("Authorization failed"),
Chris@353 1985 tr("Authorization failed"),
Chris@353 1986 tr("You may have entered an incorrect user name or password, or the remote URL may be wrong.<br><br>Or you may lack the necessary permissions on the remote repository.<br><br>Check with the administrator of your remote repository if necessary."),
Chris@353 1987 output);
Chris@353 1988 }
Chris@353 1989
Chris@238 1990 void MainWindow::commandStarting(HgAction action)
Chris@238 1991 {
Chris@540 1992 m_commandSequenceInProgress = true;
Chris@238 1993 }
Chris@238 1994
Chris@546 1995 void MainWindow::commandFailed(HgAction action, QString stdErr, QString stdOut)
Chris@62 1996 {
Chris@62 1997 DEBUG << "MainWindow::commandFailed" << endl;
Chris@540 1998
Chris@540 1999 m_commandSequenceInProgress = false;
Chris@74 2000
Chris@210 2001 QString setstr;
Chris@210 2002 #ifdef Q_OS_MAC
Chris@210 2003 setstr = tr("Preferences");
Chris@210 2004 #else
Chris@210 2005 setstr = tr("Settings");
Chris@210 2006 #endif
Chris@210 2007
Chris@353 2008 // Some commands we just have to ignore bad return values from,
Chris@353 2009 // and some output gets special treatment.
Chris@353 2010
Chris@353 2011 // Note our fallback case should always be to report a
Chris@353 2012 // non-specific error and show the text -- in case output scraping
Chris@353 2013 // fails (as it surely will). Note also that we must force the
Chris@353 2014 // locale in order to ensure the output is scrapable; this happens
Chris@353 2015 // in HgRunner and may break some system encodings.
Chris@113 2016
Chris@113 2017 switch(action.action) {
Chris@113 2018 case ACT_NONE:
Chris@113 2019 // uh huh
Chris@113 2020 return;
Chris@175 2021 case ACT_TEST_HG:
Chris@291 2022 MoreInformationDialog::warning
Chris@291 2023 (this,
Chris@291 2024 tr("Failed to run Mercurial"),
Chris@291 2025 tr("Failed to run Mercurial"),
Chris@483 2026 tr("The Mercurial program either could not be found or failed to run.<br><br>Check that the Mercurial program path is correct in %1.").arg(setstr),
Chris@545 2027 stdErr);
Chris@472 2028 settings(SettingsDialog::PathsTab);
Chris@200 2029 return;
Chris@200 2030 case ACT_TEST_HG_EXT:
Chris@309 2031 MoreInformationDialog::warning
Chris@291 2032 (this,
Chris@291 2033 tr("Failed to run Mercurial"),
Chris@291 2034 tr("Failed to run Mercurial with extension enabled"),
Chris@310 2035 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 &ldquo;Use EasyHg Mercurial Extension&rdquo; in %1. Note that remote repositories that require authentication might not work if you do this.").arg(setstr),
Chris@545 2036 stdErr);
Chris@472 2037 settings(SettingsDialog::PathsTab);
Chris@175 2038 return;
Chris@252 2039 case ACT_CLONEFROMREMOTE:
Chris@252 2040 // if clone fails, we have no repo
Chris@284 2041 m_workFolderPath = "";
Chris@252 2042 enableDisableActions();
Chris@330 2043 break; // go on to default report
Chris@113 2044 case ACT_INCOMING:
Chris@545 2045 if (stdErr.contains("authorization failed")) {
Chris@545 2046 reportAuthFailed(stdErr);
Chris@353 2047 return;
Chris@545 2048 } else if (stdErr.contains("entry cancelled")) {
Chris@358 2049 // ignore this, user cancelled username or password dialog
Chris@358 2050 return;
Chris@353 2051 } else {
Chris@545 2052 // Incoming returns non-zero code and no stdErr if the
Chris@353 2053 // check was successful but there are no changes
Chris@353 2054 // pending. This is the only case where we need to remove
Chris@353 2055 // warning messages, because it's the only case where a
Chris@353 2056 // non-zero code can be returned even though the command
Chris@353 2057 // has for our purposes succeeded
Chris@545 2058 QString replaced = stdErr;
Chris@352 2059 while (1) {
Chris@352 2060 QString r1 = replaced;
Chris@352 2061 r1.replace(QRegExp("warning: [^\\n]*"), "");
Chris@352 2062 if (r1 == replaced) break;
Chris@352 2063 replaced = r1.trimmed();
Chris@352 2064 }
Chris@352 2065 if (replaced == "") {
Chris@352 2066 showIncoming("");
Chris@352 2067 return;
Chris@352 2068 }
Chris@211 2069 }
Chris@330 2070 break; // go on to default report
Chris@353 2071 case ACT_PULL:
Chris@545 2072 if (stdErr.contains("authorization failed")) {
Chris@545 2073 reportAuthFailed(stdErr);
Chris@353 2074 return;
Chris@545 2075 } else if (stdErr.contains("entry cancelled")) {
Chris@358 2076 // ignore this, user cancelled username or password dialog
Chris@358 2077 return;
Chris@546 2078 } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) {
Chris@537 2079 // success: hg 2.1 starts returning failure code for empty pull/push
Chris@540 2080 m_commandSequenceInProgress = true; // there may be further commands
Chris@546 2081 commandCompleted(action, stdOut);
Chris@537 2082 return;
Chris@353 2083 }
Chris@353 2084 break; // go on to default report
Chris@353 2085 case ACT_PUSH:
Chris@545 2086 if (stdErr.contains("creates new remote head")) {
Chris@545 2087 reportNewRemoteHeads(stdErr);
Chris@353 2088 return;
Chris@545 2089 } else if (stdErr.contains("authorization failed")) {
Chris@545 2090 reportAuthFailed(stdErr);
Chris@353 2091 return;
Chris@545 2092 } else if (stdErr.contains("entry cancelled")) {
Chris@358 2093 // ignore this, user cancelled username or password dialog
Chris@358 2094 return;
Chris@546 2095 } else if (stdErr.contains("no changes found") || stdOut.contains("no changes found")) {
Chris@537 2096 // success: hg 2.1 starts returning failure code for empty pull/push
Chris@540 2097 m_commandSequenceInProgress = true; // there may be further commands
Chris@546 2098 commandCompleted(action, stdOut);
Chris@537 2099 return;
Chris@353 2100 }
Chris@353 2101 break; // go on to default report
Chris@506 2102 case ACT_QUERY_HEADS_ACTIVE:
Chris@162 2103 case ACT_QUERY_HEADS:
Chris@162 2104 // fails if repo is empty; we don't care (if there's a genuine
Chris@202 2105 // problem, something else will fail too). Pretend it
Chris@202 2106 // succeeded, so that any further actions that are contingent
Chris@202 2107 // on the success of the heads query get carried out properly.
Chris@540 2108 m_commandSequenceInProgress = true; // there may be further commands
Chris@202 2109 commandCompleted(action, "");
Chris@162 2110 return;
Chris@113 2111 case ACT_FOLDERDIFF:
Chris@113 2112 case ACT_CHGSETDIFF:
Chris@545 2113 // external program, unlikely to be anything useful in stdErr
Chris@113 2114 // and some return with failure codes when something as basic
Chris@113 2115 // as the user closing the window via the wm happens
Chris@113 2116 return;
Chris@328 2117 case ACT_MERGE:
Chris@545 2118 if (stdErr.contains("working directory ancestor")) {
Chris@530 2119 // arguably we should prevent this upfront, but that's
Chris@530 2120 // trickier!
Chris@530 2121 MoreInformationDialog::information
Chris@530 2122 (this, tr("Merge"), tr("Merge has no effect"),
Chris@530 2123 tr("You asked to merge a revision with one of its ancestors.<p>This has no effect, because the ancestor's changes already exist in both revisions."),
Chris@545 2124 stdErr);
Chris@530 2125 return;
Chris@530 2126 }
Chris@530 2127 // else fall through
Chris@328 2128 case ACT_RETRY_MERGE:
Chris@328 2129 MoreInformationDialog::information
Chris@328 2130 (this, tr("Merge"), tr("Merge failed"),
Chris@328 2131 tr("Some files were not merged successfully.<p>You can Merge again to repeat the interactive merge; use Revert to abandon the merge entirely; or edit the files that are in conflict in an editor and, when you are happy with them, choose Mark Resolved in each file's right-button menu."),
Chris@545 2132 stdErr);
Chris@378 2133 m_mergeCommitComment = "";
Chris@328 2134 return;
Chris@199 2135 case ACT_STAT:
Chris@330 2136 break; // go on to default report
Chris@113 2137 default:
Chris@114 2138 break;
Chris@113 2139 }
Chris@113 2140
Chris@113 2141 QString command = action.executable;
Chris@113 2142 if (command == "") command = "hg";
Chris@113 2143 foreach (QString arg, action.params) {
Chris@113 2144 command += " " + arg;
Chris@113 2145 }
Chris@113 2146
Chris@309 2147 MoreInformationDialog::warning
Chris@309 2148 (this,
Chris@309 2149 tr("Command failed"),
Chris@309 2150 tr("Command failed"),
Chris@545 2151 (stdErr == "" ?
Chris@483 2152 tr("A Mercurial command failed to run correctly. This may indicate an installation problem or some other problem with EasyMercurial.") :
Chris@483 2153 tr("A Mercurial command failed to run correctly. This may indicate an installation problem or some other problem with EasyMercurial.<br><br>See &ldquo;More Details&rdquo; for the command output.")),
Chris@545 2154 stdErr);
Chris@62 2155 }
Chris@62 2156
Chris@109 2157 void MainWindow::commandCompleted(HgAction completedAction, QString output)
jtkorhonen@0 2158 {
Chris@521 2159 // std::cerr << "commandCompleted: " << completedAction.action << std::endl;
Chris@519 2160
Chris@109 2161 HGACTIONS action = completedAction.action;
Chris@109 2162
Chris@109 2163 if (action == ACT_NONE) return;
Chris@109 2164
Chris@519 2165 output.replace("\r\n", "\n");
Chris@519 2166
Chris@150 2167 bool headsChanged = false;
Chris@150 2168 QStringList oldHeadIds;
Chris@150 2169
Chris@150 2170 switch (action) {
Chris@109 2171
Chris@175 2172 case ACT_TEST_HG:
Chris@377 2173 {
Chris@377 2174 QRegExp versionRE("^Mercurial.*version ([\\d+])\\.([\\d+])");
Chris@377 2175 int pos = versionRE.indexIn(output);
Chris@377 2176 if (pos >= 0) {
Chris@377 2177 int major = versionRE.cap(1).toInt();
Chris@377 2178 int minor = versionRE.cap(2).toInt();
Chris@377 2179 // We need v1.7 or newer
Chris@377 2180 if (major < 1 || (major == 1 && minor < 7)) {
Chris@377 2181 MoreInformationDialog::warning
Chris@377 2182 (this,
Chris@377 2183 tr("Newer Mercurial version required"),
Chris@377 2184 tr("Newer Mercurial version required"),
Chris@377 2185 tr("To use EasyMercurial, you should have at least Mercurial v1.7 installed.<br><br>The version found on this system (v%1.%2) does not support all of the features required by EasyMercurial.").arg(major).arg(minor),
Chris@377 2186 output);
Chris@377 2187 }
Chris@377 2188 }
Chris@175 2189 break;
Chris@377 2190 }
Chris@175 2191
Chris@200 2192 case ACT_TEST_HG_EXT:
Chris@200 2193 break;
Chris@200 2194
Chris@109 2195 case ACT_QUERY_PATHS:
jtkorhonen@0 2196 {
Chris@109 2197 DEBUG << "stdout is " << output << endl;
Chris@109 2198 LogParser lp(output, "=");
Chris@109 2199 LogList ll = lp.parse();
Chris@109 2200 DEBUG << ll.size() << " results" << endl;
Chris@109 2201 if (!ll.empty()) {
Chris@284 2202 m_remoteRepoPath = lp.parse()[0]["default"].trimmed();
Chris@284 2203 DEBUG << "Set remote path to " << m_remoteRepoPath << endl;
Chris@210 2204 } else {
Chris@284 2205 m_remoteRepoPath = "";
Chris@109 2206 }
Chris@284 2207 MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
Chris@284 2208 MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
Chris@287 2209 updateWorkFolderAndRepoNames();
Chris@109 2210 break;
Chris@109 2211 }
jtkorhonen@0 2212
Chris@109 2213 case ACT_QUERY_BRANCH:
Chris@284 2214 m_currentBranch = output.trimmed();
Chris@109 2215 break;
jtkorhonen@0 2216
Chris@109 2217 case ACT_STAT:
Chris@284 2218 m_lastStatOutput = output;
Chris@109 2219 break;
Chris@163 2220
Chris@163 2221 case ACT_RESOLVE_LIST:
Chris@505 2222 // This happens on every update, after the stat (above)
Chris@163 2223 if (output != "") {
Chris@163 2224 // Remove lines beginning with R (they are resolved,
Chris@163 2225 // and the file stat parser treats R as removed)
Chris@163 2226 QStringList outList = output.split('\n');
Chris@163 2227 QStringList winnowed;
Chris@163 2228 foreach (QString line, outList) {
Chris@163 2229 if (!line.startsWith("R ")) winnowed.push_back(line);
Chris@163 2230 }
Chris@163 2231 output = winnowed.join("\n");
Chris@163 2232 }
Chris@284 2233 DEBUG << "m_lastStatOutput = " << m_lastStatOutput << endl;
Chris@199 2234 DEBUG << "resolve output = " << output << endl;
Chris@284 2235 m_hgTabs->updateWorkFolderFileList(m_lastStatOutput + output);
Chris@163 2236 break;
Chris@163 2237
Chris@163 2238 case ACT_RESOLVE_MARK:
Chris@284 2239 m_shouldHgStat = true;
Chris@163 2240 break;
Chris@109 2241
Chris@109 2242 case ACT_INCOMING:
Chris@120 2243 showIncoming(output);
Chris@120 2244 break;
Chris@120 2245
Chris@109 2246 case ACT_ANNOTATE:
Chris@331 2247 {
Chris@331 2248 AnnotateDialog dialog(this, output);
Chris@331 2249 dialog.exec();
Chris@284 2250 m_shouldHgStat = true;
Chris@109 2251 break;
Chris@331 2252 }
Chris@109 2253
Chris@109 2254 case ACT_PULL:
Chris@120 2255 showPullResult(output);
Chris@284 2256 m_shouldHgStat = true;
Chris@109 2257 break;
Chris@109 2258
Chris@109 2259 case ACT_PUSH:
Chris@120 2260 showPushResult(output);
Chris@109 2261 break;
Chris@109 2262
Chris@109 2263 case ACT_INIT:
Chris@284 2264 MultiChoiceDialog::addRecentArgument("init", m_workFolderPath);
Chris@284 2265 MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
Chris@109 2266 enableDisableActions();
Chris@284 2267 m_shouldHgStat = true;
Chris@109 2268 break;
Chris@109 2269
Chris@109 2270 case ACT_CLONEFROMREMOTE:
Chris@284 2271 MultiChoiceDialog::addRecentArgument("local", m_workFolderPath);
Chris@284 2272 MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath);
Chris@284 2273 MultiChoiceDialog::addRecentArgument("remote", m_workFolderPath, true);
Chris@291 2274 MoreInformationDialog::information
Chris@291 2275 (this,
Chris@291 2276 tr("Clone"),
Chris@384 2277 tr("Open successful"),
Chris@295 2278 tr("The remote repository was successfully cloned to the local folder <code>%1</code>.").arg(xmlEncode(m_workFolderPath)),
Chris@291 2279 output);
Chris@109 2280 enableDisableActions();
Chris@284 2281 m_shouldHgStat = true;
Chris@109 2282 break;
Chris@109 2283
Chris@109 2284 case ACT_LOG:
Chris@284 2285 m_hgTabs->setNewLog(output);
Chris@284 2286 m_needNewLog = false;
Chris@120 2287 break;
Chris@120 2288
Chris@120 2289 case ACT_LOG_INCREMENTAL:
Chris@284 2290 m_hgTabs->addIncrementalLog(output);
Chris@109 2291 break;
Chris@109 2292
Chris@109 2293 case ACT_QUERY_PARENTS:
Chris@152 2294 {
Chris@284 2295 foreach (Changeset *cs, m_currentParents) delete cs;
Chris@284 2296 m_currentParents = Changeset::parseChangesets(output);
Chris@284 2297 QStringList parentIds = Changeset::getIds(m_currentParents);
Chris@284 2298 m_hgTabs->setCurrent(parentIds, m_currentBranch);
Chris@152 2299 }
Chris@109 2300 break;
Chris@109 2301
Chris@506 2302 case ACT_QUERY_HEADS_ACTIVE:
Chris@506 2303 foreach (Changeset *cs, m_activeHeads) delete cs;
Chris@506 2304 m_activeHeads = Changeset::parseChangesets(output);
Chris@506 2305 break;
Chris@506 2306
Chris@109 2307 case ACT_QUERY_HEADS:
Chris@150 2308 {
Chris@284 2309 oldHeadIds = Changeset::getIds(m_currentHeads);
Chris@150 2310 Changesets newHeads = Changeset::parseChangesets(output);
Chris@150 2311 QStringList newHeadIds = Changeset::getIds(newHeads);
Chris@150 2312 if (oldHeadIds != newHeadIds) {
Chris@150 2313 DEBUG << "Heads changed, will prompt an incremental log if appropriate" << endl;
Chris@305 2314 DEBUG << "Old heads: " << oldHeadIds.join(",") << endl;
Chris@305 2315 DEBUG << "New heads: " << newHeadIds.join(",") << endl;
Chris@150 2316 headsChanged = true;
Chris@284 2317 foreach (Changeset *cs, m_currentHeads) delete cs;
Chris@284 2318 m_currentHeads = newHeads;
Chris@506 2319 updateClosedHeads();
Chris@150 2320 }
Chris@150 2321 }
Chris@109 2322 break;
Chris@130 2323
Chris@130 2324 case ACT_COMMIT:
Chris@347 2325 if (m_currentParents.empty()) {
Chris@347 2326 // first commit to empty repo
Chris@347 2327 m_needNewLog = true;
Chris@347 2328 }
Chris@284 2329 m_hgTabs->clearSelections();
Chris@284 2330 m_justMerged = false;
Chris@284 2331 m_shouldHgStat = true;
Chris@130 2332 break;
Chris@163 2333
Chris@514 2334 case ACT_CLOSE_BRANCH:
Chris@514 2335 m_hgTabs->clearSelections();
Chris@514 2336 m_justMerged = false;
Chris@514 2337 m_shouldHgStat = true;
Chris@514 2338 break;
Chris@514 2339
Chris@163 2340 case ACT_REVERT:
Chris@326 2341 hgMarkFilesResolved(m_lastRevertedFiles);
Chris@284 2342 m_justMerged = false;
Chris@163 2343 break;
Chris@109 2344
Chris@109 2345 case ACT_REMOVE:
Chris@109 2346 case ACT_ADD:
Chris@284 2347 m_hgTabs->clearSelections();
Chris@284 2348 m_shouldHgStat = true;
Chris@116 2349 break;
Chris@116 2350
Chris@164 2351 case ACT_TAG:
Chris@284 2352 m_needNewLog = true;
Chris@284 2353 m_shouldHgStat = true;
Chris@164 2354 break;
Chris@164 2355
Chris@278 2356 case ACT_NEW_BRANCH:
Chris@307 2357 m_shouldHgStat = true;
Chris@278 2358 break;
Chris@278 2359
Chris@288 2360 case ACT_UNCOMMITTED_SUMMARY:
Chris@168 2361 QMessageBox::information(this, tr("Change summary"),
Chris@168 2362 format3(tr("Summary of uncommitted changes"),
Chris@168 2363 "",
Chris@168 2364 output));
Chris@168 2365 break;
Chris@168 2366
Chris@288 2367 case ACT_DIFF_SUMMARY:
Chris@289 2368 {
Chris@289 2369 // Output has log info first, diff following after a blank line
Chris@289 2370 QStringList olist = output.split("\n\n", QString::SkipEmptyParts);
Chris@289 2371 if (olist.size() > 1) output = olist[1];
Chris@289 2372
Chris@289 2373 Changeset *cs = (Changeset *)completedAction.extraData;
Chris@289 2374 if (cs) {
Chris@289 2375 QMessageBox::information
Chris@289 2376 (this, tr("Change summary"),
Chris@289 2377 format3(tr("Summary of changes"),
Chris@289 2378 cs->formatHtml(),
Chris@289 2379 output));
Chris@289 2380 } else if (output == "") {
Chris@289 2381 // Can happen, for a merge commit (depending on parent)
Chris@288 2382 QMessageBox::information(this, tr("Change summary"),
Chris@288 2383 format3(tr("Summary of changes"),
Chris@288 2384 tr("No changes"),
Chris@288 2385 output));
Chris@288 2386 } else {
Chris@288 2387 QMessageBox::information(this, tr("Change summary"),
Chris@288 2388 format3(tr("Summary of changes"),
Chris@288 2389 "",
Chris@288 2390 output));
Chris@288 2391 }
Chris@288 2392 break;
Chris@289 2393 }
Chris@288 2394
Chris@109 2395 case ACT_FOLDERDIFF:
Chris@109 2396 case ACT_CHGSETDIFF:
Chris@109 2397 case ACT_SERVE:
Chris@109 2398 case ACT_HG_IGNORE:
Chris@284 2399 m_shouldHgStat = true;
Chris@109 2400 break;
Chris@109 2401
Chris@109 2402 case ACT_UPDATE:
Chris@162 2403 QMessageBox::information(this, tr("Update"), tr("<qt><h3>Update successful</h3><p>%1</p>").arg(xmlEncode(output)));
Chris@284 2404 m_shouldHgStat = true;
Chris@109 2405 break;
Chris@109 2406
Chris@109 2407 case ACT_MERGE:
Chris@294 2408 MoreInformationDialog::information
Chris@294 2409 (this, tr("Merge"), tr("Merge successful"),
Chris@302 2410 tr("Remember to test and commit the result before making any further changes."),
Chris@294 2411 output);
Chris@284 2412 m_shouldHgStat = true;
Chris@284 2413 m_justMerged = true;
Chris@109 2414 break;
Chris@109 2415
Chris@109 2416 case ACT_RETRY_MERGE:
Chris@163 2417 QMessageBox::information(this, tr("Resolved"),
Chris@302 2418 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 2419 m_shouldHgStat = true;
Chris@284 2420 m_justMerged = true;
Chris@109 2421 break;
Chris@109 2422
Chris@109 2423 default:
Chris@109 2424 break;
Chris@109 2425 }
Chris@108 2426
Chris@121 2427 // Sequence when no full log required:
Chris@520 2428 // paths -> branch -> stat -> resolve-list -> heads ->
Chris@150 2429 // incremental-log (only if heads changed) -> parents
Chris@150 2430 //
Chris@121 2431 // Sequence when full log required:
Chris@520 2432 // paths -> branch -> stat -> resolve-list -> heads ->
Chris@519 2433 // parents -> log
Chris@150 2434 //
Chris@150 2435 // Note we want to call enableDisableActions only once, at the end
Chris@150 2436 // of whichever sequence is in use.
Chris@150 2437
Chris@156 2438 bool noMore = false;
Chris@156 2439
Chris@150 2440 switch (action) {
Chris@175 2441
Chris@175 2442 case ACT_TEST_HG:
Chris@248 2443 {
Chris@248 2444 QSettings settings;
Chris@248 2445 if (settings.value("useextension", true).toBool()) {
Chris@248 2446 hgTestExtension();
Chris@284 2447 } else if (m_workFolderPath == "") {
Chris@248 2448 open();
Chris@248 2449 } else {
Chris@248 2450 hgQueryPaths();
Chris@248 2451 }
Chris@200 2452 break;
Chris@248 2453 }
Chris@200 2454
Chris@200 2455 case ACT_TEST_HG_EXT:
Chris@284 2456 if (m_workFolderPath == "") {
Chris@248 2457 open();
Chris@248 2458 } else{
Chris@248 2459 hgQueryPaths();
Chris@248 2460 }
Chris@175 2461 break;
Chris@150 2462
Chris@150 2463 case ACT_QUERY_PATHS:
Chris@519 2464 // NB this call is duplicated in hgQueryPaths
Chris@109 2465 hgQueryBranch();
Chris@150 2466 break;
Chris@150 2467
Chris@150 2468 case ACT_QUERY_BRANCH:
Chris@519 2469 // NB this call is duplicated in hgQueryBranch
Chris@109 2470 hgStat();
Chris@150 2471 break;
Chris@150 2472
Chris@150 2473 case ACT_STAT:
Chris@163 2474 hgResolveList();
Chris@163 2475 break;
Chris@519 2476
Chris@163 2477 case ACT_RESOLVE_LIST:
Chris@506 2478 hgQueryHeadsActive();
Chris@506 2479 break;
Chris@506 2480
Chris@506 2481 case ACT_QUERY_HEADS_ACTIVE:
Chris@150 2482 hgQueryHeads();
Chris@150 2483 break;
Chris@150 2484
Chris@150 2485 case ACT_QUERY_HEADS:
Chris@284 2486 if (headsChanged && !m_needNewLog) {
Chris@150 2487 hgLogIncremental(oldHeadIds);
Chris@121 2488 } else {
Chris@150 2489 hgQueryParents();
Chris@121 2490 }
Chris@150 2491 break;
Chris@150 2492
Chris@150 2493 case ACT_LOG_INCREMENTAL:
Chris@109 2494 hgQueryParents();
Chris@150 2495 break;
Chris@150 2496
Chris@150 2497 case ACT_QUERY_PARENTS:
Chris@284 2498 if (m_needNewLog) {
Chris@120 2499 hgLog();
Chris@150 2500 } else {
Chris@150 2501 // we're done
Chris@156 2502 noMore = true;
Chris@120 2503 }
Chris@150 2504 break;
Chris@150 2505
Chris@150 2506 case ACT_LOG:
Chris@150 2507 // we're done
Chris@156 2508 noMore = true;
Chris@198 2509 break;
Chris@150 2510
Chris@150 2511 default:
Chris@284 2512 if (m_shouldHgStat) {
Chris@284 2513 m_shouldHgStat = false;
Chris@109 2514 hgQueryPaths();
Chris@150 2515 } else {
Chris@156 2516 noMore = true;
jtkorhonen@0 2517 }
Chris@150 2518 break;
Chris@150 2519 }
Chris@156 2520
Chris@156 2521 if (noMore) {
Chris@540 2522 m_commandSequenceInProgress = false;
Chris@284 2523 m_stateUnknown = false;
Chris@156 2524 enableDisableActions();
Chris@284 2525 m_hgTabs->updateHistory();
Chris@359 2526 updateRecentMenu();
Chris@541 2527 checkFilesystem();
Chris@156 2528 }
jtkorhonen@0 2529 }
jtkorhonen@0 2530
jtkorhonen@0 2531 void MainWindow::connectActions()
jtkorhonen@0 2532 {
Chris@284 2533 connect(m_exitAct, SIGNAL(triggered()), this, SLOT(close()));
Chris@284 2534 connect(m_aboutAct, SIGNAL(triggered()), this, SLOT(about()));
Chris@494 2535 connect(m_helpAct, SIGNAL(triggered()), this, SLOT(help()));
Chris@273 2536
Chris@284 2537 connect(m_hgRefreshAct, SIGNAL(triggered()), this, SLOT(hgRefresh()));
Chris@284 2538 connect(m_hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove()));
Chris@284 2539 connect(m_hgAddAct, SIGNAL(triggered()), this, SLOT(hgAdd()));
Chris@284 2540 connect(m_hgCommitAct, SIGNAL(triggered()), this, SLOT(hgCommit()));
Chris@425 2541 connect(m_hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore()));
Chris@425 2542 connect(m_hgEditIgnoreAct, SIGNAL(triggered()), this, SLOT(hgEditIgnore()));
Chris@284 2543 connect(m_hgFolderDiffAct, SIGNAL(triggered()), this, SLOT(hgFolderDiff()));
Chris@284 2544 connect(m_hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate()));
Chris@284 2545 connect(m_hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert()));
Chris@284 2546 connect(m_hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge()));
Chris@273 2547
Chris@284 2548 connect(m_settingsAct, SIGNAL(triggered()), this, SLOT(settings()));
Chris@284 2549 connect(m_openAct, SIGNAL(triggered()), this, SLOT(open()));
Chris@284 2550 connect(m_changeRemoteRepoAct, SIGNAL(triggered()), this, SLOT(changeRemoteRepo()));
Chris@273 2551
Chris@284 2552 connect(m_hgIncomingAct, SIGNAL(triggered()), this, SLOT(hgIncoming()));
Chris@284 2553 connect(m_hgPullAct, SIGNAL(triggered()), this, SLOT(hgPull()));
Chris@284 2554 connect(m_hgPushAct, SIGNAL(triggered()), this, SLOT(hgPush()));
Chris@273 2555
Chris@284 2556 connect(m_hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe()));
jtkorhonen@0 2557 }
Chris@141 2558
Chris@141 2559 void MainWindow::connectTabsSignals()
Chris@141 2560 {
Chris@327 2561 connect(m_hgTabs, SIGNAL(currentChanged(int)),
Chris@327 2562 this, SLOT(enableDisableActions()));
Chris@327 2563
Chris@284 2564 connect(m_hgTabs, SIGNAL(commit()),
Chris@141 2565 this, SLOT(hgCommit()));
Chris@141 2566
Chris@284 2567 connect(m_hgTabs, SIGNAL(revert()),
Chris@141 2568 this, SLOT(hgRevert()));
Chris@141 2569
Chris@284 2570 connect(m_hgTabs, SIGNAL(diffWorkingFolder()),
Chris@141 2571 this, SLOT(hgFolderDiff()));
Chris@168 2572
Chris@284 2573 connect(m_hgTabs, SIGNAL(showSummary()),
Chris@168 2574 this, SLOT(hgShowSummary()));
Chris@311 2575
Chris@311 2576 connect(m_hgTabs, SIGNAL(newBranch()),
Chris@311 2577 this, SLOT(hgNewBranch()));
Chris@311 2578
Chris@311 2579 connect(m_hgTabs, SIGNAL(noBranch()),
Chris@311 2580 this, SLOT(hgNoBranch()));
Chris@148 2581
Chris@284 2582 connect(m_hgTabs, SIGNAL(updateTo(QString)),
Chris@148 2583 this, SLOT(hgUpdateToRev(QString)));
Chris@141 2584
Chris@284 2585 connect(m_hgTabs, SIGNAL(diffToCurrent(QString)),
Chris@148 2586 this, SLOT(hgDiffToCurrent(QString)));
Chris@141 2587
Chris@284 2588 connect(m_hgTabs, SIGNAL(diffToParent(QString, QString)),
Chris@148 2589 this, SLOT(hgDiffToParent(QString, QString)));
Chris@141 2590
Chris@289 2591 connect(m_hgTabs, SIGNAL(showSummary(Changeset *)),
Chris@289 2592 this, SLOT(hgShowSummaryFor(Changeset *)));
Chris@288 2593
Chris@284 2594 connect(m_hgTabs, SIGNAL(mergeFrom(QString)),
Chris@148 2595 this, SLOT(hgMergeFrom(QString)));
Chris@164 2596
Chris@307 2597 connect(m_hgTabs, SIGNAL(newBranch(QString)),
Chris@311 2598 this, SLOT(hgNewBranch()));
Chris@278 2599
Chris@514 2600 connect(m_hgTabs, SIGNAL(closeBranch(QString)),
Chris@514 2601 this, SLOT(hgCloseBranch()));
Chris@514 2602
Chris@284 2603 connect(m_hgTabs, SIGNAL(tag(QString)),
Chris@148 2604 this, SLOT(hgTag(QString)));
Chris@326 2605
Chris@326 2606 connect(m_hgTabs, SIGNAL(annotateFiles(QStringList)),
Chris@326 2607 this, SLOT(hgAnnotateFiles(QStringList)));
Chris@326 2608
Chris@326 2609 connect(m_hgTabs, SIGNAL(diffFiles(QStringList)),
Chris@326 2610 this, SLOT(hgDiffFiles(QStringList)));
Chris@326 2611
Chris@326 2612 connect(m_hgTabs, SIGNAL(commitFiles(QStringList)),
Chris@326 2613 this, SLOT(hgCommitFiles(QStringList)));
Chris@326 2614
Chris@326 2615 connect(m_hgTabs, SIGNAL(revertFiles(QStringList)),
Chris@326 2616 this, SLOT(hgRevertFiles(QStringList)));
Chris@326 2617
Chris@361 2618 connect(m_hgTabs, SIGNAL(renameFiles(QStringList)),
Chris@361 2619 this, SLOT(hgRenameFiles(QStringList)));
Chris@361 2620
Chris@361 2621 connect(m_hgTabs, SIGNAL(copyFiles(QStringList)),
Chris@361 2622 this, SLOT(hgCopyFiles(QStringList)));
Chris@361 2623
Chris@326 2624 connect(m_hgTabs, SIGNAL(addFiles(QStringList)),
Chris@326 2625 this, SLOT(hgAddFiles(QStringList)));
Chris@326 2626
Chris@326 2627 connect(m_hgTabs, SIGNAL(removeFiles(QStringList)),
Chris@326 2628 this, SLOT(hgRemoveFiles(QStringList)));
Chris@326 2629
Chris@326 2630 connect(m_hgTabs, SIGNAL(redoFileMerges(QStringList)),
Chris@326 2631 this, SLOT(hgRedoFileMerges(QStringList)));
Chris@326 2632
Chris@326 2633 connect(m_hgTabs, SIGNAL(markFilesResolved(QStringList)),
Chris@326 2634 this, SLOT(hgMarkFilesResolved(QStringList)));
Chris@326 2635
Chris@326 2636 connect(m_hgTabs, SIGNAL(ignoreFiles(QStringList)),
Chris@326 2637 this, SLOT(hgIgnoreFiles(QStringList)));
Chris@326 2638
Chris@326 2639 connect(m_hgTabs, SIGNAL(unIgnoreFiles(QStringList)),
Chris@326 2640 this, SLOT(hgUnIgnoreFiles(QStringList)));
Chris@141 2641 }
Chris@141 2642
jtkorhonen@0 2643 void MainWindow::enableDisableActions()
jtkorhonen@0 2644 {
Chris@90 2645 DEBUG << "MainWindow::enableDisableActions" << endl;
Chris@90 2646
Chris@284 2647 QString dirname = QDir(m_workFolderPath).dirName();
Chris@340 2648
Chris@340 2649 if (m_workFolderPath != "") { // dirname of "" is ".", so test path instead
Chris@202 2650 setWindowTitle(tr("EasyMercurial: %1").arg(dirname));
Chris@202 2651 } else {
Chris@202 2652 setWindowTitle(tr("EasyMercurial"));
Chris@202 2653 }
Chris@202 2654
Chris@115 2655 //!!! should also do things like set the status texts for the
Chris@115 2656 //!!! actions appropriately by context
Chris@115 2657
jtkorhonen@0 2658 QDir localRepoDir;
jtkorhonen@0 2659 QDir workFolderDir;
jtkorhonen@0 2660
Chris@284 2661 m_remoteRepoActionsEnabled = true;
Chris@284 2662 if (m_remoteRepoPath.isEmpty()) {
Chris@284 2663 m_remoteRepoActionsEnabled = false;
jtkorhonen@0 2664 }
jtkorhonen@0 2665
Chris@284 2666 m_localRepoActionsEnabled = true;
Chris@284 2667 if (m_workFolderPath.isEmpty()) {
Chris@284 2668 m_localRepoActionsEnabled = false;
jtkorhonen@0 2669 }
jtkorhonen@0 2670
Chris@284 2671 if (m_workFolderPath == "" || !workFolderDir.exists(m_workFolderPath)) {
Chris@284 2672 m_localRepoActionsEnabled = false;
jtkorhonen@0 2673 }
jtkorhonen@0 2674
Chris@284 2675 if (!localRepoDir.exists(m_workFolderPath + "/.hg")) {
Chris@284 2676 m_localRepoActionsEnabled = false;
jtkorhonen@0 2677 }
jtkorhonen@0 2678
Chris@179 2679 bool haveDiff = false;
Chris@179 2680 QSettings settings;
Chris@179 2681 settings.beginGroup("Locations");
Chris@179 2682 if (settings.value("extdiffbinary", "").toString() != "") {
Chris@179 2683 haveDiff = true;
Chris@179 2684 }
Chris@179 2685 settings.endGroup();
Chris@112 2686
Chris@505 2687 m_hgTabs->setHaveMerge(m_currentParents.size() == 2);
Chris@505 2688
Chris@365 2689 m_hgRefreshAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2690 m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && haveDiff);
Chris@365 2691 m_hgRevertAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2692 m_hgAddAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2693 m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled);
Chris@425 2694 m_hgIgnoreAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2695 m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2696 m_hgCommitAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2697 m_hgMergeAct->setEnabled(m_localRepoActionsEnabled);
Chris@365 2698 m_hgServeAct->setEnabled(m_localRepoActionsEnabled);
Chris@413 2699 m_hgEditIgnoreAct->setEnabled(m_localRepoActionsEnabled);
Chris@273 2700
Chris@284 2701 DEBUG << "m_localRepoActionsEnabled = " << m_localRepoActionsEnabled << endl;
Chris@284 2702 DEBUG << "canCommit = " << m_hgTabs->canCommit() << endl;
Chris@273 2703
Chris@284 2704 m_hgAddAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canAdd());
Chris@284 2705 m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRemove());
Chris@284 2706 m_hgCommitAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canCommit());
Chris@284 2707 m_hgRevertAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRevert());
Chris@284 2708 m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canDiff());
Chris@425 2709 m_hgIgnoreAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canIgnore());
Chris@90 2710
Chris@108 2711 // A default merge makes sense if:
Chris@108 2712 // * there is only one parent (if there are two, we have an uncommitted merge) and
Chris@108 2713 // * there are exactly two heads that have the same branch as the current branch and
Chris@108 2714 // * our parent is one of those heads
Chris@108 2715 //
Chris@108 2716 // A default update makes sense if:
Chris@108 2717 // * there is only one parent and
Chris@108 2718 // * the parent is not one of the current heads
Chris@156 2719
Chris@108 2720 bool canMerge = false;
Chris@108 2721 bool canUpdate = false;
Chris@156 2722 bool haveMerge = false;
Chris@162 2723 bool emptyRepo = false;
Chris@225 2724 bool noWorkingCopy = false;
Chris@235 2725 bool newBranch = false;
Chris@506 2726 bool closedBranch = false;
Chris@506 2727 int currentBranchActiveHeads = 0;
Chris@273 2728
Chris@284 2729 if (m_currentParents.size() == 1) {
Chris@156 2730 bool parentIsHead = false;
Chris@506 2731 bool parentIsActiveHead = false;
Chris@284 2732 Changeset *parent = m_currentParents[0];
Chris@506 2733 foreach (Changeset *head, m_activeHeads) {
Chris@284 2734 if (head->isOnBranch(m_currentBranch)) {
Chris@506 2735 ++currentBranchActiveHeads;
Chris@235 2736 }
Chris@235 2737 if (parent->id() == head->id()) {
Chris@506 2738 parentIsActiveHead = parentIsHead = true;
Chris@108 2739 }
Chris@108 2740 }
Chris@506 2741 if (!parentIsActiveHead) {
Chris@506 2742 foreach (Changeset *head, m_currentHeads) {
Chris@506 2743 if (parent->id() == head->id()) {
Chris@506 2744 parentIsHead = true;
Chris@506 2745 }
Chris@506 2746 }
Chris@506 2747 }
Chris@506 2748 if (currentBranchActiveHeads == 2 && parentIsActiveHead) {
Chris@108 2749 canMerge = true;
Chris@108 2750 }
Chris@506 2751 if (currentBranchActiveHeads == 0 && parentIsActiveHead) {
Chris@235 2752 // Just created a new branch
Chris@235 2753 newBranch = true;
Chris@235 2754 }
Chris@108 2755 if (!parentIsHead) {
Chris@108 2756 canUpdate = true;
Chris@108 2757 DEBUG << "parent id = " << parent->id() << endl;
Chris@108 2758 DEBUG << " head ids "<<endl;
Chris@284 2759 foreach (Changeset *h, m_currentHeads) {
Chris@108 2760 DEBUG << "head id = " << h->id() << endl;
Chris@108 2761 }
Chris@506 2762 } else if (!parentIsActiveHead) {
Chris@506 2763 closedBranch = true;
Chris@108 2764 }
Chris@284 2765 m_justMerged = false;
Chris@284 2766 } else if (m_currentParents.size() == 0) {
Chris@284 2767 if (m_currentHeads.size() == 0) {
Chris@225 2768 // No heads -> empty repo
Chris@225 2769 emptyRepo = true;
Chris@225 2770 } else {
Chris@225 2771 // Heads, but no parents -> no working copy, e.g. we have
Chris@225 2772 // just converted this repo but haven't updated in it yet.
Chris@225 2773 // Uncommon but confusing; probably merits a special case
Chris@225 2774 noWorkingCopy = true;
Chris@225 2775 canUpdate = true;
Chris@225 2776 }
Chris@284 2777 m_justMerged = false;
Chris@156 2778 } else {
Chris@156 2779 haveMerge = true;
Chris@284 2780 m_justMerged = true;
Chris@108 2781 }
Chris@505 2782
Chris@363 2783 m_hgIncomingAct->setEnabled(m_remoteRepoActionsEnabled);
Chris@363 2784 m_hgPullAct->setEnabled(m_remoteRepoActionsEnabled);
Chris@363 2785 // permit push even if no remote yet; we'll ask for one
Chris@363 2786 m_hgPushAct->setEnabled(m_localRepoActionsEnabled && !emptyRepo);
Chris@363 2787
Chris@284 2788 m_hgMergeAct->setEnabled(m_localRepoActionsEnabled &&
Chris@506 2789 (canMerge || m_hgTabs->canResolve()));
Chris@284 2790 m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled &&
Chris@506 2791 (canUpdate && !m_hgTabs->haveChangesToCommit()));
Chris@115 2792
Chris@115 2793 // Set the state field on the file status widget
Chris@115 2794
Chris@115 2795 QString branchText;
Chris@284 2796 if (m_currentBranch == "" || m_currentBranch == "default") {
Chris@115 2797 branchText = tr("the default branch");
Chris@115 2798 } else {
Chris@284 2799 branchText = tr("branch \"%1\"").arg(m_currentBranch);
Chris@115 2800 }
Chris@156 2801
Chris@284 2802 if (m_stateUnknown) {
Chris@284 2803 if (m_workFolderPath == "") {
Chris@287 2804 m_workStatus->setState(tr("No repository open"));
Chris@248 2805 } else {
Chris@287 2806 m_workStatus->setState(tr("(Examining repository)"));
Chris@248 2807 }
Chris@173 2808 } else if (emptyRepo) {
Chris@287 2809 m_workStatus->setState(tr("Nothing committed to this repository yet"));
Chris@225 2810 } else if (noWorkingCopy) {
Chris@287 2811 m_workStatus->setState(tr("No working copy yet: consider updating"));
Chris@162 2812 } else if (canMerge) {
Chris@287 2813 m_workStatus->setState(tr("<b>Awaiting merge</b> on %1").arg(branchText));
Chris@284 2814 } else if (!m_hgTabs->getAllUnresolvedFiles().empty()) {
Chris@287 2815 m_workStatus->setState(tr("Have unresolved files following merge on %1").arg(branchText));
Chris@156 2816 } else if (haveMerge) {
Chris@287 2817 m_workStatus->setState(tr("Have merged but not yet committed on %1").arg(branchText));
Chris@235 2818 } else if (newBranch) {
Chris@287 2819 m_workStatus->setState(tr("On %1. New branch: has not yet been committed").arg(branchText));
Chris@506 2820 } else if (closedBranch) {
Chris@506 2821 if (canUpdate) {
Chris@506 2822 m_workStatus->setState(tr("On a closed branch. Not at the head of the branch"));
Chris@506 2823 } else {
Chris@506 2824 m_workStatus->setState(tr("At the head of a closed branch"));
Chris@506 2825 }
Chris@156 2826 } else if (canUpdate) {
Chris@284 2827 if (m_hgTabs->haveChangesToCommit()) {
Chris@163 2828 // have uncommitted changes
Chris@287 2829 m_workStatus->setState(tr("On %1. Not at the head of the branch").arg(branchText));
Chris@163 2830 } else {
Chris@163 2831 // no uncommitted changes
Chris@287 2832 m_workStatus->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText));
Chris@163 2833 }
Chris@506 2834 } else if (currentBranchActiveHeads > 1) {
Chris@506 2835 m_workStatus->setState(tr("At one of %n heads of %1", "", currentBranchActiveHeads).arg(branchText));
Chris@115 2836 } else {
Chris@287 2837 m_workStatus->setState(tr("At the head of %1").arg(branchText));
Chris@115 2838 }
jtkorhonen@0 2839 }
jtkorhonen@0 2840
Chris@359 2841
Chris@506 2842 void MainWindow::updateClosedHeads()
Chris@506 2843 {
Chris@506 2844 m_closedHeadIds.clear();
Chris@506 2845 QSet<QString> activeIds;
Chris@506 2846 foreach (Changeset *cs, m_activeHeads) {
Chris@506 2847 activeIds.insert(cs->id());
Chris@506 2848 }
Chris@506 2849 foreach (Changeset *cs, m_currentHeads) {
Chris@506 2850 if (!activeIds.contains(cs->id())) {
Chris@506 2851 m_closedHeadIds.insert(cs->id());
Chris@506 2852 }
Chris@506 2853 }
Chris@506 2854 m_hgTabs->setClosedHeadIds(m_closedHeadIds);
Chris@506 2855 }
Chris@506 2856
Chris@359 2857 void MainWindow::updateRecentMenu()
Chris@359 2858 {
Chris@359 2859 m_recentMenu->clear();
Chris@359 2860 RecentFiles rf("Recent-local");
Chris@359 2861 QStringList recent = rf.getRecent();
Chris@359 2862 if (recent.empty()) {
Chris@359 2863 QLabel *label = new QLabel(tr("No recent local repositories"));
Chris@359 2864 QWidgetAction *wa = new QWidgetAction(m_recentMenu);
Chris@359 2865 wa->setDefaultWidget(label);
Chris@359 2866 return;
Chris@359 2867 }
Chris@359 2868 foreach (QString r, recent) {
Chris@359 2869 QAction *a = m_recentMenu->addAction(r);
Chris@359 2870 connect(a, SIGNAL(activated()), this, SLOT(recentMenuActivated()));
Chris@359 2871 }
Chris@359 2872 }
Chris@359 2873
jtkorhonen@0 2874 void MainWindow::createActions()
jtkorhonen@0 2875 {
jtkorhonen@0 2876 //File actions
Chris@365 2877 m_openAct = new QAction(QIcon(":/images/fileopen.png"), tr("&Open..."), this);
Chris@365 2878 m_openAct->setStatusTip(tr("Open an existing repository or working folder"));
Chris@365 2879 m_openAct->setShortcut(tr("Ctrl+O"));
Chris@365 2880
Chris@365 2881 m_changeRemoteRepoAct = new QAction(tr("Set Remote &Location..."), this);
Chris@363 2882 m_changeRemoteRepoAct->setStatusTip(tr("Set or change the default remote repository for pull and push actions"));
Chris@273 2883
Chris@365 2884 m_settingsAct = new QAction(QIcon(":/images/settings.png"), tr("&Settings..."), this);
Chris@365 2885 m_settingsAct->setStatusTip(tr("View and change application settings"));
Chris@273 2886
Chris@343 2887 #ifdef Q_OS_WIN32
Chris@365 2888 m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("E&xit"), this);
Chris@343 2889 #else
Chris@365 2890 m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("&Quit"), this);
Chris@343 2891 #endif
Chris@284 2892 m_exitAct->setShortcuts(QKeySequence::Quit);
Chris@365 2893 m_exitAct->setStatusTip(tr("Exit EasyMercurial"));
jtkorhonen@0 2894
jtkorhonen@0 2895 //Repository actions
Chris@544 2896 m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("&Re-Read Working Folder"), this);
Chris@365 2897 m_hgRefreshAct->setShortcut(tr("Ctrl+R"));
Chris@284 2898 m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder"));
Chris@273 2899
Chris@365 2900 m_hgIncomingAct = new QAction(QIcon(":/images/incoming.png"), tr("Pre&view Incoming Changes"), this);
Chris@365 2901 m_hgIncomingAct->setIconText(tr("Preview"));
Chris@365 2902 m_hgIncomingAct->setStatusTip(tr("See what changes are available in the remote repository waiting to be pulled"));
Chris@365 2903
Chris@365 2904 m_hgPullAct = new QAction(QIcon(":/images/pull.png"), tr("Pu&ll from Remote Repository"), this);
Chris@365 2905 m_hgPullAct->setIconText(tr("Pull"));
Chris@365 2906 m_hgPullAct->setShortcut(tr("Ctrl+L"));
Chris@365 2907 m_hgPullAct->setStatusTip(tr("Pull changes from the remote repository to the local repository"));
Chris@365 2908
Chris@365 2909 m_hgPushAct = new QAction(QIcon(":/images/push.png"), tr("Pus&h to Remote Repository"), this);
Chris@365 2910 m_hgPushAct->setIconText(tr("Push"));
Chris@365 2911 m_hgPushAct->setShortcut(tr("Ctrl+H"));
Chris@284 2912 m_hgPushAct->setStatusTip(tr("Push changes from the local repository to the remote repository"));
jtkorhonen@0 2913
jtkorhonen@0 2914 //Workfolder actions
Chris@365 2915 m_hgFolderDiffAct = new QAction(QIcon(":/images/folderdiff.png"), tr("&Diff"), this);
Chris@365 2916 m_hgFolderDiffAct->setIconText(tr("Diff"));
Chris@365 2917 m_hgFolderDiffAct->setShortcut(tr("Ctrl+D"));
Chris@284 2918 m_hgFolderDiffAct->setStatusTip(tr("See what has changed in the working folder compared with the last committed state"));
Chris@273 2919
Chris@365 2920 m_hgRevertAct = new QAction(QIcon(":/images/undo.png"), tr("Re&vert"), this);
Chris@284 2921 m_hgRevertAct->setStatusTip(tr("Throw away your changes and return to the last committed state"));
Chris@273 2922
Chris@365 2923 m_hgAddAct = new QAction(QIcon(":/images/add.png"), tr("&Add Files"), this);
Chris@365 2924 m_hgAddAct->setIconText(tr("Add"));
Chris@365 2925 m_hgAddAct->setShortcut(tr("+"));
Chris@365 2926 m_hgAddAct->setStatusTip(tr("Mark the selected files to be added on the next commit"));
Chris@365 2927
Chris@365 2928 m_hgRemoveAct = new QAction(QIcon(":/images/remove.png"), tr("&Remove Files"), this);
Chris@365 2929 m_hgRemoveAct->setIconText(tr("Remove"));
Chris@365 2930 m_hgRemoveAct->setShortcut(tr("Del"));
Chris@365 2931 m_hgRemoveAct->setStatusTip(tr("Mark the selected files to be removed from version control on the next commit"));
Chris@365 2932
Chris@425 2933 m_hgIgnoreAct = new QAction(tr("&Ignore Files..."), this);
Chris@425 2934 m_hgIgnoreAct->setStatusTip(tr("Add the selected filenames to the ignored list, of files that should never be tracked in this repository"));
Chris@425 2935
Chris@425 2936 m_hgEditIgnoreAct = new QAction(tr("Edit Ignored List"), this);
Chris@425 2937 m_hgEditIgnoreAct->setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial"));
Chris@425 2938
Chris@365 2939 m_hgUpdateAct = new QAction(QIcon(":/images/update.png"), tr("&Update to Branch Head"), this);
Chris@365 2940 m_hgUpdateAct->setIconText(tr("Update"));
Chris@365 2941 m_hgUpdateAct->setShortcut(tr("Ctrl+U"));
Chris@284 2942 m_hgUpdateAct->setStatusTip(tr("Update the working folder to the head of the current repository branch"));
jtkorhonen@0 2943
Chris@365 2944 m_hgCommitAct = new QAction(QIcon(":/images/commit.png"), tr("&Commit..."), this);
Chris@365 2945 m_hgCommitAct->setShortcut(tr("Ctrl+Return"));
Chris@284 2946 m_hgCommitAct->setStatusTip(tr("Commit your changes to the local repository"));
Chris@273 2947
Chris@365 2948 m_hgMergeAct = new QAction(QIcon(":/images/merge.png"), tr("&Merge"), this);
Chris@365 2949 m_hgMergeAct->setShortcut(tr("Ctrl+M"));
Chris@284 2950 m_hgMergeAct->setStatusTip(tr("Merge the two independent sets of changes in the local repository into the working folder"));
jtkorhonen@0 2951
Chris@425 2952 m_hgServeAct = new QAction(tr("Share Repository"), this);
Chris@425 2953 m_hgServeAct->setStatusTip(tr("Serve local repository temporarily via HTTP for workgroup access"));
jtkorhonen@11 2954
jtkorhonen@0 2955 //Help actions
Chris@494 2956 #ifdef Q_OS_MAC
Chris@494 2957 m_helpAct = new QAction(tr("EasyMercurial Help"), this);
Chris@494 2958 #else
Chris@494 2959 m_helpAct = new QAction(tr("Help Topics"), this);
Chris@494 2960 #endif
Chris@494 2961 m_helpAct->setShortcuts(QKeySequence::HelpContents);
Chris@284 2962 m_aboutAct = new QAction(tr("About EasyMercurial"), this);
Chris@94 2963
Chris@94 2964 // Miscellaneous
Chris@199 2965 QShortcut *clearSelectionsShortcut = new QShortcut(Qt::Key_Escape, this);
Chris@199 2966 connect(clearSelectionsShortcut, SIGNAL(activated()),
Chris@199 2967 this, SLOT(clearSelections()));
jtkorhonen@0 2968 }
jtkorhonen@0 2969
jtkorhonen@0 2970 void MainWindow::createMenus()
jtkorhonen@0 2971 {
Chris@365 2972 m_fileMenu = menuBar()->addMenu(tr("&File"));
Chris@365 2973
Chris@365 2974 m_fileMenu->addAction(m_openAct);
Chris@365 2975 m_recentMenu = m_fileMenu->addMenu(tr("Open Re&cent"));
Chris@365 2976 m_fileMenu->addAction(m_hgRefreshAct);
Chris@365 2977 m_fileMenu->addSeparator();
Chris@425 2978 m_fileMenu->addAction(m_hgServeAct);
Chris@425 2979 m_fileMenu->addSeparator();
Chris@365 2980 m_fileMenu->addAction(m_settingsAct);
Chris@365 2981 m_fileMenu->addSeparator();
Chris@365 2982 m_fileMenu->addAction(m_exitAct);
Chris@365 2983
Chris@365 2984 QMenu *workMenu;
Chris@365 2985 workMenu = menuBar()->addMenu(tr("&Work"));
Chris@365 2986 workMenu->addAction(m_hgFolderDiffAct);
Chris@365 2987 workMenu->addSeparator();
Chris@365 2988 workMenu->addAction(m_hgUpdateAct);
Chris@365 2989 workMenu->addAction(m_hgCommitAct);
Chris@365 2990 workMenu->addAction(m_hgMergeAct);
Chris@365 2991 workMenu->addSeparator();
Chris@365 2992 workMenu->addAction(m_hgAddAct);
Chris@365 2993 workMenu->addAction(m_hgRemoveAct);
Chris@365 2994 workMenu->addSeparator();
Chris@425 2995 workMenu->addAction(m_hgIgnoreAct);
Chris@425 2996 workMenu->addAction(m_hgEditIgnoreAct);
Chris@425 2997 workMenu->addSeparator();
Chris@365 2998 workMenu->addAction(m_hgRevertAct);
Chris@365 2999
Chris@365 3000 QMenu *remoteMenu;
Chris@365 3001 remoteMenu = menuBar()->addMenu(tr("&Remote"));
Chris@425 3002 remoteMenu->addAction(m_changeRemoteRepoAct);
Chris@425 3003 remoteMenu->addSeparator();
Chris@365 3004 remoteMenu->addAction(m_hgIncomingAct);
Chris@365 3005 remoteMenu->addSeparator();
Chris@365 3006 remoteMenu->addAction(m_hgPullAct);
Chris@365 3007 remoteMenu->addAction(m_hgPushAct);
Chris@273 3008
Chris@378 3009 m_helpMenu = menuBar()->addMenu(tr("&Help"));
Chris@494 3010 m_helpMenu->addAction(m_helpAct);
Chris@284 3011 m_helpMenu->addAction(m_aboutAct);
jtkorhonen@0 3012 }
jtkorhonen@0 3013
jtkorhonen@0 3014 void MainWindow::createToolBars()
jtkorhonen@0 3015 {
Chris@442 3016 int sz = 32;
Chris@442 3017
Chris@543 3018 bool spacingReqd = false;
chris@550 3019 QString spacer = "";
Chris@543 3020 #ifndef Q_OS_MAC
Chris@543 3021 spacingReqd = true;
chris@550 3022 spacer = " ";
chris@550 3023 #ifdef Q_OS_WIN32
chris@550 3024 spacer = " ";
chris@550 3025 #endif
Chris@543 3026 #endif
Chris@273 3027
Chris@442 3028 m_workFolderToolBar = addToolBar(tr("Work"));
Chris@284 3029 addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar);
Chris@442 3030 m_workFolderToolBar->setIconSize(QSize(sz, sz));
Chris@543 3031 if (spacingReqd) {
Chris@543 3032 QWidget *w = new QWidget;
Chris@543 3033 w->setFixedHeight(6);
Chris@543 3034 m_workFolderToolBar->addWidget(w);
Chris@543 3035 }
Chris@284 3036 m_workFolderToolBar->addAction(m_hgFolderDiffAct);
Chris@284 3037 m_workFolderToolBar->addSeparator();
Chris@284 3038 m_workFolderToolBar->addAction(m_hgRevertAct);
Chris@284 3039 m_workFolderToolBar->addAction(m_hgUpdateAct);
Chris@284 3040 m_workFolderToolBar->addAction(m_hgCommitAct);
Chris@284 3041 m_workFolderToolBar->addAction(m_hgMergeAct);
Chris@284 3042 m_workFolderToolBar->addSeparator();
Chris@284 3043 m_workFolderToolBar->addAction(m_hgAddAct);
Chris@284 3044 m_workFolderToolBar->addAction(m_hgRemoveAct);
Chris@365 3045 m_workFolderToolBar->setMovable(false);
Chris@61 3046
Chris@543 3047 m_repoToolBar = addToolBar(tr("Remote"));
Chris@543 3048 m_repoToolBar->setIconSize(QSize(sz, sz));
chris@550 3049 m_repoToolBar->addWidget(new QLabel(spacer));
Chris@543 3050 m_repoToolBar->addAction(m_openAct);
chris@550 3051 m_repoToolBar->addWidget(new QLabel(spacer));
Chris@543 3052 m_repoToolBar->addSeparator();
Chris@543 3053 m_repoToolBar->addAction(m_hgIncomingAct);
Chris@543 3054 m_repoToolBar->addAction(m_hgPullAct);
Chris@543 3055 m_repoToolBar->addAction(m_hgPushAct);
Chris@543 3056 m_repoToolBar->setMovable(false);
Chris@543 3057
Chris@230 3058 updateToolBarStyle();
jtkorhonen@0 3059 }
jtkorhonen@0 3060
Chris@230 3061 void MainWindow::updateToolBarStyle()
Chris@230 3062 {
Chris@230 3063 QSettings settings;
Chris@230 3064 settings.beginGroup("Presentation");
Chris@230 3065 bool showText = settings.value("showiconlabels", true).toBool();
Chris@230 3066 settings.endGroup();
Chris@230 3067
Chris@230 3068 foreach (QToolButton *tb, findChildren<QToolButton *>()) {
Chris@230 3069 tb->setToolButtonStyle(showText ?
Chris@230 3070 Qt::ToolButtonTextUnderIcon :
Chris@230 3071 Qt::ToolButtonIconOnly);
Chris@230 3072 }
Chris@230 3073 }
jtkorhonen@0 3074
Chris@287 3075 void MainWindow::updateWorkFolderAndRepoNames()
Chris@287 3076 {
Chris@287 3077 m_hgTabs->setLocalPath(m_workFolderPath);
Chris@287 3078
Chris@287 3079 m_workStatus->setLocalPath(m_workFolderPath);
Chris@287 3080 m_workStatus->setRemoteURL(m_remoteRepoPath);
Chris@287 3081 }
Chris@287 3082
jtkorhonen@0 3083 void MainWindow::createStatusBar()
jtkorhonen@0 3084 {
jtkorhonen@0 3085 statusBar()->showMessage(tr("Ready"));
jtkorhonen@0 3086 }
jtkorhonen@0 3087
jtkorhonen@0 3088 void MainWindow::readSettings()
jtkorhonen@0 3089 {
jtkorhonen@0 3090 QDir workFolder;
jtkorhonen@0 3091
Chris@61 3092 QSettings settings;
jtkorhonen@0 3093
Chris@284 3094 m_workFolderPath = settings.value("workfolderpath", "").toString();
Chris@553 3095 if (!workFolder.exists(m_workFolderPath)) {
Chris@284 3096 m_workFolderPath = "";
jtkorhonen@0 3097 }
jtkorhonen@0 3098
jtkorhonen@0 3099 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
Chris@340 3100 QSize size = settings.value("size", QSize(550, 550)).toSize();
Chris@284 3101 m_firstStart = settings.value("firststart", QVariant(true)).toBool();
jtkorhonen@0 3102
jtkorhonen@0 3103 resize(size);
jtkorhonen@0 3104 move(pos);
jtkorhonen@0 3105 }
jtkorhonen@0 3106
jtkorhonen@0 3107 void MainWindow::writeSettings()
jtkorhonen@0 3108 {
Chris@61 3109 QSettings settings;
jtkorhonen@0 3110 settings.setValue("pos", pos());
jtkorhonen@0 3111 settings.setValue("size", size());
Chris@284 3112 settings.setValue("remoterepopath", m_remoteRepoPath);
Chris@284 3113 settings.setValue("workfolderpath", m_workFolderPath);
Chris@284 3114 settings.setValue("firststart", m_firstStart);
jtkorhonen@0 3115 }
jtkorhonen@0 3116
Chris@491 3117 void MainWindow::newerVersionAvailable(QString version)
Chris@491 3118 {
Chris@491 3119 QSettings settings;
Chris@491 3120 settings.beginGroup("NewerVersionWarning");
Chris@491 3121 QString tag = QString("version-%1-available-show").arg(version);
Chris@491 3122 if (settings.value(tag, true).toBool()) {
Chris@491 3123 QString title(tr("Newer version available"));
Chris@491 3124 QString text(tr("<h3>Newer version available</h3><p>You are using version %1 of EasyMercurial, but version %3 is now available.</p><p>Please see the <a href=\"http://easymercurial.org/\">EasyMercurial website</a> for more information.</p>").arg(EASYHG_VERSION).arg(version));
Chris@491 3125 QMessageBox::information(this, title, text);
Chris@491 3126 settings.setValue(tag, false);
Chris@491 3127 }
Chris@491 3128 settings.endGroup();
Chris@491 3129 }
Chris@491 3130
Chris@494 3131 void MainWindow::help()
Chris@494 3132 {
Chris@494 3133 if (!m_helpDialog) {
Chris@494 3134 m_helpDialog = new QDialog;
Chris@494 3135 QGridLayout *layout = new QGridLayout;
Chris@494 3136 m_helpDialog->setLayout(layout);
Chris@498 3137 QPushButton *home = new QPushButton;
Chris@498 3138 home->setIcon(QIcon(":images/home.png"));
Chris@498 3139 layout->addWidget(home, 0, 0);
Chris@498 3140 QPushButton *back = new QPushButton;
Chris@498 3141 back->setIcon(QIcon(":images/back.png"));
Chris@498 3142 layout->addWidget(back, 0, 1);
Chris@498 3143 QPushButton *fwd = new QPushButton;
Chris@498 3144 fwd->setIcon(QIcon(":images/forward.png"));
Chris@498 3145 layout->addWidget(fwd, 0, 2);
Chris@494 3146 QTextBrowser *text = new QTextBrowser;
Chris@494 3147 text->setOpenExternalLinks(true);
Chris@498 3148 layout->addWidget(text, 1, 0, 1, 4);
Chris@494 3149 text->setSource(QUrl("qrc:help/topics.html"));
Chris@494 3150 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close);
Chris@494 3151 connect(bb, SIGNAL(rejected()), m_helpDialog, SLOT(hide()));
Chris@498 3152 connect(text, SIGNAL(backwardAvailable(bool)),
Chris@498 3153 back, SLOT(setEnabled(bool)));
Chris@498 3154 connect(text, SIGNAL(forwardAvailable(bool)),
Chris@498 3155 fwd, SLOT(setEnabled(bool)));
Chris@498 3156 connect(home, SIGNAL(clicked()), text, SLOT(home()));
Chris@498 3157 connect(back, SIGNAL(clicked()), text, SLOT(backward()));
Chris@498 3158 connect(fwd, SIGNAL(clicked()), text, SLOT(forward()));
Chris@498 3159 back->setEnabled(false);
Chris@498 3160 fwd->setEnabled(false);
Chris@498 3161 layout->addWidget(bb, 2, 0, 1, 4);
Chris@498 3162 layout->setColumnStretch(3, 20);
Chris@494 3163 m_helpDialog->resize(450, 500);
Chris@494 3164 }
Chris@494 3165 QTextBrowser *tb = m_helpDialog->findChild<QTextBrowser *>();
Chris@494 3166 if (tb) tb->home();
Chris@494 3167 m_helpDialog->show();
Chris@494 3168 m_helpDialog->raise();
Chris@494 3169 }
Chris@494 3170