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>
17 #include "exceptionmanager.h"
20 #include "settingsimpl.h"
21 #include "commitimpl.h"
25 QString
CommitImpl::lastMsgBeforeError
;
27 CommitImpl::CommitImpl(Git
* g
, bool amend
) : git(g
) {
30 setAttribute(Qt::WA_DeleteOnClose
);
32 textEditMsg
->setFont(TYPE_WRITER_FONT
);
34 QVector
<QSplitter
*> v(1, splitter
);
35 QGit::restoreGeometrySetting(CMT_GEOM_KEY
, this, &v
);
38 QString
templ(settings
.value(CMT_TEMPL_KEY
, CMT_TEMPL_DEF
).toString());
42 readFromFile(templ
, msg
);
45 const RevFile
* f
= git
->getFiles(ZERO_SHA
);
46 for (int i
= 0; f
&& i
< f
->count(); ++i
) { // in case of amend f could be null
48 bool inIndex
= f
->statusCmp(i
, RevFile::IN_INDEX
);
49 bool isNew
= (f
->statusCmp(i
, RevFile::NEW
) || f
->statusCmp(i
, RevFile::UNKNOWN
));
50 QColor myColor
= Qt::black
;
52 myColor
= Qt::darkGreen
;
53 else if (f
->statusCmp(i
, RevFile::DELETED
))
56 QTreeWidgetItem
* item
= new QTreeWidgetItem(treeWidgetFiles
);
57 item
->setText(0, git
->filePath(*f
, i
));
58 item
->setText(1, inIndex
? "Updated in index" : "Not updated in index");
59 item
->setCheckState(0, inIndex
|| !isNew
? Qt::Checked
: Qt::Unchecked
);
60 item
->setForeground(0, myColor
);
62 treeWidgetFiles
->resizeColumnToContents(0);
64 // compute cursor offsets. Take advantage of fixed width font
65 textEditMsg
->setPlainText("\nx\nx"); // cursor doesn't move on empty text
66 textEditMsg
->moveCursor(QTextCursor::Start
);
67 textEditMsg
->verticalScrollBar()->setValue(0);
68 textEditMsg
->horizontalScrollBar()->setValue(0);
69 int y0
= textEditMsg
->cursorRect().y();
70 int x0
= textEditMsg
->cursorRect().x();
71 textEditMsg
->moveCursor(QTextCursor::Down
);
72 textEditMsg
->moveCursor(QTextCursor::Right
);
73 textEditMsg
->verticalScrollBar()->setValue(0);
74 int y1
= textEditMsg
->cursorRect().y();
75 int x1
= textEditMsg
->cursorRect().x();
78 textEditMsg
->moveCursor(QTextCursor::Start
);
79 textEditMsg_cursorPositionChanged();
81 if (lastMsgBeforeError
.isEmpty()) {
82 // setup textEditMsg with old commit message to be amended
85 status
= git
->getLastCommitMsg();
87 // setup textEditMsg with default value if user opted to do so (default)
88 if (testFlag(USE_CMT_MSG_F
, FLAGS_KEY
))
89 status
+= git
->getNewCommitMsg();
91 // prepend commit msg with template if available
93 status
.prepend('\n').replace(QRegExp("\\n([^#])"), "\n#\\1"); // comment all the lines
95 msg
= status
.trimmed();
97 msg
= lastMsgBeforeError
;
99 textEditMsg
->setPlainText(msg
);
100 textEditMsg
->setFocus();
102 // if message is not changed we avoid calling refresh
103 // to change patch name in stgCommit()
106 // setup button functions
108 if (git
->isStGITStack()) {
109 pushButtonOk
->setText("&Add to top");
110 pushButtonOk
->setShortcut(QKeySequence("Alt+A"));
111 pushButtonOk
->setToolTip("Refresh top stack patch");
113 pushButtonOk
->setText("&Amend");
114 pushButtonOk
->setShortcut(QKeySequence("Alt+A"));
115 pushButtonOk
->setToolTip("Amend latest commit");
117 connect(pushButtonOk
, SIGNAL(clicked()),
118 this, SLOT(pushButtonAmend_clicked()));
120 if (git
->isStGITStack()) {
121 pushButtonOk
->setText("&New patch");
122 pushButtonOk
->setShortcut(QKeySequence("Alt+N"));
123 pushButtonOk
->setToolTip("Create a new patch");
125 connect(pushButtonOk
, SIGNAL(clicked()),
126 this, SLOT(pushButtonCommit_clicked()));
128 connect(treeWidgetFiles
, SIGNAL(customContextMenuRequested(const QPoint
&)),
129 this, SLOT(contextMenuPopup(const QPoint
&)));
130 connect(textEditMsg
, SIGNAL(cursorPositionChanged()),
131 this, SLOT(textEditMsg_cursorPositionChanged()));
134 void CommitImpl::closeEvent(QCloseEvent
*) {
136 QVector
<QSplitter
*> v(1, splitter
);
137 QGit::saveGeometrySetting(CMT_GEOM_KEY
, this, &v
);
140 void CommitImpl::contextMenuPopup(const QPoint
& pos
) {
142 QMenu
* contextMenu
= new QMenu(this);
143 QAction
* a
= contextMenu
->addAction("Select All");
144 connect(a
, SIGNAL(triggered()), this, SLOT(checkAll()));
145 a
= contextMenu
->addAction("Unselect All");
146 connect(a
, SIGNAL(triggered()), this, SLOT(unCheckAll()));
147 contextMenu
->popup(mapToGlobal(pos
));
150 void CommitImpl::checkAll() { checkUncheck(true); }
151 void CommitImpl::unCheckAll() { checkUncheck(false); }
153 void CommitImpl::checkUncheck(bool checkAll
) {
155 QTreeWidgetItemIterator
it(treeWidgetFiles
);
157 (*it
)->setCheckState(0, checkAll
? Qt::Checked
: Qt::Unchecked
);
162 bool CommitImpl::getFiles(SList selFiles
) {
164 // check for files to commit
166 QTreeWidgetItemIterator
it(treeWidgetFiles
);
168 if ((*it
)->checkState(0) == Qt::Checked
)
169 selFiles
.append((*it
)->text(0));
173 return !selFiles
.isEmpty();
176 void CommitImpl::warnNoFiles() {
178 QMessageBox::warning(this, "Commit changes - QGit",
179 "Sorry, no files are selected for updating.",
180 QMessageBox::Ok
, QMessageBox::NoButton
);
183 bool CommitImpl::checkFiles(SList selFiles
) {
185 if (getFiles(selFiles
))
192 bool CommitImpl::checkMsg(QString
& msg
) {
194 msg
= textEditMsg
->toPlainText();
195 msg
.remove(QRegExp("(^|\\n)\\s*#[^\\n]*")); // strip comments
196 msg
.replace(QRegExp("[ \\t\\r\\f\\v]+\\n"), "\n"); // strip line trailing cruft
199 QMessageBox::warning(this, "Commit changes - QGit",
200 "Sorry, I don't want an empty message.",
201 QMessageBox::Ok
, QMessageBox::NoButton
);
204 // split subject from message body
205 QString
subj(msg
.section('\n', 0, 0, QString::SectionIncludeTrailingSep
));
206 QString
body(msg
.section('\n', 1).trimmed());
207 msg
= subj
+ '\n' + body
+ '\n';
211 bool CommitImpl::checkPatchName(QString
& patchName
) {
214 patchName
= patchName
.simplified();
215 patchName
.replace(' ', "_");
216 patchName
= QInputDialog::getText(this, "Create new patch - QGit", "Enter patch name:",
217 QLineEdit::Normal
, patchName
, &ok
);
218 if (!ok
|| patchName
.isEmpty())
221 QString
tmp(patchName
.trimmed());
222 if (patchName
!= tmp
.remove(' '))
223 QMessageBox::warning(this, "Create new patch - QGit", "Sorry, control "
224 "characters or spaces\n are not allowed in patch name.");
226 else if (git
->isPatchName(patchName
))
227 QMessageBox::warning(this, "Create new patch - QGit", "Sorry, patch name "
228 "already exists.\nPlease choose a different name.");
235 bool CommitImpl::checkConfirm(SCRef msg
, SCRef patchName
, SCList selFiles
, bool amend
) {
237 QTextCodec
* tc
= QTextCodec::codecForCStrings();
238 QTextCodec::setCodecForCStrings(0); // set temporary Latin-1
241 QString whatToDo
= amend
?
242 (git
->isStGITStack() ? "refresh top patch with" :
243 "amend last commit with") :
244 (git
->isStGITStack() ? "create a new patch with" : "commit");
246 QString
text("Do you want to " + whatToDo
);
248 bool const fullList
= selFiles
.size() < 20;
250 text
.append(" the following file(s)?\n\n" + selFiles
.join("\n") +
251 "\n\nwith the message:\n\n");
253 text
.append(" those " + QString::number(selFiles
.size()) +
254 " files the with the message:\n\n");
257 if (git
->isStGITStack())
258 text
.append("\n\nAnd patch name: " + patchName
);
260 QTextCodec::setCodecForCStrings(tc
);
262 QMessageBox
msgBox(this);
263 msgBox
.setWindowTitle("Commit changes - QGit");
264 msgBox
.setText(text
);
266 msgBox
.setDetailedText(selFiles
.join("\n"));
268 msgBox
.setStandardButtons(QMessageBox::Yes
| QMessageBox::No
);
269 msgBox
.setDefaultButton(QMessageBox::Yes
);
271 return msgBox
.exec() != QMessageBox::No
;
274 void CommitImpl::pushButtonSettings_clicked() {
276 SettingsImpl
setView(this, git
, 3);
280 void CommitImpl::pushButtonCancel_clicked() {
285 void CommitImpl::pushButtonCommit_clicked() {
287 QStringList selFiles
; // retrieve selected files
288 if (!checkFiles(selFiles
))
291 QString msg
; // check for commit message and strip comments
295 QString
patchName(msg
.section('\n', 0, 0)); // the subject
296 if (git
->isStGITStack() && !checkPatchName(patchName
))
299 // ask for confirmation
300 if (!checkConfirm(msg
, patchName
, selFiles
, !Git::optAmend
))
304 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
305 EM_PROCESS_EVENTS
; // to close message box
307 if (git
->isStGITStack())
308 ok
= git
->stgCommit(selFiles
, msg
, patchName
, !Git::optFold
);
310 ok
= git
->commitFiles(selFiles
, msg
, !Git::optAmend
);
312 lastMsgBeforeError
= (ok
? "" : msg
);
313 QApplication::restoreOverrideCursor();
315 emit
changesCommitted(ok
);
319 void CommitImpl::pushButtonAmend_clicked() {
321 QStringList selFiles
; // retrieve selected files
323 // FIXME: If there are no files AND no changes to message, we should not
324 // commit. Disabling the commit button in such case might be preferable.
326 QString
msg(textEditMsg
->toPlainText());
327 if (msg
== origMsg
&& selFiles
.isEmpty()) {
332 if (msg
== origMsg
&& git
->isStGITStack())
334 else if (!checkMsg(msg
))
335 // We are going to replace the message, so it better isn't empty
338 // ask for confirmation
339 // FIXME: We don't need patch name for refresh, do we?
340 if (!checkConfirm(msg
, "", selFiles
, Git::optAmend
))
344 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor
));
345 EM_PROCESS_EVENTS
; // to close message box
347 if (git
->isStGITStack())
348 ok
= git
->stgCommit(selFiles
, msg
, "", Git::optFold
);
350 ok
= git
->commitFiles(selFiles
, msg
, Git::optAmend
);
352 QApplication::restoreOverrideCursor();
354 emit
changesCommitted(ok
);
358 void CommitImpl::pushButtonUpdateCache_clicked() {
360 QStringList selFiles
;
361 if (!checkFiles(selFiles
))
364 bool ok
= git
->updateIndex(selFiles
);
366 QApplication::restoreOverrideCursor();
367 emit
changesCommitted(ok
);
371 void CommitImpl::textEditMsg_cursorPositionChanged() {
373 int col_pos
, line_pos
;
374 computePosition(col_pos
, line_pos
);
375 QString lineNumber
= QString("Line: %1 Col: %2")
376 .arg(line_pos
+ 1).arg(col_pos
+ 1);
377 textLabelLineCol
->setText(lineNumber
);
380 void CommitImpl::computePosition(int &col_pos
, int &line_pos
) {
382 QRect r
= textEditMsg
->cursorRect();
383 int vs
= textEditMsg
->verticalScrollBar()->value();
384 int hs
= textEditMsg
->horizontalScrollBar()->value();
386 // when in start position r.x() = -r.width() / 2
387 col_pos
= (r
.x() + hs
+ r
.width() / 2) / ofsX
;
388 line_pos
= (r
.y() + vs
) / ofsY
;