2 Description: qgit main view
4 Author: Marco Costalba (C) 2005-2007
6 Copyright: See COPYING file that comes with this distribution
12 #include <QFileDialog>
13 #include <QInputDialog>
15 #include <QMessageBox>
17 #include <QProgressBar>
23 #include <QWheelEvent>
25 #include "config.h" // defines PACKAGE_VERSION
26 #include "consoleimpl.h"
27 #include "commitimpl.h"
29 #include "customactionimpl.h"
35 #include "inputdialog.h"
36 #include "patchview.h"
37 #include "rangeselectimpl.h"
40 #include "settingsimpl.h"
43 #include "ui_revsview.h"
44 #include "ui_fileview.h"
45 #include "ui_patchview.h"
49 MainImpl::MainImpl(SCRef cd
, QWidget
* p
) : QMainWindow(p
) {
51 EM_INIT(exExiting
, "Exiting");
53 setAttribute(Qt::WA_DeleteOnClose
);
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
75 qApp
->installEventFilter(this);
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
88 QString
font(settings
.value(STD_FNT_KEY
).toString());
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
);
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
);
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());
169 if ( recents
.size() >= 1
170 && testFlag(REOPEN_REPO_F
, FLAGS_KEY
)
171 && checkRepo
.exists(recents
.at(0)))
173 startUpDir
= recents
.at(0);
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
219 } else { // try a multiple match search
220 highlightAbbrevSha(lineEditSHA
->text());
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() {
237 if (lineEditSHA
->text().isEmpty())
240 lineEditSHA_returnPressed();
243 // *************************** ExternalDiffViewer ***************************
245 void MainImpl::ActExternalDiff_activated() {
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
);
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());
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
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(' ');
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
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
);
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
)
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()
388 EM_REGISTER(exExiting
);
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
;
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
417 treeView
->setTreeName(n
.prepend('/').section('/', -1, -1));
420 bool ok
= git
->init(curDir
, !refresh
, passedArgs
, overwriteArgs
, &quit
); // blocking call
424 updateCommitMenu(ok
&& git
->isStGITStack());
425 ActCheckWorkDir
->setChecked(testFlag(DIFF_INDEX_F
)); // could be changed in Git::init()
428 updateGlobalActions(true);
430 updateRecentRepoMenu(curDir
);
432 statusBar()->showMessage("Not a git archive");
435 setRepositoryBusy
= false;
436 EM_REMOVE(exExiting
);
438 if (quit
&& !startUpDir
.isEmpty())
442 EM_REMOVE(exExiting
);
444 if (EM_MATCH(i
, exExiting
, "loading repository")) {
448 const QString
info("Exception \'" + EM_DESC(i
) + "\' not "
449 "handled in setRepository...re-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
);
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
;
479 const QStringList
&remote_branches
= git
->getRefName(sha
, Git::RMT_BRANCH
);
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;
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())
547 if (testFlag(OPEN_IN_EDITOR_F
, FLAGS_KEY
)) {
548 if (item
&& ActExternalEditor
->isEnabled())
549 ActExternalEditor
->activate(QAction::Trigger
);
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
);
565 if (item
&& ActViewFile
->isEnabled())
566 ActViewFile
->activate(QAction::Trigger
);
570 void MainImpl::pushButtonCloseTab_clicked() {
573 switch (currentTabType(&t
)) {
578 ActViewDiffNewTab
->setEnabled(ActViewDiff
->isEnabled() && firstTab
<PatchView
>());
582 ActViewFileNewTab
->setEnabled(ActViewFile
->isEnabled() && firstTab
<FileView
>());
585 dbs("ASSERT in pushButtonCloseTab_clicked: unknown current page");
590 void MainImpl::ActRangeDlg_activated() {
593 RangeSelectImpl
rs(this, &args
, false, git
);
594 bool quit
= (rs
.exec() == QDialog::Rejected
); // modal execution
596 const QStringList
l(args
.split(" "));
597 setRepository(curDir
, true, true, &l
, true);
601 void MainImpl::ActViewRev_activated() {
604 if (currentTabType(&t
) == TAB_FILE
) {
608 tabWdg
->setCurrentWidget(rv
->tabPage());
611 void MainImpl::ActViewFile_activated() {
613 openFileTab(firstTab
<FileView
>());
616 void MainImpl::ActViewFileNewTab_activated() {
621 void MainImpl::openFileTab(FileView
* 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());
639 void MainImpl::ActViewDiff_activated() {
642 if (currentTabType(&t
) == TAB_FILE
) {
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() {
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();
669 idx
= (++idx
== tabWdg
->count() ? 0 : idx
);
671 idx
= (--idx
< 0 ? tabWdg
->count() - 1 : idx
);
673 tabWdg
->setCurrentIndex(idx
);
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
697 QDir
dr(curDir
+ QGit::PATCHES_DIR
);
699 const QString
tmp("Please remove stale import directory " + dr
.absolutePath());
700 statusBar()->showMessage(tmp
);
703 bool workDirOnly
, fold
;
704 if (!askApplyPatchParameters(&workDirOnly
, &fold
))
708 rv
->setDropping(true);
709 dr
.setFilter(QDir::Files
);
710 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
715 QStringList::const_iterator
it(remoteRevs
.constEnd());
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
))
728 // we create patches one by one
729 if (!git
->formatPatch(QStringList(sha
), dr
.absolutePath(), remoteRepo
))
733 if (dr
.count() != 1) {
734 qDebug("ASSERT in on_droppedRevisions: found %i files "
735 "in %s", dr
.count(), QGit::PATCHES_DIR
.toLatin1().constData());
738 SCRef
fn(dr
.absoluteFilePath(dr
[0]));
739 bool is_applied
= git
->applyPatchFile(fn
, fold
, Git::optDragDrop
);
744 } while (it
!= remoteRevs
.constBegin());
746 if (it
== remoteRevs
.constBegin())
747 statusBar()->clearMessage();
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);
760 // ******************************* Filter ******************************
762 void MainImpl::newRevsAdded(const FileHistory
* fh
, const QVector
<ShaString
>&) {
764 if (!git
->isMainHistory(fh
))
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())
810 bool patchNeedsUpdate
, isRegExp
;
811 patchNeedsUpdate
= isRegExp
= false;
812 int idx
= cmbSearch
->currentIndex(), colNum
= 0;
817 shortLogRE
.setPattern(filter
);
820 colNum
= LOG_MSG_COL
;
821 longLogRE
.setPattern(filter
);
831 case CS_PATCH_REGEXP
:
832 colNum
= SHA_MAP_COL
;
833 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
834 EM_PROCESS_EVENTS
; // to paint wait cursor
836 git
->getFileFilter(filter
, shaSet
);
838 isRegExp
= (idx
== CS_PATCH_REGEXP
);
839 if (!git
->getPatchFilter(filter
, isRegExp
, shaSet
)) {
840 QApplication::restoreOverrideCursor();
841 ActSearchAndFilter
->toggle();
844 patchNeedsUpdate
= (shaSet
.count() > 0);
846 QApplication::restoreOverrideCursor();
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
);
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
);
876 return QWidget::event(e
);
878 SCRef data
= de
->myData();
881 switch ((EventType
)e
->type()) {
883 QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor
));
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(); }
892 statusBar()->showMessage(data
);
899 doFileContexPopup(data
, e
->type());
902 dbp("ASSERT in MainImpl::event unhandled event %1", e
->type());
909 int MainImpl::currentTabType(Domain
** t
) {
912 QWidget
* curPage
= tabWdg
->currentWidget();
913 if (curPage
== rv
->tabPage()) {
917 QList
<PatchView
*>* l
= getTabs
<PatchView
>(curPage
);
918 if (l
->count() > 0) {
924 QList
<FileView
*>* l2
= getTabs
<FileView
>(curPage
);
925 if (l2
->count() > 0) {
931 dbs("ASSERT in tabType file not found");
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
);
955 QList
<X
*>* l
= getTabs
<X
>();
956 for (int i
= 0; i
< l
->size(); ++i
) {
959 int idx
= tabWdg
->indexOf(d
->tabPage());
964 if (idx
< firstVal
&& idx
> startPos
) {
970 return (first
? first
: min
);
973 void MainImpl::tabWdg_currentChanged(int w
) {
978 // set correct focus for keyboard browsing
980 switch (currentTabType(&t
)) {
982 static_cast<RevsView
*>(t
)->tab()->listViewLog
->setFocus();
983 emit
closeTabButtonEnabled(false);
986 static_cast<PatchView
*>(t
)->tab()->textEditDiff
->setFocus();
987 emit
closeTabButtonEnabled(true);
990 static_cast<FileView
*>(t
)->tab()->histListView
->setFocus();
991 emit
closeTabButtonEnabled(true);
994 dbs("ASSERT in tabWdg_currentChanged: unknown current page");
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());
1029 #if QT_VERSION >= 0x050000
1030 const QKeySequence
& key
= se
->key();
1032 const int key
= se
->key();
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
)) {
1044 else if (key
== (Qt::SHIFT
| Qt::Key_Down
)) {
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
) {
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
;
1094 switch (currentTabType(&t
)) {
1096 te
= static_cast<RevsView
*>(t
)->tab()->textBrowserDesc
;
1097 if (!te
->isVisible())
1098 te
= static_cast<RevsView
*>(t
)->tab()->textEditDiff
;
1101 te
= static_cast<PatchView
*>(t
)->tab()->textEditDiff
;
1104 te
= static_cast<FileView
*>(t
)->tab()->textEditFile
;
1112 void MainImpl::scrollTextEdit(int delta
) {
1114 QTextEdit
* te
= getCurrentTextEdit();
1118 QScrollBar
* vs
= te
->verticalScrollBar();
1119 if (delta
== 1 || delta
== -1)
1120 vs
->setValue(vs
->value() + delta
* (vs
->pageStep() - vs
->singleStep()));
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
;
1132 QGit::STD_FONT
.setPointSize(ps
);
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
) {
1143 pbFileNamesLoading
->hide();
1146 pbFileNamesLoading
->setValue(value
);
1149 if (value
> 200) { // don't show for few revisions
1150 pbFileNamesLoading
->reset();
1151 pbFileNamesLoading
->setMaximum(value
);
1152 pbFileNamesLoading
->show();
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
);
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
);
1185 QStringList
recents(settings
.value(REC_REP_KEY
).toStringList());
1186 int idx
= recents
.indexOf(newEntry
);
1188 recents
.removeAt(idx
);
1190 if (!newEntry
.isEmpty())
1191 recents
.prepend(newEntry
);
1194 QStringList newRecents
;
1195 FOREACH_SL (it
, recents
) {
1196 QAction
* newAction
= File
->addAction(QString::number(idx
++) + " " + *it
);
1197 newAction
->setData(QString("RECENT ") + *it
);
1199 if (idx
> MAX_RECENT_REPOS
)
1202 settings
.setValue(REC_REP_KEY
, newRecents
);
1205 static int cntMenuEntries(const QMenu
& menu
) {
1208 QList
<QAction
*> al(menu
.actions());
1209 FOREACH (QList
<QAction
*>, it
, al
) {
1210 if (!(*it
)->isSeparator())
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
*)));
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
);
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
)
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
);
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
);
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
);
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);
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
);
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")
1375 SCRef
refSha(git
->getRefSha(act
->iconText()));
1376 rv
->st
.setSha(refSha
);
1380 void MainImpl::ActSplitView_activated() {
1383 switch (currentTabType(&t
)) {
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()); }
1391 PatchView
* pv
= static_cast<PatchView
*>(t
);
1392 QWidget
* w
= pv
->tab()->textBrowserDesc
;
1393 w
->setHidden(w
->isVisible()); }
1396 FileView
* fv
= static_cast<FileView
*>(t
);
1397 QWidget
* w
= fv
->tab()->histListView
;
1398 w
->setHidden(w
->isVisible()); }
1401 dbs("ASSERT in ActSplitView_activated: unknown current page");
1406 void MainImpl::ActToggleLogsDiff_activated() {
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
) {
1434 saveCurrentGeometry();
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())
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
1461 const QString workDir
= dataString
.mid(7);
1462 if (!workDir
.isEmpty()) {
1465 setRepository(workDir
);
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()) {
1477 setRepository(d
.absolutePath());
1481 void MainImpl::ActOpenRepoNewWindow_activated() {
1483 const QString
dirName(QFileDialog::getExistingDirectory(this, "Choose a directory", curDir
));
1484 if (!dirName
.isEmpty()) {
1486 MainImpl
* newWin
= new MainImpl(d
.absolutePath());
1491 void MainImpl::refreshRepo(bool b
) {
1493 setRepository(curDir
, true, b
);
1496 void MainImpl::ActRefresh_activated() {
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");
1509 if (selectedItems
.contains(ZERO_SHA
)) {
1510 statusBar()->showMessage("Unable to save a patch for not committed content");
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())
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
) {
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);
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;
1545 void MainImpl::ActMailApplyPatch_activated() {
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())
1555 QFileInfo
f(patchName
);
1556 settings
.setValue(PATCH_DIR_KEY
, f
.absolutePath());
1558 bool workDirOnly
, fold
;
1559 if (!askApplyPatchParameters(&workDirOnly
, &fold
))
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();
1572 void MainImpl::ActCheckWorkDir_toggled(bool b
) {
1574 if (!ActCheckWorkDir
->isEnabled()) // to avoid looping with setChecked()
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
)));
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
&)));
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
);
1616 w
->doUpdateCustomActionMenu(list
);
1620 void MainImpl::doUpdateCustomActionMenu(const QStringList
& list
) {
1622 QAction
* setupAct
= Actions
->actions().first(); // is never empty
1623 Actions
->removeAction(setupAct
);
1625 Actions
->addAction(setupAct
);
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...")
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
);
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;
1658 cmd
= dlg
.replace(revision_variables
); // replace variables
1659 } catch (const std::exception
&e
) {
1660 QMessageBox::warning(this, "Custom action command", e
.what());
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
&)));
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)));
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)));
1703 void MainImpl::changesCommitted(bool ok
) {
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
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
¤t_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
;
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);
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);
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
)
1777 cmd
.append("-B ").append(branch
); // reset an existing branch
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
);
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);
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();
1818 dlgVars
.insert("ALL_NAMES", allNames
);
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();
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
)
1840 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
1843 const QString
& msg
= dlg
.value("message").toString();
1845 if (!msg
.isEmpty()) cmd
+= "-m \"" + msg
+ "\" ";
1847 cmd
= "git branch ";
1849 if (force
) cmd
+= "-f ";
1850 cmd
+= ref
+ " " + sha
;
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
];
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
);
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
));
1911 for (RefGroupMap::const_iterator g
= groups
.begin(), gend
= groups
.end(); g
!= gend
; ++g
) {
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
);
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");
1936 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
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
);
1950 statusBar()->clearMessage();
1952 QApplication::restoreOverrideCursor();
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");
1964 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
1965 git
->stgPop(selectedItems
[0]);
1966 QApplication::restoreOverrideCursor();
1970 void MainImpl::ActFilterTree_toggled(bool b
) {
1972 if (!ActFilterTree
->isEnabled()) {
1973 dbs("ASSERT ActFilterTree_toggled while disabled");
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");
1986 statusBar()->showMessage("Filter view on " + selectedItems
.join(" "));
1987 setRepository(curDir
, true, true, &selectedItems
);
1992 void MainImpl::ActFindNext_activated() {
1994 QTextEdit
* te
= getCurrentTextEdit();
1995 if (!te
|| textToFind
.isEmpty())
1998 bool endOfDocument
= false;
2000 if (te
->find(textToFind
))
2003 if (endOfDocument
) {
2004 QMessageBox::warning(this, "Find text - QGit", "Text \"" +
2005 textToFind
+ "\" not found!", QMessageBox::Ok
, 0);
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
)
2013 endOfDocument
= true;
2014 te
->moveCursor(QTextCursor::Start
);
2018 void MainImpl::ActFind_activated() {
2020 QTextEdit
* te
= getCurrentTextEdit();
2024 QString
def(textToFind
);
2025 if (te
->textCursor().hasSelection())
2026 def
= te
->textCursor().selectedText().section('\n', 0, 0);
2028 te
->moveCursor(QTextCursor::Start
);
2031 QString
str(QInputDialog::getText(this, "Find text - QGit", "Text to find:",
2032 QLineEdit::Normal
, def
, &ok
));
2033 if (!ok
|| str
.isEmpty())
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
);
2046 ui
.textEditHelp
->setHtml(QString::fromLatin1(helpInfo
)); // defined in help.h
2047 connect(this, SIGNAL(closeAllWindows()), dlg
, SLOT(close()));
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 <cyp561@gmail.com><br>"
2075 "Copyright (c) 2011 Jean-François Dagenais <dagenaisj@sonatest.com><br>"
2076 "Copyright (c) 2011 Pavel Tikhomirov <pavtih@gmail.com><br>"
2077 "Copyright (c) 2011-2016 Cristian Tibirna <tibirna@kde.org><br>"
2078 "Copyright (c) 2011 Tim Blechmann <tim@klingt.org><br>"
2079 "Copyright (c) 2014 Gregor Mi <codestruct@posteo.org><br>"
2080 "Copyright (c) 2014 Sbytov N.N <sbytnn@gmail.com><br>"
2081 "Copyright (c) 2015 Daniel Levin <dendy.ua@gmail.com><br>"
2082 "Copyright (c) 2017 Luigi Toscano <luigi.toscano@tiscali.it><br>"
2083 "Copyright (c) 2016 Pavel Karelin <hkarel@yandex.ru><br>"
2084 "Copyright (c) 2016 Zane Bitter <zbitter@redhat.com><br>"
2085 "Copyright (c) 2016 Robert Haschke <rhaschke@techfak.uni-bielefeld.de><br>"
2086 "Copyright (c) 2017 Andrey Rahmatullin $lt;wrar@wrar.name>"
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();
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()));
2122 emit
closeAllTabs();
2124 QWidget::closeEvent(ce
);
2127 void MainImpl::ActClose_activated() {
2132 void MainImpl::ActExit_activated() {
2134 qApp
->closeAllWindows();