FIX action text being potentially altered from assigned value
[qgit4/redivivus.git] / src / mainimpl.cpp
blob73998370b71b8705e94a1ce42272b9d5264adb0c
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 <QMimeData>
17 #include <QProgressBar>
18 #include <QScrollBar>
19 #include <QSettings>
20 #include <QShortcut>
21 #include <QStatusBar>
22 #include <QTimer>
23 #include <QWheelEvent>
24 #include <QTextCodec>
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(const QString&)),
158 statusBar(), SLOT(showMessage(const QString&)));
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->getRefName(sha, Git::RMT_BRANCH);
480 QString curBranch;
481 v.insert(REV_LOCAL_BRANCHES, git->getRefName(sha, Git::BRANCH));
482 v.insert(CURRENT_BRANCH, git->getCurrentBranchName());
483 v.insert(REV_REMOTE_BRANCHES, remote_branches);
484 v.insert(REV_TAGS, git->getRefName(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::revisionsDragged(SCList selRevs) {
682 const QString h(QString::fromLatin1("@") + curDir + '\n');
683 const QString dragRevs = selRevs.join(h).append(h).trimmed();
684 QDrag* drag = new QDrag(this);
685 QMimeData* mimeData = new QMimeData;
686 mimeData->setText(dragRevs);
687 drag->setMimeData(mimeData);
688 drag->start(); // blocking until drop event
691 void MainImpl::revisionsDropped(SCList remoteRevs) {
692 // remoteRevs is already sanity checked to contain some possible valid data
694 if (rv->isDropping()) // avoid reentrancy
695 return;
697 QDir dr(curDir + QGit::PATCHES_DIR);
698 if (dr.exists()) {
699 const QString tmp("Please remove stale import directory " + dr.absolutePath());
700 statusBar()->showMessage(tmp);
701 return;
703 bool workDirOnly, fold;
704 if (!askApplyPatchParameters(&workDirOnly, &fold))
705 return;
707 // ok, let's go
708 rv->setDropping(true);
709 dr.setFilter(QDir::Files);
710 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
711 raise();
712 EM_PROCESS_EVENTS;
714 uint revNum = 0;
715 QStringList::const_iterator it(remoteRevs.constEnd());
716 do {
717 --it;
719 QString tmp("Importing revision %1 of %2");
720 statusBar()->showMessage(tmp.arg(++revNum).arg(remoteRevs.count()));
722 SCRef sha((*it).section('@', 0, 0));
723 SCRef remoteRepo((*it).section('@', 1));
725 if (!dr.exists(remoteRepo))
726 break;
728 // we create patches one by one
729 if (!git->formatPatch(QStringList(sha), dr.absolutePath(), remoteRepo))
730 break;
732 dr.refresh();
733 if (dr.count() != 1) {
734 qDebug("ASSERT in on_droppedRevisions: found %i files "
735 "in %s", dr.count(), QGit::PATCHES_DIR.toLatin1().constData());
736 break;
738 SCRef fn(dr.absoluteFilePath(dr[0]));
739 bool is_applied = git->applyPatchFile(fn, fold, Git::optDragDrop);
740 dr.remove(fn);
741 if (!is_applied)
742 break;
744 } while (it != remoteRevs.constBegin());
746 if (it == remoteRevs.constBegin())
747 statusBar()->clearMessage();
748 else
749 statusBar()->showMessage("Failed to import revision " + QString::number(revNum--));
751 if (workDirOnly && (revNum > 0))
752 git->resetCommits(revNum);
754 dr.rmdir(dr.absolutePath()); // 'dr' must be already empty
755 QApplication::restoreOverrideCursor();
756 rv->setDropping(false);
757 refreshRepo();
760 // ******************************* Filter ******************************
762 void MainImpl::newRevsAdded(const FileHistory* fh, const QVector<ShaString>&) {
764 if (!git->isMainHistory(fh))
765 return;
767 if (ActSearchAndFilter->isChecked())
768 ActSearchAndFilter_toggled(true); // filter again on new arrived data
770 if (ActSearchAndHighlight->isChecked())
771 ActSearchAndHighlight_toggled(true); // filter again on new arrived data
773 // first rev could be a StGIT unapplied patch so check more then once
774 if ( !ActCommit->isEnabled()
775 && (!git->isNothingToCommit() || git->isUnknownFiles()))
776 ActCommit_setEnabled(true);
779 void MainImpl::lineEditFilter_returnPressed() {
781 ActSearchAndFilter->setChecked(true);
784 void MainImpl::ActSearchAndFilter_toggled(bool isOn) {
786 ActSearchAndHighlight->setEnabled(!isOn);
787 ActSearchAndFilter->setEnabled(false);
788 filterList(isOn, false); // blocking call
789 ActSearchAndFilter->setEnabled(true);
792 void MainImpl::ActSearchAndHighlight_toggled(bool isOn) {
794 ActSearchAndFilter->setEnabled(!isOn);
795 ActSearchAndHighlight->setEnabled(false);
796 filterList(isOn, true); // blocking call
797 ActSearchAndHighlight->setEnabled(true);
800 void MainImpl::filterList(bool isOn, bool onlyHighlight) {
802 lineEditFilter->setEnabled(!isOn);
803 cmbSearch->setEnabled(!isOn);
805 SCRef filter(lineEditFilter->text());
806 if (filter.isEmpty())
807 return;
809 ShaSet shaSet;
810 bool patchNeedsUpdate, isRegExp;
811 patchNeedsUpdate = isRegExp = false;
812 int idx = cmbSearch->currentIndex(), colNum = 0;
813 if (isOn) {
814 switch (idx) {
815 case CS_SHORT_LOG:
816 colNum = LOG_COL;
817 shortLogRE.setPattern(filter);
818 break;
819 case CS_LOG_MSG:
820 colNum = LOG_MSG_COL;
821 longLogRE.setPattern(filter);
822 break;
823 case CS_AUTHOR:
824 colNum = AUTH_COL;
825 break;
826 case CS_SHA1:
827 colNum = COMMIT_COL;
828 break;
829 case CS_FILE:
830 case CS_PATCH:
831 case CS_PATCH_REGEXP:
832 colNum = SHA_MAP_COL;
833 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
834 EM_PROCESS_EVENTS; // to paint wait cursor
835 if (idx == CS_FILE)
836 git->getFileFilter(filter, shaSet);
837 else {
838 isRegExp = (idx == CS_PATCH_REGEXP);
839 if (!git->getPatchFilter(filter, isRegExp, shaSet)) {
840 QApplication::restoreOverrideCursor();
841 ActSearchAndFilter->toggle();
842 return;
844 patchNeedsUpdate = (shaSet.count() > 0);
846 QApplication::restoreOverrideCursor();
847 break;
849 } else {
850 patchNeedsUpdate = (idx == CS_PATCH || idx == CS_PATCH_REGEXP);
851 shortLogRE.setPattern("");
852 longLogRE.setPattern("");
854 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
856 ListView* lv = rv->tab()->listViewLog;
857 int matchedCnt = lv->filterRows(isOn, onlyHighlight, filter, colNum, &shaSet);
859 QApplication::restoreOverrideCursor();
861 emit updateRevDesc(); // could be highlighted
862 if (patchNeedsUpdate)
863 emit highlightPatch(isOn ? filter : "", isRegExp);
865 QString msg;
866 if (isOn && !onlyHighlight)
867 msg = QString("Found %1 matches. Toggle filter/highlight "
868 "button to remove the filter").arg(matchedCnt);
869 QApplication::postEvent(rv, new MessageEvent(msg)); // deferred message, after update
872 bool MainImpl::event(QEvent* e) {
874 BaseEvent* de = dynamic_cast<BaseEvent*>(e);
875 if (!de)
876 return QWidget::event(e);
878 SCRef data = de->myData();
879 bool ret = true;
881 switch ((EventType)e->type()) {
882 case ERROR_EV: {
883 QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
884 EM_PROCESS_EVENTS;
885 MainExecErrorEvent* me = (MainExecErrorEvent*)e;
886 QString text("An error occurred while executing command:\n\n");
887 text.append(me->command() + "\n\nGit says: \n\n" + me->report());
888 QMessageBox::warning(this, "Error - QGit", text);
889 QApplication::restoreOverrideCursor(); }
890 break;
891 case MSG_EV:
892 statusBar()->showMessage(data);
893 break;
894 case POPUP_LIST_EV:
895 doContexPopup(data);
896 break;
897 case POPUP_FILE_EV:
898 case POPUP_TREE_EV:
899 doFileContexPopup(data, e->type());
900 break;
901 default:
902 dbp("ASSERT in MainImpl::event unhandled event %1", e->type());
903 ret = false;
904 break;
906 return ret;
909 int MainImpl::currentTabType(Domain** t) {
911 *t = NULL;
912 QWidget* curPage = tabWdg->currentWidget();
913 if (curPage == rv->tabPage()) {
914 *t = rv;
915 return TAB_REV;
917 QList<PatchView*>* l = getTabs<PatchView>(curPage);
918 if (l->count() > 0) {
919 *t = l->first();
920 delete l;
921 return TAB_PATCH;
923 delete l;
924 QList<FileView*>* l2 = getTabs<FileView>(curPage);
925 if (l2->count() > 0) {
926 *t = l2->first();
927 delete l2;
928 return TAB_FILE;
930 if (l2->count() > 0)
931 dbs("ASSERT in tabType file not found");
933 delete l2;
934 return -1;
937 template<class X> QList<X*>* MainImpl::getTabs(QWidget* tabPage) {
939 QList<X*> l = this->findChildren<X*>();
940 QList<X*>* ret = new QList<X*>;
942 for (int i = 0; i < l.size(); ++i) {
943 if (!tabPage || l.at(i)->tabPage() == tabPage)
944 ret->append(l.at(i));
946 return ret; // 'ret' must be deleted by caller
949 template<class X> X* MainImpl::firstTab(QWidget* startPage) {
951 int minVal = 99, firstVal = 99;
952 int startPos = tabWdg->indexOf(startPage);
953 X* min = NULL;
954 X* first = NULL;
955 QList<X*>* l = getTabs<X>();
956 for (int i = 0; i < l->size(); ++i) {
958 X* d = l->at(i);
959 int idx = tabWdg->indexOf(d->tabPage());
960 if (idx < minVal) {
961 minVal = idx;
962 min = d;
964 if (idx < firstVal && idx > startPos) {
965 firstVal = idx;
966 first = d;
969 delete l;
970 return (first ? first : min);
973 void MainImpl::tabWdg_currentChanged(int w) {
975 if (w == -1)
976 return;
978 // set correct focus for keyboard browsing
979 Domain* t;
980 switch (currentTabType(&t)) {
981 case TAB_REV:
982 static_cast<RevsView*>(t)->tab()->listViewLog->setFocus();
983 emit closeTabButtonEnabled(false);
984 break;
985 case TAB_PATCH:
986 static_cast<PatchView*>(t)->tab()->textEditDiff->setFocus();
987 emit closeTabButtonEnabled(true);
988 break;
989 case TAB_FILE:
990 static_cast<FileView*>(t)->tab()->histListView->setFocus();
991 emit closeTabButtonEnabled(true);
992 break;
993 default:
994 dbs("ASSERT in tabWdg_currentChanged: unknown current page");
995 break;
999 void MainImpl::setupShortcuts() {
1001 new QShortcut(Qt::Key_I, this, SLOT(shortCutActivated()));
1002 new QShortcut(Qt::Key_K, this, SLOT(shortCutActivated()));
1003 new QShortcut(Qt::Key_N, this, SLOT(shortCutActivated()));
1004 new QShortcut(Qt::Key_Left, this, SLOT(shortCutActivated()));
1005 new QShortcut(Qt::Key_Right, this, SLOT(shortCutActivated()));
1007 new QShortcut(Qt::Key_Delete, this, SLOT(shortCutActivated()));
1008 new QShortcut(Qt::Key_Backspace, this, SLOT(shortCutActivated()));
1009 new QShortcut(Qt::Key_Space, this, SLOT(shortCutActivated()));
1011 new QShortcut(Qt::Key_B, this, SLOT(shortCutActivated()));
1012 new QShortcut(Qt::Key_D, this, SLOT(shortCutActivated()));
1013 new QShortcut(Qt::Key_F, this, SLOT(shortCutActivated()));
1014 new QShortcut(Qt::Key_P, this, SLOT(shortCutActivated()));
1015 new QShortcut(Qt::Key_R, this, SLOT(shortCutActivated()));
1016 new QShortcut(Qt::Key_U, this, SLOT(shortCutActivated()));
1018 new QShortcut(Qt::SHIFT | Qt::Key_Up, this, SLOT(shortCutActivated()));
1019 new QShortcut(Qt::SHIFT | Qt::Key_Down, this, SLOT(shortCutActivated()));
1020 new QShortcut(Qt::CTRL | Qt::Key_Plus, this, SLOT(shortCutActivated()));
1021 new QShortcut(Qt::CTRL | Qt::Key_Minus, this, SLOT(shortCutActivated()));
1024 void MainImpl::shortCutActivated() {
1026 QShortcut* se = dynamic_cast<QShortcut*>(sender());
1028 if (se) {
1029 #if QT_VERSION >= 0x050000
1030 const QKeySequence& key = se->key();
1031 #else
1032 const int key = se->key();
1033 #endif
1035 if (key == Qt::Key_I) {
1036 rv->tab()->listViewLog->on_keyUp();
1038 else if ((key == Qt::Key_K) || (key == Qt::Key_N)) {
1039 rv->tab()->listViewLog->on_keyDown();
1041 else if (key == (Qt::SHIFT | Qt::Key_Up)) {
1042 goMatch(-1);
1044 else if (key == (Qt::SHIFT | Qt::Key_Down)) {
1045 goMatch(1);
1047 else if (key == Qt::Key_Left) {
1048 ActBack_activated();
1050 else if (key == Qt::Key_Right) {
1051 ActForward_activated();
1053 else if (key == (Qt::CTRL | Qt::Key_Plus)) {
1054 adjustFontSize(1); //TODO replace magic constant
1056 else if (key == (Qt::CTRL | Qt::Key_Minus)) {
1057 adjustFontSize(-1); //TODO replace magic constant
1059 else if (key == Qt::Key_U) {
1060 scrollTextEdit(-18); //TODO replace magic constant
1062 else if (key == Qt::Key_D) {
1063 scrollTextEdit(18); //TODO replace magic constant
1065 else if (key == Qt::Key_Delete || key == Qt::Key_B || key == Qt::Key_Backspace) {
1066 scrollTextEdit(-1); //TODO replace magic constant
1068 else if (key == Qt::Key_Space) {
1069 scrollTextEdit(1);
1071 else if (key == Qt::Key_R) {
1072 tabWdg->setCurrentWidget(rv->tabPage());
1074 else if (key == Qt::Key_P || key == Qt::Key_F) {
1075 QWidget* cp = tabWdg->currentWidget();
1076 Domain* d = (key == Qt::Key_P)
1077 ? static_cast<Domain*>(firstTab<PatchView>(cp))
1078 : static_cast<Domain*>(firstTab<FileView>(cp));
1079 if (d) tabWdg->setCurrentWidget(d->tabPage());
1084 void MainImpl::goMatch(int delta) {
1086 if (ActSearchAndHighlight->isChecked())
1087 rv->tab()->listViewLog->scrollToNextHighlighted(delta);
1090 QTextEdit* MainImpl::getCurrentTextEdit() {
1092 QTextEdit* te = NULL;
1093 Domain* t;
1094 switch (currentTabType(&t)) {
1095 case TAB_REV:
1096 te = static_cast<RevsView*>(t)->tab()->textBrowserDesc;
1097 if (!te->isVisible())
1098 te = static_cast<RevsView*>(t)->tab()->textEditDiff;
1099 break;
1100 case TAB_PATCH:
1101 te = static_cast<PatchView*>(t)->tab()->textEditDiff;
1102 break;
1103 case TAB_FILE:
1104 te = static_cast<FileView*>(t)->tab()->textEditFile;
1105 break;
1106 default:
1107 break;
1109 return te;
1112 void MainImpl::scrollTextEdit(int delta) {
1114 QTextEdit* te = getCurrentTextEdit();
1115 if (!te)
1116 return;
1118 QScrollBar* vs = te->verticalScrollBar();
1119 if (delta == 1 || delta == -1)
1120 vs->setValue(vs->value() + delta * (vs->pageStep() - vs->singleStep()));
1121 else
1122 vs->setValue(vs->value() + delta * vs->singleStep());
1125 void MainImpl::adjustFontSize(int delta) {
1126 // font size is changed on a 'per instance' base and only on list views
1128 int ps = QGit::STD_FONT.pointSize() + delta;
1129 if (ps < 2)
1130 return;
1132 QGit::STD_FONT.setPointSize(ps);
1134 QSettings settings;
1135 settings.setValue(QGit::STD_FNT_KEY, QGit::STD_FONT.toString());
1136 emit changeFont(QGit::STD_FONT);
1139 void MainImpl::fileNamesLoad(int status, int value) {
1141 switch (status) {
1142 case 1: // stop
1143 pbFileNamesLoading->hide();
1144 break;
1145 case 2: // update
1146 pbFileNamesLoading->setValue(value);
1147 break;
1148 case 3: // start
1149 if (value > 200) { // don't show for few revisions
1150 pbFileNamesLoading->reset();
1151 pbFileNamesLoading->setMaximum(value);
1152 pbFileNamesLoading->show();
1154 break;
1158 // ****************************** Menu *********************************
1160 void MainImpl::updateCommitMenu(bool isStGITStack) {
1162 ActCommit->setText(isStGITStack ? "Commit St&GIT patch..." : "&Commit...");
1163 ActAmend->setText(isStGITStack ? "Refresh St&GIT patch..." : "&Amend commit...");
1166 void MainImpl::updateRecentRepoMenu(SCRef newEntry) {
1168 // update menu of all windows
1169 foreach (QWidget* widget, QApplication::topLevelWidgets()) {
1171 MainImpl* w = dynamic_cast<MainImpl*>(widget);
1172 if (w)
1173 w->doUpdateRecentRepoMenu(newEntry);
1177 void MainImpl::doUpdateRecentRepoMenu(SCRef newEntry) {
1179 QList<QAction*> al(File->actions());
1180 FOREACH (QList<QAction*>, it, al) {
1181 if ((*it)->data().toString().startsWith("RECENT"))
1182 File->removeAction(*it);
1184 QSettings settings;
1185 QStringList recents(settings.value(REC_REP_KEY).toStringList());
1186 int idx = recents.indexOf(newEntry);
1187 if (idx != -1)
1188 recents.removeAt(idx);
1190 if (!newEntry.isEmpty())
1191 recents.prepend(newEntry);
1193 idx = 1;
1194 QStringList newRecents;
1195 FOREACH_SL (it, recents) {
1196 QAction* newAction = File->addAction(QString::number(idx++) + " " + *it);
1197 newAction->setData(QString("RECENT ") + *it);
1198 newRecents << *it;
1199 if (idx > MAX_RECENT_REPOS)
1200 break;
1202 settings.setValue(REC_REP_KEY, newRecents);
1205 static int cntMenuEntries(const QMenu& menu) {
1207 int cnt = 0;
1208 QList<QAction*> al(menu.actions());
1209 FOREACH (QList<QAction*>, it, al) {
1210 if (!(*it)->isSeparator())
1211 cnt++;
1213 return cnt;
1216 void MainImpl::doContexPopup(SCRef sha) {
1218 QMenu contextMenu(this);
1219 QMenu contextBrnMenu("More branches...", this);
1220 QMenu contextTagMenu("More tags...", this);
1221 QMenu contextRmtMenu("Remote branches...", this);
1223 connect(&contextMenu, SIGNAL(triggered(QAction*)), this, SLOT(goRef_triggered(QAction*)));
1225 Domain* t;
1226 int tt = currentTabType(&t);
1227 bool isRevPage = (tt == TAB_REV);
1228 bool isPatchPage = (tt == TAB_PATCH);
1229 bool isFilePage = (tt == TAB_FILE);
1231 if (isFilePage && ActViewRev->isEnabled())
1232 contextMenu.addAction(ActViewRev);
1234 if (!isPatchPage && ActViewDiff->isEnabled())
1235 contextMenu.addAction(ActViewDiff);
1237 if (isRevPage && ActViewDiffNewTab->isEnabled())
1238 contextMenu.addAction(ActViewDiffNewTab);
1240 if (!isFilePage && ActExternalDiff->isEnabled())
1241 contextMenu.addAction(ActExternalDiff);
1243 if (isFilePage && ActExternalEditor->isEnabled())
1244 contextMenu.addAction(ActExternalEditor);
1246 if (isRevPage) {
1247 updateRevVariables(sha);
1249 if (ActCommit->isEnabled() && (sha == ZERO_SHA))
1250 contextMenu.addAction(ActCommit);
1251 if (ActCheckout->isEnabled())
1252 contextMenu.addAction(ActCheckout);
1253 if (ActBranch->isEnabled())
1254 contextMenu.addAction(ActBranch);
1255 if (ActTag->isEnabled())
1256 contextMenu.addAction(ActTag);
1257 if (ActDelete->isEnabled())
1258 contextMenu.addAction(ActDelete);
1259 if (ActMailFormatPatch->isEnabled())
1260 contextMenu.addAction(ActMailFormatPatch);
1261 if (ActPush->isEnabled())
1262 contextMenu.addAction(ActPush);
1263 if (ActPop->isEnabled())
1264 contextMenu.addAction(ActPop);
1266 const QStringList& bn(git->getAllRefNames(Git::BRANCH, Git::optOnlyLoaded));
1267 const QStringList& rbn(git->getAllRefNames(Git::RMT_BRANCH, Git::optOnlyLoaded));
1268 const QStringList& tn(git->getAllRefNames(Git::TAG, Git::optOnlyLoaded));
1269 QAction* act = NULL;
1271 FOREACH_SL (it, rbn) {
1272 act = contextRmtMenu.addAction(*it);
1273 act->setData("Ref");
1276 // halve the possible remaining entries for branches and tags
1277 int remainingEntries = (MAX_MENU_ENTRIES - cntMenuEntries(contextMenu));
1278 if (!contextRmtMenu.isEmpty()) --remainingEntries;
1279 int tagEntries = remainingEntries / 2;
1280 int brnEntries = remainingEntries - tagEntries;
1282 // display more branches, if there are few tags
1283 if (tagEntries > tn.count())
1284 tagEntries = tn.count();
1286 // one branch less because of the "More branches..." submenu
1287 if ((bn.count() > brnEntries) && tagEntries)
1288 tagEntries++;
1290 if (!bn.empty())
1291 contextMenu.addSeparator();
1293 FOREACH_SL (it, bn) {
1294 if ( cntMenuEntries(contextMenu) < MAX_MENU_ENTRIES - tagEntries
1295 || (*it == bn.last() && contextBrnMenu.isEmpty()))
1296 act = contextMenu.addAction(*it);
1297 else
1298 act = contextBrnMenu.addAction(*it);
1300 act->setData("Ref");
1302 if (!contextBrnMenu.isEmpty())
1303 contextMenu.addMenu(&contextBrnMenu);
1305 if (!contextRmtMenu.isEmpty())
1306 contextMenu.addMenu(&contextRmtMenu);
1308 if (!tn.empty())
1309 contextMenu.addSeparator();
1311 FOREACH_SL (it, tn) {
1312 if ( cntMenuEntries(contextMenu) < MAX_MENU_ENTRIES
1313 || (*it == tn.last() && contextTagMenu.isEmpty()))
1314 act = contextMenu.addAction(*it);
1315 else
1316 act = contextTagMenu.addAction(*it);
1318 act->setData("Ref");
1320 if (!contextTagMenu.isEmpty())
1321 contextMenu.addMenu(&contextTagMenu);
1323 QPoint p = QCursor::pos();
1324 p += QPoint(10, 10);
1325 contextMenu.exec(p);
1327 // remove selected ref name after showing the popup
1328 revision_variables.remove(SELECTED_NAME);
1331 void MainImpl::doFileContexPopup(SCRef fileName, int type) {
1333 QMenu contextMenu(this);
1335 Domain* t;
1336 int tt = currentTabType(&t);
1337 bool isRevPage = (tt == TAB_REV);
1338 bool isPatchPage = (tt == TAB_PATCH);
1339 bool isDir = treeView->isDir(fileName);
1341 if (type == POPUP_FILE_EV)
1342 if (!isPatchPage && ActViewDiff->isEnabled())
1343 contextMenu.addAction(ActViewDiff);
1345 if (!isDir && ActViewFile->isEnabled())
1346 contextMenu.addAction(ActViewFile);
1348 if (!isDir && ActViewFileNewTab->isEnabled())
1349 contextMenu.addAction(ActViewFileNewTab);
1351 if (!isRevPage && (type == POPUP_FILE_EV) && ActViewRev->isEnabled())
1352 contextMenu.addAction(ActViewRev);
1354 if (ActFilterTree->isEnabled())
1355 contextMenu.addAction(ActFilterTree);
1357 if (!isDir) {
1358 if (ActSaveFile->isEnabled())
1359 contextMenu.addAction(ActSaveFile);
1360 if ((type == POPUP_FILE_EV) && ActExternalDiff->isEnabled())
1361 contextMenu.addAction(ActExternalDiff);
1362 if ((type == POPUP_FILE_EV) && ActExternalEditor->isEnabled())
1363 contextMenu.addAction(ActExternalEditor);
1364 if (ActExternalEditor->isEnabled())
1365 contextMenu.addAction(ActExternalEditor);
1367 contextMenu.exec(QCursor::pos());
1370 void MainImpl::goRef_triggered(QAction* act) {
1372 if (!act || act->data() != "Ref")
1373 return;
1375 SCRef refSha(git->getRefSha(act->iconText()));
1376 rv->st.setSha(refSha);
1377 UPDATE_DOMAIN(rv);
1380 void MainImpl::ActSplitView_activated() {
1382 Domain* t;
1383 switch (currentTabType(&t)) {
1384 case TAB_REV: {
1385 RevsView* rv = static_cast<RevsView*>(t);
1386 QWidget* w = rv->tab()->fileList;
1387 QSplitter* sp = static_cast<QSplitter*>(w->parent());
1388 sp->setHidden(w->isVisible()); }
1389 break;
1390 case TAB_PATCH: {
1391 PatchView* pv = static_cast<PatchView*>(t);
1392 QWidget* w = pv->tab()->textBrowserDesc;
1393 w->setHidden(w->isVisible()); }
1394 break;
1395 case TAB_FILE: {
1396 FileView* fv = static_cast<FileView*>(t);
1397 QWidget* w = fv->tab()->histListView;
1398 w->setHidden(w->isVisible()); }
1399 break;
1400 default:
1401 dbs("ASSERT in ActSplitView_activated: unknown current page");
1402 break;
1406 void MainImpl::ActToggleLogsDiff_activated() {
1408 Domain* t;
1409 if (currentTabType(&t) == TAB_REV) {
1410 RevsView* rv = static_cast<RevsView*>(t);
1411 rv->toggleDiffView();
1415 const QString MainImpl::getRevisionDesc(SCRef sha) {
1417 bool showHeader = ActShowDescHeader->isChecked();
1418 return git->getDesc(sha, shortLogRE, longLogRE, showHeader, NULL);
1421 void MainImpl::ActShowDescHeader_activated() {
1423 // each open tab get his description,
1424 // could be different for each tab
1425 emit updateRevDesc();
1428 void MainImpl::ActShowTree_toggled(bool b) {
1430 if (b) {
1431 treeView->show();
1432 UPDATE_DOMAIN(rv);
1433 } else {
1434 saveCurrentGeometry();
1435 treeView->hide();
1439 void MainImpl::ActSaveFile_activated() {
1441 QFileInfo f(rv->st.fileName());
1442 const QString fileName(QFileDialog::getSaveFileName(this, "Save file as", f.fileName()));
1443 if (fileName.isEmpty())
1444 return;
1446 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1447 QString fileSha(git->getFileSha(rv->st.fileName(), rv->st.sha()));
1448 if (!git->saveFile(fileSha, rv->st.fileName(), fileName))
1449 statusBar()->showMessage("Unable to save " + fileName);
1451 QApplication::restoreOverrideCursor();
1454 void MainImpl::openRecent_triggered(QAction* act) {
1456 const QString dataString = act->data().toString();
1457 if (!dataString.startsWith("RECENT"))
1458 // only recent repos entries have "RECENT" in data field
1459 return;
1461 const QString workDir = dataString.mid(7);
1462 if (!workDir.isEmpty()) {
1463 QDir d(workDir);
1464 if (d.exists())
1465 setRepository(workDir);
1466 else
1467 statusBar()->showMessage("Directory '" + workDir +
1468 "' does not seem to exist anymore");
1472 void MainImpl::ActOpenRepo_activated() {
1474 const QString dirName(QFileDialog::getExistingDirectory(this, "Choose a directory", curDir));
1475 if (!dirName.isEmpty()) {
1476 QDir d(dirName);
1477 setRepository(d.absolutePath());
1481 void MainImpl::ActOpenRepoNewWindow_activated() {
1483 const QString dirName(QFileDialog::getExistingDirectory(this, "Choose a directory", curDir));
1484 if (!dirName.isEmpty()) {
1485 QDir d(dirName);
1486 MainImpl* newWin = new MainImpl(d.absolutePath());
1487 newWin->show();
1491 void MainImpl::refreshRepo(bool b) {
1493 setRepository(curDir, true, b);
1496 void MainImpl::ActRefresh_activated() {
1498 refreshRepo(true);
1501 void MainImpl::ActMailFormatPatch_activated() {
1503 QStringList selectedItems;
1504 rv->tab()->listViewLog->getSelectedItems(selectedItems);
1505 if (selectedItems.isEmpty()) {
1506 statusBar()->showMessage("At least one selected revision needed");
1507 return;
1509 if (selectedItems.contains(ZERO_SHA)) {
1510 statusBar()->showMessage("Unable to save a patch for not committed content");
1511 return;
1513 QSettings settings;
1514 QString outDir(settings.value(PATCH_DIR_KEY, curDir).toString());
1515 QString dirPath(QFileDialog::getExistingDirectory(this,
1516 "Choose destination directory - Save Patch", outDir));
1517 if (dirPath.isEmpty())
1518 return;
1520 QDir d(dirPath);
1521 settings.setValue(PATCH_DIR_KEY, d.absolutePath());
1522 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1523 git->formatPatch(selectedItems, d.absolutePath());
1524 QApplication::restoreOverrideCursor();
1527 bool MainImpl::askApplyPatchParameters(bool* workDirOnly, bool* fold) {
1529 int ret = 0;
1530 if (!git->isStGITStack()) {
1531 ret = QMessageBox::question(this, "Apply Patch",
1532 "Do you want to commit or just to apply changes to "
1533 "working directory?", "&Cancel", "&Working directory", "&Commit", 0, 0);
1534 *workDirOnly = (ret == 1);
1535 *fold = false;
1536 } else {
1537 ret = QMessageBox::question(this, "Apply Patch", "Do you want to "
1538 "import or fold the patch?", "&Cancel", "&Fold", "&Import", 0, 0);
1539 *workDirOnly = false;
1540 *fold = (ret == 1);
1542 return (ret != 0);
1545 void MainImpl::ActMailApplyPatch_activated() {
1547 QSettings settings;
1548 QString outDir(settings.value(PATCH_DIR_KEY, curDir).toString());
1549 QString patchName(QFileDialog::getOpenFileName(this,
1550 "Choose the patch file - Apply Patch", outDir,
1551 "Patches (*.patch *.diff *.eml)\nAll Files (*.*)"));
1552 if (patchName.isEmpty())
1553 return;
1555 QFileInfo f(patchName);
1556 settings.setValue(PATCH_DIR_KEY, f.absolutePath());
1558 bool workDirOnly, fold;
1559 if (!askApplyPatchParameters(&workDirOnly, &fold))
1560 return;
1562 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1564 bool ok = git->applyPatchFile(f.absoluteFilePath(), fold, !Git::optDragDrop);
1565 if (workDirOnly && ok)
1566 git->resetCommits(1);
1568 QApplication::restoreOverrideCursor();
1569 refreshRepo(false);
1572 void MainImpl::ActCheckWorkDir_toggled(bool b) {
1574 if (!ActCheckWorkDir->isEnabled()) // to avoid looping with setChecked()
1575 return;
1577 setFlag(DIFF_INDEX_F, b);
1578 bool keepSelection = (rv->st.sha() != ZERO_SHA);
1579 refreshRepo(keepSelection);
1582 void MainImpl::ActSettings_activated() {
1584 SettingsImpl setView(this, git);
1585 connect(&setView, SIGNAL(typeWriterFontChanged()),
1586 this, SIGNAL(typeWriterFontChanged()));
1588 connect(&setView, SIGNAL(flagChanged(uint)),
1589 this, SIGNAL(flagChanged(uint)));
1591 setView.exec();
1593 // update ActCheckWorkDir if necessary
1594 if (ActCheckWorkDir->isChecked() != testFlag(DIFF_INDEX_F))
1595 ActCheckWorkDir->toggle();
1598 void MainImpl::ActCustomActionSetup_activated() {
1600 CustomActionImpl* ca = new CustomActionImpl(); // has Qt::WA_DeleteOnClose
1602 connect(this, SIGNAL(closeAllWindows()), ca, SLOT(close()));
1603 connect(ca, SIGNAL(listChanged(const QStringList&)),
1604 this, SLOT(customActionListChanged(const QStringList&)));
1606 ca->show();
1609 void MainImpl::customActionListChanged(const QStringList& list) {
1611 // update menu of all windows
1612 foreach (QWidget* widget, QApplication::topLevelWidgets()) {
1614 MainImpl* w = dynamic_cast<MainImpl*>(widget);
1615 if (w)
1616 w->doUpdateCustomActionMenu(list);
1620 void MainImpl::doUpdateCustomActionMenu(const QStringList& list) {
1622 QAction* setupAct = Actions->actions().first(); // is never empty
1623 Actions->removeAction(setupAct);
1624 Actions->clear();
1625 Actions->addAction(setupAct);
1627 if (list.isEmpty())
1628 return;
1630 Actions->addSeparator();
1631 FOREACH_SL (it, list)
1632 Actions->addAction(*it);
1635 void MainImpl::customAction_triggered(QAction* act) {
1637 QString actionName = act->text();
1638 if (actionName == "Setup actions...")
1639 return;
1641 QSettings set;
1642 QStringList actionsList = set.value(ACT_LIST_KEY).toStringList();
1643 if (!(actionsList.contains(actionName) || actionsList.contains(actionName.remove(QChar('&'))))) {
1644 dbp("ASSERT in customAction_activated, action %1 not found", actionName);
1645 return;
1647 QString cmd = set.value(ACT_GROUP_KEY + actionName + ACT_TEXT_KEY).toString().trimmed();
1648 if (testFlag(ACT_CMD_LINE_F, ACT_GROUP_KEY + actionName + ACT_FLAGS_KEY)) {
1649 // for backwards compatibility: if ACT_CMD_LINE_F is set, insert a dialog token in first line
1650 int pos = cmd.indexOf('\n');
1651 if (pos < 0) pos = cmd.length();
1652 cmd.insert(pos, " %lineedit:cmdline args%");
1654 updateRevVariables(lineEditSHA->text());
1655 InputDialog dlg(cmd, revision_variables, "Run custom action: " + actionName, this);
1656 if (!dlg.empty() && dlg.exec() != QDialog::Accepted) return;
1657 try {
1658 cmd = dlg.replace(revision_variables); // replace variables
1659 } catch (const std::exception &e) {
1660 QMessageBox::warning(this, "Custom action command", e.what());
1661 return;
1664 if (cmd.isEmpty())
1665 return;
1667 ConsoleImpl* c = new ConsoleImpl(actionName, git); // has Qt::WA_DeleteOnClose attribute
1669 connect(this, SIGNAL(typeWriterFontChanged()),
1670 c, SLOT(typeWriterFontChanged()));
1672 connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
1673 connect(c, SIGNAL(customAction_exited(const QString&)),
1674 this, SLOT(customAction_exited(const QString&)));
1676 if (c->start(cmd))
1677 c->show();
1680 void MainImpl::customAction_exited(const QString& name) {
1682 const QString flags(ACT_GROUP_KEY + name + ACT_FLAGS_KEY);
1683 if (testFlag(ACT_REFRESH_F, flags))
1684 QTimer::singleShot(10, this, SLOT(refreshRepo())); // outside of event handler
1687 void MainImpl::ActCommit_activated() {
1689 CommitImpl* c = new CommitImpl(git, false); // has Qt::WA_DeleteOnClose attribute
1690 connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
1691 connect(c, SIGNAL(changesCommitted(bool)), this, SLOT(changesCommitted(bool)));
1692 c->show();
1695 void MainImpl::ActAmend_activated() {
1697 CommitImpl* c = new CommitImpl(git, true); // has Qt::WA_DeleteOnClose attribute
1698 connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
1699 connect(c, SIGNAL(changesCommitted(bool)), this, SLOT(changesCommitted(bool)));
1700 c->show();
1703 void MainImpl::changesCommitted(bool ok) {
1705 if (ok)
1706 refreshRepo(false);
1707 else
1708 statusBar()->showMessage("Failed to commit changes");
1711 void MainImpl::ActCommit_setEnabled(bool b) {
1713 // pop and push commands fail if there are local changes,
1714 // so in this case we disable ActPop and ActPush
1715 if (b) {
1716 ActPush->setEnabled(false);
1717 ActPop->setEnabled(false);
1719 ActCommit->setEnabled(b);
1722 /** Checkout supports various operation modes:
1723 * - switching to an existing branch (standard use case)
1724 * - create and checkout a new branch
1725 * - resetting an existing branch to a new sha
1727 void MainImpl::ActCheckout_activated()
1729 QString sha = lineEditSHA->text(), rev = sha;
1730 const QString branchKey("local branch name");
1731 QString cmd = "git checkout -q ";
1733 const QString &selected_name = revision_variables.value(SELECTED_NAME).toString();
1734 const QString &current_branch = revision_variables.value(CURRENT_BRANCH).toString();
1735 const QStringList &local_branches = revision_variables.value(REV_LOCAL_BRANCHES).toStringList();
1737 if (!selected_name.isEmpty() &&
1738 local_branches.contains(selected_name) &&
1739 selected_name != current_branch) {
1740 // standard branch switching: directly checkout selected branch
1741 rev = selected_name;
1742 } else {
1743 // ask for (new) local branch name
1744 QString title = QString("Checkout ");
1745 if (selected_name.isEmpty()) {
1746 title += QString("revision ") + sha.mid(0, 8);
1747 } else {
1748 title += QString("branch ") + selected_name;
1749 rev = selected_name;
1751 // merge all reference names into a single list
1752 const QStringList &rmts = revision_variables.value(REV_REMOTE_BRANCHES).toStringList();
1753 QStringList all_names;
1754 all_names << revision_variables.value(REV_LOCAL_BRANCHES).toStringList();
1755 for(QStringList::const_iterator it=rmts.begin(), end=rmts.end(); it!=end; ++it) {
1756 // drop initial <origin>/ from name
1757 int pos = it->indexOf('/'); if (pos < 0) continue;
1758 all_names << it->mid(pos+1);
1760 revision_variables.insert("ALL_NAMES", all_names);
1762 InputDialog dlg(QString("%combobox[editable,ref,empty]:%1=$ALL_NAMES%").arg(branchKey), revision_variables, title, this);
1763 if (dlg.exec() != QDialog::Accepted) return;
1765 QString branch = dlg.value(branchKey).toString();
1766 if (!branch.isEmpty()) {
1767 SCRef refsha = git->getRefSha(branch, Git::BRANCH, true);
1768 if (refsha == sha)
1769 rev = branch; // checkout existing branch, even if name wasn't directly selected
1770 else if (!refsha.isEmpty()) {
1771 if (QMessageBox::warning(this, "Checkout " + branch,
1772 QString("Branch %1 already exists. Reset?").arg(branch),
1773 QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
1774 != QMessageBox::Yes)
1775 return;
1776 else
1777 cmd.append("-B ").append(branch); // reset an existing branch
1778 } else {
1779 cmd.append("-b ").append(branch); // create new local branch
1781 } // if new branch name is empty, checkout detached
1784 cmd.append(" ").append(rev);
1785 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1786 if (!git->run(cmd)) statusBar()->showMessage("Failed to checkout " + rev);
1787 refreshRepo(true);
1788 QApplication::restoreOverrideCursor();
1791 void MainImpl::ActBranch_activated() {
1793 doBranchOrTag(false);
1796 void MainImpl::ActTag_activated() {
1798 doBranchOrTag(true);
1801 const QStringList& stripNames(QStringList& names) {
1802 for(QStringList::iterator it=names.begin(), end=names.end(); it!=end; ++it)
1803 *it = it->section('/', -1);
1804 return names;
1807 void MainImpl::doBranchOrTag(bool isTag) {
1808 const QString sha = lineEditSHA->text();
1809 QString refDesc = isTag ? "tag" : "branch";
1810 QString dlgTitle = "Create " + refDesc + " - QGit";
1812 QString dlgDesc = "%lineedit[ref]:name=$ALL_NAMES%";
1813 InputDialog::VariableMap dlgVars;
1814 QStringList allNames = git->getAllRefNames(Git::BRANCH | Git::RMT_BRANCH | Git::TAG, false);
1815 stripNames(allNames);
1816 allNames.removeDuplicates();
1817 allNames.sort();
1818 dlgVars.insert("ALL_NAMES", allNames);
1820 if (isTag) {
1821 QString revDesc(rv->tab()->listViewLog->currentText(LOG_COL));
1822 dlgDesc += "%textedit:message=$MESSAGE%";
1823 dlgVars.insert("MESSAGE", revDesc);
1826 InputDialog dlg(dlgDesc, dlgVars, dlgTitle, this);
1827 if (dlg.exec() != QDialog::Accepted) return;
1828 const QString& ref = dlg.value("name").toString();
1830 bool force = false;
1831 if (!git->getRefSha(ref, isTag ? Git::TAG : Git::BRANCH, false).isEmpty()) {
1832 if (QMessageBox::warning(this, dlgTitle,
1833 refDesc + " name '" + ref + "' already exists.\n"
1834 "Force reset?", QMessageBox::Yes | QMessageBox::No,
1835 QMessageBox::No) != QMessageBox::Yes)
1836 return;
1837 force = true;
1840 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1841 QString cmd;
1842 if (isTag) {
1843 const QString& msg = dlg.value("message").toString();
1844 cmd = "git tag ";
1845 if (!msg.isEmpty()) cmd += "-m \"" + msg + "\" ";
1846 } else {
1847 cmd = "git branch ";
1849 if (force) cmd += "-f ";
1850 cmd += ref + " " + sha;
1852 if (git->run(cmd))
1853 refreshRepo(true);
1854 else
1855 statusBar()->showMessage("Failed to create " + refDesc + " " + ref);
1857 QApplication::restoreOverrideCursor();
1860 // put a ref name into a corresponding StringList for tags, remotes, and local branches
1861 typedef QMap<QString, QStringList> RefGroupMap;
1862 static void groupRef(const QString& ref, RefGroupMap& groups) {
1863 QString group, name;
1864 if (ref.startsWith("tags/")) { group = ref.left(5); name = ref.mid(5); }
1865 else if (ref.startsWith("remotes/")) { group = ref.section('/', 1, 1); name = ref.section('/', 2); }
1866 else { group = ""; name = ref; }
1867 if (!groups.contains(group))
1868 groups.insert(group, QStringList());
1869 QStringList &l = groups[group];
1870 l << name;
1873 void MainImpl::ActDelete_activated() {
1875 const QString &selected_name = revision_variables.value(SELECTED_NAME).toString();
1876 const QStringList &tags = revision_variables.value(REV_TAGS).toStringList();
1877 const QStringList &rmts = revision_variables.value(REV_REMOTE_BRANCHES).toStringList();
1879 // merge all reference names into a single list
1880 QStringList all_names;
1881 all_names << revision_variables.value(REV_LOCAL_BRANCHES).toStringList();
1882 for (QStringList::const_iterator it=rmts.begin(), end=rmts.end(); it!=end; ++it)
1883 all_names << "remotes/" + *it;
1884 for (QStringList::const_iterator it=tags.begin(), end=tags.end(); it!=end; ++it)
1885 all_names << "tags/" + *it;
1887 // group selected names by origin
1888 QMap <QString, QStringList> groups;
1889 if (!selected_name.isEmpty()) {
1890 groupRef(selected_name, groups);
1891 } else if (all_names.size() == 1) {
1892 groupRef(all_names.first(), groups);
1893 } else {
1894 revision_variables.insert("ALL_NAMES", all_names);
1895 InputDialog dlg("%listbox:_refs=$ALL_NAMES%", revision_variables,
1896 "Delete references - QGit", this);
1897 QListView *w = dynamic_cast<QListView*>(dlg.widget("_refs"));
1898 w->setSelectionMode(QAbstractItemView::ExtendedSelection);
1899 if (dlg.exec() != QDialog::Accepted) return;
1901 QModelIndexList selected = w->selectionModel()->selectedIndexes();
1902 for (QModelIndexList::const_iterator it=selected.begin(), end=selected.end(); it!=end; ++it)
1903 groupRef(it->data().toString(), groups);
1905 if (groups.empty()) return;
1908 // group selected names by origin
1909 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1910 bool ok = true;
1911 for (RefGroupMap::const_iterator g = groups.begin(), gend = groups.end(); g != gend; ++g) {
1912 QString cmd;
1913 if (g.key() == "") // local branches
1914 cmd = "git branch -d " + g.value().join(" ");
1915 else if (g.key() == "tags/") // tags
1916 cmd = "git tag -d " + g.value().join(" ");
1917 else // remote branches
1918 cmd = "git push -q " + g.key() + " :" + g.value().join(" :");
1919 ok &= git->run(cmd);
1921 refreshRepo(true);
1922 QApplication::restoreOverrideCursor();
1923 if (!ok) statusBar()->showMessage("Failed, to remove some refs.");
1926 void MainImpl::ActPush_activated() {
1928 QStringList selectedItems;
1929 rv->tab()->listViewLog->getSelectedItems(selectedItems);
1930 for (int i = 0; i < selectedItems.count(); i++) {
1931 if (!git->checkRef(selectedItems[i], Git::UN_APPLIED)) {
1932 statusBar()->showMessage("Please, select only unapplied patches");
1933 return;
1936 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1937 bool ok = true;
1938 for (int i = 0; i < selectedItems.count(); i++) {
1939 const QString tmp(QString("Pushing patch %1 of %2")
1940 .arg(i+1).arg(selectedItems.count()));
1941 statusBar()->showMessage(tmp);
1942 SCRef sha = selectedItems[selectedItems.count() - i - 1];
1943 if (!git->stgPush(sha)) {
1944 statusBar()->showMessage("Failed to push patch " + sha);
1945 ok = false;
1946 break;
1949 if (ok)
1950 statusBar()->clearMessage();
1952 QApplication::restoreOverrideCursor();
1953 refreshRepo(false);
1956 void MainImpl::ActPop_activated() {
1958 QStringList selectedItems;
1959 rv->tab()->listViewLog->getSelectedItems(selectedItems);
1960 if (selectedItems.count() > 1) {
1961 statusBar()->showMessage("Please, select one revision only");
1962 return;
1964 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1965 git->stgPop(selectedItems[0]);
1966 QApplication::restoreOverrideCursor();
1967 refreshRepo(false);
1970 void MainImpl::ActFilterTree_toggled(bool b) {
1972 if (!ActFilterTree->isEnabled()) {
1973 dbs("ASSERT ActFilterTree_toggled while disabled");
1974 return;
1976 if (b) {
1977 QStringList selectedItems;
1978 if (!treeView->isVisible())
1979 treeView->updateTree(); // force tree updating
1981 treeView->getTreeSelectedItems(selectedItems);
1982 if (selectedItems.count() == 0) {
1983 dbs("ASSERT tree filter action activated with no selected items");
1984 return;
1986 statusBar()->showMessage("Filter view on " + selectedItems.join(" "));
1987 setRepository(curDir, true, true, &selectedItems);
1988 } else
1989 refreshRepo(true);
1992 void MainImpl::ActFindNext_activated() {
1994 QTextEdit* te = getCurrentTextEdit();
1995 if (!te || textToFind.isEmpty())
1996 return;
1998 bool endOfDocument = false;
1999 while (true) {
2000 if (te->find(textToFind))
2001 return;
2003 if (endOfDocument) {
2004 QMessageBox::warning(this, "Find text - QGit", "Text \"" +
2005 textToFind + "\" not found!", QMessageBox::Ok, 0);
2006 return;
2008 if (QMessageBox::question(this, "Find text - QGit", "End of document "
2009 "reached\n\nDo you want to continue from beginning?", QMessageBox::Yes,
2010 QMessageBox::No | QMessageBox::Escape) == QMessageBox::No)
2011 return;
2013 endOfDocument = true;
2014 te->moveCursor(QTextCursor::Start);
2018 void MainImpl::ActFind_activated() {
2020 QTextEdit* te = getCurrentTextEdit();
2021 if (!te)
2022 return;
2024 QString def(textToFind);
2025 if (te->textCursor().hasSelection())
2026 def = te->textCursor().selectedText().section('\n', 0, 0);
2027 else
2028 te->moveCursor(QTextCursor::Start);
2030 bool ok;
2031 QString str(QInputDialog::getText(this, "Find text - QGit", "Text to find:",
2032 QLineEdit::Normal, def, &ok));
2033 if (!ok || str.isEmpty())
2034 return;
2036 textToFind = str; // update with valid data only
2037 ActFindNext_activated();
2040 void MainImpl::ActHelp_activated() {
2042 QDialog* dlg = new QDialog();
2043 dlg->setAttribute(Qt::WA_DeleteOnClose);
2044 Ui::HelpBase ui;
2045 ui.setupUi(dlg);
2046 ui.textEditHelp->setHtml(QString::fromLatin1(helpInfo)); // defined in help.h
2047 connect(this, SIGNAL(closeAllWindows()), dlg, SLOT(close()));
2048 dlg->show();
2049 dlg->raise();
2052 void MainImpl::ActMarkDiffToSha_activated()
2054 ListView* lv = rv->tab()->listViewLog;
2055 lv->markDiffToSha(lineEditSHA->text());
2058 void MainImpl::ActAbout_activated() {
2060 static const char* aboutMsg =
2061 "<p><b>QGit version " PACKAGE_VERSION "</b></p>"
2062 "<p>Copyright (c) 2005, 2007, 2008 Marco Costalba</p>"
2063 "<p>Use and redistribute under the terms of the<br>"
2064 "<a href=\"http://www.gnu.org/licenses/old-licenses/gpl-2.0.html\">GNU General Public License Version 2</a></p>"
2065 "<p>Contributors:<br>"
2066 "Copyright (c) 2007 Andy Parkins<br>"
2067 "Copyright (c) 2007 Pavel Roskin<br>"
2068 "Copyright (c) 2007 Peter Oberndorfer<br>"
2069 "Copyright (c) 2007 Yaacov Akiba<br>"
2070 "Copyright (c) 2007 James McKaskill<br>"
2071 "Copyright (c) 2008 Jan Hudec<br>"
2072 "Copyright (c) 2008 Paul Gideon Dann<br>"
2073 "Copyright (c) 2008 Oliver Bock<br>"
2074 "Copyright (c) 2010 Cyp &lt;cyp561@gmail.com&gt;<br>"
2075 "Copyright (c) 2011 Jean-Fran&ccedil;ois Dagenais &lt;dagenaisj@sonatest.com&gt;<br>"
2076 "Copyright (c) 2011 Pavel Tikhomirov &lt;pavtih@gmail.com&gt;<br>"
2077 "Copyright (c) 2011-2016 Cristian Tibirna &lt;tibirna@kde.org&gt;<br>"
2078 "Copyright (c) 2011 Tim Blechmann &lt;tim@klingt.org&gt;<br>"
2079 "Copyright (c) 2014 Gregor Mi &lt;codestruct@posteo.org&gt;<br>"
2080 "Copyright (c) 2014 Sbytov N.N &lt;sbytnn@gmail.com&gt;<br>"
2081 "Copyright (c) 2015 Daniel Levin &lt;dendy.ua@gmail.com&gt;<br>"
2082 "Copyright (c) 2017 Luigi Toscano &lt;luigi.toscano@tiscali.it&gt;<br>"
2083 "Copyright (c) 2016 Pavel Karelin &lt;hkarel@yandex.ru&gt;<br>"
2084 "Copyright (c) 2016 Zane Bitter &lt;zbitter@redhat.com&gt;<br>"
2085 "Copyright (c) 2016 Robert Haschke &lt;rhaschke@techfak.uni-bielefeld.de&gt;<br>"
2086 "Copyright (c) 2017 Andrey Rahmatullin $lt;wrar@wrar.name&gt;"
2087 "</p>"
2089 "<p>This version was compiled against Qt " QT_VERSION_STR "</p>";
2090 QMessageBox::about(this, "About QGit", QString::fromLatin1(aboutMsg));
2093 void MainImpl::closeEvent(QCloseEvent* ce) {
2095 saveCurrentGeometry();
2097 // lastWindowClosed() signal is emitted by close(), after sending
2098 // closeEvent(), so we need to close _here_ all secondary windows before
2099 // the close() method checks for lastWindowClosed flag to avoid missing
2100 // the signal and stay in the main loop forever, because lastWindowClosed()
2101 // signal is connected to qApp->quit()
2103 // note that we cannot rely on setting 'this' parent in secondary windows
2104 // because when close() is called children are still alive and, finally,
2105 // when children are deleted, d'tor do not call close() anymore. So we miss
2106 // lastWindowClosed() signal in this case.
2107 emit closeAllWindows();
2108 hide();
2110 EM_RAISE(exExiting);
2112 git->stop(Git::optSaveCache);
2114 if (!git->findChildren<QProcess*>().isEmpty()) {
2115 // if not all processes have been deleted, there is
2116 // still some run() call not returned somewhere, it is
2117 // not safe to delete run() callers objects now
2118 QTimer::singleShot(100, this, SLOT(ActClose_activated()));
2119 ce->ignore();
2120 return;
2122 emit closeAllTabs();
2123 delete rv;
2124 QWidget::closeEvent(ce);
2127 void MainImpl::ActClose_activated() {
2129 close();
2132 void MainImpl::ActExit_activated() {
2134 qApp->closeAllWindows();