X Tutup
/* Description: changes commit dialog Author: Marco Costalba (C) 2005-2007 Copyright: See COPYING file that comes with this distribution */ #include #include #include #include #include #include #include #include #include #include "exceptionmanager.h" #include "common.h" #include "git.h" #include "settingsimpl.h" #include "commitimpl.h" using namespace QGit; QString CommitImpl::lastMsgBeforeError; CommitImpl::CommitImpl(Git* g, bool amend) : git(g) { // adjust GUI setAttribute(Qt::WA_DeleteOnClose); setupUi(this); textEditMsg->setFont(TYPE_WRITER_FONT); QVector v(1, splitter); QGit::restoreGeometrySetting(CMT_GEOM_KEY, this, &v); QSettings settings; QString templ(settings.value(CMT_TEMPL_KEY, CMT_TEMPL_DEF).toString()); QString msg; QDir d; if (d.exists(templ)) readFromFile(templ, msg); // set-up files list const RevFile* f = git->getFiles(ZERO_SHA); for (int i = 0; f && i < f->count(); ++i) { // in case of amend f could be null bool inIndex = f->statusCmp(i, RevFile::IN_INDEX); bool isNew = (f->statusCmp(i, RevFile::NEW) || f->statusCmp(i, RevFile::UNKNOWN)); QColor myColor = QPalette().color(QPalette::WindowText); if (isNew) myColor = Qt::darkGreen; else if (f->statusCmp(i, RevFile::DELETED)) myColor = Qt::red; QTreeWidgetItem* item = new QTreeWidgetItem(treeWidgetFiles); item->setText(0, git->filePath(*f, i)); item->setText(1, inIndex ? "Updated in index" : "Not updated in index"); item->setCheckState(0, inIndex || !isNew ? Qt::Checked : Qt::Unchecked); item->setForeground(0, myColor); } treeWidgetFiles->resizeColumnToContents(0); // compute cursor offsets. Take advantage of fixed width font textEditMsg->setPlainText("\nx\nx"); // cursor doesn't move on empty text textEditMsg->moveCursor(QTextCursor::Start); textEditMsg->verticalScrollBar()->setValue(0); textEditMsg->horizontalScrollBar()->setValue(0); int y0 = textEditMsg->cursorRect().y(); int x0 = textEditMsg->cursorRect().x(); textEditMsg->moveCursor(QTextCursor::Down); textEditMsg->moveCursor(QTextCursor::Right); textEditMsg->verticalScrollBar()->setValue(0); int y1 = textEditMsg->cursorRect().y(); int x1 = textEditMsg->cursorRect().x(); ofsX = x1 - x0; ofsY = y1 - y0; textEditMsg->moveCursor(QTextCursor::Start); textEditMsg_cursorPositionChanged(); if (lastMsgBeforeError.isEmpty()) { // setup textEditMsg with old commit message to be amended QString status(""); if (amend) status = git->getLastCommitMsg(); // setup textEditMsg with default value if user opted to do so (default) if (testFlag(USE_CMT_MSG_F, FLAGS_KEY)) status += git->getNewCommitMsg(); // prepend commit msg with template if available if (!amend) status.prepend('\n').replace(QRegExp("\\n([^#])"), "\n#\\1"); // comment all the lines msg = status.trimmed(); } else msg = lastMsgBeforeError; textEditMsg->setPlainText(msg); textEditMsg->setFocus(); // if message is not changed we avoid calling refresh // to change patch name in stgCommit() origMsg = msg; // setup button functions if (amend) { if (git->isStGITStack()) { pushButtonOk->setText("&Add to top"); pushButtonOk->setShortcut(QKeySequence("Alt+A")); pushButtonOk->setToolTip("Refresh top stack patch"); } else { pushButtonOk->setText("&Amend"); pushButtonOk->setShortcut(QKeySequence("Alt+A")); pushButtonOk->setToolTip("Amend latest commit"); } connect(pushButtonOk, SIGNAL(clicked()), this, SLOT(pushButtonAmend_clicked())); } else { if (git->isStGITStack()) { pushButtonOk->setText("&New patch"); pushButtonOk->setShortcut(QKeySequence("Alt+N")); pushButtonOk->setToolTip("Create a new patch"); } connect(pushButtonOk, SIGNAL(clicked()), this, SLOT(pushButtonCommit_clicked())); } connect(treeWidgetFiles, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(contextMenuPopup(const QPoint&))); connect(textEditMsg, SIGNAL(cursorPositionChanged()), this, SLOT(textEditMsg_cursorPositionChanged())); } void CommitImpl::closeEvent(QCloseEvent*) { QVector v(1, splitter); QGit::saveGeometrySetting(CMT_GEOM_KEY, this, &v); } void CommitImpl::contextMenuPopup(const QPoint& pos) { QMenu* contextMenu = new QMenu(this); QAction* a = contextMenu->addAction("Select All"); connect(a, SIGNAL(triggered()), this, SLOT(checkAll())); a = contextMenu->addAction("Unselect All"); connect(a, SIGNAL(triggered()), this, SLOT(unCheckAll())); contextMenu->popup(mapToGlobal(pos)); } void CommitImpl::checkAll() { checkUncheck(true); } void CommitImpl::unCheckAll() { checkUncheck(false); } void CommitImpl::checkUncheck(bool checkAll) { QTreeWidgetItemIterator it(treeWidgetFiles); while (*it) { (*it)->setCheckState(0, checkAll ? Qt::Checked : Qt::Unchecked); ++it; } } bool CommitImpl::getFiles(SList selFiles) { // check for files to commit selFiles.clear(); QTreeWidgetItemIterator it(treeWidgetFiles); while (*it) { if ((*it)->checkState(0) == Qt::Checked) selFiles.append((*it)->text(0)); ++it; } return !selFiles.isEmpty(); } void CommitImpl::warnNoFiles() { QMessageBox::warning(this, "Commit changes - QGit", "Sorry, no files are selected for updating.", QMessageBox::Ok, QMessageBox::NoButton); } bool CommitImpl::checkFiles(SList selFiles) { if (getFiles(selFiles)) return true; warnNoFiles(); return false; } bool CommitImpl::checkMsg(QString& msg) { msg = textEditMsg->toPlainText(); msg.remove(QRegExp("(^|\\n)\\s*#[^\\n]*")); // strip comments msg.replace(QRegExp("[ \\t\\r\\f\\v]+\\n"), "\n"); // strip line trailing cruft msg = msg.trimmed(); if (msg.isEmpty()) { QMessageBox::warning(this, "Commit changes - QGit", "Sorry, I don't want an empty message.", QMessageBox::Ok, QMessageBox::NoButton); return false; } // split subject from message body QString subj(msg.section('\n', 0, 0, QString::SectionIncludeTrailingSep)); QString body(msg.section('\n', 1).trimmed()); msg = subj + '\n' + body + '\n'; return true; } bool CommitImpl::checkPatchName(QString& patchName) { bool ok; patchName = patchName.simplified(); patchName.replace(' ', "_"); patchName = QInputDialog::getText(this, "Create new patch - QGit", "Enter patch name:", QLineEdit::Normal, patchName, &ok); if (!ok || patchName.isEmpty()) return false; QString tmp(patchName.trimmed()); if (patchName != tmp.remove(' ')) QMessageBox::warning(this, "Create new patch - QGit", "Sorry, control " "characters or spaces\n are not allowed in patch name."); else if (git->isPatchName(patchName)) QMessageBox::warning(this, "Create new patch - QGit", "Sorry, patch name " "already exists.\nPlease choose a different name."); else return true; return false; } bool CommitImpl::checkConfirm(SCRef msg, SCRef patchName, SCList selFiles, bool amend) { QTextCodec* tc = QTextCodec::codecForCStrings(); QTextCodec::setCodecForCStrings(0); // set temporary Latin-1 // NOTEME: i18n-ugly QString whatToDo = amend ? (git->isStGITStack() ? "refresh top patch with" : "amend last commit with") : (git->isStGITStack() ? "create a new patch with" : "commit"); QString text("Do you want to " + whatToDo); bool const fullList = selFiles.size() < 20; if (fullList) text.append(" the following file(s)?\n\n" + selFiles.join("\n") + "\n\nwith the message:\n\n"); else text.append(" those " + QString::number(selFiles.size()) + " files the with the message:\n\n"); text.append(msg); if (git->isStGITStack()) text.append("\n\nAnd patch name: " + patchName); QTextCodec::setCodecForCStrings(tc); QMessageBox msgBox(this); msgBox.setWindowTitle("Commit changes - QGit"); msgBox.setText(text); if (!fullList) msgBox.setDetailedText(selFiles.join("\n")); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::Yes); return msgBox.exec() != QMessageBox::No; } void CommitImpl::pushButtonSettings_clicked() { SettingsImpl setView(this, git, 3); setView.exec(); } void CommitImpl::pushButtonCancel_clicked() { close(); } void CommitImpl::pushButtonCommit_clicked() { QStringList selFiles; // retrieve selected files if (!checkFiles(selFiles)) return; QString msg; // check for commit message and strip comments if (!checkMsg(msg)) return; QString patchName(msg.section('\n', 0, 0)); // the subject if (git->isStGITStack() && !checkPatchName(patchName)) return; // ask for confirmation if (!checkConfirm(msg, patchName, selFiles, !Git::optAmend)) return; // ok, let's go QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); EM_PROCESS_EVENTS; // to close message box bool ok; if (git->isStGITStack()) ok = git->stgCommit(selFiles, msg, patchName, !Git::optFold); else ok = git->commitFiles(selFiles, msg, !Git::optAmend); lastMsgBeforeError = (ok ? "" : msg); QApplication::restoreOverrideCursor(); hide(); emit changesCommitted(ok); close(); } void CommitImpl::pushButtonAmend_clicked() { QStringList selFiles; // retrieve selected files getFiles(selFiles); // FIXME: If there are no files AND no changes to message, we should not // commit. Disabling the commit button in such case might be preferable. QString msg(textEditMsg->toPlainText()); if (msg == origMsg && selFiles.isEmpty()) { warnNoFiles(); return; } if (msg == origMsg && git->isStGITStack()) msg = ""; else if (!checkMsg(msg)) // We are going to replace the message, so it better isn't empty return; // ask for confirmation // FIXME: We don't need patch name for refresh, do we? if (!checkConfirm(msg, "", selFiles, Git::optAmend)) return; // ok, let's go QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); EM_PROCESS_EVENTS; // to close message box bool ok; if (git->isStGITStack()) ok = git->stgCommit(selFiles, msg, "", Git::optFold); else ok = git->commitFiles(selFiles, msg, Git::optAmend); QApplication::restoreOverrideCursor(); hide(); emit changesCommitted(ok); close(); } void CommitImpl::pushButtonUpdateCache_clicked() { QStringList selFiles; if (!checkFiles(selFiles)) return; bool ok = git->updateIndex(selFiles); QApplication::restoreOverrideCursor(); emit changesCommitted(ok); close(); } void CommitImpl::textEditMsg_cursorPositionChanged() { int col_pos, line_pos; computePosition(col_pos, line_pos); QString lineNumber = QString("Line: %1 Col: %2") .arg(line_pos + 1).arg(col_pos + 1); textLabelLineCol->setText(lineNumber); } void CommitImpl::computePosition(int &col_pos, int &line_pos) { QRect r = textEditMsg->cursorRect(); int vs = textEditMsg->verticalScrollBar()->value(); int hs = textEditMsg->horizontalScrollBar()->value(); // when in start position r.x() = -r.width() / 2 col_pos = (r.x() + hs + r.width() / 2) / ofsX; line_pos = (r.y() + vs) / ofsY; }
X Tutup