Silence compilation warnings
[qgit4/redivivus.git] / src / mainimpl.cpp
blob21d4a9dee5946e673b0f714ad24c9604cc1c8213
1 /*
2 Description: qgit main view
4 Author: Marco Costalba (C) 2005-2007
6 Copyright: See COPYING file that comes with this distribution
8 */
9 #include <QCloseEvent>
10 #include <QEvent>
11 #include <QFileDialog>
12 #include <QInputDialog>
13 #include <QMenu>
14 #include <QMessageBox>
15 #include <QMimeData>
16 #include <QProgressBar>
17 #include <QScrollBar>
18 #include <QSettings>
19 #include <QShortcut>
20 #include <QStatusBar>
21 #include <QTimer>
22 #include <QWheelEvent>
23 #include <QTextCodec>
24 #include <assert.h>
25 #include "config.h" // defines PACKAGE_VERSION
26 #include "consoleimpl.h"
27 #include "commitimpl.h"
28 #include "common.h"
29 #include "customactionimpl.h"
30 #include "fileview.h"
31 #include "git.h"
32 #include "help.h"
33 #include "listview.h"
34 #include "mainimpl.h"
35 #include "inputdialog.h"
36 #include "patchview.h"
37 #include "rangeselectimpl.h"
38 #include "revdesc.h"
39 #include "revsview.h"
40 #include "settingsimpl.h"
41 #include "treeview.h"
42 #include "ui_help.h"
43 #include "ui_revsview.h"
44 #include "ui_fileview.h"
45 #include "ui_patchview.h"
47 using namespace QGit;
49 MainImpl::MainImpl(SCRef cd, QWidget* p) : QMainWindow(p) {
51 EM_INIT(exExiting, "Exiting");
53 setAttribute(Qt::WA_DeleteOnClose);
54 setupUi(this);
56 // manual setup widgets not buildable with Qt designer
57 lineEditSHA = new QLineEdit(NULL);
58 lineEditFilter = new QLineEdit(NULL);
59 cmbSearch = new QComboBox(NULL);
60 QString list("Short log,Log msg,Author,SHA1,File,Patch,Patch (regExp)");
61 cmbSearch->addItems(list.split(","));
62 toolBar->addWidget(lineEditSHA);
63 QAction* act = toolBar->insertWidget(ActSearchAndFilter, lineEditFilter);
64 toolBar->insertWidget(act, cmbSearch);
65 connect(lineEditSHA, SIGNAL(returnPressed()), this, SLOT(lineEditSHA_returnPressed()));
66 connect(lineEditFilter, SIGNAL(returnPressed()), this, SLOT(lineEditFilter_returnPressed()));
68 // create light and dark colors for alternate background
69 ODD_LINE_COL = palette().color(QPalette::Base);
70 EVEN_LINE_COL = ODD_LINE_COL.dark(103);
72 // our interface to git world
73 git = new Git(this);
74 setupShortcuts();
75 qApp->installEventFilter(this);
77 // init native types
78 setRepositoryBusy = false;
80 // init filter match highlighters
81 shortLogRE.setMinimal(true);
82 shortLogRE.setCaseSensitivity(Qt::CaseInsensitive);
83 longLogRE.setMinimal(true);
84 longLogRE.setCaseSensitivity(Qt::CaseInsensitive);
86 // set-up standard revisions and files list font
87 QSettings settings;
88 QString font(settings.value(STD_FNT_KEY).toString());
89 if (font.isEmpty())
90 font = QApplication::font().toString();
91 QGit::STD_FONT.fromString(font);
93 // set-up typewriter (fixed width) font
94 font = settings.value(TYPWRT_FNT_KEY).toString();
95 if (font.isEmpty()) { // choose a sensible default
96 QFont fnt = QApplication::font();
97 fnt.setStyleHint(QFont::TypeWriter, QFont::PreferDefault);
98 fnt.setFixedPitch(true);
99 fnt.setFamily(fnt.defaultFamily()); // the family corresponding
100 font = fnt.toString(); // to current style hint
102 QGit::TYPE_WRITER_FONT.fromString(font);
104 // set-up tab view
105 delete tabWdg->currentWidget(); // cannot be done in Qt Designer
106 rv = new RevsView(this, git, true); // set has main domain
107 tabWdg->addTab(rv->tabPage(), "&Rev list");
109 // set-up tab corner widget ('close tab' button)
110 QToolButton* ct = new QToolButton(tabWdg);
111 ct->setIcon(QIcon(QString::fromUtf8(":/icons/resources/tab_remove.png")));
112 ct->setToolTip("Close tab");
113 ct->setEnabled(false);
114 tabWdg->setCornerWidget(ct);
115 connect(ct, SIGNAL(clicked()), this, SLOT(pushButtonCloseTab_clicked()));
116 connect(this, SIGNAL(closeTabButtonEnabled(bool)), ct, SLOT(setEnabled(bool)));
118 // set-up file names loading progress bar
119 pbFileNamesLoading = new QProgressBar(statusBar());
120 pbFileNamesLoading->setTextVisible(false);
121 pbFileNamesLoading->setToolTip("Background file names loading");
122 pbFileNamesLoading->hide();
123 statusBar()->addPermanentWidget(pbFileNamesLoading);
125 QVector<QSplitter*> v(1, treeSplitter);
126 QGit::restoreGeometrySetting(QGit::MAIN_GEOM_KEY, this, &v);
127 treeView->hide();
129 // set-up menu for recent visited repositories
130 connect(File, SIGNAL(triggered(QAction*)), this, SLOT(openRecent_triggered(QAction*)));
131 doUpdateRecentRepoMenu("");
133 // set-up menu for custom actions
134 connect(Actions, SIGNAL(triggered(QAction*)), this, SLOT(customAction_triggered(QAction*)));
135 doUpdateCustomActionMenu(settings.value(ACT_LIST_KEY).toStringList());
137 // manual adjust lineEditSHA width
138 QString tmp(41, '8');
139 int wd = lineEditSHA->fontMetrics().boundingRect(tmp).width();
140 lineEditSHA->setMinimumWidth(wd);
142 // disable all actions
143 updateGlobalActions(false);
145 connect(git, SIGNAL(fileNamesLoad(int, int)), this, SLOT(fileNamesLoad(int, int)));
147 connect(git, SIGNAL(newRevsAdded(const FileHistory*, const QVector<ShaString>&)),
148 this, SLOT(newRevsAdded(const FileHistory*, const QVector<ShaString>&)));
150 connect(this, SIGNAL(typeWriterFontChanged()), this, SIGNAL(updateRevDesc()));
152 connect(this, SIGNAL(changeFont(const QFont&)), git, SIGNAL(changeFont(const QFont&)));
154 // connect cross-domain update signals
155 connect(rv->tab()->listViewLog, SIGNAL(doubleClicked(const QModelIndex&)),
156 this, SLOT(listViewLog_doubleClicked(const QModelIndex&)));
157 connect(rv->tab()->listViewLog, SIGNAL(showStatusMessage(QString,int)),
158 statusBar(), SLOT(showMessage(QString,int)));
160 connect(rv->tab()->fileList, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
161 this, SLOT(fileList_itemDoubleClicked(QListWidgetItem*)));
163 connect(treeView, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)),
164 this, SLOT(treeView_doubleClicked(QTreeWidgetItem*, int)));
166 // use most recent repo as startup dir if it exists and user opted to do so
167 QStringList recents(settings.value(REC_REP_KEY).toStringList());
168 QDir checkRepo;
169 if ( recents.size() >= 1
170 && testFlag(REOPEN_REPO_F, FLAGS_KEY)
171 && checkRepo.exists(recents.at(0)))
173 startUpDir = recents.at(0);
175 else {
176 startUpDir = (cd.isEmpty() ? QDir::current().absolutePath() : cd);
179 // MainImpl c'tor is called before to enter event loop,
180 // but some stuff requires event loop to init properly
181 QTimer::singleShot(10, this, SLOT(initWithEventLoopActive()));
184 void MainImpl::initWithEventLoopActive() {
186 git->checkEnvironment();
187 setRepository(startUpDir);
188 startUpDir = ""; // one shot
191 void MainImpl::saveCurrentGeometry() {
193 QVector<QSplitter*> v(1, treeSplitter);
194 QGit::saveGeometrySetting(QGit::MAIN_GEOM_KEY, this, &v);
197 void MainImpl::highlightAbbrevSha(SCRef abbrevSha) {
198 // reset any previous highlight
199 if (ActSearchAndHighlight->isChecked())
200 ActSearchAndHighlight->toggle();
202 // set to highlight on SHA matching
203 cmbSearch->setCurrentIndex(CS_SHA1);
205 // set substring to search for
206 lineEditFilter->setText(abbrevSha);
208 // go with highlighting
209 ActSearchAndHighlight->toggle();
212 void MainImpl::lineEditSHA_returnPressed() {
214 QString sha = git->getRefSha(lineEditSHA->text());
215 if (!sha.isEmpty()) // good, we can resolve to an unique sha
217 rv->st.setSha(sha);
218 UPDATE_DOMAIN(rv);
219 } else { // try a multiple match search
220 highlightAbbrevSha(lineEditSHA->text());
221 goMatch(0);
225 void MainImpl::ActBack_activated() {
227 lineEditSHA->undo(); // first for insert(text)
228 if (lineEditSHA->text().isEmpty())
229 lineEditSHA->undo(); // double undo, see RevsView::updateLineEditSHA()
231 lineEditSHA_returnPressed();
234 void MainImpl::ActForward_activated() {
236 lineEditSHA->redo();
237 if (lineEditSHA->text().isEmpty())
238 lineEditSHA->redo();
240 lineEditSHA_returnPressed();
243 // *************************** ExternalDiffViewer ***************************
245 void MainImpl::ActExternalDiff_activated() {
247 QStringList args;
248 QStringList filenames;
249 getExternalDiffArgs(&args, &filenames);
250 ExternalDiffProc* externalDiff = new ExternalDiffProc(filenames, this);
251 externalDiff->setWorkingDirectory(curDir);
253 if (!QGit::startProcess(externalDiff, args)) {
254 QString text("Cannot start external viewer: ");
255 text.append(args[0]);
256 QMessageBox::warning(this, "Error - QGit", text);
257 delete externalDiff;
261 void MainImpl::getExternalDiffArgs(QStringList* args, QStringList* filenames) {
263 // save files to diff in working directory,
264 // will be removed by ExternalDiffProc on exit
265 QFileInfo f(rv->st.fileName());
266 QString prevRevSha(rv->st.diffToSha());
267 if (prevRevSha.isEmpty()) { // default to first parent
268 const Rev* r = git->revLookup(rv->st.sha());
269 prevRevSha = (r && r->parentsCount() > 0 ? r->parent(0) : rv->st.sha());
271 QFileInfo fi(f);
272 QString fName1(curDir + "/" + rv->st.sha().left(6) + "_" + fi.fileName());
273 QString fName2(curDir + "/" + prevRevSha.left(6) + "_" + fi.fileName());
275 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
277 QByteArray fileContent;
278 QTextCodec* tc = QTextCodec::codecForLocale();
279 QString fileSha(git->getFileSha(rv->st.fileName(), rv->st.sha()));
280 git->getFile(fileSha, NULL, &fileContent, rv->st.fileName());
281 if (!writeToFile(fName1, tc->toUnicode(fileContent)))
282 statusBar()->showMessage("Unable to save " + fName1);
284 fileSha = git->getFileSha(rv->st.fileName(), prevRevSha);
285 git->getFile(fileSha, NULL, &fileContent, rv->st.fileName());
286 if (!writeToFile(fName2, tc->toUnicode(fileContent)))
287 statusBar()->showMessage("Unable to save " + fName2);
289 // get external diff viewer command
290 QSettings settings;
291 QString extDiff(settings.value(EXT_DIFF_KEY, EXT_DIFF_DEF).toString());
293 QApplication::restoreOverrideCursor();
295 // if command doesn't have %1 and %2 to denote filenames, add them to end
296 if (!extDiff.contains("%1")) {
297 extDiff.append(" %1");
299 if (!extDiff.contains("%2")) {
300 extDiff.append(" %2");
303 // set process arguments
304 QStringList extDiffArgs = extDiff.split(' ');
305 QString curArg;
306 for (int i = 0; i < extDiffArgs.count(); i++) {
307 curArg = extDiffArgs.value(i);
309 // perform any filename replacements that are necessary
310 // (done inside the loop to handle whitespace in paths properly)
311 curArg.replace("%1", fName2);
312 curArg.replace("%2", fName1);
314 args->append(curArg);
317 // set filenames so that they can be deleted when the process completes
318 filenames->append(fName1);
319 filenames->append(fName2);
322 // *************************** ExternalEditor ***************************
324 void MainImpl::ActExternalEditor_activated() {
326 const QStringList &args = getExternalEditorArgs();
327 ExternalEditorProc* externalEditor = new ExternalEditorProc(this);
328 externalEditor->setWorkingDirectory(curDir);
330 if (!QGit::startProcess(externalEditor, args)) {
331 QString text("Cannot start external editor: ");
332 text.append(args[0]);
333 QMessageBox::warning(this, "Error - QGit", text);
334 delete externalEditor;
338 QStringList MainImpl::getExternalEditorArgs() {
340 QString fName1(curDir + "/" + rv->st.fileName());
342 // get external diff viewer command
343 QSettings settings;
344 QString extEditor(settings.value(EXT_EDITOR_KEY, EXT_EDITOR_DEF).toString());
346 // if command doesn't have %1 to denote filename, add to end
347 if (!extEditor.contains("%1")) extEditor.append(" %1");
349 // set process arguments
350 QStringList args = extEditor.split(' ');
351 for (int i = 0; i < args.count(); i++) {
352 QString &curArg = args[i];
354 // perform any filename replacements that are necessary
355 // (done inside the loop to handle whitespace in paths properly)
356 curArg.replace("%1", fName1);
358 return args;
360 // ********************** Repository open or changed *************************
362 void MainImpl::setRepository(SCRef newDir, bool refresh, bool keepSelection,
363 const QStringList* passedArgs, bool overwriteArgs) {
366 Because Git::init calls processEvents(), if setRepository() is called in
367 a tight loop (as example keeping pressed F5 refresh button) then a lot
368 of pending init() calls would be stacked.
369 On returning from processEvents() an exception is trown and init is exited,
370 so we end up with a long list of 'exception thrown' messages.
371 But the worst thing is that we have to wait for _all_ the init call to exit
372 and this could take a long time as example in case of working directory refreshing
373 'git update-index' of a big tree.
374 So we use a guard flag to guarantee we have only one init() call 'in flight'
376 if (setRepositoryBusy)
377 return;
379 setRepositoryBusy = true;
381 // check for a refresh or open of a new repository while in filtered view
382 if (ActFilterTree->isChecked() && passedArgs == NULL)
383 // toggle() triggers a refresh and a following setRepository()
384 // call that is filtered out by setRepositoryBusy guard flag
385 ActFilterTree->toggle(); // triggers ActFilterTree_toggled()
387 try {
388 EM_REGISTER(exExiting);
390 bool archiveChanged;
391 git->getBaseDir(newDir, curDir, archiveChanged);
393 git->stop(archiveChanged); // stop all pending processes, non blocking
395 if (archiveChanged && refresh)
396 dbs("ASSERT in setRepository: different dir with no range select");
398 // now we can clear all our data
399 setWindowTitle(curDir + " - QGit");
400 bool complete = !refresh || !keepSelection;
401 rv->clear(complete);
402 if (archiveChanged)
403 emit closeAllTabs();
405 // disable all actions
406 updateGlobalActions(false);
407 updateContextActions("", "", false, false);
408 ActCommit_setEnabled(false);
410 if (ActFilterTree->isChecked())
411 setWindowTitle(windowTitle() + " - FILTER ON < " +
412 passedArgs->join(" ") + " >");
414 // tree name should be set before init because in case of
415 // StGIT archives the first revs are sent before init returns
416 QString n(curDir);
417 treeView->setTreeName(n.prepend('/').section('/', -1, -1));
419 bool quit;
420 bool ok = git->init(curDir, !refresh, passedArgs, overwriteArgs, &quit); // blocking call
421 if (quit)
422 goto exit;
424 updateCommitMenu(ok && git->isStGITStack());
425 ActCheckWorkDir->setChecked(testFlag(DIFF_INDEX_F)); // could be changed in Git::init()
427 if (ok) {
428 updateGlobalActions(true);
429 if (archiveChanged)
430 updateRecentRepoMenu(curDir);
431 } else
432 statusBar()->showMessage("Not a git archive");
434 exit:
435 setRepositoryBusy = false;
436 EM_REMOVE(exExiting);
438 if (quit && !startUpDir.isEmpty())
439 close();
441 } catch (int i) {
442 EM_REMOVE(exExiting);
444 if (EM_MATCH(i, exExiting, "loading repository")) {
445 EM_THROW_PENDING;
446 return;
448 const QString info("Exception \'" + EM_DESC(i) + "\' not "
449 "handled in setRepository...re-throw");
450 dbs(info);
451 throw;
455 void MainImpl::updateGlobalActions(bool b) {
457 ActRefresh->setEnabled(b);
458 ActCheckWorkDir->setEnabled(b);
459 ActViewRev->setEnabled(b);
460 ActViewDiff->setEnabled(b);
461 ActViewDiffNewTab->setEnabled(b && firstTab<PatchView>());
462 ActShowTree->setEnabled(b);
463 ActMailApplyPatch->setEnabled(b);
464 ActMailFormatPatch->setEnabled(b);
466 rv->setEnabled(b);
469 const QString REV_LOCAL_BRANCHES("REV_LOCAL_BRANCHES");
470 const QString REV_REMOTE_BRANCHES("REV_REMOTE_BRANCHES");
471 const QString REV_TAGS("REV_TAGS");
472 const QString CURRENT_BRANCH("CURRENT_BRANCH");
473 const QString SELECTED_NAME("SELECTED_NAME");
475 void MainImpl::updateRevVariables(SCRef sha) {
476 QMap<QString, QVariant> &v = revision_variables;
477 v.clear();
479 const QStringList &remote_branches = git->getRefNames(sha, Git::RMT_BRANCH);
480 QString curBranch;
481 v.insert(REV_LOCAL_BRANCHES, git->getRefNames(sha, Git::BRANCH));
482 v.insert(CURRENT_BRANCH, git->getCurrentBranchName());
483 v.insert(REV_REMOTE_BRANCHES, remote_branches);
484 v.insert(REV_TAGS, git->getRefNames(sha, Git::TAG));
485 v.insert("SHA", sha);
487 // determine which name the user clicked on
488 ListView* lv = rv->tab()->listViewLog;
489 v.insert(SELECTED_NAME, lv->selectedRefName());
492 void MainImpl::updateContextActions(SCRef newRevSha, SCRef newFileName,
493 bool isDir, bool found) {
495 bool pathActionsEnabled = !newFileName.isEmpty();
496 bool fileActionsEnabled = (pathActionsEnabled && !isDir);
498 ActViewFile->setEnabled(fileActionsEnabled);
499 ActViewFileNewTab->setEnabled(fileActionsEnabled && firstTab<FileView>());
500 ActExternalDiff->setEnabled(fileActionsEnabled);
501 ActExternalEditor->setEnabled(fileActionsEnabled);
502 ActSaveFile->setEnabled(fileActionsEnabled);
503 ActFilterTree->setEnabled(pathActionsEnabled || ActFilterTree->isChecked());
505 // bool isTag = false;
506 bool isUnApplied = false;
507 bool isApplied = false;
509 uint ref_type = 0;
511 if (found) {
512 const Rev* r = git->revLookup(newRevSha);
513 ref_type = git->checkRef(newRevSha, Git::ANY_REF);
514 // isTag = ref_type & Git::TAG;
515 isUnApplied = r->isUnApplied;
516 isApplied = r->isApplied;
518 ActMarkDiffToSha->setEnabled(newRevSha != ZERO_SHA);
519 ActCheckout->setEnabled(found && (newRevSha != ZERO_SHA) && !isUnApplied);
520 ActBranch->setEnabled(found && (newRevSha != ZERO_SHA) && !isUnApplied);
521 ActTag->setEnabled(found && (newRevSha != ZERO_SHA) && !isUnApplied);
522 ActDelete->setEnabled(ref_type != 0);
523 ActPush->setEnabled(found && isUnApplied && git->isNothingToCommit());
524 ActPop->setEnabled(found && isApplied && git->isNothingToCommit());
527 // ************************* cross-domain update Actions ***************************
529 void MainImpl::listViewLog_doubleClicked(const QModelIndex& index) {
531 if (index.isValid() && ActViewDiff->isEnabled())
532 ActViewDiff->activate(QAction::Trigger);
535 void MainImpl::histListView_doubleClicked(const QModelIndex& index) {
537 if (index.isValid() && ActViewRev->isEnabled())
538 ActViewRev->activate(QAction::Trigger);
541 void MainImpl::fileList_itemDoubleClicked(QListWidgetItem* item) {
543 bool isFirst = (item && item->listWidget()->item(0) == item);
544 if (isFirst && rv->st.isMerge())
545 return;
547 if (testFlag(OPEN_IN_EDITOR_F, FLAGS_KEY)) {
548 if (item && ActExternalEditor->isEnabled())
549 ActExternalEditor->activate(QAction::Trigger);
550 } else {
551 bool isMainView = (item && item->listWidget() == rv->tab()->fileList);
552 if (isMainView && ActViewDiff->isEnabled())
553 ActViewDiff->activate(QAction::Trigger);
555 if (item && !isMainView && ActViewFile->isEnabled())
556 ActViewFile->activate(QAction::Trigger);
560 void MainImpl::treeView_doubleClicked(QTreeWidgetItem* item, int) {
561 if (testFlag(OPEN_IN_EDITOR_F, FLAGS_KEY)) {
562 if (item && ActExternalEditor->isEnabled())
563 ActExternalEditor->activate(QAction::Trigger);
564 } else {
565 if (item && ActViewFile->isEnabled())
566 ActViewFile->activate(QAction::Trigger);
570 void MainImpl::pushButtonCloseTab_clicked() {
572 Domain* t;
573 switch (currentTabType(&t)) {
574 case TAB_REV:
575 break;
576 case TAB_PATCH:
577 t->deleteWhenDone();
578 ActViewDiffNewTab->setEnabled(ActViewDiff->isEnabled() && firstTab<PatchView>());
579 break;
580 case TAB_FILE:
581 t->deleteWhenDone();
582 ActViewFileNewTab->setEnabled(ActViewFile->isEnabled() && firstTab<FileView>());
583 break;
584 default:
585 dbs("ASSERT in pushButtonCloseTab_clicked: unknown current page");
586 break;
590 void MainImpl::ActRangeDlg_activated() {
592 QString args;
593 RangeSelectImpl rs(this, &args, false, git);
594 bool quit = (rs.exec() == QDialog::Rejected); // modal execution
595 if (!quit) {
596 const QStringList l(args.split(" "));
597 setRepository(curDir, true, true, &l, true);
601 void MainImpl::ActViewRev_activated() {
603 Domain* t;
604 if (currentTabType(&t) == TAB_FILE) {
605 rv->st = t->st;
606 UPDATE_DOMAIN(rv);
608 tabWdg->setCurrentWidget(rv->tabPage());
611 void MainImpl::ActViewFile_activated() {
613 openFileTab(firstTab<FileView>());
616 void MainImpl::ActViewFileNewTab_activated() {
618 openFileTab();
621 void MainImpl::openFileTab(FileView* fv) {
623 if (!fv) {
624 fv = new FileView(this, git);
625 tabWdg->addTab(fv->tabPage(), "File");
627 connect(fv->tab()->histListView, SIGNAL(doubleClicked(const QModelIndex&)),
628 this, SLOT(histListView_doubleClicked(const QModelIndex&)));
630 connect(this, SIGNAL(closeAllTabs()), fv, SLOT(on_closeAllTabs()));
632 ActViewFileNewTab->setEnabled(ActViewFile->isEnabled());
634 tabWdg->setCurrentWidget(fv->tabPage());
635 fv->st = rv->st;
636 UPDATE_DOMAIN(fv);
639 void MainImpl::ActViewDiff_activated() {
641 Domain* t;
642 if (currentTabType(&t) == TAB_FILE) {
643 rv->st = t->st;
644 UPDATE_DOMAIN(rv);
646 rv->viewPatch(false);
647 ActViewDiffNewTab->setEnabled(true);
649 if (ActSearchAndFilter->isChecked() || ActSearchAndHighlight->isChecked()) {
650 bool isRegExp = (cmbSearch->currentIndex() == CS_PATCH_REGEXP);
651 emit highlightPatch(lineEditFilter->text(), isRegExp);
655 void MainImpl::ActViewDiffNewTab_activated() {
657 rv->viewPatch(true);
660 bool MainImpl::eventFilter(QObject* obj, QEvent* ev) {
662 if (ev->type() == QEvent::Wheel) {
664 QWheelEvent* e = static_cast<QWheelEvent*>(ev);
665 if (e->modifiers() == Qt::AltModifier) {
667 int idx = tabWdg->currentIndex();
668 if (e->delta() < 0)
669 idx = (++idx == tabWdg->count() ? 0 : idx);
670 else
671 idx = (--idx < 0 ? tabWdg->count() - 1 : idx);
673 tabWdg->setCurrentIndex(idx);
674 return true;
677 return QWidget::eventFilter(obj, ev);
680 void MainImpl::applyRevisions(SCList remoteRevs, SCRef remoteRepo) {
681 // remoteRevs is already sanity checked to contain some possible valid data
683 QDir dr(curDir + QGit::PATCHES_DIR);
684 dr.setFilter(QDir::Files);
685 if (!dr.exists(remoteRepo)) {
686 statusBar()->showMessage("Remote repository missing: " + remoteRepo);
687 return;
689 if (dr.exists() && dr.count()) {
690 statusBar()->showMessage(QString("Please remove stale import directory " + dr.absolutePath()));
691 return;
693 bool workDirOnly, fold;
694 if (!askApplyPatchParameters(&workDirOnly, &fold))
695 return;
697 // ok, let's go
698 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
699 raise();
700 EM_PROCESS_EVENTS;
702 uint revNum = 0;
703 QStringList::const_iterator it(remoteRevs.constEnd());
704 do {
705 --it;
706 SCRef sha = *it;
707 statusBar()->showMessage(QString("Importing revision %1 of %2: %3")
708 .arg(++revNum).arg(remoteRevs.count()).arg(sha));
710 // we create patches one by one
711 if (!git->formatPatch(QStringList(sha), dr.absolutePath(), remoteRepo))
712 break;
714 dr.refresh();
715 if (dr.count() != 1) {
716 qDebug("ASSERT in on_droppedRevisions: found %i files "
717 "in %s", dr.count(), qPrintable(dr.absolutePath()));
718 break;
720 SCRef fn(dr.absoluteFilePath(dr[0]));
721 bool is_applied = git->applyPatchFile(fn, fold, Git::optDragDrop);
722 dr.remove(fn);
723 if (!is_applied) {
724 statusBar()->showMessage(QString("Failed to import revision %1 of %2: %3")
725 .arg(revNum).arg(remoteRevs.count()).arg(sha));
726 break;
729 } while (it != remoteRevs.constBegin());
731 if (it == remoteRevs.constBegin())
732 statusBar()->clearMessage();
734 if (workDirOnly && (revNum > 0))
735 git->resetCommits(revNum);
737 dr.rmdir(dr.absolutePath()); // 'dr' must be already empty
738 QApplication::restoreOverrideCursor();
739 refreshRepo();
742 bool MainImpl::applyPatches(const QStringList &files) {
743 bool workDirOnly, fold;
744 if (!askApplyPatchParameters(&workDirOnly, &fold))
745 return false;
747 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
748 QStringList::const_iterator it=files.begin(), end=files.end();
749 for(; it!=end; ++it) {
750 statusBar()->showMessage("Applying " + *it);
751 if (!git->applyPatchFile(*it, fold, Git::optDragDrop))
752 statusBar()->showMessage("Failed to apply " + *it);
754 if (it == end) statusBar()->clearMessage();
756 if (workDirOnly && (files.count() > 0))
757 git->resetCommits(files.count());
759 QApplication::restoreOverrideCursor();
760 refreshRepo();
761 return true;
764 void MainImpl::rebase(const QString &from, const QString &to, const QString &onto)
766 bool success = false;
767 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
768 if (from.isEmpty()) {
769 success = git->run(QString("git checkout -q %1").arg(to)) &&
770 git->run(QString("git rebase %1").arg(onto));
771 } else {
772 success = git->run(QString("git rebase --onto %3 %1^ %2").arg(from, to, onto));
774 if (!success) {
775 // TODO say something about rebase failure
777 refreshRepo(true);
778 QApplication::restoreOverrideCursor();
781 void MainImpl::merge(const QStringList &shas, const QString &into)
783 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
784 QString output;
785 if (git->merge(into, shas, &output)) {
786 refreshRepo(true);
787 statusBar()->showMessage(QString("Successfully merged into %1").arg(into));
788 ActCommit_activated();
789 } else if (!output.isEmpty()) {
790 QMessageBox::warning(this, "git merge failed",
791 QString("\n\nGit says: \n\n" + output));
793 refreshRepo(true);
794 QApplication::restoreOverrideCursor();
797 void MainImpl::moveRef(const QString &target, const QString &toSHA)
799 QString cmd;
800 if (target.startsWith("remotes/")) {
801 QString remote = target.section("/", 1, 1);
802 QString name = target.section("/", 2);
803 cmd = QString("git push -q %1 %2:%3").arg(remote, toSHA, name);
804 } else if (target.startsWith("tags/")) {
805 cmd = QString("git tag -f %1 %2").arg(target.section("/",1), toSHA);
806 } else if (!target.isEmpty()) {
807 const QString &sha = git->getRefSha(target, Git::BRANCH, false);
808 if (sha.isEmpty()) return;
809 const QStringList &children = git->getChildren(sha);
810 if ((children.count() == 0 || (children.count() == 1 && children.front() == ZERO_SHA)) && // no children
811 git->getRefNames(sha, Git::ANY_REF).count() == 1 && // last ref name
812 QMessageBox::question(this, "move branch",
813 QString("This is the last reference to this branch.\n"
814 "Do you really want to move '%1'?").arg(target))
815 == QMessageBox::No)
816 return;
818 if (target == git->getCurrentBranchName()) // move current branch
819 cmd = QString("git checkout -q -B %1 %2").arg(target, toSHA);
820 else // move any other local branch
821 cmd = QString("git branch -f %1 %2").arg(target, toSHA);
823 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
824 if (git->run(cmd)) refreshRepo(true);
825 QApplication::restoreOverrideCursor();
828 // ******************************* Filter ******************************
830 void MainImpl::newRevsAdded(const FileHistory* fh, const QVector<ShaString>&) {
832 if (!git->isMainHistory(fh))
833 return;
835 if (ActSearchAndFilter->isChecked())
836 ActSearchAndFilter_toggled(true); // filter again on new arrived data
838 if (ActSearchAndHighlight->isChecked())
839 ActSearchAndHighlight_toggled(true); // filter again on new arrived data
841 // first rev could be a StGIT unapplied patch so check more then once
842 if ( !ActCommit->isEnabled()
843 && (!git->isNothingToCommit() || git->isUnknownFiles()))
844 ActCommit_setEnabled(true);
847 void MainImpl::lineEditFilter_returnPressed() {
849 ActSearchAndFilter->setChecked(true);
852 void MainImpl::ActSearchAndFilter_toggled(bool isOn) {
854 ActSearchAndHighlight->setEnabled(!isOn);
855 ActSearchAndFilter->setEnabled(false);
856 filterList(isOn, false); // blocking call
857 ActSearchAndFilter->setEnabled(true);
860 void MainImpl::ActSearchAndHighlight_toggled(bool isOn) {
862 ActSearchAndFilter->setEnabled(!isOn);
863 ActSearchAndHighlight->setEnabled(false);
864 filterList(isOn, true); // blocking call
865 ActSearchAndHighlight->setEnabled(true);
868 void MainImpl::filterList(bool isOn, bool onlyHighlight) {
870 lineEditFilter->setEnabled(!isOn);
871 cmbSearch->setEnabled(!isOn);
873 SCRef filter(lineEditFilter->text());
874 if (filter.isEmpty())
875 return;
877 ShaSet shaSet;
878 bool patchNeedsUpdate, isRegExp;
879 patchNeedsUpdate = isRegExp = false;
880 int idx = cmbSearch->currentIndex(), colNum = 0;
881 if (isOn) {
882 switch (idx) {
883 case CS_SHORT_LOG:
884 colNum = LOG_COL;
885 shortLogRE.setPattern(filter);
886 break;
887 case CS_LOG_MSG:
888 colNum = LOG_MSG_COL;
889 longLogRE.setPattern(filter);
890 break;
891 case CS_AUTHOR:
892 colNum = AUTH_COL;
893 break;
894 case CS_SHA1:
895 colNum = COMMIT_COL;
896 break;
897 case CS_FILE:
898 case CS_PATCH:
899 case CS_PATCH_REGEXP:
900 colNum = SHA_MAP_COL;
901 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
902 EM_PROCESS_EVENTS; // to paint wait cursor
903 if (idx == CS_FILE)
904 git->getFileFilter(filter, shaSet);
905 else {
906 isRegExp = (idx == CS_PATCH_REGEXP);
907 if (!git->getPatchFilter(filter, isRegExp, shaSet)) {
908 QApplication::restoreOverrideCursor();
909 ActSearchAndFilter->toggle();
910 return;
912 patchNeedsUpdate = (shaSet.count() > 0);
914 QApplication::restoreOverrideCursor();
915 break;
917 } else {
918 patchNeedsUpdate = (idx == CS_PATCH || idx == CS_PATCH_REGEXP);
919 shortLogRE.setPattern("");
920 longLogRE.setPattern("");
922 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
924 ListView* lv = rv->tab()->listViewLog;
925 int matchedCnt = lv->filterRows(isOn, onlyHighlight, filter, colNum, &shaSet);
927 QApplication::restoreOverrideCursor();
929 emit updateRevDesc(); // could be highlighted
930 if (patchNeedsUpdate)
931 emit highlightPatch(isOn ? filter : "", isRegExp);
933 QString msg;
934 if (isOn && !onlyHighlight)
935 msg = QString("Found %1 matches. Toggle filter/highlight "
936 "button to remove the filter").arg(matchedCnt);
937 QApplication::postEvent(rv, new MessageEvent(msg)); // deferred message, after update
940 bool MainImpl::event(QEvent* e) {
942 BaseEvent* de = dynamic_cast<BaseEvent*>(e);
943 if (!de)
944 return QWidget::event(e);
946 SCRef data = de->myData();
947 bool ret = true;
949 switch ((EventType)e->type()) {
950 case ERROR_EV: {
951 QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
952 EM_PROCESS_EVENTS;
953 MainExecErrorEvent* me = (MainExecErrorEvent*)e;
954 QString text("An error occurred while executing command:\n\n");
955 text.append(me->command() + "\n\nGit says: \n\n" + me->report());
956 QMessageBox::warning(this, "Error - QGit", text);
957 QApplication::restoreOverrideCursor(); }
958 break;
959 case MSG_EV:
960 statusBar()->showMessage(data);
961 break;
962 case POPUP_LIST_EV:
963 doContexPopup(data);
964 break;
965 case POPUP_FILE_EV:
966 case POPUP_TREE_EV:
967 doFileContexPopup(data, e->type());
968 break;
969 default:
970 dbp("ASSERT in MainImpl::event unhandled event %1", e->type());
971 ret = false;
972 break;
974 return ret;
977 int MainImpl::currentTabType(Domain** t) {
979 *t = NULL;
980 QWidget* curPage = tabWdg->currentWidget();
981 if (curPage == rv->tabPage()) {
982 *t = rv;
983 return TAB_REV;
985 QList<PatchView*>* l = getTabs<PatchView>(curPage);
986 if (l->count() > 0) {
987 *t = l->first();
988 delete l;
989 return TAB_PATCH;
991 delete l;
992 QList<FileView*>* l2 = getTabs<FileView>(curPage);
993 if (l2->count() > 0) {
994 *t = l2->first();
995 delete l2;
996 return TAB_FILE;
998 if (l2->count() > 0)
999 dbs("ASSERT in tabType file not found");
1001 delete l2;
1002 return -1;
1005 template<class X> QList<X*>* MainImpl::getTabs(QWidget* tabPage) {
1007 QList<X*> l = this->findChildren<X*>();
1008 QList<X*>* ret = new QList<X*>;
1010 for (int i = 0; i < l.size(); ++i) {
1011 if (!tabPage || l.at(i)->tabPage() == tabPage)
1012 ret->append(l.at(i));
1014 return ret; // 'ret' must be deleted by caller
1017 template<class X> X* MainImpl::firstTab(QWidget* startPage) {
1019 int minVal = 99, firstVal = 99;
1020 int startPos = tabWdg->indexOf(startPage);
1021 X* min = NULL;
1022 X* first = NULL;
1023 QList<X*>* l = getTabs<X>();
1024 for (int i = 0; i < l->size(); ++i) {
1026 X* d = l->at(i);
1027 int idx = tabWdg->indexOf(d->tabPage());
1028 if (idx < minVal) {
1029 minVal = idx;
1030 min = d;
1032 if (idx < firstVal && idx > startPos) {
1033 firstVal = idx;
1034 first = d;
1037 delete l;
1038 return (first ? first : min);
1041 void MainImpl::tabWdg_currentChanged(int w) {
1043 if (w == -1)
1044 return;
1046 // set correct focus for keyboard browsing
1047 Domain* t;
1048 switch (currentTabType(&t)) {
1049 case TAB_REV:
1050 static_cast<RevsView*>(t)->tab()->listViewLog->setFocus();
1051 emit closeTabButtonEnabled(false);
1052 break;
1053 case TAB_PATCH:
1054 static_cast<PatchView*>(t)->tab()->textEditDiff->setFocus();
1055 emit closeTabButtonEnabled(true);
1056 break;
1057 case TAB_FILE:
1058 static_cast<FileView*>(t)->tab()->histListView->setFocus();
1059 emit closeTabButtonEnabled(true);
1060 break;
1061 default:
1062 dbs("ASSERT in tabWdg_currentChanged: unknown current page");
1063 break;
1067 void MainImpl::setupShortcuts() {
1069 new QShortcut(Qt::Key_I, this, SLOT(shortCutActivated()));
1070 new QShortcut(Qt::Key_K, this, SLOT(shortCutActivated()));
1071 new QShortcut(Qt::Key_N, this, SLOT(shortCutActivated()));
1072 new QShortcut(Qt::Key_Left, this, SLOT(shortCutActivated()));
1073 new QShortcut(Qt::Key_Right, this, SLOT(shortCutActivated()));
1075 new QShortcut(Qt::Key_Delete, this, SLOT(shortCutActivated()));
1076 new QShortcut(Qt::Key_Backspace, this, SLOT(shortCutActivated()));
1077 new QShortcut(Qt::Key_Space, this, SLOT(shortCutActivated()));
1079 new QShortcut(Qt::Key_B, this, SLOT(shortCutActivated()));
1080 new QShortcut(Qt::Key_D, this, SLOT(shortCutActivated()));
1081 new QShortcut(Qt::Key_F, this, SLOT(shortCutActivated()));
1082 new QShortcut(Qt::Key_P, this, SLOT(shortCutActivated()));
1083 new QShortcut(Qt::Key_R, this, SLOT(shortCutActivated()));
1084 new QShortcut(Qt::Key_U, this, SLOT(shortCutActivated()));
1086 new QShortcut(Qt::SHIFT | Qt::Key_Up, this, SLOT(shortCutActivated()));
1087 new QShortcut(Qt::SHIFT | Qt::Key_Down, this, SLOT(shortCutActivated()));
1088 new QShortcut(Qt::CTRL | Qt::Key_Plus, this, SLOT(shortCutActivated()));
1089 new QShortcut(Qt::CTRL | Qt::Key_Minus, this, SLOT(shortCutActivated()));
1092 void MainImpl::shortCutActivated() {
1094 QShortcut* se = dynamic_cast<QShortcut*>(sender());
1096 if (se) {
1097 #if QT_VERSION >= 0x050000
1098 const QKeySequence& key = se->key();
1099 #else
1100 const int key = se->key();
1101 #endif
1103 if (key == Qt::Key_I) {
1104 rv->tab()->listViewLog->on_keyUp();
1106 else if ((key == Qt::Key_K) || (key == Qt::Key_N)) {
1107 rv->tab()->listViewLog->on_keyDown();
1109 else if (key == (Qt::SHIFT | Qt::Key_Up)) {
1110 goMatch(-1);
1112 else if (key == (Qt::SHIFT | Qt::Key_Down)) {
1113 goMatch(1);
1115 else if (key == Qt::Key_Left) {
1116 ActBack_activated();
1118 else if (key == Qt::Key_Right) {
1119 ActForward_activated();
1121 else if (key == (Qt::CTRL | Qt::Key_Plus)) {
1122 adjustFontSize(1); //TODO replace magic constant
1124 else if (key == (Qt::CTRL | Qt::Key_Minus)) {
1125 adjustFontSize(-1); //TODO replace magic constant
1127 else if (key == Qt::Key_U) {
1128 scrollTextEdit(-18); //TODO replace magic constant
1130 else if (key == Qt::Key_D) {
1131 scrollTextEdit(18); //TODO replace magic constant
1133 else if (key == Qt::Key_Delete || key == Qt::Key_B || key == Qt::Key_Backspace) {
1134 scrollTextEdit(-1); //TODO replace magic constant
1136 else if (key == Qt::Key_Space) {
1137 scrollTextEdit(1);
1139 else if (key == Qt::Key_R) {
1140 tabWdg->setCurrentWidget(rv->tabPage());
1142 else if (key == Qt::Key_P || key == Qt::Key_F) {
1143 QWidget* cp = tabWdg->currentWidget();
1144 Domain* d = (key == Qt::Key_P)
1145 ? static_cast<Domain*>(firstTab<PatchView>(cp))
1146 : static_cast<Domain*>(firstTab<FileView>(cp));
1147 if (d) tabWdg->setCurrentWidget(d->tabPage());
1152 void MainImpl::goMatch(int delta) {
1154 if (ActSearchAndHighlight->isChecked())
1155 rv->tab()->listViewLog->scrollToNextHighlighted(delta);
1158 QTextEdit* MainImpl::getCurrentTextEdit() {
1160 QTextEdit* te = NULL;
1161 Domain* t;
1162 switch (currentTabType(&t)) {
1163 case TAB_REV:
1164 te = static_cast<RevsView*>(t)->tab()->textBrowserDesc;
1165 if (!te->isVisible())
1166 te = static_cast<RevsView*>(t)->tab()->textEditDiff;
1167 break;
1168 case TAB_PATCH:
1169 te = static_cast<PatchView*>(t)->tab()->textEditDiff;
1170 break;
1171 case TAB_FILE:
1172 te = static_cast<FileView*>(t)->tab()->textEditFile;
1173 break;
1174 default:
1175 break;
1177 return te;
1180 void MainImpl::scrollTextEdit(int delta) {
1182 QTextEdit* te = getCurrentTextEdit();
1183 if (!te)
1184 return;
1186 QScrollBar* vs = te->verticalScrollBar();
1187 if (delta == 1 || delta == -1)
1188 vs->setValue(vs->value() + delta * (vs->pageStep() - vs->singleStep()));
1189 else
1190 vs->setValue(vs->value() + delta * vs->singleStep());
1193 void MainImpl::adjustFontSize(int delta) {
1194 // font size is changed on a 'per instance' base and only on list views
1196 int ps = QGit::STD_FONT.pointSize() + delta;
1197 if (ps < 2)
1198 return;
1200 QGit::STD_FONT.setPointSize(ps);
1202 QSettings settings;
1203 settings.setValue(QGit::STD_FNT_KEY, QGit::STD_FONT.toString());
1204 emit changeFont(QGit::STD_FONT);
1207 void MainImpl::fileNamesLoad(int status, int value) {
1209 switch (status) {
1210 case 1: // stop
1211 pbFileNamesLoading->hide();
1212 break;
1213 case 2: // update
1214 pbFileNamesLoading->setValue(value);
1215 break;
1216 case 3: // start
1217 if (value > 200) { // don't show for few revisions
1218 pbFileNamesLoading->reset();
1219 pbFileNamesLoading->setMaximum(value);
1220 pbFileNamesLoading->show();
1222 break;
1226 // ****************************** Menu *********************************
1228 void MainImpl::updateCommitMenu(bool isStGITStack) {
1230 ActCommit->setText(isStGITStack ? "Commit St&GIT patch..." : "&Commit...");
1231 ActAmend->setText(isStGITStack ? "Refresh St&GIT patch..." : "&Amend commit...");
1234 void MainImpl::updateRecentRepoMenu(SCRef newEntry) {
1236 // update menu of all windows
1237 foreach (QWidget* widget, QApplication::topLevelWidgets()) {
1239 MainImpl* w = dynamic_cast<MainImpl*>(widget);
1240 if (w)
1241 w->doUpdateRecentRepoMenu(newEntry);
1245 void MainImpl::doUpdateRecentRepoMenu(SCRef newEntry) {
1247 QList<QAction*> al(File->actions());
1248 FOREACH (QList<QAction*>, it, al) {
1249 if ((*it)->data().toString().startsWith("RECENT"))
1250 File->removeAction(*it);
1252 QSettings settings;
1253 QStringList recents(settings.value(REC_REP_KEY).toStringList());
1254 int idx = recents.indexOf(newEntry);
1255 if (idx != -1)
1256 recents.removeAt(idx);
1258 if (!newEntry.isEmpty())
1259 recents.prepend(newEntry);
1261 idx = 1;
1262 QStringList newRecents;
1263 FOREACH_SL (it, recents) {
1264 QAction* newAction = File->addAction(QString::number(idx++) + " " + *it);
1265 newAction->setData(QString("RECENT ") + *it);
1266 newRecents << *it;
1267 if (idx > MAX_RECENT_REPOS)
1268 break;
1270 settings.setValue(REC_REP_KEY, newRecents);
1273 static int cntMenuEntries(const QMenu& menu) {
1275 int cnt = 0;
1276 QList<QAction*> al(menu.actions());
1277 FOREACH (QList<QAction*>, it, al) {
1278 if (!(*it)->isSeparator())
1279 cnt++;
1281 return cnt;
1284 void MainImpl::doContexPopup(SCRef sha) {
1286 QMenu contextMenu(this);
1287 QMenu contextBrnMenu("More branches...", this);
1288 QMenu contextTagMenu("More tags...", this);
1289 QMenu contextRmtMenu("Remote branches...", this);
1291 connect(&contextMenu, SIGNAL(triggered(QAction*)), this, SLOT(goRef_triggered(QAction*)));
1293 Domain* t;
1294 int tt = currentTabType(&t);
1295 bool isRevPage = (tt == TAB_REV);
1296 bool isPatchPage = (tt == TAB_PATCH);
1297 bool isFilePage = (tt == TAB_FILE);
1299 if (isFilePage && ActViewRev->isEnabled())
1300 contextMenu.addAction(ActViewRev);
1302 if (!isPatchPage && ActViewDiff->isEnabled())
1303 contextMenu.addAction(ActViewDiff);
1305 if (isRevPage && ActViewDiffNewTab->isEnabled())
1306 contextMenu.addAction(ActViewDiffNewTab);
1308 if (!isFilePage && ActExternalDiff->isEnabled())
1309 contextMenu.addAction(ActExternalDiff);
1311 if (isFilePage && ActExternalEditor->isEnabled())
1312 contextMenu.addAction(ActExternalEditor);
1314 if (isRevPage) {
1315 updateRevVariables(sha);
1317 if (ActCommit->isEnabled() && (sha == ZERO_SHA))
1318 contextMenu.addAction(ActCommit);
1319 if (ActCheckout->isEnabled())
1320 contextMenu.addAction(ActCheckout);
1321 if (ActBranch->isEnabled())
1322 contextMenu.addAction(ActBranch);
1323 if (ActTag->isEnabled())
1324 contextMenu.addAction(ActTag);
1325 if (ActDelete->isEnabled())
1326 contextMenu.addAction(ActDelete);
1327 if (ActMailFormatPatch->isEnabled())
1328 contextMenu.addAction(ActMailFormatPatch);
1329 if (ActPush->isEnabled())
1330 contextMenu.addAction(ActPush);
1331 if (ActPop->isEnabled())
1332 contextMenu.addAction(ActPop);
1334 const QStringList& bn(git->getAllRefNames(Git::BRANCH, Git::optOnlyLoaded));
1335 const QStringList& rbn(git->getAllRefNames(Git::RMT_BRANCH, Git::optOnlyLoaded));
1336 const QStringList& tn(git->getAllRefNames(Git::TAG, Git::optOnlyLoaded));
1337 QAction* act = NULL;
1339 FOREACH_SL (it, rbn) {
1340 act = contextRmtMenu.addAction(*it);
1341 act->setData("Ref");
1344 // halve the possible remaining entries for branches and tags
1345 int remainingEntries = (MAX_MENU_ENTRIES - cntMenuEntries(contextMenu));
1346 if (!contextRmtMenu.isEmpty()) --remainingEntries;
1347 int tagEntries = remainingEntries / 2;
1348 int brnEntries = remainingEntries - tagEntries;
1350 // display more branches, if there are few tags
1351 if (tagEntries > tn.count())
1352 tagEntries = tn.count();
1354 // one branch less because of the "More branches..." submenu
1355 if ((bn.count() > brnEntries) && tagEntries)
1356 tagEntries++;
1358 if (!bn.empty())
1359 contextMenu.addSeparator();
1361 FOREACH_SL (it, bn) {
1362 if ( cntMenuEntries(contextMenu) < MAX_MENU_ENTRIES - tagEntries
1363 || (*it == bn.last() && contextBrnMenu.isEmpty()))
1364 act = contextMenu.addAction(*it);
1365 else
1366 act = contextBrnMenu.addAction(*it);
1368 act->setData("Ref");
1370 if (!contextBrnMenu.isEmpty())
1371 contextMenu.addMenu(&contextBrnMenu);
1373 if (!contextRmtMenu.isEmpty())
1374 contextMenu.addMenu(&contextRmtMenu);
1376 if (!tn.empty())
1377 contextMenu.addSeparator();
1379 FOREACH_SL (it, tn) {
1380 if ( cntMenuEntries(contextMenu) < MAX_MENU_ENTRIES
1381 || (*it == tn.last() && contextTagMenu.isEmpty()))
1382 act = contextMenu.addAction(*it);
1383 else
1384 act = contextTagMenu.addAction(*it);
1386 act->setData("Ref");
1388 if (!contextTagMenu.isEmpty())
1389 contextMenu.addMenu(&contextTagMenu);
1391 QPoint p = QCursor::pos();
1392 p += QPoint(10, 10);
1393 contextMenu.exec(p);
1395 // remove selected ref name after showing the popup
1396 revision_variables.remove(SELECTED_NAME);
1399 void MainImpl::doFileContexPopup(SCRef fileName, int type) {
1401 QMenu contextMenu(this);
1403 Domain* t;
1404 int tt = currentTabType(&t);
1405 bool isRevPage = (tt == TAB_REV);
1406 bool isPatchPage = (tt == TAB_PATCH);
1407 bool isDir = treeView->isDir(fileName);
1409 if (type == POPUP_FILE_EV)
1410 if (!isPatchPage && ActViewDiff->isEnabled())
1411 contextMenu.addAction(ActViewDiff);
1413 if (!isDir && ActViewFile->isEnabled())
1414 contextMenu.addAction(ActViewFile);
1416 if (!isDir && ActViewFileNewTab->isEnabled())
1417 contextMenu.addAction(ActViewFileNewTab);
1419 if (!isRevPage && (type == POPUP_FILE_EV) && ActViewRev->isEnabled())
1420 contextMenu.addAction(ActViewRev);
1422 if (ActFilterTree->isEnabled())
1423 contextMenu.addAction(ActFilterTree);
1425 if (!isDir) {
1426 if (ActSaveFile->isEnabled())
1427 contextMenu.addAction(ActSaveFile);
1428 if ((type == POPUP_FILE_EV) && ActExternalDiff->isEnabled())
1429 contextMenu.addAction(ActExternalDiff);
1430 if ((type == POPUP_FILE_EV) && ActExternalEditor->isEnabled())
1431 contextMenu.addAction(ActExternalEditor);
1432 if (ActExternalEditor->isEnabled())
1433 contextMenu.addAction(ActExternalEditor);
1435 contextMenu.exec(QCursor::pos());
1438 void MainImpl::goRef_triggered(QAction* act) {
1440 if (!act || act->data() != "Ref")
1441 return;
1443 SCRef refSha(git->getRefSha(act->iconText()));
1444 rv->st.setSha(refSha);
1445 UPDATE_DOMAIN(rv);
1448 void MainImpl::ActSplitView_activated() {
1450 Domain* t;
1451 switch (currentTabType(&t)) {
1452 case TAB_REV: {
1453 RevsView* rv = static_cast<RevsView*>(t);
1454 QWidget* w = rv->tab()->fileList;
1455 QSplitter* sp = static_cast<QSplitter*>(w->parent());
1456 sp->setHidden(w->isVisible()); }
1457 break;
1458 case TAB_PATCH: {
1459 PatchView* pv = static_cast<PatchView*>(t);
1460 QWidget* w = pv->tab()->textBrowserDesc;
1461 w->setHidden(w->isVisible()); }
1462 break;
1463 case TAB_FILE: {
1464 FileView* fv = static_cast<FileView*>(t);
1465 QWidget* w = fv->tab()->histListView;
1466 w->setHidden(w->isVisible()); }
1467 break;
1468 default:
1469 dbs("ASSERT in ActSplitView_activated: unknown current page");
1470 break;
1474 void MainImpl::ActToggleLogsDiff_activated() {
1476 Domain* t;
1477 if (currentTabType(&t) == TAB_REV) {
1478 RevsView* rv = static_cast<RevsView*>(t);
1479 rv->toggleDiffView();
1483 const QString MainImpl::getRevisionDesc(SCRef sha) {
1485 bool showHeader = ActShowDescHeader->isChecked();
1486 return git->getDesc(sha, shortLogRE, longLogRE, showHeader, NULL);
1489 void MainImpl::ActShowDescHeader_activated() {
1491 // each open tab get his description,
1492 // could be different for each tab
1493 emit updateRevDesc();
1496 void MainImpl::ActShowTree_toggled(bool b) {
1498 if (b) {
1499 treeView->show();
1500 UPDATE_DOMAIN(rv);
1501 } else {
1502 saveCurrentGeometry();
1503 treeView->hide();
1507 void MainImpl::ActSaveFile_activated() {
1509 QFileInfo f(rv->st.fileName());
1510 const QString fileName(QFileDialog::getSaveFileName(this, "Save file as", f.fileName()));
1511 if (fileName.isEmpty())
1512 return;
1514 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1515 QString fileSha(git->getFileSha(rv->st.fileName(), rv->st.sha()));
1516 if (!git->saveFile(fileSha, rv->st.fileName(), fileName))
1517 statusBar()->showMessage("Unable to save " + fileName);
1519 QApplication::restoreOverrideCursor();
1522 void MainImpl::openRecent_triggered(QAction* act) {
1524 const QString dataString = act->data().toString();
1525 if (!dataString.startsWith("RECENT"))
1526 // only recent repos entries have "RECENT" in data field
1527 return;
1529 const QString workDir = dataString.mid(7);
1530 if (!workDir.isEmpty()) {
1531 QDir d(workDir);
1532 if (d.exists())
1533 setRepository(workDir);
1534 else
1535 statusBar()->showMessage("Directory '" + workDir +
1536 "' does not seem to exist anymore");
1540 void MainImpl::ActOpenRepo_activated() {
1542 const QString dirName(QFileDialog::getExistingDirectory(this, "Choose a directory", curDir));
1543 if (!dirName.isEmpty()) {
1544 QDir d(dirName);
1545 setRepository(d.absolutePath());
1549 void MainImpl::ActOpenRepoNewWindow_activated() {
1551 const QString dirName(QFileDialog::getExistingDirectory(this, "Choose a directory", curDir));
1552 if (!dirName.isEmpty()) {
1553 QDir d(dirName);
1554 MainImpl* newWin = new MainImpl(d.absolutePath());
1555 newWin->show();
1559 void MainImpl::refreshRepo(bool b) {
1561 setRepository(curDir, true, b);
1564 void MainImpl::ActRefresh_activated() {
1566 refreshRepo(true);
1569 void MainImpl::ActMailFormatPatch_activated() {
1571 QStringList selectedItems;
1572 rv->tab()->listViewLog->getSelectedItems(selectedItems);
1573 if (selectedItems.isEmpty()) {
1574 statusBar()->showMessage("At least one selected revision needed");
1575 return;
1577 if (selectedItems.contains(ZERO_SHA)) {
1578 statusBar()->showMessage("Unable to save a patch for not committed content");
1579 return;
1581 QSettings settings;
1582 QString outDir(settings.value(PATCH_DIR_KEY, curDir).toString());
1583 QString dirPath(QFileDialog::getExistingDirectory(this,
1584 "Choose destination directory - Save Patch", outDir));
1585 if (dirPath.isEmpty())
1586 return;
1588 QDir d(dirPath);
1589 settings.setValue(PATCH_DIR_KEY, d.absolutePath());
1590 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1591 git->formatPatch(selectedItems, d.absolutePath());
1592 QApplication::restoreOverrideCursor();
1595 bool MainImpl::askApplyPatchParameters(bool* workDirOnly, bool* fold) {
1597 int ret = 0;
1598 if (!git->isStGITStack()) {
1599 ret = QMessageBox::question(this, "Apply Patch",
1600 "Do you want to commit or just to apply changes to "
1601 "working directory?", "&Cancel", "&Working directory", "&Commit", 0, 0);
1602 *workDirOnly = (ret == 1);
1603 *fold = false;
1604 } else {
1605 ret = QMessageBox::question(this, "Apply Patch", "Do you want to "
1606 "import or fold the patch?", "&Cancel", "&Fold", "&Import", 0, 0);
1607 *workDirOnly = false;
1608 *fold = (ret == 1);
1610 return (ret != 0);
1613 void MainImpl::ActMailApplyPatch_activated() {
1615 QSettings settings;
1616 QString outDir(settings.value(PATCH_DIR_KEY, curDir).toString());
1617 QString patchName(QFileDialog::getOpenFileName(this,
1618 "Choose the patch file - Apply Patch", outDir,
1619 "Patches (*.patch *.diff *.eml)\nAll Files (*.*)"));
1620 if (patchName.isEmpty())
1621 return;
1623 QFileInfo f(patchName);
1624 settings.setValue(PATCH_DIR_KEY, f.absolutePath());
1626 bool workDirOnly, fold;
1627 if (!askApplyPatchParameters(&workDirOnly, &fold))
1628 return;
1630 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1632 bool ok = git->applyPatchFile(f.absoluteFilePath(), fold, !Git::optDragDrop);
1633 if (workDirOnly && ok)
1634 git->resetCommits(1);
1636 QApplication::restoreOverrideCursor();
1637 refreshRepo(false);
1640 void MainImpl::ActCheckWorkDir_toggled(bool b) {
1642 if (!ActCheckWorkDir->isEnabled()) // to avoid looping with setChecked()
1643 return;
1645 setFlag(DIFF_INDEX_F, b);
1646 bool keepSelection = (rv->st.sha() != ZERO_SHA);
1647 refreshRepo(keepSelection);
1650 void MainImpl::ActSettings_activated() {
1652 SettingsImpl setView(this, git);
1653 connect(&setView, SIGNAL(typeWriterFontChanged()),
1654 this, SIGNAL(typeWriterFontChanged()));
1656 connect(&setView, SIGNAL(flagChanged(uint)),
1657 this, SIGNAL(flagChanged(uint)));
1659 setView.exec();
1661 // update ActCheckWorkDir if necessary
1662 if (ActCheckWorkDir->isChecked() != testFlag(DIFF_INDEX_F))
1663 ActCheckWorkDir->toggle();
1666 void MainImpl::ActCustomActionSetup_activated() {
1668 CustomActionImpl* ca = new CustomActionImpl(); // has Qt::WA_DeleteOnClose
1670 connect(this, SIGNAL(closeAllWindows()), ca, SLOT(close()));
1671 connect(ca, SIGNAL(listChanged(const QStringList&)),
1672 this, SLOT(customActionListChanged(const QStringList&)));
1674 ca->show();
1677 void MainImpl::customActionListChanged(const QStringList& list) {
1679 // update menu of all windows
1680 foreach (QWidget* widget, QApplication::topLevelWidgets()) {
1682 MainImpl* w = dynamic_cast<MainImpl*>(widget);
1683 if (w)
1684 w->doUpdateCustomActionMenu(list);
1688 void MainImpl::doUpdateCustomActionMenu(const QStringList& list) {
1690 QAction* setupAct = Actions->actions().first(); // is never empty
1691 Actions->removeAction(setupAct);
1692 Actions->clear();
1693 Actions->addAction(setupAct);
1695 if (list.isEmpty())
1696 return;
1698 Actions->addSeparator();
1699 FOREACH_SL (it, list)
1700 Actions->addAction(*it);
1703 void MainImpl::customAction_triggered(QAction* act) {
1705 QString actionName = act->text();
1706 if (actionName == "Setup actions...")
1707 return;
1709 QSettings set;
1710 QStringList actionsList = set.value(ACT_LIST_KEY).toStringList();
1711 if (!(actionsList.contains(actionName) || actionsList.contains(actionName.remove(QChar('&'))))) {
1712 dbp("ASSERT in customAction_activated, action %1 not found", actionName);
1713 return;
1715 QString cmd = set.value(ACT_GROUP_KEY + actionName + ACT_TEXT_KEY).toString().trimmed();
1716 if (testFlag(ACT_CMD_LINE_F, ACT_GROUP_KEY + actionName + ACT_FLAGS_KEY)) {
1717 // for backwards compatibility: if ACT_CMD_LINE_F is set, insert a dialog token in first line
1718 int pos = cmd.indexOf('\n');
1719 if (pos < 0) pos = cmd.length();
1720 cmd.insert(pos, " %lineedit:cmdline args%");
1722 updateRevVariables(lineEditSHA->text());
1723 InputDialog dlg(cmd, revision_variables, "Run custom action: " + actionName, this);
1724 if (!dlg.empty() && dlg.exec() != QDialog::Accepted) return;
1725 try {
1726 cmd = dlg.replace(revision_variables); // replace variables
1727 } catch (const std::exception &e) {
1728 QMessageBox::warning(this, "Custom action command", e.what());
1729 return;
1732 if (cmd.isEmpty())
1733 return;
1735 ConsoleImpl* c = new ConsoleImpl(actionName, git); // has Qt::WA_DeleteOnClose attribute
1737 connect(this, SIGNAL(typeWriterFontChanged()),
1738 c, SLOT(typeWriterFontChanged()));
1740 connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
1741 connect(c, SIGNAL(customAction_exited(const QString&)),
1742 this, SLOT(customAction_exited(const QString&)));
1744 if (c->start(cmd))
1745 c->show();
1748 void MainImpl::customAction_exited(const QString& name) {
1750 const QString flags(ACT_GROUP_KEY + name + ACT_FLAGS_KEY);
1751 if (testFlag(ACT_REFRESH_F, flags))
1752 QTimer::singleShot(10, this, SLOT(refreshRepo())); // outside of event handler
1755 void MainImpl::ActCommit_activated() {
1757 CommitImpl* c = new CommitImpl(git, false); // has Qt::WA_DeleteOnClose attribute
1758 connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
1759 connect(c, SIGNAL(changesCommitted(bool)), this, SLOT(changesCommitted(bool)));
1760 c->show();
1763 void MainImpl::ActAmend_activated() {
1765 CommitImpl* c = new CommitImpl(git, true); // has Qt::WA_DeleteOnClose attribute
1766 connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
1767 connect(c, SIGNAL(changesCommitted(bool)), this, SLOT(changesCommitted(bool)));
1768 c->show();
1771 void MainImpl::changesCommitted(bool ok) {
1773 if (ok)
1774 refreshRepo(false);
1775 else
1776 statusBar()->showMessage("Failed to commit changes");
1779 void MainImpl::ActCommit_setEnabled(bool b) {
1781 // pop and push commands fail if there are local changes,
1782 // so in this case we disable ActPop and ActPush
1783 if (b) {
1784 ActPush->setEnabled(false);
1785 ActPop->setEnabled(false);
1787 ActCommit->setEnabled(b);
1790 /** Checkout supports various operation modes:
1791 * - switching to an existing branch (standard use case)
1792 * - create and checkout a new branch
1793 * - resetting an existing branch to a new sha
1795 void MainImpl::ActCheckout_activated()
1797 QString sha = lineEditSHA->text(), rev = sha;
1798 const QString branchKey("local branch name");
1799 QString cmd = "git checkout -q ";
1801 const QString &selected_name = revision_variables.value(SELECTED_NAME).toString();
1802 const QString &current_branch = revision_variables.value(CURRENT_BRANCH).toString();
1803 const QStringList &local_branches = revision_variables.value(REV_LOCAL_BRANCHES).toStringList();
1805 if (!selected_name.isEmpty() &&
1806 local_branches.contains(selected_name) &&
1807 selected_name != current_branch) {
1808 // standard branch switching: directly checkout selected branch
1809 rev = selected_name;
1810 } else {
1811 // ask for (new) local branch name
1812 QString title = QString("Checkout ");
1813 if (selected_name.isEmpty()) {
1814 title += QString("revision ") + sha.mid(0, 8);
1815 } else {
1816 title += QString("branch ") + selected_name;
1817 rev = selected_name;
1819 // merge all reference names into a single list
1820 const QStringList &rmts = revision_variables.value(REV_REMOTE_BRANCHES).toStringList();
1821 QStringList all_names;
1822 all_names << revision_variables.value(REV_LOCAL_BRANCHES).toStringList();
1823 for(QStringList::const_iterator it=rmts.begin(), end=rmts.end(); it!=end; ++it) {
1824 // drop initial <origin>/ from name
1825 int pos = it->indexOf('/'); if (pos < 0) continue;
1826 all_names << it->mid(pos+1);
1828 revision_variables.insert("ALL_NAMES", all_names);
1830 InputDialog dlg(QString("%combobox[editable,ref,empty]:%1=$ALL_NAMES%").arg(branchKey), revision_variables, title, this);
1831 if (dlg.exec() != QDialog::Accepted) return;
1833 QString branch = dlg.value(branchKey).toString();
1834 if (!branch.isEmpty()) {
1835 SCRef refsha = git->getRefSha(branch, Git::BRANCH, true);
1836 if (refsha == sha)
1837 rev = branch; // checkout existing branch, even if name wasn't directly selected
1838 else if (!refsha.isEmpty()) {
1839 if (QMessageBox::warning(this, "Checkout " + branch,
1840 QString("Branch %1 already exists. Reset?").arg(branch),
1841 QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
1842 != QMessageBox::Yes)
1843 return;
1844 else
1845 cmd.append("-B ").append(branch); // reset an existing branch
1846 } else {
1847 cmd.append("-b ").append(branch); // create new local branch
1849 } // if new branch name is empty, checkout detached
1852 cmd.append(" ").append(rev);
1853 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1854 if (!git->run(cmd)) statusBar()->showMessage("Failed to checkout " + rev);
1855 refreshRepo(true);
1856 QApplication::restoreOverrideCursor();
1859 void MainImpl::ActBranch_activated() {
1861 doBranchOrTag(false);
1864 void MainImpl::ActTag_activated() {
1866 doBranchOrTag(true);
1869 const QStringList& stripNames(QStringList& names) {
1870 for(QStringList::iterator it=names.begin(), end=names.end(); it!=end; ++it)
1871 *it = it->section('/', -1);
1872 return names;
1875 void MainImpl::doBranchOrTag(bool isTag) {
1876 const QString sha = lineEditSHA->text();
1877 QString refDesc = isTag ? "tag" : "branch";
1878 QString dlgTitle = "Create " + refDesc + " - QGit";
1880 QString dlgDesc = "%lineedit[ref]:name=$ALL_NAMES%";
1881 InputDialog::VariableMap dlgVars;
1882 QStringList allNames = git->getAllRefNames(Git::BRANCH | Git::RMT_BRANCH | Git::TAG, false);
1883 stripNames(allNames);
1884 allNames.removeDuplicates();
1885 allNames.sort();
1886 dlgVars.insert("ALL_NAMES", allNames);
1888 if (isTag) {
1889 QString revDesc(rv->tab()->listViewLog->currentText(LOG_COL));
1890 dlgDesc += "%textedit:message=$MESSAGE%";
1891 dlgVars.insert("MESSAGE", revDesc);
1894 InputDialog dlg(dlgDesc, dlgVars, dlgTitle, this);
1895 if (dlg.exec() != QDialog::Accepted) return;
1896 const QString& ref = dlg.value("name").toString();
1898 bool force = false;
1899 if (!git->getRefSha(ref, isTag ? Git::TAG : Git::BRANCH, false).isEmpty()) {
1900 if (QMessageBox::warning(this, dlgTitle,
1901 refDesc + " name '" + ref + "' already exists.\n"
1902 "Force reset?", QMessageBox::Yes | QMessageBox::No,
1903 QMessageBox::No) != QMessageBox::Yes)
1904 return;
1905 force = true;
1908 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1909 QString cmd;
1910 if (isTag) {
1911 const QString& msg = dlg.value("message").toString();
1912 cmd = "git tag ";
1913 if (!msg.isEmpty()) cmd += "-m \"" + msg + "\" ";
1914 } else {
1915 cmd = "git branch ";
1917 if (force) cmd += "-f ";
1918 cmd += ref + " " + sha;
1920 if (git->run(cmd))
1921 refreshRepo(true);
1922 else
1923 statusBar()->showMessage("Failed to create " + refDesc + " " + ref);
1925 QApplication::restoreOverrideCursor();
1928 // put a ref name into a corresponding StringList for tags, remotes, and local branches
1929 typedef QMap<QString, QStringList> RefGroupMap;
1930 static void groupRef(const QString& ref, RefGroupMap& groups) {
1931 QString group, name;
1932 if (ref.startsWith("tags/")) { group = ref.left(5); name = ref.mid(5); }
1933 else if (ref.startsWith("remotes/")) { group = ref.section('/', 1, 1); name = ref.section('/', 2); }
1934 else { group = ""; name = ref; }
1935 if (!groups.contains(group))
1936 groups.insert(group, QStringList());
1937 QStringList &l = groups[group];
1938 l << name;
1941 void MainImpl::ActDelete_activated() {
1943 const QString &selected_name = revision_variables.value(SELECTED_NAME).toString();
1944 const QStringList &tags = revision_variables.value(REV_TAGS).toStringList();
1945 const QStringList &rmts = revision_variables.value(REV_REMOTE_BRANCHES).toStringList();
1947 // merge all reference names into a single list
1948 QStringList all_names;
1949 all_names << revision_variables.value(REV_LOCAL_BRANCHES).toStringList();
1950 for (QStringList::const_iterator it=rmts.begin(), end=rmts.end(); it!=end; ++it)
1951 all_names << "remotes/" + *it;
1952 for (QStringList::const_iterator it=tags.begin(), end=tags.end(); it!=end; ++it)
1953 all_names << "tags/" + *it;
1955 // group selected names by origin and determine which ref names will remain
1956 QMap <QString, QStringList> groups;
1957 QStringList remaining = all_names;
1958 if (!selected_name.isEmpty()) {
1959 groupRef(selected_name, groups);
1960 remaining.removeOne(selected_name);
1961 } else if (all_names.size() == 1) {
1962 const QString &name = all_names.first();
1963 groupRef(name, groups);
1964 remaining.removeOne(name);
1965 } else {
1966 revision_variables.insert("ALL_NAMES", all_names);
1967 InputDialog dlg("%listbox:_refs=$ALL_NAMES%", revision_variables,
1968 "Delete references - QGit", this);
1969 QListView *w = dynamic_cast<QListView*>(dlg.widget("_refs"));
1970 w->setSelectionMode(QAbstractItemView::ExtendedSelection);
1971 if (dlg.exec() != QDialog::Accepted) return;
1973 QModelIndexList selected = w->selectionModel()->selectedIndexes();
1974 for (QModelIndexList::const_iterator it=selected.begin(), end=selected.end(); it!=end; ++it) {
1975 const QString &name = it->data().toString();
1976 groupRef(name, groups);
1977 remaining.removeOne(name);
1980 if (groups.empty()) return;
1982 // check whether all refs will be removed
1983 const QString sha = revision_variables.value("SHA").toString();
1984 const QStringList &children = git->getChildren(sha);
1985 if ((children.count() == 0 || (children.count() == 1 && children.front() == ZERO_SHA)) && // no children
1986 remaining.count() == 0 && // all refs will be removed
1987 QMessageBox::warning(this, "remove references",
1988 "Do you really want to remove all\nremaining references to this branch?",
1989 QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
1990 == QMessageBox::No)
1991 return;
1993 // group selected names by origin
1994 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1995 bool ok = true;
1996 for (RefGroupMap::const_iterator g = groups.begin(), gend = groups.end(); g != gend; ++g) {
1997 QString cmd;
1998 if (g.key() == "") // local branches
1999 cmd = "git branch -D " + g.value().join(" ");
2000 else if (g.key() == "tags/") // tags
2001 cmd = "git tag -d " + g.value().join(" ");
2002 else // remote branches
2003 cmd = "git push -q " + g.key() + " :" + g.value().join(" :");
2004 ok &= git->run(cmd);
2006 refreshRepo(true);
2007 QApplication::restoreOverrideCursor();
2008 if (!ok) statusBar()->showMessage("Failed, to remove some refs.");
2011 void MainImpl::ActPush_activated() {
2013 QStringList selectedItems;
2014 rv->tab()->listViewLog->getSelectedItems(selectedItems);
2015 for (int i = 0; i < selectedItems.count(); i++) {
2016 if (!git->checkRef(selectedItems[i], Git::UN_APPLIED)) {
2017 statusBar()->showMessage("Please, select only unapplied patches");
2018 return;
2021 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
2022 bool ok = true;
2023 for (int i = 0; i < selectedItems.count(); i++) {
2024 const QString tmp(QString("Pushing patch %1 of %2")
2025 .arg(i+1).arg(selectedItems.count()));
2026 statusBar()->showMessage(tmp);
2027 SCRef sha = selectedItems[selectedItems.count() - i - 1];
2028 if (!git->stgPush(sha)) {
2029 statusBar()->showMessage("Failed to push patch " + sha);
2030 ok = false;
2031 break;
2034 if (ok)
2035 statusBar()->clearMessage();
2037 QApplication::restoreOverrideCursor();
2038 refreshRepo(false);
2041 void MainImpl::ActPop_activated() {
2043 QStringList selectedItems;
2044 rv->tab()->listViewLog->getSelectedItems(selectedItems);
2045 if (selectedItems.count() > 1) {
2046 statusBar()->showMessage("Please, select one revision only");
2047 return;
2049 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
2050 git->stgPop(selectedItems[0]);
2051 QApplication::restoreOverrideCursor();
2052 refreshRepo(false);
2055 void MainImpl::ActFilterTree_toggled(bool b) {
2057 if (!ActFilterTree->isEnabled()) {
2058 dbs("ASSERT ActFilterTree_toggled while disabled");
2059 return;
2061 if (b) {
2062 QStringList selectedItems;
2063 if (!treeView->isVisible())
2064 treeView->updateTree(); // force tree updating
2066 treeView->getTreeSelectedItems(selectedItems);
2067 if (selectedItems.count() == 0) {
2068 dbs("ASSERT tree filter action activated with no selected items");
2069 return;
2071 statusBar()->showMessage("Filter view on " + selectedItems.join(" "));
2072 setRepository(curDir, true, true, &selectedItems);
2073 } else
2074 refreshRepo(true);
2077 void MainImpl::ActFindNext_activated() {
2079 QTextEdit* te = getCurrentTextEdit();
2080 if (!te || textToFind.isEmpty())
2081 return;
2083 bool endOfDocument = false;
2084 while (true) {
2085 if (te->find(textToFind))
2086 return;
2088 if (endOfDocument) {
2089 QMessageBox::warning(this, "Find text - QGit", "Text \"" +
2090 textToFind + "\" not found!", QMessageBox::Ok, 0);
2091 return;
2093 if (QMessageBox::question(this, "Find text - QGit", "End of document "
2094 "reached\n\nDo you want to continue from beginning?", QMessageBox::Yes,
2095 QMessageBox::No | QMessageBox::Escape) == QMessageBox::No)
2096 return;
2098 endOfDocument = true;
2099 te->moveCursor(QTextCursor::Start);
2103 void MainImpl::ActFind_activated() {
2105 QTextEdit* te = getCurrentTextEdit();
2106 if (!te)
2107 return;
2109 QString def(textToFind);
2110 if (te->textCursor().hasSelection())
2111 def = te->textCursor().selectedText().section('\n', 0, 0);
2112 else
2113 te->moveCursor(QTextCursor::Start);
2115 bool ok;
2116 QString str(QInputDialog::getText(this, "Find text - QGit", "Text to find:",
2117 QLineEdit::Normal, def, &ok));
2118 if (!ok || str.isEmpty())
2119 return;
2121 textToFind = str; // update with valid data only
2122 ActFindNext_activated();
2125 void MainImpl::ActHelp_activated() {
2127 QDialog* dlg = new QDialog();
2128 dlg->setAttribute(Qt::WA_DeleteOnClose);
2129 Ui::HelpBase ui;
2130 ui.setupUi(dlg);
2131 ui.textEditHelp->setHtml(QString::fromLatin1(helpInfo)); // defined in help.h
2132 connect(this, SIGNAL(closeAllWindows()), dlg, SLOT(close()));
2133 dlg->show();
2134 dlg->raise();
2137 void MainImpl::ActMarkDiffToSha_activated()
2139 ListView* lv = rv->tab()->listViewLog;
2140 lv->markDiffToSha(lineEditSHA->text());
2143 void MainImpl::ActAbout_activated() {
2145 static const char* aboutMsg =
2146 "<p><b>QGit version " PACKAGE_VERSION "</b></p>"
2147 "<p>Copyright (c) 2005, 2007, 2008 Marco Costalba<br>"
2148 "Copyright (c) 2011-2018 <a href='mailto:tibirna@kde.org'>Cristian Tibirna</a></p>"
2149 "<p>Use and redistribute under the terms of the<br>"
2150 "<a href=\"http://www.gnu.org/licenses/old-licenses/gpl-2.0.html\">GNU General Public License Version 2</a></p>"
2151 "<p>Contributors:<br>"
2152 "Copyright (c) "
2153 "<nobr>2007 Andy Parkins,</nobr> "
2154 "<nobr>2007 Pavel Roskin,</nobr> "
2155 "<nobr>2007 Peter Oberndorfer,</nobr> "
2156 "<nobr>2007 Yaacov Akiba,</nobr> "
2157 "<nobr>2007 James McKaskill,</nobr> "
2158 "<nobr>2008 Jan Hudec,</nobr> "
2159 "<nobr>2008 Paul Gideon Dann,</nobr> "
2160 "<nobr>2008 Oliver Bock,</nobr> "
2161 "<nobr>2010 <a href='mailto:cyp561@gmail.com'>Cyp</a>,</nobr> "
2162 "<nobr>2011 <a href='dagenaisj@sonatest.com'>Jean-Fran&ccedil;ois Dagenais</a>,</nobr> "
2163 "<nobr>2011 <a href='mailto:pavtih@gmail.com'>Pavel Tikhomirov</a>,</nobr> "
2164 "<nobr>2011 <a href='mailto:tim@klingt.org'>Tim Blechmann</a>,</nobr> "
2165 "<nobr>2014 <a href='mailto:codestruct@posteo.org'>Gregor Mi</a>,</nobr> "
2166 "<nobr>2014 <a href='mailto:sbytnn@gmail.com'>Sbytov N.N</a>,</nobr> "
2167 "<nobr>2015 <a href='mailto:dendy.ua@gmail.com'>Daniel Levin</a>,</nobr> "
2168 "<nobr>2017 <a href='mailto:luigi.toscano@tiscali.it'>Luigi Toscano</a>,</nobr> "
2169 "<nobr>2016 <a href='mailto:hkarel@yandex.ru'>Pavel Karelin</a>,</nobr> "
2170 "<nobr>2016 <a href='mailto:zbitter@redhat.com'>Zane Bitter</a>,</nobr> "
2171 "<nobr>2016 <a href='mailto:rhaschke@techfak.uni-bielefeld.de'>Robert Haschke</a>,</nobr> "
2172 "<nobr>2017 <a href='mailto:wrar@wrar.name'>Andrey Rahmatullin</a>,</nobr> "
2173 "<nobr>2017 <a href='mailto:alex-github@wenlex.nl'>Alex Hermann</a>,</nobr> "
2174 "<nobr>2017 <a href='mailto:shalokshalom@protonmail.ch'>Matthias Schuster</a>,</nobr> "
2175 "<nobr>2017 <a href='mailto:u.joss@calltrade.ch'>Urs Joss</a>,</nobr> "
2176 "<nobr>2017 <a href='mailto:patrick.m.lacasse@gmail.com'>Patrick Lacasse</a>,</nobr> "
2177 "<nobr>2018 <a href='mailto:deveee@gmail.com'>Deve</a>,</nobr> "
2178 "<nobr>2018 <a href='mailto:asturm@gentoo.org'>Andreas Sturmlechner</a>,</nobr> "
2179 "<nobr>2018 <a href='mailto:kde@davidedmundson.co.uk'>David Edmundson</a></nobr>"
2180 "</p>"
2182 "<p>This version was compiled against Qt " QT_VERSION_STR "</p>";
2183 QMessageBox::about(this, "About QGit", QString::fromLatin1(aboutMsg));
2186 void MainImpl::closeEvent(QCloseEvent* ce) {
2188 saveCurrentGeometry();
2190 // lastWindowClosed() signal is emitted by close(), after sending
2191 // closeEvent(), so we need to close _here_ all secondary windows before
2192 // the close() method checks for lastWindowClosed flag to avoid missing
2193 // the signal and stay in the main loop forever, because lastWindowClosed()
2194 // signal is connected to qApp->quit()
2196 // note that we cannot rely on setting 'this' parent in secondary windows
2197 // because when close() is called children are still alive and, finally,
2198 // when children are deleted, d'tor do not call close() anymore. So we miss
2199 // lastWindowClosed() signal in this case.
2200 emit closeAllWindows();
2201 hide();
2203 EM_RAISE(exExiting);
2205 git->stop(Git::optSaveCache);
2207 if (!git->findChildren<QProcess*>().isEmpty()) {
2208 // if not all processes have been deleted, there is
2209 // still some run() call not returned somewhere, it is
2210 // not safe to delete run() callers objects now
2211 QTimer::singleShot(100, this, SLOT(ActClose_activated()));
2212 ce->ignore();
2213 return;
2215 emit closeAllTabs();
2216 delete rv;
2217 QWidget::closeEvent(ce);
2220 void MainImpl::ActClose_activated() {
2222 close();
2225 void MainImpl::ActExit_activated() {
2227 qApp->closeAllWindows();