fix a compiler warning
[qgit4/redivivus.git] / src / mainimpl.cpp
blob4286a43916f657c3179a40d2208fde942440760b
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 <QDrag>
11 #include <QEvent>
12 #include <QFileDialog>
13 #include <QInputDialog>
14 #include <QMenu>
15 #include <QMessageBox>
16 #include <QProgressBar>
17 #include <QScrollBar>
18 #include <QSettings>
19 #include <QShortcut>
20 #include <QStatusBar>
21 #include <QTimer>
22 #include <QWheelEvent>
23 #include "config.h" // defines PACKAGE_VERSION
24 #include "consoleimpl.h"
25 #include "commitimpl.h"
26 #include "common.h"
27 #include "customactionimpl.h"
28 #include "fileview.h"
29 #include "git.h"
30 #include "help.h"
31 #include "listview.h"
32 #include "mainimpl.h"
33 #include "patchview.h"
34 #include "rangeselectimpl.h"
35 #include "revdesc.h"
36 #include "revsview.h"
37 #include "settingsimpl.h"
38 #include "treeview.h"
39 #include "ui_help.h"
40 #include "ui_revsview.h"
41 #include "ui_fileview.h"
42 #include "ui_patchview.h"
44 using namespace QGit;
46 MainImpl::MainImpl(SCRef cd, QWidget* p) : QMainWindow(p) {
48 EM_INIT(exExiting, "Exiting");
50 setAttribute(Qt::WA_DeleteOnClose);
51 setupUi(this);
53 // manual setup widgets not buildable with Qt designer
54 lineEditSHA = new QLineEdit(NULL);
55 lineEditFilter = new QLineEdit(NULL);
56 cmbSearch = new QComboBox(NULL);
57 QString list("Short log,Log msg,Author,SHA1,File,Patch,Patch (regExp)");
58 cmbSearch->addItems(list.split(","));
59 toolBar->addWidget(lineEditSHA);
60 QAction* act = toolBar->insertWidget(ActSearchAndFilter, lineEditFilter);
61 toolBar->insertWidget(act, cmbSearch);
62 connect(lineEditSHA, SIGNAL(returnPressed()), this, SLOT(lineEditSHA_returnPressed()));
63 connect(lineEditFilter, SIGNAL(returnPressed()), this, SLOT(lineEditFilter_returnPressed()));
65 // create light and dark colors for alternate background
66 ODD_LINE_COL = palette().color(QPalette::Base);
67 EVEN_LINE_COL = ODD_LINE_COL.dark(103);
69 // our interface to git world
70 git = new Git(this);
71 setupShortcuts();
72 qApp->installEventFilter(this);
74 // init native types
75 setRepositoryBusy = false;
77 // init filter match highlighters
78 shortLogRE.setMinimal(true);
79 shortLogRE.setCaseSensitivity(Qt::CaseInsensitive);
80 longLogRE.setMinimal(true);
81 longLogRE.setCaseSensitivity(Qt::CaseInsensitive);
83 // set-up standard revisions and files list font
84 QSettings settings;
85 QString font(settings.value(STD_FNT_KEY).toString());
86 if (font.isEmpty())
87 font = QApplication::font().toString();
88 QGit::STD_FONT.fromString(font);
90 // set-up typewriter (fixed width) font
91 font = settings.value(TYPWRT_FNT_KEY).toString();
92 if (font.isEmpty()) { // choose a sensible default
93 QFont fnt = QApplication::font();
94 fnt.setStyleHint(QFont::TypeWriter, QFont::PreferDefault);
95 fnt.setFixedPitch(true);
96 fnt.setFamily(fnt.defaultFamily()); // the family corresponding
97 font = fnt.toString(); // to current style hint
99 QGit::TYPE_WRITER_FONT.fromString(font);
101 // set-up tab view
102 delete tabWdg->currentWidget(); // cannot be done in Qt Designer
103 rv = new RevsView(this, git, true); // set has main domain
104 tabWdg->addTab(rv->tabPage(), "&Rev list");
106 // set-up tab corner widget ('close tab' button)
107 QToolButton* ct = new QToolButton(tabWdg);
108 ct->setIcon(QIcon(QString::fromUtf8(":/icons/resources/tab_remove.png")));
109 ct->setToolTip("Close tab");
110 ct->setEnabled(false);
111 tabWdg->setCornerWidget(ct);
112 connect(ct, SIGNAL(clicked()), this, SLOT(pushButtonCloseTab_clicked()));
113 connect(this, SIGNAL(closeTabButtonEnabled(bool)), ct, SLOT(setEnabled(bool)));
115 // set-up file names loading progress bar
116 pbFileNamesLoading = new QProgressBar(statusBar());
117 pbFileNamesLoading->setTextVisible(false);
118 pbFileNamesLoading->setToolTip("Background file names loading");
119 pbFileNamesLoading->hide();
120 statusBar()->addPermanentWidget(pbFileNamesLoading);
122 QVector<QSplitter*> v(1, treeSplitter);
123 QGit::restoreGeometrySetting(QGit::MAIN_GEOM_KEY, this, &v);
124 treeView->hide();
126 // set-up menu for recent visited repositories
127 connect(File, SIGNAL(triggered(QAction*)), this, SLOT(openRecent_triggered(QAction*)));
128 doUpdateRecentRepoMenu("");
130 // set-up menu for custom actions
131 connect(Actions, SIGNAL(triggered(QAction*)), this, SLOT(customAction_triggered(QAction*)));
132 doUpdateCustomActionMenu(settings.value(ACT_LIST_KEY).toStringList());
134 // manual adjust lineEditSHA width
135 QString tmp(41, '8');
136 int wd = lineEditSHA->fontMetrics().boundingRect(tmp).width();
137 lineEditSHA->setMinimumWidth(wd);
139 // disable all actions
140 updateGlobalActions(false);
142 connect(git, SIGNAL(fileNamesLoad(int, int)), this, SLOT(fileNamesLoad(int, int)));
144 connect(git, SIGNAL(newRevsAdded(const FileHistory*, const QVector<ShaString>&)),
145 this, SLOT(newRevsAdded(const FileHistory*, const QVector<ShaString>&)));
147 connect(this, SIGNAL(typeWriterFontChanged()), this, SIGNAL(updateRevDesc()));
149 connect(this, SIGNAL(changeFont(const QFont&)), git, SIGNAL(changeFont(const QFont&)));
151 // connect cross-domain update signals
152 connect(rv->tab()->listViewLog, SIGNAL(doubleClicked(const QModelIndex&)),
153 this, SLOT(listViewLog_doubleClicked(const QModelIndex&)));
155 connect(rv->tab()->fileList, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
156 this, SLOT(fileList_itemDoubleClicked(QListWidgetItem*)));
158 connect(treeView, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)),
159 this, SLOT(treeView_doubleClicked(QTreeWidgetItem*, int)));
161 // use most recent repo as startup dir if it exists and user opted to do so
162 QStringList recents(settings.value(REC_REP_KEY).toStringList());
163 QDir checkRepo;
164 if ( recents.size() >= 1
165 && testFlag(REOPEN_REPO_F, FLAGS_KEY)
166 && checkRepo.exists(recents.at(0)))
168 startUpDir = recents.at(0);
170 else {
171 startUpDir = (cd.isEmpty() ? QDir::current().absolutePath() : cd);
174 // MainImpl c'tor is called before to enter event loop,
175 // but some stuff requires event loop to init properly
176 QTimer::singleShot(10, this, SLOT(initWithEventLoopActive()));
179 void MainImpl::initWithEventLoopActive() {
181 git->checkEnvironment();
182 setRepository(startUpDir);
183 startUpDir = ""; // one shot
186 void MainImpl::saveCurrentGeometry() {
188 QVector<QSplitter*> v(1, treeSplitter);
189 QGit::saveGeometrySetting(QGit::MAIN_GEOM_KEY, this, &v);
192 void MainImpl::highlightAbbrevSha(SCRef abbrevSha) {
193 // reset any previous highlight
194 if (ActSearchAndHighlight->isChecked())
195 ActSearchAndHighlight->toggle();
197 // set to highlight on SHA matching
198 cmbSearch->setCurrentIndex(CS_SHA1);
200 // set substring to search for
201 lineEditFilter->setText(abbrevSha);
203 // go with highlighting
204 ActSearchAndHighlight->toggle();
207 void MainImpl::lineEditSHA_returnPressed() {
209 QString sha = git->getRefSha(lineEditSHA->text());
210 if (!sha.isEmpty()) // good, we can resolve to an unique sha
212 rv->st.setSha(sha);
213 UPDATE_DOMAIN(rv);
214 } else { // try a multiple match search
215 highlightAbbrevSha(lineEditSHA->text());
216 goMatch(0);
220 void MainImpl::ActBack_activated() {
222 lineEditSHA->undo(); // first for insert(text)
223 if (lineEditSHA->text().isEmpty())
224 lineEditSHA->undo(); // double undo, see RevsView::updateLineEditSHA()
226 lineEditSHA_returnPressed();
229 void MainImpl::ActForward_activated() {
231 lineEditSHA->redo();
232 if (lineEditSHA->text().isEmpty())
233 lineEditSHA->redo();
235 lineEditSHA_returnPressed();
238 // *************************** ExternalDiffViewer ***************************
240 void MainImpl::ActExternalDiff_activated() {
242 QStringList args;
243 QStringList filenames;
244 getExternalDiffArgs(&args, &filenames);
245 ExternalDiffProc* externalDiff = new ExternalDiffProc(filenames, this);
246 externalDiff->setWorkingDirectory(curDir);
248 if (!QGit::startProcess(externalDiff, args)) {
249 QString text("Cannot start external viewer: ");
250 text.append(args[0]);
251 QMessageBox::warning(this, "Error - QGit", text);
252 delete externalDiff;
256 void MainImpl::getExternalDiffArgs(QStringList* args, QStringList* filenames) {
258 // save files to diff in working directory,
259 // will be removed by ExternalDiffProc on exit
260 QFileInfo f(rv->st.fileName());
261 QString prevRevSha(rv->st.diffToSha());
262 if (prevRevSha.isEmpty()) { // default to first parent
263 const Rev* r = git->revLookup(rv->st.sha());
264 prevRevSha = (r && r->parentsCount() > 0 ? r->parent(0) : rv->st.sha());
266 QFileInfo fi(f);
267 QString fName1(curDir + "/" + rv->st.sha().left(6) + "_" + fi.fileName());
268 QString fName2(curDir + "/" + prevRevSha.left(6) + "_" + fi.fileName());
270 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
272 QByteArray fileContent;
273 QString fileSha(git->getFileSha(rv->st.fileName(), rv->st.sha()));
274 git->getFile(fileSha, NULL, &fileContent, rv->st.fileName());
275 if (!writeToFile(fName1, QString(fileContent)))
276 statusBar()->showMessage("Unable to save " + fName1);
278 fileSha = git->getFileSha(rv->st.fileName(), prevRevSha);
279 git->getFile(fileSha, NULL, &fileContent, rv->st.fileName());
280 if (!writeToFile(fName2, QString(fileContent)))
281 statusBar()->showMessage("Unable to save " + fName2);
283 // get external diff viewer command
284 QSettings settings;
285 QString extDiff(settings.value(EXT_DIFF_KEY, EXT_DIFF_DEF).toString());
287 QApplication::restoreOverrideCursor();
289 // if command doesn't have %1 and %2 to denote filenames, add them to end
290 if (!extDiff.contains("%1")) {
291 extDiff.append(" %1");
293 if (!extDiff.contains("%2")) {
294 extDiff.append(" %2");
297 // set process arguments
298 QStringList extDiffArgs = extDiff.split(' ');
299 QString curArg;
300 for (int i = 0; i < extDiffArgs.count(); i++) {
301 curArg = extDiffArgs.value(i);
303 // perform any filename replacements that are necessary
304 // (done inside the loop to handle whitespace in paths properly)
305 curArg.replace("%1", fName2);
306 curArg.replace("%2", fName1);
308 args->append(curArg);
311 // set filenames so that they can be deleted when the process completes
312 filenames->append(fName1);
313 filenames->append(fName2);
316 // ********************** Repository open or changed *************************
318 void MainImpl::setRepository(SCRef newDir, bool refresh, bool keepSelection,
319 const QStringList* passedArgs, bool overwriteArgs) {
322 Because Git::init calls processEvents(), if setRepository() is called in
323 a tight loop (as example keeping pressed F5 refresh button) then a lot
324 of pending init() calls would be stacked.
325 On returning from processEvents() an exception is trown and init is exited,
326 so we end up with a long list of 'exception thrown' messages.
327 But the worst thing is that we have to wait for _all_ the init call to exit
328 and this could take a long time as example in case of working dir refreshing
329 'git update-index' of a big tree.
330 So we use a guard flag to guarantee we have only one init() call 'in flight'
332 if (setRepositoryBusy)
333 return;
335 setRepositoryBusy = true;
337 // check for a refresh or open of a new repository while in filtered view
338 if (ActFilterTree->isChecked() && passedArgs == NULL)
339 // toggle() triggers a refresh and a following setRepository()
340 // call that is filtered out by setRepositoryBusy guard flag
341 ActFilterTree->toggle(); // triggers ActFilterTree_toggled()
343 try {
344 EM_REGISTER(exExiting);
346 bool archiveChanged;
347 curDir = git->getBaseDir(&archiveChanged, newDir);
349 git->stop(archiveChanged); // stop all pending processes, non blocking
351 if (archiveChanged && refresh)
352 dbs("ASSERT in setRepository: different dir with no range select");
354 // now we can clear all our data
355 setWindowTitle(curDir + " - QGit");
356 bool complete = !refresh || !keepSelection;
357 rv->clear(complete);
358 if (archiveChanged)
359 emit closeAllTabs();
361 // disable all actions
362 updateGlobalActions(false);
363 updateContextActions("", "", false, false);
364 ActCommit_setEnabled(false);
366 if (ActFilterTree->isChecked())
367 setWindowTitle(windowTitle() + " - FILTER ON < " +
368 passedArgs->join(" ") + " >");
370 // tree name should be set before init because in case of
371 // StGIT archives the first revs are sent before init returns
372 QString n(curDir);
373 treeView->setTreeName(n.prepend('/').section('/', -1, -1));
375 bool quit;
376 bool ok = git->init(curDir, !refresh, passedArgs, overwriteArgs, &quit); // blocking call
377 if (quit)
378 goto exit;
380 updateCommitMenu(ok && git->isStGITStack());
381 ActCheckWorkDir->setChecked(testFlag(DIFF_INDEX_F)); // could be changed in Git::init()
383 if (ok) {
384 updateGlobalActions(true);
385 if (archiveChanged)
386 updateRecentRepoMenu(curDir);
387 } else
388 statusBar()->showMessage("Not a git archive");
390 exit:
391 setRepositoryBusy = false;
392 EM_REMOVE(exExiting);
394 if (quit && !startUpDir.isEmpty())
395 close();
397 } catch (int i) {
398 EM_REMOVE(exExiting);
400 if (EM_MATCH(i, exExiting, "loading repository")) {
401 EM_THROW_PENDING;
402 return;
404 const QString info("Exception \'" + EM_DESC(i) + "\' not "
405 "handled in setRepository...re-throw");
406 dbs(info);
407 throw;
411 void MainImpl::updateGlobalActions(bool b) {
413 ActRefresh->setEnabled(b);
414 ActCheckWorkDir->setEnabled(b);
415 ActViewRev->setEnabled(b);
416 ActViewDiff->setEnabled(b);
417 ActViewDiffNewTab->setEnabled(b && firstTab<PatchView>());
418 ActShowTree->setEnabled(b);
419 ActMailApplyPatch->setEnabled(b);
420 ActMailFormatPatch->setEnabled(b);
422 rv->setEnabled(b);
425 void MainImpl::updateContextActions(SCRef newRevSha, SCRef newFileName,
426 bool isDir, bool found) {
428 bool pathActionsEnabled = !newFileName.isEmpty();
429 bool fileActionsEnabled = (pathActionsEnabled && !isDir);
431 ActViewFile->setEnabled(fileActionsEnabled);
432 ActViewFileNewTab->setEnabled(fileActionsEnabled && firstTab<FileView>());
433 ActExternalDiff->setEnabled(fileActionsEnabled);
434 ActSaveFile->setEnabled(fileActionsEnabled);
435 ActFilterTree->setEnabled(pathActionsEnabled || ActFilterTree->isChecked());
437 bool isTag, isUnApplied, isApplied;
438 isTag = isUnApplied = isApplied = false;
440 if (found) {
441 const Rev* r = git->revLookup(newRevSha);
442 isTag = git->checkRef(newRevSha, Git::TAG);
443 isUnApplied = r->isUnApplied;
444 isApplied = r->isApplied;
446 ActBranch->setEnabled(found && (newRevSha != ZERO_SHA) && !isUnApplied);
447 ActTag->setEnabled(found && (newRevSha != ZERO_SHA) && !isUnApplied);
448 ActTagDelete->setEnabled(found && isTag && (newRevSha != ZERO_SHA) && !isUnApplied);
449 ActPush->setEnabled(found && isUnApplied && git->isNothingToCommit());
450 ActPop->setEnabled(found && isApplied && git->isNothingToCommit());
453 // ************************* cross-domain update Actions ***************************
455 void MainImpl::listViewLog_doubleClicked(const QModelIndex& index) {
457 if (index.isValid() && ActViewDiff->isEnabled())
458 ActViewDiff->activate(QAction::Trigger);
461 void MainImpl::histListView_doubleClicked(const QModelIndex& index) {
463 if (index.isValid() && ActViewRev->isEnabled())
464 ActViewRev->activate(QAction::Trigger);
467 void MainImpl::fileList_itemDoubleClicked(QListWidgetItem* item) {
469 bool isFirst = (item && item->listWidget()->item(0) == item);
470 if (isFirst && rv->st.isMerge())
471 return;
473 bool isMainView = (item && item->listWidget() == rv->tab()->fileList);
474 if (isMainView && ActViewDiff->isEnabled())
475 ActViewDiff->activate(QAction::Trigger);
477 if (item && !isMainView && ActViewFile->isEnabled())
478 ActViewFile->activate(QAction::Trigger);
481 void MainImpl::treeView_doubleClicked(QTreeWidgetItem* item, int) {
483 if (item && ActViewFile->isEnabled())
484 ActViewFile->activate(QAction::Trigger);
487 void MainImpl::pushButtonCloseTab_clicked() {
489 Domain* t;
490 switch (currentTabType(&t)) {
491 case TAB_REV:
492 break;
493 case TAB_PATCH:
494 t->deleteWhenDone();
495 ActViewDiffNewTab->setEnabled(ActViewDiff->isEnabled() && firstTab<PatchView>());
496 break;
497 case TAB_FILE:
498 t->deleteWhenDone();
499 ActViewFileNewTab->setEnabled(ActViewFile->isEnabled() && firstTab<FileView>());
500 break;
501 default:
502 dbs("ASSERT in pushButtonCloseTab_clicked: unknown current page");
503 break;
507 void MainImpl::ActRangeDlg_activated() {
509 QString args;
510 RangeSelectImpl rs(this, &args, false, git);
511 bool quit = (rs.exec() == QDialog::Rejected); // modal execution
512 if (!quit) {
513 const QStringList l(args.split(" "));
514 setRepository(curDir, true, true, &l, true);
518 void MainImpl::ActViewRev_activated() {
520 Domain* t;
521 if (currentTabType(&t) == TAB_FILE) {
522 rv->st = t->st;
523 UPDATE_DOMAIN(rv);
525 tabWdg->setCurrentWidget(rv->tabPage());
528 void MainImpl::ActViewFile_activated() {
530 openFileTab(firstTab<FileView>());
533 void MainImpl::ActViewFileNewTab_activated() {
535 openFileTab();
538 void MainImpl::openFileTab(FileView* fv) {
540 if (!fv) {
541 fv = new FileView(this, git);
542 tabWdg->addTab(fv->tabPage(), "File");
544 connect(fv->tab()->histListView, SIGNAL(doubleClicked(const QModelIndex&)),
545 this, SLOT(histListView_doubleClicked(const QModelIndex&)));
547 connect(this, SIGNAL(closeAllTabs()), fv, SLOT(on_closeAllTabs()));
549 ActViewFileNewTab->setEnabled(ActViewFile->isEnabled());
551 tabWdg->setCurrentWidget(fv->tabPage());
552 fv->st = rv->st;
553 UPDATE_DOMAIN(fv);
556 void MainImpl::ActViewDiff_activated() {
558 Domain* t;
559 if (currentTabType(&t) == TAB_FILE) {
560 rv->st = t->st;
561 UPDATE_DOMAIN(rv);
563 rv->viewPatch(false);
564 ActViewDiffNewTab->setEnabled(true);
566 if (ActSearchAndFilter->isChecked() || ActSearchAndHighlight->isChecked()) {
567 bool isRegExp = (cmbSearch->currentIndex() == CS_PATCH_REGEXP);
568 emit highlightPatch(lineEditFilter->text(), isRegExp);
572 void MainImpl::ActViewDiffNewTab_activated() {
574 rv->viewPatch(true);
577 bool MainImpl::eventFilter(QObject* obj, QEvent* ev) {
579 if (ev->type() == QEvent::Wheel) {
581 QWheelEvent* e = static_cast<QWheelEvent*>(ev);
582 if (e->modifiers() == Qt::AltModifier) {
584 int idx = tabWdg->currentIndex();
585 if (e->delta() < 0)
586 idx = (++idx == tabWdg->count() ? 0 : idx);
587 else
588 idx = (--idx < 0 ? tabWdg->count() - 1 : idx);
590 tabWdg->setCurrentIndex(idx);
591 return true;
594 return QWidget::eventFilter(obj, ev);
597 void MainImpl::revisionsDragged(SCList selRevs) {
599 const QString h(QString::fromLatin1("@") + curDir + '\n');
600 const QString dragRevs = selRevs.join(h).append(h).trimmed();
601 QDrag* drag = new QDrag(this);
602 QMimeData* mimeData = new QMimeData;
603 mimeData->setText(dragRevs);
604 drag->setMimeData(mimeData);
605 drag->start(); // blocking until drop event
608 void MainImpl::revisionsDropped(SCList remoteRevs) {
609 // remoteRevs is already sanity checked to contain some possible valid data
611 if (rv->isDropping()) // avoid reentrancy
612 return;
614 QDir dr(curDir + QGit::PATCHES_DIR);
615 if (dr.exists()) {
616 const QString tmp("Please remove stale import directory " + dr.absolutePath());
617 statusBar()->showMessage(tmp);
618 return;
620 bool workDirOnly, fold;
621 if (!askApplyPatchParameters(&workDirOnly, &fold))
622 return;
624 // ok, let's go
625 rv->setDropping(true);
626 dr.setFilter(QDir::Files);
627 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
628 raise();
629 EM_PROCESS_EVENTS;
631 uint revNum = 0;
632 QStringList::const_iterator it(remoteRevs.constEnd());
633 do {
634 --it;
636 QString tmp("Importing revision %1 of %2");
637 statusBar()->showMessage(tmp.arg(++revNum).arg(remoteRevs.count()));
639 SCRef sha((*it).section('@', 0, 0));
640 SCRef remoteRepo((*it).section('@', 1));
642 if (!dr.exists(remoteRepo))
643 break;
645 // we create patches one by one
646 if (!git->formatPatch(QStringList(sha), dr.absolutePath(), remoteRepo))
647 break;
649 dr.refresh();
650 if (dr.count() != 1) {
651 qDebug("ASSERT in on_droppedRevisions: found %i files "
652 "in %s", dr.count(), QGit::PATCHES_DIR.toLatin1().constData());
653 break;
655 SCRef fn(dr.absoluteFilePath(dr[0]));
656 bool is_applied = git->applyPatchFile(fn, fold, Git::optDragDrop);
657 dr.remove(fn);
658 if (!is_applied)
659 break;
661 } while (it != remoteRevs.constBegin());
663 if (it == remoteRevs.constBegin())
664 statusBar()->clearMessage();
665 else
666 statusBar()->showMessage("Failed to import revision " + QString::number(revNum--));
668 if (workDirOnly && (revNum > 0))
669 git->resetCommits(revNum);
671 dr.rmdir(dr.absolutePath()); // 'dr' must be already empty
672 QApplication::restoreOverrideCursor();
673 rv->setDropping(false);
674 refreshRepo();
677 // ******************************* Filter ******************************
679 void MainImpl::newRevsAdded(const FileHistory* fh, const QVector<ShaString>&) {
681 if (!git->isMainHistory(fh))
682 return;
684 if (ActSearchAndFilter->isChecked())
685 ActSearchAndFilter_toggled(true); // filter again on new arrived data
687 if (ActSearchAndHighlight->isChecked())
688 ActSearchAndHighlight_toggled(true); // filter again on new arrived data
690 // first rev could be a StGIT unapplied patch so check more then once
691 if ( !ActCommit->isEnabled()
692 && (!git->isNothingToCommit() || git->isUnknownFiles())
693 && !git->isCommittingMerge())
694 ActCommit_setEnabled(true);
697 void MainImpl::lineEditFilter_returnPressed() {
699 ActSearchAndFilter->setChecked(true);
702 void MainImpl::ActSearchAndFilter_toggled(bool isOn) {
704 ActSearchAndHighlight->setEnabled(!isOn);
705 ActSearchAndFilter->setEnabled(false);
706 filterList(isOn, false); // blocking call
707 ActSearchAndFilter->setEnabled(true);
710 void MainImpl::ActSearchAndHighlight_toggled(bool isOn) {
712 ActSearchAndFilter->setEnabled(!isOn);
713 ActSearchAndHighlight->setEnabled(false);
714 filterList(isOn, true); // blocking call
715 ActSearchAndHighlight->setEnabled(true);
718 void MainImpl::filterList(bool isOn, bool onlyHighlight) {
720 lineEditFilter->setEnabled(!isOn);
721 cmbSearch->setEnabled(!isOn);
723 SCRef filter(lineEditFilter->text());
724 if (filter.isEmpty())
725 return;
727 ShaSet shaSet;
728 bool patchNeedsUpdate, isRegExp;
729 patchNeedsUpdate = isRegExp = false;
730 int idx = cmbSearch->currentIndex(), colNum = 0;
731 if (isOn) {
732 switch (idx) {
733 case CS_SHORT_LOG:
734 colNum = LOG_COL;
735 shortLogRE.setPattern(filter);
736 break;
737 case CS_LOG_MSG:
738 colNum = LOG_MSG_COL;
739 longLogRE.setPattern(filter);
740 break;
741 case CS_AUTHOR:
742 colNum = AUTH_COL;
743 break;
744 case CS_SHA1:
745 colNum = COMMIT_COL;
746 break;
747 case CS_FILE:
748 case CS_PATCH:
749 case CS_PATCH_REGEXP:
750 colNum = SHA_MAP_COL;
751 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
752 EM_PROCESS_EVENTS; // to paint wait cursor
753 if (idx == CS_FILE)
754 git->getFileFilter(filter, shaSet);
755 else {
756 isRegExp = (idx == CS_PATCH_REGEXP);
757 if (!git->getPatchFilter(filter, isRegExp, shaSet)) {
758 QApplication::restoreOverrideCursor();
759 ActSearchAndFilter->toggle();
760 return;
762 patchNeedsUpdate = (shaSet.count() > 0);
764 QApplication::restoreOverrideCursor();
765 break;
767 } else {
768 patchNeedsUpdate = (idx == CS_PATCH || idx == CS_PATCH_REGEXP);
769 shortLogRE.setPattern("");
770 longLogRE.setPattern("");
772 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
774 ListView* lv = rv->tab()->listViewLog;
775 int matchedCnt = lv->filterRows(isOn, onlyHighlight, filter, colNum, &shaSet);
777 QApplication::restoreOverrideCursor();
779 emit updateRevDesc(); // could be highlighted
780 if (patchNeedsUpdate)
781 emit highlightPatch(isOn ? filter : "", isRegExp);
783 QString msg;
784 if (isOn && !onlyHighlight)
785 msg = QString("Found %1 matches. Toggle filter/highlight "
786 "button to remove the filter").arg(matchedCnt);
787 QApplication::postEvent(rv, new MessageEvent(msg)); // deferred message, after update
790 bool MainImpl::event(QEvent* e) {
792 BaseEvent* de = dynamic_cast<BaseEvent*>(e);
793 if (!de)
794 return QWidget::event(e);
796 SCRef data = de->myData();
797 bool ret = true;
799 switch ((EventType)e->type()) {
800 case ERROR_EV: {
801 QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
802 EM_PROCESS_EVENTS;
803 MainExecErrorEvent* me = (MainExecErrorEvent*)e;
804 QString text("An error occurred while executing command:\n\n");
805 text.append(me->command() + "\n\nGit says: \n\n" + me->report());
806 QMessageBox::warning(this, "Error - QGit", text);
807 QApplication::restoreOverrideCursor(); }
808 break;
809 case MSG_EV:
810 statusBar()->showMessage(data);
811 break;
812 case POPUP_LIST_EV:
813 doContexPopup(data);
814 break;
815 case POPUP_FILE_EV:
816 case POPUP_TREE_EV:
817 doFileContexPopup(data, e->type());
818 break;
819 default:
820 dbp("ASSERT in MainImpl::event unhandled event %1", e->type());
821 ret = false;
822 break;
824 return ret;
827 int MainImpl::currentTabType(Domain** t) {
829 *t = NULL;
830 QWidget* curPage = tabWdg->currentWidget();
831 if (curPage == rv->tabPage()) {
832 *t = rv;
833 return TAB_REV;
835 QList<PatchView*>* l = getTabs<PatchView>(curPage);
836 if (l->count() > 0) {
837 *t = l->first();
838 delete l;
839 return TAB_PATCH;
841 delete l;
842 QList<FileView*>* l2 = getTabs<FileView>(curPage);
843 if (l2->count() > 0) {
844 *t = l2->first();
845 delete l2;
846 return TAB_FILE;
848 if (l2->count() > 0)
849 dbs("ASSERT in tabType file not found");
851 delete l2;
852 return -1;
855 template<class X> QList<X*>* MainImpl::getTabs(QWidget* tabPage) {
857 QList<X*> l = this->findChildren<X*>();
858 QList<X*>* ret = new QList<X*>;
860 for (int i = 0; i < l.size(); ++i) {
861 if (!tabPage || l.at(i)->tabPage() == tabPage)
862 ret->append(l.at(i));
864 return ret; // 'ret' must be deleted by caller
867 template<class X> X* MainImpl::firstTab(QWidget* startPage) {
869 int minVal = 99, firstVal = 99;
870 int startPos = tabWdg->indexOf(startPage);
871 X* min = NULL;
872 X* first = NULL;
873 QList<X*>* l = getTabs<X>();
874 for (int i = 0; i < l->size(); ++i) {
876 X* d = l->at(i);
877 int idx = tabWdg->indexOf(d->tabPage());
878 if (idx < minVal) {
879 minVal = idx;
880 min = d;
882 if (idx < firstVal && idx > startPos) {
883 firstVal = idx;
884 first = d;
887 delete l;
888 return (first ? first : min);
891 void MainImpl::tabWdg_currentChanged(int w) {
893 if (w == -1)
894 return;
896 // set correct focus for keyboard browsing
897 Domain* t;
898 switch (currentTabType(&t)) {
899 case TAB_REV:
900 static_cast<RevsView*>(t)->tab()->listViewLog->setFocus();
901 emit closeTabButtonEnabled(false);
902 break;
903 case TAB_PATCH:
904 static_cast<PatchView*>(t)->tab()->textEditDiff->setFocus();
905 emit closeTabButtonEnabled(true);
906 break;
907 case TAB_FILE:
908 static_cast<FileView*>(t)->tab()->histListView->setFocus();
909 emit closeTabButtonEnabled(true);
910 break;
911 default:
912 dbs("ASSERT in tabWdg_currentChanged: unknown current page");
913 break;
917 void MainImpl::setupShortcuts() {
919 new QShortcut(Qt::Key_I, this, SLOT(shortCutActivated()));
920 new QShortcut(Qt::Key_K, this, SLOT(shortCutActivated()));
921 new QShortcut(Qt::Key_N, this, SLOT(shortCutActivated()));
922 new QShortcut(Qt::Key_Left, this, SLOT(shortCutActivated()));
923 new QShortcut(Qt::Key_Right, this, SLOT(shortCutActivated()));
925 new QShortcut(Qt::Key_Delete, this, SLOT(shortCutActivated()));
926 new QShortcut(Qt::Key_Backspace, this, SLOT(shortCutActivated()));
927 new QShortcut(Qt::Key_Space, this, SLOT(shortCutActivated()));
929 new QShortcut(Qt::Key_B, this, SLOT(shortCutActivated()));
930 new QShortcut(Qt::Key_D, this, SLOT(shortCutActivated()));
931 new QShortcut(Qt::Key_F, this, SLOT(shortCutActivated()));
932 new QShortcut(Qt::Key_P, this, SLOT(shortCutActivated()));
933 new QShortcut(Qt::Key_R, this, SLOT(shortCutActivated()));
934 new QShortcut(Qt::Key_U, this, SLOT(shortCutActivated()));
936 new QShortcut(Qt::SHIFT | Qt::Key_Up, this, SLOT(shortCutActivated()));
937 new QShortcut(Qt::SHIFT | Qt::Key_Down, this, SLOT(shortCutActivated()));
938 new QShortcut(Qt::CTRL | Qt::Key_Plus, this, SLOT(shortCutActivated()));
939 new QShortcut(Qt::CTRL | Qt::Key_Minus, this, SLOT(shortCutActivated()));
942 void MainImpl::shortCutActivated() {
944 QShortcut* se = dynamic_cast<QShortcut*>(sender());
945 if (!se)
946 return;
948 bool isKey_P = false;
950 switch (se->key()) {
952 case Qt::Key_I:
953 rv->tab()->listViewLog->on_keyUp();
954 break;
955 case Qt::Key_K:
956 case Qt::Key_N:
957 rv->tab()->listViewLog->on_keyDown();
958 break;
959 case Qt::SHIFT | Qt::Key_Up:
960 goMatch(-1);
961 break;
962 case Qt::SHIFT | Qt::Key_Down:
963 goMatch(1);
964 break;
965 case Qt::Key_Left:
966 ActBack_activated();
967 break;
968 case Qt::Key_Right:
969 ActForward_activated();
970 break;
971 case Qt::CTRL | Qt::Key_Plus:
972 adjustFontSize(1);
973 break;
974 case Qt::CTRL | Qt::Key_Minus:
975 adjustFontSize(-1);
976 break;
977 case Qt::Key_U:
978 scrollTextEdit(-18);
979 break;
980 case Qt::Key_D:
981 scrollTextEdit(18);
982 break;
983 case Qt::Key_Delete:
984 case Qt::Key_B:
985 case Qt::Key_Backspace:
986 scrollTextEdit(-1);
987 break;
988 case Qt::Key_Space:
989 scrollTextEdit(1);
990 break;
991 case Qt::Key_R:
992 tabWdg->setCurrentWidget(rv->tabPage());
993 break;
994 case Qt::Key_P:
995 isKey_P = true;
996 case Qt::Key_F: {
997 QWidget* cp = tabWdg->currentWidget();
998 Domain* d = isKey_P ? static_cast<Domain*>(firstTab<PatchView>(cp)) :
999 static_cast<Domain*>(firstTab<FileView>(cp));
1000 if (d)
1001 tabWdg->setCurrentWidget(d->tabPage()); }
1002 break;
1006 void MainImpl::goMatch(int delta) {
1008 if (ActSearchAndHighlight->isChecked())
1009 rv->tab()->listViewLog->scrollToNextHighlighted(delta);
1012 QTextEdit* MainImpl::getCurrentTextEdit() {
1014 QTextEdit* te = NULL;
1015 Domain* t;
1016 switch (currentTabType(&t)) {
1017 case TAB_REV:
1018 te = static_cast<RevsView*>(t)->tab()->textBrowserDesc;
1019 if (!te->isVisible())
1020 te = static_cast<RevsView*>(t)->tab()->textEditDiff;
1021 break;
1022 case TAB_PATCH:
1023 te = static_cast<PatchView*>(t)->tab()->textEditDiff;
1024 break;
1025 case TAB_FILE:
1026 te = static_cast<FileView*>(t)->tab()->textEditFile;
1027 break;
1028 default:
1029 break;
1031 return te;
1034 void MainImpl::scrollTextEdit(int delta) {
1036 QTextEdit* te = getCurrentTextEdit();
1037 if (!te)
1038 return;
1040 QScrollBar* vs = te->verticalScrollBar();
1041 if (delta == 1 || delta == -1)
1042 vs->setValue(vs->value() + delta * (vs->pageStep() - vs->singleStep()));
1043 else
1044 vs->setValue(vs->value() + delta * vs->singleStep());
1047 void MainImpl::adjustFontSize(int delta) {
1048 // font size is changed on a 'per instance' base and only on list views
1050 int ps = QGit::STD_FONT.pointSize() + delta;
1051 if (ps < 2)
1052 return;
1054 QGit::STD_FONT.setPointSize(ps);
1056 QSettings settings;
1057 settings.setValue(QGit::STD_FNT_KEY, QGit::STD_FONT.toString());
1058 emit changeFont(QGit::STD_FONT);
1061 void MainImpl::fileNamesLoad(int status, int value) {
1063 switch (status) {
1064 case 1: // stop
1065 pbFileNamesLoading->hide();
1066 break;
1067 case 2: // update
1068 pbFileNamesLoading->setValue(value);
1069 break;
1070 case 3: // start
1071 if (value > 200) { // don't show for few revisions
1072 pbFileNamesLoading->reset();
1073 pbFileNamesLoading->setMaximum(value);
1074 pbFileNamesLoading->show();
1076 break;
1080 // ****************************** Menu *********************************
1082 void MainImpl::updateCommitMenu(bool isStGITStack) {
1084 ActCommit->setText(isStGITStack ? "Commit St&GIT patch..." : "&Commit...");
1085 ActAmend->setText(isStGITStack ? "Refresh St&GIT patch..." : "&Amend commit...");
1088 void MainImpl::updateRecentRepoMenu(SCRef newEntry) {
1090 // update menu of all windows
1091 foreach (QWidget* widget, QApplication::topLevelWidgets()) {
1093 MainImpl* w = dynamic_cast<MainImpl*>(widget);
1094 if (w)
1095 w->doUpdateRecentRepoMenu(newEntry);
1099 void MainImpl::doUpdateRecentRepoMenu(SCRef newEntry) {
1101 QList<QAction*> al(File->actions());
1102 FOREACH (QList<QAction*>, it, al) {
1103 SCRef txt = (*it)->text();
1104 if (!txt.isEmpty() && txt.at(0).isDigit())
1105 File->removeAction(*it);
1107 QSettings settings;
1108 QStringList recents(settings.value(REC_REP_KEY).toStringList());
1109 int idx = recents.indexOf(newEntry);
1110 if (idx != -1)
1111 recents.removeAt(idx);
1113 if (!newEntry.isEmpty())
1114 recents.prepend(newEntry);
1116 idx = 1;
1117 QStringList newRecents;
1118 FOREACH_SL (it, recents) {
1119 File->addAction(QString::number(idx++) + " " + *it);
1120 newRecents << *it;
1121 if (idx > MAX_RECENT_REPOS)
1122 break;
1124 settings.setValue(REC_REP_KEY, newRecents);
1127 static int cntMenuEntries(const QMenu& menu) {
1129 int cnt = 0;
1130 QList<QAction*> al(menu.actions());
1131 FOREACH (QList<QAction*>, it, al) {
1132 if (!(*it)->isSeparator())
1133 cnt++;
1135 return cnt;
1138 void MainImpl::doContexPopup(SCRef sha) {
1140 QMenu contextMenu(this);
1141 QMenu contextBrnMenu("More branches...", this);
1142 QMenu contextTagMenu("More tags...", this);
1143 QMenu contextRmtMenu("Remote branches", this);
1145 connect(&contextMenu, SIGNAL(triggered(QAction*)), this, SLOT(goRef_triggered(QAction*)));
1147 Domain* t;
1148 int tt = currentTabType(&t);
1149 bool isRevPage = (tt == TAB_REV);
1150 bool isPatchPage = (tt == TAB_PATCH);
1151 bool isFilePage = (tt == TAB_FILE);
1153 if (!isFilePage && ActCheckWorkDir->isEnabled()) {
1154 contextMenu.addAction(ActCheckWorkDir);
1155 contextMenu.addSeparator();
1157 if (isFilePage && ActViewRev->isEnabled())
1158 contextMenu.addAction(ActViewRev);
1160 if (!isPatchPage && ActViewDiff->isEnabled())
1161 contextMenu.addAction(ActViewDiff);
1163 if (isRevPage && ActViewDiffNewTab->isEnabled())
1164 contextMenu.addAction(ActViewDiffNewTab);
1166 if (!isFilePage && ActExternalDiff->isEnabled())
1167 contextMenu.addAction(ActExternalDiff);
1169 if (isRevPage) {
1170 if (ActCommit->isEnabled() && (sha == ZERO_SHA))
1171 contextMenu.addAction(ActCommit);
1172 if (ActBranch->isEnabled())
1173 contextMenu.addAction(ActBranch);
1174 if (ActTag->isEnabled())
1175 contextMenu.addAction(ActTag);
1176 if (ActTagDelete->isEnabled())
1177 contextMenu.addAction(ActTagDelete);
1178 if (ActMailFormatPatch->isEnabled())
1179 contextMenu.addAction(ActMailFormatPatch);
1180 if (ActPush->isEnabled())
1181 contextMenu.addAction(ActPush);
1182 if (ActPop->isEnabled())
1183 contextMenu.addAction(ActPop);
1185 const QStringList& bn(git->getAllRefNames(Git::BRANCH, Git::optOnlyLoaded));
1186 const QStringList& rbn(git->getAllRefNames(Git::RMT_BRANCH, Git::optOnlyLoaded));
1187 const QStringList& tn(git->getAllRefNames(Git::TAG, Git::optOnlyLoaded));
1188 QAction* act = NULL;
1190 FOREACH_SL (it, rbn) {
1191 act = contextRmtMenu.addAction(*it);
1192 act->setData("Ref");
1194 if (!contextRmtMenu.isEmpty())
1195 contextMenu.addMenu(&contextRmtMenu);
1197 // halve the possible remaining entries for branches and tags
1198 int remainingEntries = (MAX_MENU_ENTRIES - cntMenuEntries(contextMenu));
1199 int tagEntries = remainingEntries / 2;
1200 int brnEntries = remainingEntries - tagEntries;
1202 // display more branches, if there are few tags
1203 if (tagEntries > tn.count())
1204 tagEntries = tn.count();
1206 // one branch less because of the "More branches..." submenu
1207 if ((bn.count() > brnEntries) && tagEntries)
1208 tagEntries++;
1210 if (!bn.empty())
1211 contextMenu.addSeparator();
1213 FOREACH_SL (it, bn) {
1214 if ( cntMenuEntries(contextMenu) < MAX_MENU_ENTRIES - tagEntries
1215 || (*it == bn.last() && contextBrnMenu.isEmpty()))
1216 act = contextMenu.addAction(*it);
1217 else
1218 act = contextBrnMenu.addAction(*it);
1220 act->setData("Ref");
1222 if (!contextBrnMenu.isEmpty())
1223 contextMenu.addMenu(&contextBrnMenu);
1225 if (!tn.empty())
1226 contextMenu.addSeparator();
1228 FOREACH_SL (it, tn) {
1229 if ( cntMenuEntries(contextMenu) < MAX_MENU_ENTRIES
1230 || (*it == tn.last() && contextTagMenu.isEmpty()))
1231 act = contextMenu.addAction(*it);
1232 else
1233 act = contextTagMenu.addAction(*it);
1235 act->setData("Ref");
1237 if (!contextTagMenu.isEmpty())
1238 contextMenu.addMenu(&contextTagMenu);
1240 QPoint p = QCursor::pos();
1241 p += QPoint(10, 10);
1242 contextMenu.exec(p);
1245 void MainImpl::doFileContexPopup(SCRef fileName, int type) {
1247 QMenu contextMenu(this);
1249 Domain* t;
1250 int tt = currentTabType(&t);
1251 bool isRevPage = (tt == TAB_REV);
1252 bool isPatchPage = (tt == TAB_PATCH);
1253 bool isDir = treeView->isDir(fileName);
1255 if (type == POPUP_FILE_EV)
1256 if (!isPatchPage && ActViewDiff->isEnabled())
1257 contextMenu.addAction(ActViewDiff);
1259 if (!isDir && ActViewFile->isEnabled())
1260 contextMenu.addAction(ActViewFile);
1262 if (!isDir && ActViewFileNewTab->isEnabled())
1263 contextMenu.addAction(ActViewFileNewTab);
1265 if (!isRevPage && (type == POPUP_FILE_EV) && ActViewRev->isEnabled())
1266 contextMenu.addAction(ActViewRev);
1268 if (ActFilterTree->isEnabled())
1269 contextMenu.addAction(ActFilterTree);
1271 if (!isDir) {
1272 if (ActSaveFile->isEnabled())
1273 contextMenu.addAction(ActSaveFile);
1274 if ((type == POPUP_FILE_EV) && ActExternalDiff->isEnabled())
1275 contextMenu.addAction(ActExternalDiff);
1277 contextMenu.exec(QCursor::pos());
1280 void MainImpl::goRef_triggered(QAction* act) {
1282 if (!act || act->data() != "Ref")
1283 return;
1285 SCRef refSha(git->getRefSha(act->text()));
1286 rv->st.setSha(refSha);
1287 UPDATE_DOMAIN(rv);
1290 void MainImpl::ActSplitView_activated() {
1292 Domain* t;
1293 switch (currentTabType(&t)) {
1294 case TAB_REV: {
1295 RevsView* rv = static_cast<RevsView*>(t);
1296 QWidget* w = rv->tab()->fileList;
1297 QSplitter* sp = static_cast<QSplitter*>(w->parent());
1298 sp->setHidden(w->isVisible()); }
1299 break;
1300 case TAB_PATCH: {
1301 PatchView* pv = static_cast<PatchView*>(t);
1302 QWidget* w = pv->tab()->textBrowserDesc;
1303 w->setHidden(w->isVisible()); }
1304 break;
1305 case TAB_FILE: {
1306 FileView* fv = static_cast<FileView*>(t);
1307 QWidget* w = fv->tab()->histListView;
1308 w->setHidden(w->isVisible()); }
1309 break;
1310 default:
1311 dbs("ASSERT in ActSplitView_activated: unknown current page");
1312 break;
1316 void MainImpl::ActToggleLogsDiff_activated() {
1318 Domain* t;
1319 if (currentTabType(&t) == TAB_REV) {
1320 RevsView* rv = static_cast<RevsView*>(t);
1321 rv->toggleDiffView();
1325 const QString MainImpl::getRevisionDesc(SCRef sha) {
1327 bool showHeader = ActShowDescHeader->isChecked();
1328 return git->getDesc(sha, shortLogRE, longLogRE, showHeader, NULL);
1331 void MainImpl::ActShowDescHeader_activated() {
1333 // each open tab get his description,
1334 // could be different for each tab
1335 emit updateRevDesc();
1338 void MainImpl::ActShowTree_toggled(bool b) {
1340 if (b) {
1341 treeView->show();
1342 UPDATE_DOMAIN(rv);
1343 } else {
1344 saveCurrentGeometry();
1345 treeView->hide();
1349 void MainImpl::ActSaveFile_activated() {
1351 QFileInfo f(rv->st.fileName());
1352 const QString fileName(QFileDialog::getSaveFileName(this, "Save file as", f.fileName()));
1353 if (fileName.isEmpty())
1354 return;
1356 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1357 QString fileSha(git->getFileSha(rv->st.fileName(), rv->st.sha()));
1358 if (!git->saveFile(fileSha, rv->st.fileName(), fileName))
1359 statusBar()->showMessage("Unable to save " + fileName);
1361 QApplication::restoreOverrideCursor();
1364 void MainImpl::openRecent_triggered(QAction* act) {
1366 bool ok;
1367 act->text().left(1).toInt(&ok);
1368 if (!ok) // only recent repos entries have a number in first char
1369 return;
1371 const QString workDir(act->text().section(' ', 1));
1372 if (!workDir.isEmpty()) {
1373 QDir d(workDir);
1374 if (d.exists())
1375 setRepository(workDir);
1376 else
1377 statusBar()->showMessage("Directory '" + workDir +
1378 "' does not seem to exsist anymore");
1382 void MainImpl::ActOpenRepo_activated() {
1384 const QString dirName(QFileDialog::getExistingDirectory(this, "Choose a directory", curDir));
1385 if (!dirName.isEmpty()) {
1386 QDir d(dirName);
1387 setRepository(d.absolutePath());
1391 void MainImpl::ActOpenRepoNewWindow_activated() {
1393 const QString dirName(QFileDialog::getExistingDirectory(this, "Choose a directory", curDir));
1394 if (!dirName.isEmpty()) {
1395 QDir d(dirName);
1396 MainImpl* newWin = new MainImpl(d.absolutePath());
1397 newWin->show();
1401 void MainImpl::refreshRepo(bool b) {
1403 setRepository(curDir, true, b);
1406 void MainImpl::ActRefresh_activated() {
1408 refreshRepo(true);
1411 void MainImpl::ActMailFormatPatch_activated() {
1413 QStringList selectedItems;
1414 rv->tab()->listViewLog->getSelectedItems(selectedItems);
1415 if (selectedItems.isEmpty()) {
1416 statusBar()->showMessage("At least one selected revision needed");
1417 return;
1419 if (selectedItems.contains(ZERO_SHA)) {
1420 statusBar()->showMessage("Unable to save a patch for not committed content");
1421 return;
1423 QSettings settings;
1424 QString outDir(settings.value(PATCH_DIR_KEY, curDir).toString());
1425 QString dirPath(QFileDialog::getExistingDirectory(this,
1426 "Choose destination directory - Save Patch", outDir));
1427 if (dirPath.isEmpty())
1428 return;
1430 QDir d(dirPath);
1431 settings.setValue(PATCH_DIR_KEY, d.absolutePath());
1432 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1433 git->formatPatch(selectedItems, d.absolutePath());
1434 QApplication::restoreOverrideCursor();
1437 bool MainImpl::askApplyPatchParameters(bool* workDirOnly, bool* fold) {
1439 int ret = 0;
1440 if (!git->isStGITStack()) {
1441 ret = QMessageBox::question(this, "Apply Patch",
1442 "Do you want to commit or just to apply changes to "
1443 "working directory?", "&Cancel", "&Working dir", "&Commit", 0, 0);
1444 *workDirOnly = (ret == 1);
1445 *fold = false;
1446 } else {
1447 ret = QMessageBox::question(this, "Apply Patch", "Do you want to "
1448 "import or fold the patch?", "&Cancel", "&Fold", "&Import", 0, 0);
1449 *workDirOnly = false;
1450 *fold = (ret == 1);
1452 return (ret != 0);
1455 void MainImpl::ActMailApplyPatch_activated() {
1457 QSettings settings;
1458 QString outDir(settings.value(PATCH_DIR_KEY, curDir).toString());
1459 QString patchName(QFileDialog::getOpenFileName(this,
1460 "Choose the patch file - Apply Patch", outDir,
1461 "Patches (*.patch *.diff *.eml)\nAll Files (*.*)"));
1462 if (patchName.isEmpty())
1463 return;
1465 QFileInfo f(patchName);
1466 settings.setValue(PATCH_DIR_KEY, f.absolutePath());
1468 bool workDirOnly, fold;
1469 if (!askApplyPatchParameters(&workDirOnly, &fold))
1470 return;
1472 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1474 bool ok = git->applyPatchFile(f.absoluteFilePath(), fold, !Git::optDragDrop);
1475 if (workDirOnly && ok)
1476 git->resetCommits(1);
1478 QApplication::restoreOverrideCursor();
1479 refreshRepo(false);
1482 void MainImpl::ActCheckWorkDir_toggled(bool b) {
1484 if (!ActCheckWorkDir->isEnabled()) // to avoid looping with setChecked()
1485 return;
1487 setFlag(DIFF_INDEX_F, b);
1488 bool keepSelection = (rv->st.sha() != ZERO_SHA);
1489 refreshRepo(keepSelection);
1492 void MainImpl::ActSettings_activated() {
1494 SettingsImpl setView(this, git);
1495 connect(&setView, SIGNAL(typeWriterFontChanged()),
1496 this, SIGNAL(typeWriterFontChanged()));
1498 connect(&setView, SIGNAL(flagChanged(uint)),
1499 this, SIGNAL(flagChanged(uint)));
1501 setView.exec();
1503 // update ActCheckWorkDir if necessary
1504 if (ActCheckWorkDir->isChecked() != testFlag(DIFF_INDEX_F))
1505 ActCheckWorkDir->toggle();
1508 void MainImpl::ActCustomActionSetup_activated() {
1510 CustomActionImpl* ca = new CustomActionImpl(); // has Qt::WA_DeleteOnClose
1512 connect(this, SIGNAL(closeAllWindows()), ca, SLOT(close()));
1513 connect(ca, SIGNAL(listChanged(const QStringList&)),
1514 this, SLOT(customActionListChanged(const QStringList&)));
1516 ca->show();
1519 void MainImpl::customActionListChanged(const QStringList& list) {
1521 // update menu of all windows
1522 foreach (QWidget* widget, QApplication::topLevelWidgets()) {
1524 MainImpl* w = dynamic_cast<MainImpl*>(widget);
1525 if (w)
1526 w->doUpdateCustomActionMenu(list);
1530 void MainImpl::doUpdateCustomActionMenu(const QStringList& list) {
1532 QAction* setupAct = Actions->actions().first(); // is never empty
1533 Actions->removeAction(setupAct);
1534 Actions->clear();
1535 Actions->addAction(setupAct);
1537 if (list.isEmpty())
1538 return;
1540 Actions->addSeparator();
1541 FOREACH_SL (it, list)
1542 Actions->addAction(*it);
1545 void MainImpl::customAction_triggered(QAction* act) {
1547 SCRef actionName = act->text();
1548 if (actionName == "Setup actions...")
1549 return;
1551 QSettings set;
1552 if (!set.value(ACT_LIST_KEY).toStringList().contains(actionName)) {
1553 dbp("ASSERT in customAction_activated, action %1 not found", actionName);
1554 return;
1556 QString cmdArgs;
1557 if (testFlag(ACT_CMD_LINE_F, ACT_GROUP_KEY + actionName + ACT_FLAGS_KEY)) {
1558 bool ok;
1559 cmdArgs = QInputDialog::getText(this, "Run action - QGit", "Enter command line "
1560 "arguments for '" + actionName + "'", QLineEdit::Normal, "", &ok);
1561 cmdArgs.prepend(' ');
1562 if (!ok)
1563 return;
1565 SCRef cmd = set.value(ACT_GROUP_KEY + actionName + ACT_TEXT_KEY).toString();
1566 if (cmd.isEmpty())
1567 return;
1569 ConsoleImpl* c = new ConsoleImpl(actionName, git); // has Qt::WA_DeleteOnClose attribute
1571 connect(this, SIGNAL(typeWriterFontChanged()),
1572 c, SLOT(typeWriterFontChanged()));
1574 connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
1575 connect(c, SIGNAL(customAction_exited(const QString&)),
1576 this, SLOT(customAction_exited(const QString&)));
1578 if (c->start(cmd, cmdArgs))
1579 c->show();
1582 void MainImpl::customAction_exited(const QString& name) {
1584 const QString flags(ACT_GROUP_KEY + name + ACT_FLAGS_KEY);
1585 if (testFlag(ACT_REFRESH_F, flags))
1586 QTimer::singleShot(10, this, SLOT(refreshRepo())); // outside of event handler
1589 void MainImpl::ActCommit_activated() {
1591 CommitImpl* c = new CommitImpl(git, false); // has Qt::WA_DeleteOnClose attribute
1592 connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
1593 connect(c, SIGNAL(changesCommitted(bool)), this, SLOT(changesCommitted(bool)));
1594 c->show();
1597 void MainImpl::ActAmend_activated() {
1599 CommitImpl* c = new CommitImpl(git, true); // has Qt::WA_DeleteOnClose attribute
1600 connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
1601 connect(c, SIGNAL(changesCommitted(bool)), this, SLOT(changesCommitted(bool)));
1602 c->show();
1605 void MainImpl::changesCommitted(bool ok) {
1607 if (ok)
1608 refreshRepo(false);
1609 else
1610 statusBar()->showMessage("Failed to commit changes");
1613 void MainImpl::ActCommit_setEnabled(bool b) {
1615 // pop and push commands fail if there are local changes,
1616 // so in this case we disable ActPop and ActPush
1617 if (b) {
1618 ActPush->setEnabled(false);
1619 ActPop->setEnabled(false);
1621 ActCommit->setEnabled(b);
1624 void MainImpl::ActBranch_activated() {
1626 doBranchOrTag(false);
1629 void MainImpl::ActTag_activated() {
1631 doBranchOrTag(true);
1634 void MainImpl::doBranchOrTag(bool isTag) {
1636 QString refDesc = isTag ? "tag" : "branch";
1637 QString boxDesc = "Make " + refDesc + " - QGit";
1638 QString revDesc(rv->tab()->listViewLog->currentText(LOG_COL));
1639 bool ok;
1640 QString ref = QInputDialog::getText(this, boxDesc, "Enter " + refDesc
1641 + " name:", QLineEdit::Normal, "", &ok);
1642 if (!ok || ref.isEmpty())
1643 return;
1645 QString tmp(ref.trimmed());
1646 if (ref != tmp.remove(' ')) {
1647 QMessageBox::warning(this, boxDesc,
1648 "Sorry, control characters or spaces\n"
1649 "are not allowed in " + refDesc + " name.");
1650 return;
1652 if (!git->getRefSha(ref, isTag ? Git::TAG : Git::BRANCH, false).isEmpty()) {
1653 QMessageBox::warning(this, boxDesc,
1654 "Sorry, " + refDesc + " name already exists.\n"
1655 "Please choose a different name.");
1656 return;
1658 QString msg;
1659 if (isTag) {
1660 msg = QInputDialog::getText(this, boxDesc, "Enter tag message, if any:",
1661 QLineEdit::Normal, revDesc, &ok);
1662 if (!ok) return;
1664 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1665 if (isTag)
1666 ok = git->makeTag(lineEditSHA->text(), ref, msg);
1667 else
1668 ok = git->makeBranch(lineEditSHA->text(), ref);
1670 QApplication::restoreOverrideCursor();
1671 if (ok)
1672 refreshRepo(true);
1673 else
1674 statusBar()->showMessage("Sorry, unable to tag the revision");
1677 void MainImpl::ActTagDelete_activated() {
1679 if (QMessageBox::question(this, "Delete tag - QGit",
1680 "Do you want to un-tag selected revision?",
1681 "&Yes", "&No", QString(), 0, 1) == 1)
1682 return;
1684 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1685 bool ok = git->deleteTag(lineEditSHA->text());
1686 QApplication::restoreOverrideCursor();
1687 if (ok)
1688 refreshRepo(true);
1689 else
1690 statusBar()->showMessage("Sorry, unable to un-tag the revision");
1693 void MainImpl::ActPush_activated() {
1695 QStringList selectedItems;
1696 rv->tab()->listViewLog->getSelectedItems(selectedItems);
1697 for (int i = 0; i < selectedItems.count(); i++) {
1698 if (!git->checkRef(selectedItems[i], Git::UN_APPLIED)) {
1699 statusBar()->showMessage("Please, select only unapplied patches");
1700 return;
1703 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1704 bool ok = true;
1705 for (int i = 0; i < selectedItems.count(); i++) {
1706 const QString tmp(QString("Pushing patch %1 of %2")
1707 .arg(i+1).arg(selectedItems.count()));
1708 statusBar()->showMessage(tmp);
1709 SCRef sha = selectedItems[selectedItems.count() - i - 1];
1710 if (!git->stgPush(sha)) {
1711 statusBar()->showMessage("Failed to push patch " + sha);
1712 ok = false;
1713 break;
1716 if (ok)
1717 statusBar()->clearMessage();
1719 QApplication::restoreOverrideCursor();
1720 refreshRepo(false);
1723 void MainImpl::ActPop_activated() {
1725 QStringList selectedItems;
1726 rv->tab()->listViewLog->getSelectedItems(selectedItems);
1727 if (selectedItems.count() > 1) {
1728 statusBar()->showMessage("Please, select one revision only");
1729 return;
1731 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1732 git->stgPop(selectedItems[0]);
1733 QApplication::restoreOverrideCursor();
1734 refreshRepo(false);
1737 void MainImpl::ActFilterTree_toggled(bool b) {
1739 if (!ActFilterTree->isEnabled()) {
1740 dbs("ASSERT ActFilterTree_toggled while disabled");
1741 return;
1743 if (b) {
1744 QStringList selectedItems;
1745 if (!treeView->isVisible())
1746 treeView->updateTree(); // force tree updating
1748 treeView->getTreeSelectedItems(selectedItems);
1749 if (selectedItems.count() == 0) {
1750 dbs("ASSERT tree filter action activated with no selected items");
1751 return;
1753 statusBar()->showMessage("Filter view on " + selectedItems.join(" "));
1754 setRepository(curDir, true, true, &selectedItems);
1755 } else
1756 refreshRepo(true);
1759 void MainImpl::ActFindNext_activated() {
1761 QTextEdit* te = getCurrentTextEdit();
1762 if (!te || textToFind.isEmpty())
1763 return;
1765 bool endOfDocument = false;
1766 while (true) {
1767 if (te->find(textToFind))
1768 return;
1770 if (endOfDocument) {
1771 QMessageBox::warning(this, "Find text - QGit", "Text \"" +
1772 textToFind + "\" not found!", QMessageBox::Ok, 0);
1773 return;
1775 if (QMessageBox::question(this, "Find text - QGit", "End of document "
1776 "reached\n\nDo you want to continue from beginning?", QMessageBox::Yes,
1777 QMessageBox::No | QMessageBox::Escape) == QMessageBox::No)
1778 return;
1780 endOfDocument = true;
1781 te->moveCursor(QTextCursor::Start);
1785 void MainImpl::ActFind_activated() {
1787 QTextEdit* te = getCurrentTextEdit();
1788 if (!te)
1789 return;
1791 QString def(textToFind);
1792 if (te->textCursor().hasSelection())
1793 def = te->textCursor().selectedText().section('\n', 0, 0);
1794 else
1795 te->moveCursor(QTextCursor::Start);
1797 bool ok;
1798 QString str(QInputDialog::getText(this, "Find text - QGit", "Text to find:",
1799 QLineEdit::Normal, def, &ok));
1800 if (!ok || str.isEmpty())
1801 return;
1803 textToFind = str; // update with valid data only
1804 ActFindNext_activated();
1807 void MainImpl::ActHelp_activated() {
1809 QDialog* dlg = new QDialog();
1810 dlg->setAttribute(Qt::WA_DeleteOnClose);
1811 Ui::HelpBase ui;
1812 ui.setupUi(dlg);
1813 ui.textEditHelp->setHtml(QString::fromLatin1(helpInfo)); // defined in help.h
1814 connect(this, SIGNAL(closeAllWindows()), dlg, SLOT(close()));
1815 dlg->show();
1816 dlg->raise();
1819 void MainImpl::ActAbout_activated() {
1821 static const char* aboutMsg =
1822 "<p><b>QGit version " PACKAGE_VERSION "</b></p>"
1823 "<p>Copyright (c) 2005, 2007, 2008 Marco Costalba</p>"
1824 "<p>Use and redistribute under the terms of the<br>"
1825 "<a href=\"http://www.gnu.org/licenses/old-licenses/gpl-2.0.html\">GNU General Public License Version 2</a></p>"
1826 "<p>Contributors:<br>"
1827 "Copyright (c) 2007 Andy Parkins<br>"
1828 "Copyright (c) 2007 Pavel Roskin<br>"
1829 "Copyright (c) 2007 Peter Oberndorfer<br>"
1830 "Copyright (c) 2007 Yaacov Akiba<br>"
1831 "Copyright (c) 2007 James McKaskill<br>"
1832 "Copyright (c) 2008 Jan Hudec<br>"
1833 "Copyright (c) 2008 Paul Gideon Dann<br>"
1834 "Copyright (c) 2008 Oliver Bock</p>"
1835 "<p>This version was compiled against Qt " QT_VERSION_STR "</p>";
1836 QMessageBox::about(this, "About QGit", QString::fromLatin1(aboutMsg));
1839 void MainImpl::closeEvent(QCloseEvent* ce) {
1841 saveCurrentGeometry();
1843 // lastWindowClosed() signal is emitted by close(), after sending
1844 // closeEvent(), so we need to close _here_ all secondary windows before
1845 // the close() method checks for lastWindowClosed flag to avoid missing
1846 // the signal and stay in the main loop forever, because lastWindowClosed()
1847 // signal is connected to qApp->quit()
1849 // note that we cannot rely on setting 'this' parent in secondary windows
1850 // because when close() is called children are still alive and, finally,
1851 // when children are deleted, d'tor do not call close() anymore. So we miss
1852 // lastWindowClosed() signal in this case.
1853 emit closeAllWindows();
1854 hide();
1856 EM_RAISE(exExiting);
1858 git->stop(Git::optSaveCache);
1860 if (!git->findChildren<QProcess*>().isEmpty()) {
1861 // if not all processes have been deleted, there is
1862 // still some run() call not returned somewhere, it is
1863 // not safe to delete run() callers objects now
1864 QTimer::singleShot(100, this, SLOT(ActClose_activated()));
1865 ce->ignore();
1866 return;
1868 emit closeAllTabs();
1869 delete rv;
1870 QWidget::closeEvent(ce);
1873 void MainImpl::ActClose_activated() {
1875 close();
1878 void MainImpl::ActExit_activated() {
1880 qApp->closeAllWindows();