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