2 Description: changes commit dialog
4 Author: Marco Costalba (C) 2005-2007
6 Copyright: See COPYING file that comes with this distribution
13 #include <QMessageBox>
14 #include <QInputDialog>
18 #include "exceptionmanager.h"
21 #include "settingsimpl.h"
22 #include "commitimpl.h"
26 QString
CommitImpl::lastMsgBeforeError
;
28 CommitImpl::CommitImpl(Git
* g
, bool amend
) : git(g
) {
31 setAttribute(Qt::WA_DeleteOnClose
);
33 textEditMsg
->setFont(TYPE_WRITER_FONT
);
35 QVector
<QSplitter
*> v(1, splitter
);
36 QGit::restoreGeometrySetting(CMT_GEOM_KEY
, this, &v
);
39 QString
templ(settings
.value(CMT_TEMPL_KEY
, CMT_TEMPL_DEF
).toString());
43 readFromFile(templ
, msg
);
46 const RevFile
* f
= git
->getFiles(ZERO_SHA
);
47 for (int i
= 0; f
&& i
< f
->count(); ++i
) { // in case of amend f could be null
49 bool inIndex
= f
->statusCmp(i
, RevFile::IN_INDEX
);
50 bool isNew
= (f
->statusCmp(i
, RevFile::NEW
) || f
->statusCmp(i
, RevFile::UNKNOWN
));
51 QColor myColor
= QPalette().color(QPalette::WindowText
);
53 myColor
= Qt::darkGreen
;
54 else if (f
->statusCmp(i
, RevFile::DELETED
))
57 QTreeWidgetItem
* item
= new QTreeWidgetItem(treeWidgetFiles
);
58 item
->setText(0, git
->filePath(*f
, i
));
59 item
->setText(1, inIndex
? "Updated in index" : "Not updated in index");
60 item
->setCheckState(0, inIndex
|| !isNew
? Qt::Checked
: Qt::Unchecked
);
61 item
->setForeground(0, myColor
);
63 treeWidgetFiles
->resizeColumnToContents(0);
65 // compute cursor offsets. Take advantage of fixed width font
66 textEditMsg
->setPlainText("\nx\nx"); // cursor doesn't move on empty text
67 textEditMsg
->moveCursor(QTextCursor::Start
);
68 textEditMsg
->verticalScrollBar()->setValue(0);
69 textEditMsg
->horizontalScrollBar()->setValue(0);
70 int y0
= textEditMsg
->cursorRect().y();
71 int x0
= textEditMsg
->cursorRect().x();
72 textEditMsg
->moveCursor(QTextCursor::Down
);
73 textEditMsg
->moveCursor(QTextCursor::Right
);
74 textEditMsg
->verticalScrollBar()->setValue(0);
75 int y1
= textEditMsg
->cursorRect().y();
76 int x1
= textEditMsg
->cursorRect().x();
79 textEditMsg
->moveCursor(QTextCursor::Start
);
80 textEditMsg_cursorPositionChanged();
82 if (lastMsgBeforeError
.isEmpty()) {
83 // setup textEditMsg with old commit message to be amended
86 status
= git
->getLastCommitMsg();
88 // setup textEditMsg with default value if user opted to do so (default)
89 if (testFlag(USE_CMT_MSG_F
, FLAGS_KEY
))
90 status
+= git
->getNewCommitMsg();
92 msg
= status
.trimmed();
94 msg
= lastMsgBeforeError
;
96 textEditMsg
->setPlainText(msg
);
97 textEditMsg
->setFocus();
99 // if message is not changed we avoid calling refresh
100 // to change patch name in stgCommit()
103 // setup button functions
105 if (git
->isStGITStack()) {
106 pushButtonOk
->setText("&Add to top");
107 pushButtonOk
->setShortcut(QKeySequence("Alt+A"));
108 pushButtonOk
->setToolTip("Refresh top stack patch");
110 pushButtonOk
->setText("&Amend");
111 pushButtonOk
->setShortcut(QKeySequence("Alt+A"));
112 pushButtonOk
->setToolTip("Amend latest commit");
114 connect(pushButtonOk
, SIGNAL(clicked()),
115 this, SLOT(pushButtonAmend_clicked()));
117 if (git
->isStGITStack()) {
118 pushButtonOk
->setText("&New patch");
119 pushButtonOk
->setShortcut(QKeySequence("Alt+N"));
120 pushButtonOk
->setToolTip("Create a new patch");
122 connect(pushButtonOk
, SIGNAL(clicked()),
123 this, SLOT(pushButtonCommit_clicked()));
125 connect(treeWidgetFiles
, SIGNAL(customContextMenuRequested(const QPoint
&)),
126 this, SLOT(contextMenuPopup(const QPoint
&)));
127 connect(textEditMsg
, SIGNAL(cursorPositionChanged()),
128 this, SLOT(textEditMsg_cursorPositionChanged()));
130 textEditMsg
->installEventFilter(this);
133 void CommitImpl::closeEvent(QCloseEvent
*) {
135 QVector
<QSplitter
*> v(1, splitter
);
136 QGit::saveGeometrySetting(CMT_GEOM_KEY
, this, &v
);
139 void CommitImpl::contextMenuPopup(const QPoint
& pos
) {
141 QMenu
* contextMenu
= new QMenu(this);
142 QAction
* a
= contextMenu
->addAction("Select All");
143 connect(a
, SIGNAL(triggered()), this, SLOT(checkAll()));
144 a
= contextMenu
->addAction("Unselect All");
145 connect(a
, SIGNAL(triggered()), this, SLOT(unCheckAll()));
146 contextMenu
->popup(mapToGlobal(pos
));
149 void CommitImpl::checkAll() { checkUncheck(true); }
150 void CommitImpl::unCheckAll() { checkUncheck(false); }
152 void CommitImpl::checkUncheck(bool checkAll
) {
154 QTreeWidgetItemIterator
it(treeWidgetFiles
);
156 (*it
)->setCheckState(0, checkAll
? Qt::Checked
: Qt::Unchecked
);
161 bool CommitImpl::getFiles(SList selFiles
) {
163 // check for files to commit
165 QTreeWidgetItemIterator
it(treeWidgetFiles
);
167 if ((*it
)->checkState(0) == Qt::Checked
)
168 selFiles
.append((*it
)->text(0));
172 return !selFiles
.isEmpty();
175 void CommitImpl::warnNoFiles() {
177 QMessageBox::warning(this, "Commit changes - QGit",
178 "Sorry, no files are selected for updating.",
179 QMessageBox::Ok
, QMessageBox::NoButton
);
182 bool CommitImpl::checkFiles(SList selFiles
) {
184 if (getFiles(selFiles
))
191 bool CommitImpl::checkMsg(QString
& msg
) {
193 msg
= textEditMsg
->toPlainText();
194 msg
.remove(QRegExp("(^|\\n)\\s*#[^\\n]*")); // strip comments
195 msg
.replace(QRegExp("[ \\t\\r\\f\\v]+\\n"), "\n"); // strip line trailing cruft
198 QMessageBox::warning(this, "Commit changes - QGit",
199 "Sorry, I don't want an empty message.",
200 QMessageBox::Ok
, QMessageBox::NoButton
);
203 // split subject from message body
204 QString
subj(msg
.section('\n', 0, 0, QString::SectionIncludeTrailingSep
));
205 QString
body(msg
.section('\n', 1).trimmed());
206 msg
= subj
+ '\n' + body
+ '\n';
210 bool CommitImpl::checkPatchName(QString
& patchName
) {
213 patchName
= patchName
.simplified();
214 patchName
.replace(' ', "_");
215 patchName
= QInputDialog::getText(this, "Create new patch - QGit", "Enter patch name:",
216 QLineEdit::Normal
, patchName
, &ok
);
217 if (!ok
|| patchName
.isEmpty())
220 QString
tmp(patchName
.trimmed());
221 if (patchName
!= tmp
.remove(' '))
222 QMessageBox::warning(this, "Create new patch - QGit", "Sorry, control "
223 "characters or spaces\n are not allowed in patch name.");
225 else if (git
->isPatchName(patchName
))
226 QMessageBox::warning(this, "Create new patch - QGit", "Sorry, patch name "
227 "already exists.\nPlease choose a different name.");
234 bool CommitImpl::checkConfirm(SCRef msg
, SCRef patchName
, SCList selFiles
, bool amend
) {
236 // QTextCodec* tc = QTextCodec::codecForCStrings();
237 // QTextCodec::setCodecForCStrings(0); // set temporary Latin-1
240 QString whatToDo
= amend
?
241 (git
->isStGITStack() ? "refresh top patch with" :
242 "amend last commit with") :
243 (git
->isStGITStack() ? "create a new patch with" : "commit");
245 QString
text("Do you want to " + whatToDo
);
247 bool const fullList
= selFiles
.size() < 20;
249 text
.append(" the following file(s)?\n\n" + selFiles
.join("\n") +
250 "\n\nwith the message:\n\n");
252 text
.append(" those " + QString::number(selFiles
.size()) +
253 " files the with the message:\n\n");
256 if (git
->isStGITStack())
257 text
.append("\n\nAnd patch name: " + patchName
);
259 // QTextCodec::setCodecForCStrings(tc);
261 QMessageBox
msgBox(this);
262 msgBox
.setWindowTitle("Commit changes - QGit");
263 msgBox
.setText(text
);
265 msgBox
.setDetailedText(selFiles
.join("\n"));
267 msgBox
.setStandardButtons(QMessageBox::Yes
| QMessageBox::No
);
268 msgBox
.setDefaultButton(QMessageBox::Yes
);
270 return msgBox
.exec() != QMessageBox::No
;
273 void CommitImpl::pushButtonSettings_clicked() {
275 SettingsImpl
setView(this, git
, 3);
279 void CommitImpl::pushButtonCancel_clicked() {
284 void CommitImpl::pushButtonCommit_clicked() {
286 QStringList selFiles
; // retrieve selected files
287 if (!checkFiles(selFiles
))
290 QString msg
; // check for commit message and strip comments
294 QString
patchName(msg
.section('\n', 0, 0)); // the subject
295 if (git
->isStGITStack() && !checkPatchName(patchName
))
298 // ask for confirmation
299 if (!checkConfirm(msg
, patchName
, selFiles
, !Git::optAmend
))
303 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
304 EM_PROCESS_EVENTS
; // to close message box
306 if (git
->isStGITStack())
307 ok
= git
->stgCommit(selFiles
, msg
, patchName
, !Git::optFold
);
309 ok
= git
->commitFiles(selFiles
, msg
, !Git::optAmend
);
311 lastMsgBeforeError
= (ok
? "" : msg
);
312 QApplication::restoreOverrideCursor();
314 emit
changesCommitted(ok
);
318 void CommitImpl::pushButtonAmend_clicked() {
320 QStringList selFiles
; // retrieve selected files
322 // FIXME: If there are no files AND no changes to message, we should not
323 // commit. Disabling the commit button in such case might be preferable.
325 QString
msg(textEditMsg
->toPlainText());
326 if (msg
== origMsg
&& selFiles
.isEmpty()) {
331 if (msg
== origMsg
&& git
->isStGITStack())
333 else if (!checkMsg(msg
))
334 // We are going to replace the message, so it better isn't empty
337 // ask for confirmation
338 // FIXME: We don't need patch name for refresh, do we?
339 if (!checkConfirm(msg
, "", selFiles
, Git::optAmend
))
343 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
344 EM_PROCESS_EVENTS
; // to close message box
346 if (git
->isStGITStack())
347 ok
= git
->stgCommit(selFiles
, msg
, "", Git::optFold
);
349 ok
= git
->commitFiles(selFiles
, msg
, Git::optAmend
);
351 QApplication::restoreOverrideCursor();
353 emit
changesCommitted(ok
);
357 void CommitImpl::pushButtonUpdateCache_clicked() {
359 QStringList selFiles
;
360 if (!checkFiles(selFiles
))
363 bool ok
= git
->updateIndex(selFiles
);
365 QApplication::restoreOverrideCursor();
366 emit
changesCommitted(ok
);
370 void CommitImpl::textEditMsg_cursorPositionChanged() {
372 int col_pos
, line_pos
;
373 computePosition(col_pos
, line_pos
);
374 QString lineNumber
= QString("Line: %1 Col: %2")
375 .arg(line_pos
+ 1).arg(col_pos
+ 1);
376 textLabelLineCol
->setText(lineNumber
);
379 void CommitImpl::computePosition(int &col_pos
, int &line_pos
) {
381 QRect r
= textEditMsg
->cursorRect();
382 int vs
= textEditMsg
->verticalScrollBar()->value();
383 int hs
= textEditMsg
->horizontalScrollBar()->value();
385 // when in start position r.x() = -r.width() / 2
386 col_pos
= (r
.x() + hs
+ r
.width() / 2) / ofsX
;
387 line_pos
= (r
.y() + vs
) / ofsY
;
390 bool CommitImpl::eventFilter(QObject
* obj
, QEvent
* event
) {
392 if (obj
== textEditMsg
) {
393 if (event
->type() == QEvent::KeyPress
) {
394 QKeyEvent
* keyEvent
= static_cast<QKeyEvent
*>(event
);
395 if (( keyEvent
->key() == Qt::Key_Return
396 || keyEvent
->key() == Qt::Key_Enter
398 && keyEvent
->modifiers() & Qt::ControlModifier
) {
400 QMetaObject::invokeMethod(pushButtonOk
, "clicked", Qt::QueuedConnection
);
406 return QObject::eventFilter(obj
, event
);