2 Description: qgit main view
4 Author: Marco Costalba (C) 2005-2007
6 Copyright: See COPYING file that comes with this distribution
11 #include <QFileDialog>
12 #include <QInputDialog>
14 #include <QMessageBox>
16 #include <QProgressBar>
22 #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(QString
,int)),
158 statusBar(), SLOT(showMessage(QString
,int)));
160 connect(rv
->tab()->fileList
, SIGNAL(itemDoubleClicked(QListWidgetItem
*)),
161 this, SLOT(fileList_itemDoubleClicked(QListWidgetItem
*)));
163 connect(treeView
, SIGNAL(itemDoubleClicked(QTreeWidgetItem
*, int)),
164 this, SLOT(treeView_doubleClicked(QTreeWidgetItem
*, int)));
166 // use most recent repo as startup dir if it exists and user opted to do so
167 QStringList
recents(settings
.value(REC_REP_KEY
).toStringList());
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
->getRefNames(sha
, Git::RMT_BRANCH
);
481 v
.insert(REV_LOCAL_BRANCHES
, git
->getRefNames(sha
, Git::BRANCH
));
482 v
.insert(CURRENT_BRANCH
, git
->getCurrentBranchName());
483 v
.insert(REV_REMOTE_BRANCHES
, remote_branches
);
484 v
.insert(REV_TAGS
, git
->getRefNames(sha
, Git::TAG
));
485 v
.insert("SHA", sha
);
487 // determine which name the user clicked on
488 ListView
* lv
= rv
->tab()->listViewLog
;
489 v
.insert(SELECTED_NAME
, lv
->selectedRefName());
492 void MainImpl::updateContextActions(SCRef newRevSha
, SCRef newFileName
,
493 bool isDir
, bool found
) {
495 bool pathActionsEnabled
= !newFileName
.isEmpty();
496 bool fileActionsEnabled
= (pathActionsEnabled
&& !isDir
);
498 ActViewFile
->setEnabled(fileActionsEnabled
);
499 ActViewFileNewTab
->setEnabled(fileActionsEnabled
&& firstTab
<FileView
>());
500 ActExternalDiff
->setEnabled(fileActionsEnabled
);
501 ActExternalEditor
->setEnabled(fileActionsEnabled
);
502 ActSaveFile
->setEnabled(fileActionsEnabled
);
503 ActFilterTree
->setEnabled(pathActionsEnabled
|| ActFilterTree
->isChecked());
505 // bool isTag = false;
506 bool isUnApplied
= false;
507 bool isApplied
= false;
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::applyRevisions(SCList remoteRevs
, SCRef remoteRepo
) {
681 // remoteRevs is already sanity checked to contain some possible valid data
683 QDir
dr(curDir
+ QGit::PATCHES_DIR
);
684 dr
.setFilter(QDir::Files
);
685 if (!dr
.exists(remoteRepo
)) {
686 statusBar()->showMessage("Remote repository missing: " + remoteRepo
);
689 if (dr
.exists() && dr
.count()) {
690 statusBar()->showMessage(QString("Please remove stale import directory " + dr
.absolutePath()));
693 bool workDirOnly
, fold
;
694 if (!askApplyPatchParameters(&workDirOnly
, &fold
))
698 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
703 QStringList::const_iterator
it(remoteRevs
.constEnd());
707 statusBar()->showMessage(QString("Importing revision %1 of %2: %3")
708 .arg(++revNum
).arg(remoteRevs
.count()).arg(sha
));
710 // we create patches one by one
711 if (!git
->formatPatch(QStringList(sha
), dr
.absolutePath(), remoteRepo
))
715 if (dr
.count() != 1) {
716 qDebug("ASSERT in on_droppedRevisions: found %i files "
717 "in %s", dr
.count(), qPrintable(dr
.absolutePath()));
720 SCRef
fn(dr
.absoluteFilePath(dr
[0]));
721 bool is_applied
= git
->applyPatchFile(fn
, fold
, Git::optDragDrop
);
724 statusBar()->showMessage(QString("Failed to import revision %1 of %2: %3")
725 .arg(revNum
).arg(remoteRevs
.count()).arg(sha
));
729 } while (it
!= remoteRevs
.constBegin());
731 if (it
== remoteRevs
.constBegin())
732 statusBar()->clearMessage();
734 if (workDirOnly
&& (revNum
> 0))
735 git
->resetCommits(revNum
);
737 dr
.rmdir(dr
.absolutePath()); // 'dr' must be already empty
738 QApplication::restoreOverrideCursor();
742 bool MainImpl::applyPatches(const QStringList
&files
) {
743 bool workDirOnly
, fold
;
744 if (!askApplyPatchParameters(&workDirOnly
, &fold
))
747 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
748 QStringList::const_iterator it
=files
.begin(), end
=files
.end();
749 for(; it
!=end
; ++it
) {
750 statusBar()->showMessage("Applying " + *it
);
751 if (!git
->applyPatchFile(*it
, fold
, Git::optDragDrop
))
752 statusBar()->showMessage("Failed to apply " + *it
);
754 if (it
== end
) statusBar()->clearMessage();
756 if (workDirOnly
&& (files
.count() > 0))
757 git
->resetCommits(files
.count());
759 QApplication::restoreOverrideCursor();
764 void MainImpl::rebase(const QString
&from
, const QString
&to
, const QString
&onto
)
766 bool success
= false;
767 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
768 if (from
.isEmpty()) {
769 success
= git
->run(QString("git checkout -q %1").arg(to
)) &&
770 git
->run(QString("git rebase %1").arg(onto
));
772 success
= git
->run(QString("git rebase --onto %3 %1^ %2").arg(from
, to
, onto
));
775 // TODO say something about rebase failure
778 QApplication::restoreOverrideCursor();
781 void MainImpl::merge(const QStringList
&shas
, const QString
&into
)
783 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
785 if (git
->merge(into
, shas
, &output
)) {
787 statusBar()->showMessage(QString("Successfully merged into %1").arg(into
));
788 ActCommit_activated();
789 } else if (!output
.isEmpty()) {
790 QMessageBox::warning(this, "git merge failed",
791 QString("\n\nGit says: \n\n" + output
));
794 QApplication::restoreOverrideCursor();
797 void MainImpl::moveRef(const QString
&target
, const QString
&toSHA
)
800 if (target
.startsWith("remotes/")) {
801 QString remote
= target
.section("/", 1, 1);
802 QString name
= target
.section("/", 2);
803 cmd
= QString("git push -q %1 %2:%3").arg(remote
, toSHA
, name
);
804 } else if (target
.startsWith("tags/")) {
805 cmd
= QString("git tag -f %1 %2").arg(target
.section("/",1), toSHA
);
806 } else if (!target
.isEmpty()) {
807 const QString
&sha
= git
->getRefSha(target
, Git::BRANCH
, false);
808 if (sha
.isEmpty()) return;
809 const QStringList
&children
= git
->getChildren(sha
);
810 if ((children
.count() == 0 || (children
.count() == 1 && children
.front() == ZERO_SHA
)) && // no children
811 git
->getRefNames(sha
, Git::ANY_REF
).count() == 1 && // last ref name
812 QMessageBox::question(this, "move branch",
813 QString("This is the last reference to this branch.\n"
814 "Do you really want to move '%1'?").arg(target
))
818 if (target
== git
->getCurrentBranchName()) // move current branch
819 cmd
= QString("git checkout -q -B %1 %2").arg(target
, toSHA
);
820 else // move any other local branch
821 cmd
= QString("git branch -f %1 %2").arg(target
, toSHA
);
823 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
824 if (git
->run(cmd
)) refreshRepo(true);
825 QApplication::restoreOverrideCursor();
828 // ******************************* Filter ******************************
830 void MainImpl::newRevsAdded(const FileHistory
* fh
, const QVector
<ShaString
>&) {
832 if (!git
->isMainHistory(fh
))
835 if (ActSearchAndFilter
->isChecked())
836 ActSearchAndFilter_toggled(true); // filter again on new arrived data
838 if (ActSearchAndHighlight
->isChecked())
839 ActSearchAndHighlight_toggled(true); // filter again on new arrived data
841 // first rev could be a StGIT unapplied patch so check more then once
842 if ( !ActCommit
->isEnabled()
843 && (!git
->isNothingToCommit() || git
->isUnknownFiles()))
844 ActCommit_setEnabled(true);
847 void MainImpl::lineEditFilter_returnPressed() {
849 ActSearchAndFilter
->setChecked(true);
852 void MainImpl::ActSearchAndFilter_toggled(bool isOn
) {
854 ActSearchAndHighlight
->setEnabled(!isOn
);
855 ActSearchAndFilter
->setEnabled(false);
856 filterList(isOn
, false); // blocking call
857 ActSearchAndFilter
->setEnabled(true);
860 void MainImpl::ActSearchAndHighlight_toggled(bool isOn
) {
862 ActSearchAndFilter
->setEnabled(!isOn
);
863 ActSearchAndHighlight
->setEnabled(false);
864 filterList(isOn
, true); // blocking call
865 ActSearchAndHighlight
->setEnabled(true);
868 void MainImpl::filterList(bool isOn
, bool onlyHighlight
) {
870 lineEditFilter
->setEnabled(!isOn
);
871 cmbSearch
->setEnabled(!isOn
);
873 SCRef
filter(lineEditFilter
->text());
874 if (filter
.isEmpty())
878 bool patchNeedsUpdate
, isRegExp
;
879 patchNeedsUpdate
= isRegExp
= false;
880 int idx
= cmbSearch
->currentIndex(), colNum
= 0;
885 shortLogRE
.setPattern(filter
);
888 colNum
= LOG_MSG_COL
;
889 longLogRE
.setPattern(filter
);
899 case CS_PATCH_REGEXP
:
900 colNum
= SHA_MAP_COL
;
901 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
902 EM_PROCESS_EVENTS
; // to paint wait cursor
904 git
->getFileFilter(filter
, shaSet
);
906 isRegExp
= (idx
== CS_PATCH_REGEXP
);
907 if (!git
->getPatchFilter(filter
, isRegExp
, shaSet
)) {
908 QApplication::restoreOverrideCursor();
909 ActSearchAndFilter
->toggle();
912 patchNeedsUpdate
= (shaSet
.count() > 0);
914 QApplication::restoreOverrideCursor();
918 patchNeedsUpdate
= (idx
== CS_PATCH
|| idx
== CS_PATCH_REGEXP
);
919 shortLogRE
.setPattern("");
920 longLogRE
.setPattern("");
922 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
924 ListView
* lv
= rv
->tab()->listViewLog
;
925 int matchedCnt
= lv
->filterRows(isOn
, onlyHighlight
, filter
, colNum
, &shaSet
);
927 QApplication::restoreOverrideCursor();
929 emit
updateRevDesc(); // could be highlighted
930 if (patchNeedsUpdate
)
931 emit
highlightPatch(isOn
? filter
: "", isRegExp
);
934 if (isOn
&& !onlyHighlight
)
935 msg
= QString("Found %1 matches. Toggle filter/highlight "
936 "button to remove the filter").arg(matchedCnt
);
937 QApplication::postEvent(rv
, new MessageEvent(msg
)); // deferred message, after update
940 bool MainImpl::event(QEvent
* e
) {
942 BaseEvent
* de
= dynamic_cast<BaseEvent
*>(e
);
944 return QWidget::event(e
);
946 SCRef data
= de
->myData();
949 switch ((EventType
)e
->type()) {
951 QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor
));
953 MainExecErrorEvent
* me
= (MainExecErrorEvent
*)e
;
954 QString
text("An error occurred while executing command:\n\n");
955 text
.append(me
->command() + "\n\nGit says: \n\n" + me
->report());
956 QMessageBox::warning(this, "Error - QGit", text
);
957 QApplication::restoreOverrideCursor(); }
960 statusBar()->showMessage(data
);
967 doFileContexPopup(data
, e
->type());
970 dbp("ASSERT in MainImpl::event unhandled event %1", e
->type());
977 int MainImpl::currentTabType(Domain
** t
) {
980 QWidget
* curPage
= tabWdg
->currentWidget();
981 if (curPage
== rv
->tabPage()) {
985 QList
<PatchView
*>* l
= getTabs
<PatchView
>(curPage
);
986 if (l
->count() > 0) {
992 QList
<FileView
*>* l2
= getTabs
<FileView
>(curPage
);
993 if (l2
->count() > 0) {
999 dbs("ASSERT in tabType file not found");
1005 template<class X
> QList
<X
*>* MainImpl::getTabs(QWidget
* tabPage
) {
1007 QList
<X
*> l
= this->findChildren
<X
*>();
1008 QList
<X
*>* ret
= new QList
<X
*>;
1010 for (int i
= 0; i
< l
.size(); ++i
) {
1011 if (!tabPage
|| l
.at(i
)->tabPage() == tabPage
)
1012 ret
->append(l
.at(i
));
1014 return ret
; // 'ret' must be deleted by caller
1017 template<class X
> X
* MainImpl::firstTab(QWidget
* startPage
) {
1019 int minVal
= 99, firstVal
= 99;
1020 int startPos
= tabWdg
->indexOf(startPage
);
1023 QList
<X
*>* l
= getTabs
<X
>();
1024 for (int i
= 0; i
< l
->size(); ++i
) {
1027 int idx
= tabWdg
->indexOf(d
->tabPage());
1032 if (idx
< firstVal
&& idx
> startPos
) {
1038 return (first
? first
: min
);
1041 void MainImpl::tabWdg_currentChanged(int w
) {
1046 // set correct focus for keyboard browsing
1048 switch (currentTabType(&t
)) {
1050 static_cast<RevsView
*>(t
)->tab()->listViewLog
->setFocus();
1051 emit
closeTabButtonEnabled(false);
1054 static_cast<PatchView
*>(t
)->tab()->textEditDiff
->setFocus();
1055 emit
closeTabButtonEnabled(true);
1058 static_cast<FileView
*>(t
)->tab()->histListView
->setFocus();
1059 emit
closeTabButtonEnabled(true);
1062 dbs("ASSERT in tabWdg_currentChanged: unknown current page");
1067 void MainImpl::setupShortcuts() {
1069 new QShortcut(Qt::Key_I
, this, SLOT(shortCutActivated()));
1070 new QShortcut(Qt::Key_K
, this, SLOT(shortCutActivated()));
1071 new QShortcut(Qt::Key_N
, this, SLOT(shortCutActivated()));
1072 new QShortcut(Qt::Key_Left
, this, SLOT(shortCutActivated()));
1073 new QShortcut(Qt::Key_Right
, this, SLOT(shortCutActivated()));
1075 new QShortcut(Qt::Key_Delete
, this, SLOT(shortCutActivated()));
1076 new QShortcut(Qt::Key_Backspace
, this, SLOT(shortCutActivated()));
1077 new QShortcut(Qt::Key_Space
, this, SLOT(shortCutActivated()));
1079 new QShortcut(Qt::Key_B
, this, SLOT(shortCutActivated()));
1080 new QShortcut(Qt::Key_D
, this, SLOT(shortCutActivated()));
1081 new QShortcut(Qt::Key_F
, this, SLOT(shortCutActivated()));
1082 new QShortcut(Qt::Key_P
, this, SLOT(shortCutActivated()));
1083 new QShortcut(Qt::Key_R
, this, SLOT(shortCutActivated()));
1084 new QShortcut(Qt::Key_U
, this, SLOT(shortCutActivated()));
1086 new QShortcut(Qt::SHIFT
| Qt::Key_Up
, this, SLOT(shortCutActivated()));
1087 new QShortcut(Qt::SHIFT
| Qt::Key_Down
, this, SLOT(shortCutActivated()));
1088 new QShortcut(Qt::CTRL
| Qt::Key_Plus
, this, SLOT(shortCutActivated()));
1089 new QShortcut(Qt::CTRL
| Qt::Key_Minus
, this, SLOT(shortCutActivated()));
1092 void MainImpl::shortCutActivated() {
1094 QShortcut
* se
= dynamic_cast<QShortcut
*>(sender());
1097 #if QT_VERSION >= 0x050000
1098 const QKeySequence
& key
= se
->key();
1100 const int key
= se
->key();
1103 if (key
== Qt::Key_I
) {
1104 rv
->tab()->listViewLog
->on_keyUp();
1106 else if ((key
== Qt::Key_K
) || (key
== Qt::Key_N
)) {
1107 rv
->tab()->listViewLog
->on_keyDown();
1109 else if (key
== (Qt::SHIFT
| Qt::Key_Up
)) {
1112 else if (key
== (Qt::SHIFT
| Qt::Key_Down
)) {
1115 else if (key
== Qt::Key_Left
) {
1116 ActBack_activated();
1118 else if (key
== Qt::Key_Right
) {
1119 ActForward_activated();
1121 else if (key
== (Qt::CTRL
| Qt::Key_Plus
)) {
1122 adjustFontSize(1); //TODO replace magic constant
1124 else if (key
== (Qt::CTRL
| Qt::Key_Minus
)) {
1125 adjustFontSize(-1); //TODO replace magic constant
1127 else if (key
== Qt::Key_U
) {
1128 scrollTextEdit(-18); //TODO replace magic constant
1130 else if (key
== Qt::Key_D
) {
1131 scrollTextEdit(18); //TODO replace magic constant
1133 else if (key
== Qt::Key_Delete
|| key
== Qt::Key_B
|| key
== Qt::Key_Backspace
) {
1134 scrollTextEdit(-1); //TODO replace magic constant
1136 else if (key
== Qt::Key_Space
) {
1139 else if (key
== Qt::Key_R
) {
1140 tabWdg
->setCurrentWidget(rv
->tabPage());
1142 else if (key
== Qt::Key_P
|| key
== Qt::Key_F
) {
1143 QWidget
* cp
= tabWdg
->currentWidget();
1144 Domain
* d
= (key
== Qt::Key_P
)
1145 ? static_cast<Domain
*>(firstTab
<PatchView
>(cp
))
1146 : static_cast<Domain
*>(firstTab
<FileView
>(cp
));
1147 if (d
) tabWdg
->setCurrentWidget(d
->tabPage());
1152 void MainImpl::goMatch(int delta
) {
1154 if (ActSearchAndHighlight
->isChecked())
1155 rv
->tab()->listViewLog
->scrollToNextHighlighted(delta
);
1158 QTextEdit
* MainImpl::getCurrentTextEdit() {
1160 QTextEdit
* te
= NULL
;
1162 switch (currentTabType(&t
)) {
1164 te
= static_cast<RevsView
*>(t
)->tab()->textBrowserDesc
;
1165 if (!te
->isVisible())
1166 te
= static_cast<RevsView
*>(t
)->tab()->textEditDiff
;
1169 te
= static_cast<PatchView
*>(t
)->tab()->textEditDiff
;
1172 te
= static_cast<FileView
*>(t
)->tab()->textEditFile
;
1180 void MainImpl::scrollTextEdit(int delta
) {
1182 QTextEdit
* te
= getCurrentTextEdit();
1186 QScrollBar
* vs
= te
->verticalScrollBar();
1187 if (delta
== 1 || delta
== -1)
1188 vs
->setValue(vs
->value() + delta
* (vs
->pageStep() - vs
->singleStep()));
1190 vs
->setValue(vs
->value() + delta
* vs
->singleStep());
1193 void MainImpl::adjustFontSize(int delta
) {
1194 // font size is changed on a 'per instance' base and only on list views
1196 int ps
= QGit::STD_FONT
.pointSize() + delta
;
1200 QGit::STD_FONT
.setPointSize(ps
);
1203 settings
.setValue(QGit::STD_FNT_KEY
, QGit::STD_FONT
.toString());
1204 emit
changeFont(QGit::STD_FONT
);
1207 void MainImpl::fileNamesLoad(int status
, int value
) {
1211 pbFileNamesLoading
->hide();
1214 pbFileNamesLoading
->setValue(value
);
1217 if (value
> 200) { // don't show for few revisions
1218 pbFileNamesLoading
->reset();
1219 pbFileNamesLoading
->setMaximum(value
);
1220 pbFileNamesLoading
->show();
1226 // ****************************** Menu *********************************
1228 void MainImpl::updateCommitMenu(bool isStGITStack
) {
1230 ActCommit
->setText(isStGITStack
? "Commit St&GIT patch..." : "&Commit...");
1231 ActAmend
->setText(isStGITStack
? "Refresh St&GIT patch..." : "&Amend commit...");
1234 void MainImpl::updateRecentRepoMenu(SCRef newEntry
) {
1236 // update menu of all windows
1237 foreach (QWidget
* widget
, QApplication::topLevelWidgets()) {
1239 MainImpl
* w
= dynamic_cast<MainImpl
*>(widget
);
1241 w
->doUpdateRecentRepoMenu(newEntry
);
1245 void MainImpl::doUpdateRecentRepoMenu(SCRef newEntry
) {
1247 QList
<QAction
*> al(File
->actions());
1248 FOREACH (QList
<QAction
*>, it
, al
) {
1249 if ((*it
)->data().toString().startsWith("RECENT"))
1250 File
->removeAction(*it
);
1253 QStringList
recents(settings
.value(REC_REP_KEY
).toStringList());
1254 int idx
= recents
.indexOf(newEntry
);
1256 recents
.removeAt(idx
);
1258 if (!newEntry
.isEmpty())
1259 recents
.prepend(newEntry
);
1262 QStringList newRecents
;
1263 FOREACH_SL (it
, recents
) {
1264 QAction
* newAction
= File
->addAction(QString::number(idx
++) + " " + *it
);
1265 newAction
->setData(QString("RECENT ") + *it
);
1267 if (idx
> MAX_RECENT_REPOS
)
1270 settings
.setValue(REC_REP_KEY
, newRecents
);
1273 static int cntMenuEntries(const QMenu
& menu
) {
1276 QList
<QAction
*> al(menu
.actions());
1277 FOREACH (QList
<QAction
*>, it
, al
) {
1278 if (!(*it
)->isSeparator())
1284 void MainImpl::doContexPopup(SCRef sha
) {
1286 QMenu
contextMenu(this);
1287 QMenu
contextBrnMenu("More branches...", this);
1288 QMenu
contextTagMenu("More tags...", this);
1289 QMenu
contextRmtMenu("Remote branches...", this);
1291 connect(&contextMenu
, SIGNAL(triggered(QAction
*)), this, SLOT(goRef_triggered(QAction
*)));
1294 int tt
= currentTabType(&t
);
1295 bool isRevPage
= (tt
== TAB_REV
);
1296 bool isPatchPage
= (tt
== TAB_PATCH
);
1297 bool isFilePage
= (tt
== TAB_FILE
);
1299 if (isFilePage
&& ActViewRev
->isEnabled())
1300 contextMenu
.addAction(ActViewRev
);
1302 if (!isPatchPage
&& ActViewDiff
->isEnabled())
1303 contextMenu
.addAction(ActViewDiff
);
1305 if (isRevPage
&& ActViewDiffNewTab
->isEnabled())
1306 contextMenu
.addAction(ActViewDiffNewTab
);
1308 if (!isFilePage
&& ActExternalDiff
->isEnabled())
1309 contextMenu
.addAction(ActExternalDiff
);
1311 if (isFilePage
&& ActExternalEditor
->isEnabled())
1312 contextMenu
.addAction(ActExternalEditor
);
1315 updateRevVariables(sha
);
1317 if (ActCommit
->isEnabled() && (sha
== ZERO_SHA
))
1318 contextMenu
.addAction(ActCommit
);
1319 if (ActCheckout
->isEnabled())
1320 contextMenu
.addAction(ActCheckout
);
1321 if (ActBranch
->isEnabled())
1322 contextMenu
.addAction(ActBranch
);
1323 if (ActTag
->isEnabled())
1324 contextMenu
.addAction(ActTag
);
1325 if (ActDelete
->isEnabled())
1326 contextMenu
.addAction(ActDelete
);
1327 if (ActMailFormatPatch
->isEnabled())
1328 contextMenu
.addAction(ActMailFormatPatch
);
1329 if (ActPush
->isEnabled())
1330 contextMenu
.addAction(ActPush
);
1331 if (ActPop
->isEnabled())
1332 contextMenu
.addAction(ActPop
);
1334 const QStringList
& bn(git
->getAllRefNames(Git::BRANCH
, Git::optOnlyLoaded
));
1335 const QStringList
& rbn(git
->getAllRefNames(Git::RMT_BRANCH
, Git::optOnlyLoaded
));
1336 const QStringList
& tn(git
->getAllRefNames(Git::TAG
, Git::optOnlyLoaded
));
1337 QAction
* act
= NULL
;
1339 FOREACH_SL (it
, rbn
) {
1340 act
= contextRmtMenu
.addAction(*it
);
1341 act
->setData("Ref");
1344 // halve the possible remaining entries for branches and tags
1345 int remainingEntries
= (MAX_MENU_ENTRIES
- cntMenuEntries(contextMenu
));
1346 if (!contextRmtMenu
.isEmpty()) --remainingEntries
;
1347 int tagEntries
= remainingEntries
/ 2;
1348 int brnEntries
= remainingEntries
- tagEntries
;
1350 // display more branches, if there are few tags
1351 if (tagEntries
> tn
.count())
1352 tagEntries
= tn
.count();
1354 // one branch less because of the "More branches..." submenu
1355 if ((bn
.count() > brnEntries
) && tagEntries
)
1359 contextMenu
.addSeparator();
1361 FOREACH_SL (it
, bn
) {
1362 if ( cntMenuEntries(contextMenu
) < MAX_MENU_ENTRIES
- tagEntries
1363 || (*it
== bn
.last() && contextBrnMenu
.isEmpty()))
1364 act
= contextMenu
.addAction(*it
);
1366 act
= contextBrnMenu
.addAction(*it
);
1368 act
->setData("Ref");
1370 if (!contextBrnMenu
.isEmpty())
1371 contextMenu
.addMenu(&contextBrnMenu
);
1373 if (!contextRmtMenu
.isEmpty())
1374 contextMenu
.addMenu(&contextRmtMenu
);
1377 contextMenu
.addSeparator();
1379 FOREACH_SL (it
, tn
) {
1380 if ( cntMenuEntries(contextMenu
) < MAX_MENU_ENTRIES
1381 || (*it
== tn
.last() && contextTagMenu
.isEmpty()))
1382 act
= contextMenu
.addAction(*it
);
1384 act
= contextTagMenu
.addAction(*it
);
1386 act
->setData("Ref");
1388 if (!contextTagMenu
.isEmpty())
1389 contextMenu
.addMenu(&contextTagMenu
);
1391 QPoint p
= QCursor::pos();
1392 p
+= QPoint(10, 10);
1393 contextMenu
.exec(p
);
1395 // remove selected ref name after showing the popup
1396 revision_variables
.remove(SELECTED_NAME
);
1399 void MainImpl::doFileContexPopup(SCRef fileName
, int type
) {
1401 QMenu
contextMenu(this);
1404 int tt
= currentTabType(&t
);
1405 bool isRevPage
= (tt
== TAB_REV
);
1406 bool isPatchPage
= (tt
== TAB_PATCH
);
1407 bool isDir
= treeView
->isDir(fileName
);
1409 if (type
== POPUP_FILE_EV
)
1410 if (!isPatchPage
&& ActViewDiff
->isEnabled())
1411 contextMenu
.addAction(ActViewDiff
);
1413 if (!isDir
&& ActViewFile
->isEnabled())
1414 contextMenu
.addAction(ActViewFile
);
1416 if (!isDir
&& ActViewFileNewTab
->isEnabled())
1417 contextMenu
.addAction(ActViewFileNewTab
);
1419 if (!isRevPage
&& (type
== POPUP_FILE_EV
) && ActViewRev
->isEnabled())
1420 contextMenu
.addAction(ActViewRev
);
1422 if (ActFilterTree
->isEnabled())
1423 contextMenu
.addAction(ActFilterTree
);
1426 if (ActSaveFile
->isEnabled())
1427 contextMenu
.addAction(ActSaveFile
);
1428 if ((type
== POPUP_FILE_EV
) && ActExternalDiff
->isEnabled())
1429 contextMenu
.addAction(ActExternalDiff
);
1430 if ((type
== POPUP_FILE_EV
) && ActExternalEditor
->isEnabled())
1431 contextMenu
.addAction(ActExternalEditor
);
1432 if (ActExternalEditor
->isEnabled())
1433 contextMenu
.addAction(ActExternalEditor
);
1435 contextMenu
.exec(QCursor::pos());
1438 void MainImpl::goRef_triggered(QAction
* act
) {
1440 if (!act
|| act
->data() != "Ref")
1443 SCRef
refSha(git
->getRefSha(act
->iconText()));
1444 rv
->st
.setSha(refSha
);
1448 void MainImpl::ActSplitView_activated() {
1451 switch (currentTabType(&t
)) {
1453 RevsView
* rv
= static_cast<RevsView
*>(t
);
1454 QWidget
* w
= rv
->tab()->fileList
;
1455 QSplitter
* sp
= static_cast<QSplitter
*>(w
->parent());
1456 sp
->setHidden(w
->isVisible()); }
1459 PatchView
* pv
= static_cast<PatchView
*>(t
);
1460 QWidget
* w
= pv
->tab()->textBrowserDesc
;
1461 w
->setHidden(w
->isVisible()); }
1464 FileView
* fv
= static_cast<FileView
*>(t
);
1465 QWidget
* w
= fv
->tab()->histListView
;
1466 w
->setHidden(w
->isVisible()); }
1469 dbs("ASSERT in ActSplitView_activated: unknown current page");
1474 void MainImpl::ActToggleLogsDiff_activated() {
1477 if (currentTabType(&t
) == TAB_REV
) {
1478 RevsView
* rv
= static_cast<RevsView
*>(t
);
1479 rv
->toggleDiffView();
1483 const QString
MainImpl::getRevisionDesc(SCRef sha
) {
1485 bool showHeader
= ActShowDescHeader
->isChecked();
1486 return git
->getDesc(sha
, shortLogRE
, longLogRE
, showHeader
, NULL
);
1489 void MainImpl::ActShowDescHeader_activated() {
1491 // each open tab get his description,
1492 // could be different for each tab
1493 emit
updateRevDesc();
1496 void MainImpl::ActShowTree_toggled(bool b
) {
1502 saveCurrentGeometry();
1507 void MainImpl::ActSaveFile_activated() {
1509 QFileInfo
f(rv
->st
.fileName());
1510 const QString
fileName(QFileDialog::getSaveFileName(this, "Save file as", f
.fileName()));
1511 if (fileName
.isEmpty())
1514 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
1515 QString
fileSha(git
->getFileSha(rv
->st
.fileName(), rv
->st
.sha()));
1516 if (!git
->saveFile(fileSha
, rv
->st
.fileName(), fileName
))
1517 statusBar()->showMessage("Unable to save " + fileName
);
1519 QApplication::restoreOverrideCursor();
1522 void MainImpl::openRecent_triggered(QAction
* act
) {
1524 const QString dataString
= act
->data().toString();
1525 if (!dataString
.startsWith("RECENT"))
1526 // only recent repos entries have "RECENT" in data field
1529 const QString workDir
= dataString
.mid(7);
1530 if (!workDir
.isEmpty()) {
1533 setRepository(workDir
);
1535 statusBar()->showMessage("Directory '" + workDir
+
1536 "' does not seem to exist anymore");
1540 void MainImpl::ActOpenRepo_activated() {
1542 const QString
dirName(QFileDialog::getExistingDirectory(this, "Choose a directory", curDir
));
1543 if (!dirName
.isEmpty()) {
1545 setRepository(d
.absolutePath());
1549 void MainImpl::ActOpenRepoNewWindow_activated() {
1551 const QString
dirName(QFileDialog::getExistingDirectory(this, "Choose a directory", curDir
));
1552 if (!dirName
.isEmpty()) {
1554 MainImpl
* newWin
= new MainImpl(d
.absolutePath());
1559 void MainImpl::refreshRepo(bool b
) {
1561 setRepository(curDir
, true, b
);
1564 void MainImpl::ActRefresh_activated() {
1569 void MainImpl::ActMailFormatPatch_activated() {
1571 QStringList selectedItems
;
1572 rv
->tab()->listViewLog
->getSelectedItems(selectedItems
);
1573 if (selectedItems
.isEmpty()) {
1574 statusBar()->showMessage("At least one selected revision needed");
1577 if (selectedItems
.contains(ZERO_SHA
)) {
1578 statusBar()->showMessage("Unable to save a patch for not committed content");
1582 QString
outDir(settings
.value(PATCH_DIR_KEY
, curDir
).toString());
1583 QString
dirPath(QFileDialog::getExistingDirectory(this,
1584 "Choose destination directory - Save Patch", outDir
));
1585 if (dirPath
.isEmpty())
1589 settings
.setValue(PATCH_DIR_KEY
, d
.absolutePath());
1590 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
1591 git
->formatPatch(selectedItems
, d
.absolutePath());
1592 QApplication::restoreOverrideCursor();
1595 bool MainImpl::askApplyPatchParameters(bool* workDirOnly
, bool* fold
) {
1598 if (!git
->isStGITStack()) {
1599 ret
= QMessageBox::question(this, "Apply Patch",
1600 "Do you want to commit or just to apply changes to "
1601 "working directory?", "&Cancel", "&Working directory", "&Commit", 0, 0);
1602 *workDirOnly
= (ret
== 1);
1605 ret
= QMessageBox::question(this, "Apply Patch", "Do you want to "
1606 "import or fold the patch?", "&Cancel", "&Fold", "&Import", 0, 0);
1607 *workDirOnly
= false;
1613 void MainImpl::ActMailApplyPatch_activated() {
1616 QString
outDir(settings
.value(PATCH_DIR_KEY
, curDir
).toString());
1617 QString
patchName(QFileDialog::getOpenFileName(this,
1618 "Choose the patch file - Apply Patch", outDir
,
1619 "Patches (*.patch *.diff *.eml)\nAll Files (*.*)"));
1620 if (patchName
.isEmpty())
1623 QFileInfo
f(patchName
);
1624 settings
.setValue(PATCH_DIR_KEY
, f
.absolutePath());
1626 bool workDirOnly
, fold
;
1627 if (!askApplyPatchParameters(&workDirOnly
, &fold
))
1630 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
1632 bool ok
= git
->applyPatchFile(f
.absoluteFilePath(), fold
, !Git::optDragDrop
);
1633 if (workDirOnly
&& ok
)
1634 git
->resetCommits(1);
1636 QApplication::restoreOverrideCursor();
1640 void MainImpl::ActCheckWorkDir_toggled(bool b
) {
1642 if (!ActCheckWorkDir
->isEnabled()) // to avoid looping with setChecked()
1645 setFlag(DIFF_INDEX_F
, b
);
1646 bool keepSelection
= (rv
->st
.sha() != ZERO_SHA
);
1647 refreshRepo(keepSelection
);
1650 void MainImpl::ActSettings_activated() {
1652 SettingsImpl
setView(this, git
);
1653 connect(&setView
, SIGNAL(typeWriterFontChanged()),
1654 this, SIGNAL(typeWriterFontChanged()));
1656 connect(&setView
, SIGNAL(flagChanged(uint
)),
1657 this, SIGNAL(flagChanged(uint
)));
1661 // update ActCheckWorkDir if necessary
1662 if (ActCheckWorkDir
->isChecked() != testFlag(DIFF_INDEX_F
))
1663 ActCheckWorkDir
->toggle();
1666 void MainImpl::ActCustomActionSetup_activated() {
1668 CustomActionImpl
* ca
= new CustomActionImpl(); // has Qt::WA_DeleteOnClose
1670 connect(this, SIGNAL(closeAllWindows()), ca
, SLOT(close()));
1671 connect(ca
, SIGNAL(listChanged(const QStringList
&)),
1672 this, SLOT(customActionListChanged(const QStringList
&)));
1677 void MainImpl::customActionListChanged(const QStringList
& list
) {
1679 // update menu of all windows
1680 foreach (QWidget
* widget
, QApplication::topLevelWidgets()) {
1682 MainImpl
* w
= dynamic_cast<MainImpl
*>(widget
);
1684 w
->doUpdateCustomActionMenu(list
);
1688 void MainImpl::doUpdateCustomActionMenu(const QStringList
& list
) {
1690 QAction
* setupAct
= Actions
->actions().first(); // is never empty
1691 Actions
->removeAction(setupAct
);
1693 Actions
->addAction(setupAct
);
1698 Actions
->addSeparator();
1699 FOREACH_SL (it
, list
)
1700 Actions
->addAction(*it
);
1703 void MainImpl::customAction_triggered(QAction
* act
) {
1705 QString actionName
= act
->text();
1706 if (actionName
== "Setup actions...")
1710 QStringList actionsList
= set
.value(ACT_LIST_KEY
).toStringList();
1711 if (!(actionsList
.contains(actionName
) || actionsList
.contains(actionName
.remove(QChar('&'))))) {
1712 dbp("ASSERT in customAction_activated, action %1 not found", actionName
);
1715 QString cmd
= set
.value(ACT_GROUP_KEY
+ actionName
+ ACT_TEXT_KEY
).toString().trimmed();
1716 if (testFlag(ACT_CMD_LINE_F
, ACT_GROUP_KEY
+ actionName
+ ACT_FLAGS_KEY
)) {
1717 // for backwards compatibility: if ACT_CMD_LINE_F is set, insert a dialog token in first line
1718 int pos
= cmd
.indexOf('\n');
1719 if (pos
< 0) pos
= cmd
.length();
1720 cmd
.insert(pos
, " %lineedit:cmdline args%");
1722 updateRevVariables(lineEditSHA
->text());
1723 InputDialog
dlg(cmd
, revision_variables
, "Run custom action: " + actionName
, this);
1724 if (!dlg
.empty() && dlg
.exec() != QDialog::Accepted
) return;
1726 cmd
= dlg
.replace(revision_variables
); // replace variables
1727 } catch (const std::exception
&e
) {
1728 QMessageBox::warning(this, "Custom action command", e
.what());
1735 ConsoleImpl
* c
= new ConsoleImpl(actionName
, git
); // has Qt::WA_DeleteOnClose attribute
1737 connect(this, SIGNAL(typeWriterFontChanged()),
1738 c
, SLOT(typeWriterFontChanged()));
1740 connect(this, SIGNAL(closeAllWindows()), c
, SLOT(close()));
1741 connect(c
, SIGNAL(customAction_exited(const QString
&)),
1742 this, SLOT(customAction_exited(const QString
&)));
1748 void MainImpl::customAction_exited(const QString
& name
) {
1750 const QString
flags(ACT_GROUP_KEY
+ name
+ ACT_FLAGS_KEY
);
1751 if (testFlag(ACT_REFRESH_F
, flags
))
1752 QTimer::singleShot(10, this, SLOT(refreshRepo())); // outside of event handler
1755 void MainImpl::ActCommit_activated() {
1757 CommitImpl
* c
= new CommitImpl(git
, false); // has Qt::WA_DeleteOnClose attribute
1758 connect(this, SIGNAL(closeAllWindows()), c
, SLOT(close()));
1759 connect(c
, SIGNAL(changesCommitted(bool)), this, SLOT(changesCommitted(bool)));
1763 void MainImpl::ActAmend_activated() {
1765 CommitImpl
* c
= new CommitImpl(git
, true); // has Qt::WA_DeleteOnClose attribute
1766 connect(this, SIGNAL(closeAllWindows()), c
, SLOT(close()));
1767 connect(c
, SIGNAL(changesCommitted(bool)), this, SLOT(changesCommitted(bool)));
1771 void MainImpl::changesCommitted(bool ok
) {
1776 statusBar()->showMessage("Failed to commit changes");
1779 void MainImpl::ActCommit_setEnabled(bool b
) {
1781 // pop and push commands fail if there are local changes,
1782 // so in this case we disable ActPop and ActPush
1784 ActPush
->setEnabled(false);
1785 ActPop
->setEnabled(false);
1787 ActCommit
->setEnabled(b
);
1790 /** Checkout supports various operation modes:
1791 * - switching to an existing branch (standard use case)
1792 * - create and checkout a new branch
1793 * - resetting an existing branch to a new sha
1795 void MainImpl::ActCheckout_activated()
1797 QString sha
= lineEditSHA
->text(), rev
= sha
;
1798 const QString
branchKey("local branch name");
1799 QString cmd
= "git checkout -q ";
1801 const QString
&selected_name
= revision_variables
.value(SELECTED_NAME
).toString();
1802 const QString
¤t_branch
= revision_variables
.value(CURRENT_BRANCH
).toString();
1803 const QStringList
&local_branches
= revision_variables
.value(REV_LOCAL_BRANCHES
).toStringList();
1805 if (!selected_name
.isEmpty() &&
1806 local_branches
.contains(selected_name
) &&
1807 selected_name
!= current_branch
) {
1808 // standard branch switching: directly checkout selected branch
1809 rev
= selected_name
;
1811 // ask for (new) local branch name
1812 QString title
= QString("Checkout ");
1813 if (selected_name
.isEmpty()) {
1814 title
+= QString("revision ") + sha
.mid(0, 8);
1816 title
+= QString("branch ") + selected_name
;
1817 rev
= selected_name
;
1819 // merge all reference names into a single list
1820 const QStringList
&rmts
= revision_variables
.value(REV_REMOTE_BRANCHES
).toStringList();
1821 QStringList all_names
;
1822 all_names
<< revision_variables
.value(REV_LOCAL_BRANCHES
).toStringList();
1823 for(QStringList::const_iterator it
=rmts
.begin(), end
=rmts
.end(); it
!=end
; ++it
) {
1824 // drop initial <origin>/ from name
1825 int pos
= it
->indexOf('/'); if (pos
< 0) continue;
1826 all_names
<< it
->mid(pos
+1);
1828 revision_variables
.insert("ALL_NAMES", all_names
);
1830 InputDialog
dlg(QString("%combobox[editable,ref,empty]:%1=$ALL_NAMES%").arg(branchKey
), revision_variables
, title
, this);
1831 if (dlg
.exec() != QDialog::Accepted
) return;
1833 QString branch
= dlg
.value(branchKey
).toString();
1834 if (!branch
.isEmpty()) {
1835 SCRef refsha
= git
->getRefSha(branch
, Git::BRANCH
, true);
1837 rev
= branch
; // checkout existing branch, even if name wasn't directly selected
1838 else if (!refsha
.isEmpty()) {
1839 if (QMessageBox::warning(this, "Checkout " + branch
,
1840 QString("Branch %1 already exists. Reset?").arg(branch
),
1841 QMessageBox::Yes
| QMessageBox::No
, QMessageBox::No
)
1842 != QMessageBox::Yes
)
1845 cmd
.append("-B ").append(branch
); // reset an existing branch
1847 cmd
.append("-b ").append(branch
); // create new local branch
1849 } // if new branch name is empty, checkout detached
1852 cmd
.append(" ").append(rev
);
1853 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
1854 if (!git
->run(cmd
)) statusBar()->showMessage("Failed to checkout " + rev
);
1856 QApplication::restoreOverrideCursor();
1859 void MainImpl::ActBranch_activated() {
1861 doBranchOrTag(false);
1864 void MainImpl::ActTag_activated() {
1866 doBranchOrTag(true);
1869 const QStringList
& stripNames(QStringList
& names
) {
1870 for(QStringList::iterator it
=names
.begin(), end
=names
.end(); it
!=end
; ++it
)
1871 *it
= it
->section('/', -1);
1875 void MainImpl::doBranchOrTag(bool isTag
) {
1876 const QString sha
= lineEditSHA
->text();
1877 QString refDesc
= isTag
? "tag" : "branch";
1878 QString dlgTitle
= "Create " + refDesc
+ " - QGit";
1880 QString dlgDesc
= "%lineedit[ref]:name=$ALL_NAMES%";
1881 InputDialog::VariableMap dlgVars
;
1882 QStringList allNames
= git
->getAllRefNames(Git::BRANCH
| Git::RMT_BRANCH
| Git::TAG
, false);
1883 stripNames(allNames
);
1884 allNames
.removeDuplicates();
1886 dlgVars
.insert("ALL_NAMES", allNames
);
1889 QString
revDesc(rv
->tab()->listViewLog
->currentText(LOG_COL
));
1890 dlgDesc
+= "%textedit:message=$MESSAGE%";
1891 dlgVars
.insert("MESSAGE", revDesc
);
1894 InputDialog
dlg(dlgDesc
, dlgVars
, dlgTitle
, this);
1895 if (dlg
.exec() != QDialog::Accepted
) return;
1896 const QString
& ref
= dlg
.value("name").toString();
1899 if (!git
->getRefSha(ref
, isTag
? Git::TAG
: Git::BRANCH
, false).isEmpty()) {
1900 if (QMessageBox::warning(this, dlgTitle
,
1901 refDesc
+ " name '" + ref
+ "' already exists.\n"
1902 "Force reset?", QMessageBox::Yes
| QMessageBox::No
,
1903 QMessageBox::No
) != QMessageBox::Yes
)
1908 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
1911 const QString
& msg
= dlg
.value("message").toString();
1913 if (!msg
.isEmpty()) cmd
+= "-m \"" + msg
+ "\" ";
1915 cmd
= "git branch ";
1917 if (force
) cmd
+= "-f ";
1918 cmd
+= ref
+ " " + sha
;
1923 statusBar()->showMessage("Failed to create " + refDesc
+ " " + ref
);
1925 QApplication::restoreOverrideCursor();
1928 // put a ref name into a corresponding StringList for tags, remotes, and local branches
1929 typedef QMap
<QString
, QStringList
> RefGroupMap
;
1930 static void groupRef(const QString
& ref
, RefGroupMap
& groups
) {
1931 QString group
, name
;
1932 if (ref
.startsWith("tags/")) { group
= ref
.left(5); name
= ref
.mid(5); }
1933 else if (ref
.startsWith("remotes/")) { group
= ref
.section('/', 1, 1); name
= ref
.section('/', 2); }
1934 else { group
= ""; name
= ref
; }
1935 if (!groups
.contains(group
))
1936 groups
.insert(group
, QStringList());
1937 QStringList
&l
= groups
[group
];
1941 void MainImpl::ActDelete_activated() {
1943 const QString
&selected_name
= revision_variables
.value(SELECTED_NAME
).toString();
1944 const QStringList
&tags
= revision_variables
.value(REV_TAGS
).toStringList();
1945 const QStringList
&rmts
= revision_variables
.value(REV_REMOTE_BRANCHES
).toStringList();
1947 // merge all reference names into a single list
1948 QStringList all_names
;
1949 all_names
<< revision_variables
.value(REV_LOCAL_BRANCHES
).toStringList();
1950 for (QStringList::const_iterator it
=rmts
.begin(), end
=rmts
.end(); it
!=end
; ++it
)
1951 all_names
<< "remotes/" + *it
;
1952 for (QStringList::const_iterator it
=tags
.begin(), end
=tags
.end(); it
!=end
; ++it
)
1953 all_names
<< "tags/" + *it
;
1955 // group selected names by origin and determine which ref names will remain
1956 QMap
<QString
, QStringList
> groups
;
1957 QStringList remaining
= all_names
;
1958 if (!selected_name
.isEmpty()) {
1959 groupRef(selected_name
, groups
);
1960 remaining
.removeOne(selected_name
);
1961 } else if (all_names
.size() == 1) {
1962 const QString
&name
= all_names
.first();
1963 groupRef(name
, groups
);
1964 remaining
.removeOne(name
);
1966 revision_variables
.insert("ALL_NAMES", all_names
);
1967 InputDialog
dlg("%listbox:_refs=$ALL_NAMES%", revision_variables
,
1968 "Delete references - QGit", this);
1969 QListView
*w
= dynamic_cast<QListView
*>(dlg
.widget("_refs"));
1970 w
->setSelectionMode(QAbstractItemView::ExtendedSelection
);
1971 if (dlg
.exec() != QDialog::Accepted
) return;
1973 QModelIndexList selected
= w
->selectionModel()->selectedIndexes();
1974 for (QModelIndexList::const_iterator it
=selected
.begin(), end
=selected
.end(); it
!=end
; ++it
) {
1975 const QString
&name
= it
->data().toString();
1976 groupRef(name
, groups
);
1977 remaining
.removeOne(name
);
1980 if (groups
.empty()) return;
1982 // check whether all refs will be removed
1983 const QString sha
= revision_variables
.value("SHA").toString();
1984 const QStringList
&children
= git
->getChildren(sha
);
1985 if ((children
.count() == 0 || (children
.count() == 1 && children
.front() == ZERO_SHA
)) && // no children
1986 remaining
.count() == 0 && // all refs will be removed
1987 QMessageBox::warning(this, "remove references",
1988 "Do you really want to remove all\nremaining references to this branch?",
1989 QMessageBox::Yes
| QMessageBox::No
, QMessageBox::No
)
1993 // group selected names by origin
1994 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
1996 for (RefGroupMap::const_iterator g
= groups
.begin(), gend
= groups
.end(); g
!= gend
; ++g
) {
1998 if (g
.key() == "") // local branches
1999 cmd
= "git branch -D " + g
.value().join(" ");
2000 else if (g
.key() == "tags/") // tags
2001 cmd
= "git tag -d " + g
.value().join(" ");
2002 else // remote branches
2003 cmd
= "git push -q " + g
.key() + " :" + g
.value().join(" :");
2004 ok
&= git
->run(cmd
);
2007 QApplication::restoreOverrideCursor();
2008 if (!ok
) statusBar()->showMessage("Failed, to remove some refs.");
2011 void MainImpl::ActPush_activated() {
2013 QStringList selectedItems
;
2014 rv
->tab()->listViewLog
->getSelectedItems(selectedItems
);
2015 for (int i
= 0; i
< selectedItems
.count(); i
++) {
2016 if (!git
->checkRef(selectedItems
[i
], Git::UN_APPLIED
)) {
2017 statusBar()->showMessage("Please, select only unapplied patches");
2021 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
2023 for (int i
= 0; i
< selectedItems
.count(); i
++) {
2024 const QString
tmp(QString("Pushing patch %1 of %2")
2025 .arg(i
+1).arg(selectedItems
.count()));
2026 statusBar()->showMessage(tmp
);
2027 SCRef sha
= selectedItems
[selectedItems
.count() - i
- 1];
2028 if (!git
->stgPush(sha
)) {
2029 statusBar()->showMessage("Failed to push patch " + sha
);
2035 statusBar()->clearMessage();
2037 QApplication::restoreOverrideCursor();
2041 void MainImpl::ActPop_activated() {
2043 QStringList selectedItems
;
2044 rv
->tab()->listViewLog
->getSelectedItems(selectedItems
);
2045 if (selectedItems
.count() > 1) {
2046 statusBar()->showMessage("Please, select one revision only");
2049 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
2050 git
->stgPop(selectedItems
[0]);
2051 QApplication::restoreOverrideCursor();
2055 void MainImpl::ActFilterTree_toggled(bool b
) {
2057 if (!ActFilterTree
->isEnabled()) {
2058 dbs("ASSERT ActFilterTree_toggled while disabled");
2062 QStringList selectedItems
;
2063 if (!treeView
->isVisible())
2064 treeView
->updateTree(); // force tree updating
2066 treeView
->getTreeSelectedItems(selectedItems
);
2067 if (selectedItems
.count() == 0) {
2068 dbs("ASSERT tree filter action activated with no selected items");
2071 statusBar()->showMessage("Filter view on " + selectedItems
.join(" "));
2072 setRepository(curDir
, true, true, &selectedItems
);
2077 void MainImpl::ActFindNext_activated() {
2079 QTextEdit
* te
= getCurrentTextEdit();
2080 if (!te
|| textToFind
.isEmpty())
2083 bool endOfDocument
= false;
2085 if (te
->find(textToFind
))
2088 if (endOfDocument
) {
2089 QMessageBox::warning(this, "Find text - QGit", "Text \"" +
2090 textToFind
+ "\" not found!", QMessageBox::Ok
, 0);
2093 if (QMessageBox::question(this, "Find text - QGit", "End of document "
2094 "reached\n\nDo you want to continue from beginning?", QMessageBox::Yes
,
2095 QMessageBox::No
| QMessageBox::Escape
) == QMessageBox::No
)
2098 endOfDocument
= true;
2099 te
->moveCursor(QTextCursor::Start
);
2103 void MainImpl::ActFind_activated() {
2105 QTextEdit
* te
= getCurrentTextEdit();
2109 QString
def(textToFind
);
2110 if (te
->textCursor().hasSelection())
2111 def
= te
->textCursor().selectedText().section('\n', 0, 0);
2113 te
->moveCursor(QTextCursor::Start
);
2116 QString
str(QInputDialog::getText(this, "Find text - QGit", "Text to find:",
2117 QLineEdit::Normal
, def
, &ok
));
2118 if (!ok
|| str
.isEmpty())
2121 textToFind
= str
; // update with valid data only
2122 ActFindNext_activated();
2125 void MainImpl::ActHelp_activated() {
2127 QDialog
* dlg
= new QDialog();
2128 dlg
->setAttribute(Qt::WA_DeleteOnClose
);
2131 ui
.textEditHelp
->setHtml(QString::fromLatin1(helpInfo
)); // defined in help.h
2132 connect(this, SIGNAL(closeAllWindows()), dlg
, SLOT(close()));
2137 void MainImpl::ActMarkDiffToSha_activated()
2139 ListView
* lv
= rv
->tab()->listViewLog
;
2140 lv
->markDiffToSha(lineEditSHA
->text());
2143 void MainImpl::ActAbout_activated() {
2145 static const char* aboutMsg
=
2146 "<p><b>QGit version " PACKAGE_VERSION
"</b></p>"
2147 "<p>Copyright (c) 2005, 2007, 2008 Marco Costalba<br>"
2148 "Copyright (c) 2011-2018 <a href='mailto:tibirna@kde.org'>Cristian Tibirna</a></p>"
2149 "<p>Use and redistribute under the terms of the<br>"
2150 "<a href=\"http://www.gnu.org/licenses/old-licenses/gpl-2.0.html\">GNU General Public License Version 2</a></p>"
2151 "<p>Contributors:<br>"
2153 "<nobr>2007 Andy Parkins,</nobr> "
2154 "<nobr>2007 Pavel Roskin,</nobr> "
2155 "<nobr>2007 Peter Oberndorfer,</nobr> "
2156 "<nobr>2007 Yaacov Akiba,</nobr> "
2157 "<nobr>2007 James McKaskill,</nobr> "
2158 "<nobr>2008 Jan Hudec,</nobr> "
2159 "<nobr>2008 Paul Gideon Dann,</nobr> "
2160 "<nobr>2008 Oliver Bock,</nobr> "
2161 "<nobr>2010 <a href='mailto:cyp561@gmail.com'>Cyp</a>,</nobr> "
2162 "<nobr>2011 <a href='dagenaisj@sonatest.com'>Jean-François Dagenais</a>,</nobr> "
2163 "<nobr>2011 <a href='mailto:pavtih@gmail.com'>Pavel Tikhomirov</a>,</nobr> "
2164 "<nobr>2011 <a href='mailto:tim@klingt.org'>Tim Blechmann</a>,</nobr> "
2165 "<nobr>2014 <a href='mailto:codestruct@posteo.org'>Gregor Mi</a>,</nobr> "
2166 "<nobr>2014 <a href='mailto:sbytnn@gmail.com'>Sbytov N.N</a>,</nobr> "
2167 "<nobr>2015 <a href='mailto:dendy.ua@gmail.com'>Daniel Levin</a>,</nobr> "
2168 "<nobr>2017 <a href='mailto:luigi.toscano@tiscali.it'>Luigi Toscano</a>,</nobr> "
2169 "<nobr>2016 <a href='mailto:hkarel@yandex.ru'>Pavel Karelin</a>,</nobr> "
2170 "<nobr>2016 <a href='mailto:zbitter@redhat.com'>Zane Bitter</a>,</nobr> "
2171 "<nobr>2016 <a href='mailto:rhaschke@techfak.uni-bielefeld.de'>Robert Haschke</a>,</nobr> "
2172 "<nobr>2017 <a href='mailto:wrar@wrar.name'>Andrey Rahmatullin</a>,</nobr> "
2173 "<nobr>2017 <a href='mailto:alex-github@wenlex.nl'>Alex Hermann</a>,</nobr> "
2174 "<nobr>2017 <a href='mailto:shalokshalom@protonmail.ch'>Matthias Schuster</a>,</nobr> "
2175 "<nobr>2017 <a href='mailto:u.joss@calltrade.ch'>Urs Joss</a>,</nobr> "
2176 "<nobr>2017 <a href='mailto:patrick.m.lacasse@gmail.com'>Patrick Lacasse</a>,</nobr> "
2177 "<nobr>2018 <a href='mailto:deveee@gmail.com'>Deve</a>,</nobr> "
2178 "<nobr>2018 <a href='mailto:asturm@gentoo.org'>Andreas Sturmlechner</a>,</nobr> "
2179 "<nobr>2018 <a href='mailto:kde@davidedmundson.co.uk'>David Edmundson</a></nobr>"
2182 "<p>This version was compiled against Qt " QT_VERSION_STR
"</p>";
2183 QMessageBox::about(this, "About QGit", QString::fromLatin1(aboutMsg
));
2186 void MainImpl::closeEvent(QCloseEvent
* ce
) {
2188 saveCurrentGeometry();
2190 // lastWindowClosed() signal is emitted by close(), after sending
2191 // closeEvent(), so we need to close _here_ all secondary windows before
2192 // the close() method checks for lastWindowClosed flag to avoid missing
2193 // the signal and stay in the main loop forever, because lastWindowClosed()
2194 // signal is connected to qApp->quit()
2196 // note that we cannot rely on setting 'this' parent in secondary windows
2197 // because when close() is called children are still alive and, finally,
2198 // when children are deleted, d'tor do not call close() anymore. So we miss
2199 // lastWindowClosed() signal in this case.
2200 emit
closeAllWindows();
2203 EM_RAISE(exExiting
);
2205 git
->stop(Git::optSaveCache
);
2207 if (!git
->findChildren
<QProcess
*>().isEmpty()) {
2208 // if not all processes have been deleted, there is
2209 // still some run() call not returned somewhere, it is
2210 // not safe to delete run() callers objects now
2211 QTimer::singleShot(100, this, SLOT(ActClose_activated()));
2215 emit
closeAllTabs();
2217 QWidget::closeEvent(ce
);
2220 void MainImpl::ActClose_activated() {
2225 void MainImpl::ActExit_activated() {
2227 qApp
->closeAllWindows();