annotate src/mainwindow.cpp @ 736:3e6995d01c15

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