commitimpl.cpp

Go to the documentation of this file.
00001 /*
00002         Description: changes commit dialog
00003 
00004         Author: Marco Costalba (C) 2005-2006
00005 
00006         Copyright: See COPYING file that comes with this distribution
00007 
00008    Part of this code is taken from Fredrik Kuivinen "Gct"
00009    tool. I have just translated from Python to C++
00010 */
00011 #include <qlistview.h>
00012 #include <qsettings.h>
00013 #include <qlabel.h>
00014 #include <qtextedit.h>
00015 #include <qmessagebox.h>
00016 #include <qapplication.h>
00017 #include <qcursor.h>
00018 #include <qsplitter.h>
00019 #include <qpushbutton.h>
00020 #include <qinputdialog.h>
00021 #include <qpopupmenu.h>
00022 #include <qregexp.h>
00023 #include <qtooltip.h>
00024 #include <qstylesheet.h>
00025 #include <qtextcodec.h>
00026 #include "exceptionmanager.h"
00027 #include "common.h"
00028 #include "git.h"
00029 #include "settingsimpl.h"
00030 #include "commitimpl.h"
00031 
00032 using namespace QGit;
00033 
00034 // ******************************* CheckListFileItem ****************************
00035 
00036 class CheckListFileItem: public QCheckListItem {
00037 public:
00038         CheckListFileItem(QListView* lv, SCRef file, const RevFile* f, int i) :
00039                           QCheckListItem(lv, file, QCheckListItem::CheckBox) {
00040 
00041                 bool inIndex = f->statusCmp(i, RevFile::IN_INDEX);
00042                 bool isNew = (f->statusCmp(i, RevFile::NEW) || f->statusCmp(i, RevFile::UNKNOWN));
00043                 setText(1, inIndex ? "In sync" : "Out of sync");
00044                 setOn(inIndex || !isNew);
00045                 myColor = Qt::black;
00046                 if (isNew)
00047                         myColor = Qt::darkGreen;
00048 
00049                 else if (f->statusCmp(i, RevFile::DELETED))
00050                         myColor = Qt::red;
00051         }
00052         virtual void paintCell(QPainter *p, const QColorGroup& cg, int column,
00053                                int width, int alignment) {
00054 
00055                 QColorGroup _cg(cg);
00056                 if (column == 0)
00057                         _cg.setColor(QColorGroup::Text, myColor);
00058 
00059                 QCheckListItem::paintCell(p, _cg, column, width, alignment);
00060         }
00061 private:
00062         QColor myColor;
00063 };
00064 
00065 // ******************************* CommitImpl ****************************
00066 
00067 CommitImpl::CommitImpl(Git* g) : CommitBase(0, 0, Qt::WDestructiveClose), git(g) {
00068 
00069         // adjust GUI
00070         listViewFiles->setColumnAlignment(1, Qt::AlignHCenter);
00071         listViewFiles->setColumnAlignment(2, Qt::AlignHCenter);
00072         textEditMsg->setFont(TYPE_WRITER_FONT);
00073 
00074         // read settings
00075         QSettings settings;
00076         QString tmp = settings.readEntry(APP_KEY + CMT_GEOM_KEY, CMT_GEOM_DEF);
00077         QStringList sl = QStringList::split(',', tmp);
00078         QPoint pos(sl[0].toInt(), sl[1].toInt());
00079         QSize size(sl[2].toInt(), sl[3].toInt());
00080         resize(size);
00081         move(pos);
00082         tmp = settings.readEntry(APP_KEY + CMT_SPLIT_KEY, CMT_SPLIT_DEF);
00083         sl = QStringList::split(',', tmp);
00084         QValueList<int> sz;
00085         sz.append(sl[0].toInt());
00086         sz.append(sl[1].toInt());
00087         splitter->setSizes(sz);
00088         tmp = settings.readEntry(APP_KEY + CMT_TEMPL_KEY, CMT_TEMPL_DEF);
00089         QString msg;
00090         QDir d;
00091         if (d.exists(tmp))
00092                 readFromFile(tmp, msg);
00093 
00094         // set-up files list
00095         const RevFile* f = git->getFiles(ZERO_SHA);
00096         for (int i = 0; i < f->count(); ++i)
00097                 new CheckListFileItem(listViewFiles, git->filePath(*f, i), f, i);
00098 
00099         // setup textEditMsg with default value
00100         QString status(git->getDefCommitMsg());
00101         status.prepend('\n').replace(QRegExp("\\n([^#])"), "\n#\\1"); // comment all the lines
00102         msg.append(status.stripWhiteSpace());
00103         textEditMsg->setText(msg);
00104         textEditMsg->setCursorPosition(0, 0);
00105 
00106         // if message is not changed we avoid calling refresh
00107         // to change patch name in stgCommit()
00108         origMsg = msg;
00109 
00110         // setup button functions
00111         if (git->isStGITStack()) {
00112                 pushButtonOk->setText("&New patch");
00113                 pushButtonOk->setAccel(QKeySequence("Alt+N"));
00114                 QToolTip::remove(pushButtonOk);
00115                 QToolTip::add(pushButtonOk, "Create a new patch");
00116                 pushButtonUpdateCache->setText("&Add to top");
00117                 pushButtonOk->setAccel(QKeySequence("Alt+A"));
00118                 QToolTip::remove(pushButtonUpdateCache);
00119                 QToolTip::add(pushButtonUpdateCache, "Refresh top stack patch");
00120         }
00121         // setup listViewFiles popup
00122         contextMenu = new QPopupMenu(this); // will be deleted when this is destroyed
00123         CHECK_ALL = contextMenu->insertItem("Select All");
00124         UNCHECK_ALL = contextMenu->insertItem("Unselect All");
00125         connect(contextMenu, SIGNAL(activated(int)), this, SLOT(checkUncheck(int)));
00126         connect(listViewFiles, SIGNAL(contextMenuRequested(QListViewItem*, const QPoint&, int)),
00127                 this, SLOT(contextMenuPopup(QListViewItem*, const QPoint&, int)));
00128 }
00129 
00130 CommitImpl::~CommitImpl() {
00131 
00132         QSettings settings;
00133         QString tmp = QString("%1,%2,%3,%4").arg(pos().x()).arg(pos().y())
00134                               .arg(size().width()).arg(size().height());
00135         settings.writeEntry(APP_KEY + CMT_GEOM_KEY, tmp);
00136         QValueList<int> sz = splitter->sizes();
00137         tmp = QString::number(sz[0]) + "," + QString::number(sz[1]);
00138         settings.writeEntry(APP_KEY + CMT_SPLIT_KEY, tmp);
00139         close();
00140 }
00141 
00142 void CommitImpl::contextMenuPopup(QListViewItem*, const QPoint& pos, int)  {
00143 
00144         contextMenu->popup(pos);
00145 }
00146 
00147 void CommitImpl::checkUncheck(int id) {
00148 
00149         QListViewItemIterator it(listViewFiles);
00150         while (it.current()) {
00151                 ((QCheckListItem*)it.current())->setOn(id == CHECK_ALL);
00152                 ++it;
00153         }
00154 }
00155 
00156 bool CommitImpl::checkFiles(SList selFiles) {
00157 
00158         // check for files to commit
00159         selFiles.clear();
00160         QListViewItemIterator it(listViewFiles);
00161         while (it.current()) {
00162                 if (((QCheckListItem*)it.current())->isOn())
00163                         selFiles.append(it.current()->text(0));
00164                 ++it;
00165         }
00166         if (selFiles.isEmpty())
00167                 QMessageBox::warning(this, "Commit changes - QGit",
00168                                      "Sorry, no files are selected for updating.",
00169                                      QMessageBox::Ok, QMessageBox::NoButton);
00170         return !selFiles.isEmpty();
00171 }
00172 
00173 bool CommitImpl::checkMsg(QString& msg) {
00174 
00175         msg = textEditMsg->text();
00176         msg.remove(QRegExp("(^|\\n)\\s*#[^\\n]*")); // strip comments
00177         msg.replace(QRegExp("[ \\t\\r\\f\\v]+\\n"), "\n"); // strip line trailing cruft
00178         msg = msg.stripWhiteSpace();
00179         if (msg.isEmpty()) {
00180                 QMessageBox::warning(this, "Commit changes - QGit",
00181                                      "Sorry, I don't want an empty message.",
00182                                      QMessageBox::Ok, QMessageBox::NoButton);
00183                 return false;
00184         }
00185         // split subject from message body
00186         QString subj(msg.section('\n', 0, 0, QString::SectionIncludeTrailingSep));
00187         QString body(msg.section('\n', 1).stripWhiteSpace());
00188         msg = subj + '\n' + body + '\n';
00189         return true;
00190 }
00191 
00192 bool CommitImpl::checkPatchName(QString& patchName) {
00193 
00194         bool ok;
00195         patchName = patchName.simplifyWhiteSpace().stripWhiteSpace();
00196         patchName.replace(' ', "_");
00197         patchName = QInputDialog::getText("Create new patch - QGit", "Enter patch name:",
00198                                           QLineEdit::Normal, patchName, &ok, this);
00199         if (!ok || patchName.isEmpty())
00200                 return false;
00201 
00202         QString tmp(patchName.simplifyWhiteSpace());
00203         if (patchName != tmp.remove(' ')) {
00204                 QMessageBox::warning(this, "Create new patch - QGit", "Sorry, control "
00205                                      "characters or spaces\n are not allowed in patch name.");
00206                 return false;
00207         }
00208         if (git->isPatchName(patchName)) {
00209                 QMessageBox::warning(this, "Create new patch - QGit", "Sorry, patch name "
00210                                      "already exists.\nPlease choose a different name.");
00211                 return false;
00212         }
00213         return true;
00214 }
00215 
00216 bool CommitImpl::checkConfirm(SCRef msg, SCRef patchName, SCList selFiles) {
00217 
00218         QTextCodec* tc = QTextCodec::codecForCStrings();
00219         QTextCodec::setCodecForCStrings(0); // set temporary Latin-1
00220 
00221         QString whatToDo = (git->isStGITStack()) ? "create a new patch with" : "commit";
00222         QString text(msg);
00223         text.replace("\n", "\n\t");
00224         text.prepend("Do you want to " + whatToDo + " the following file(s)?\n\n\t" +
00225                      selFiles.join("\n\t") +"\n\nwith the message:\n\n\t");
00226 
00227         if (git->isStGITStack())
00228                 text.append("\n\nAnd patch name: " + patchName);
00229 
00230         text = QStyleSheet::convertFromPlainText(text);
00231         QTextCodec::setCodecForCStrings(tc);
00232 
00233         if (QMessageBox::question(this, "Commit changes - QGit", text, "&Yes",
00234                                   "&No", QString::null, 0, 1) == 1)
00235                 return false;
00236 
00237         return true;
00238 }
00239 
00240 void CommitImpl::pushButtonOk_clicked() {
00241 
00242         QStringList selFiles; // retrieve selected files
00243         if (!checkFiles(selFiles))
00244                 return;
00245 
00246         QString msg; // check for commit message and strip comments
00247         if (!checkMsg(msg))
00248                 return;
00249 
00250         QString patchName(msg.section('\n', 0, 0)); // the subject
00251         if (git->isStGITStack() && !checkPatchName(patchName))
00252                 return;
00253 
00254         if (!checkConfirm(msg, patchName, selFiles)) // ask for confirmation
00255                 return;
00256 
00257         // ok, let's go
00258         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
00259         EM_PROCESS_EVENTS; // to close message box
00260         bool ok;
00261         if (git->isStGITStack())
00262                 ok = git->stgCommit(selFiles, msg, patchName, !Git::optFold);
00263         else
00264                 ok = git->commitFiles(selFiles, msg);
00265 
00266         QApplication::restoreOverrideCursor();
00267         hide();
00268         emit changesCommitted(ok);
00269         close();
00270 }
00271 
00272 void CommitImpl::pushButtonUpdateCache_clicked() {
00273 
00274         QStringList selFiles;
00275         if (!checkFiles(selFiles))
00276                 return;
00277 
00278         if (git->isStGITStack())
00279                 if (QMessageBox::question(this, "Refresh stack - QGit",
00280                         "Do you want to refresh current top stack patch?",
00281                         "&Yes", "&No", QString::null, 0, 1) == 1)
00282                         return;
00283 
00284         QString msg(textEditMsg->text());
00285         if (msg == origMsg)
00286                 msg = ""; // to tell stgCommit() not to refresh patch name
00287 
00288         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
00289         EM_PROCESS_EVENTS; // to close message box
00290         bool ok;
00291         if (git->isStGITStack())
00292                 ok = git->stgCommit(selFiles, msg, "", Git::optFold);
00293         else
00294                 ok = git->updateIndex(selFiles);
00295 
00296         QApplication::restoreOverrideCursor();
00297         emit changesCommitted(ok);
00298         close();
00299 }
00300 
00301 void CommitImpl::pushButtonSettings_clicked() {
00302 
00303         SettingsImpl* setView = new SettingsImpl(this, git, 3);
00304         setView->exec();
00305         // SettingsImpl has Qt::WDestructiveClose, no need to delete
00306 }
00307 
00308 void CommitImpl::pushButtonCancel_clicked() {
00309 
00310         close();
00311 }
00312 
00313 void CommitImpl::textEditMsg_cursorPositionChanged(int para, int pos) {
00314 
00315         int col_pos, line_pos;
00316         computePosition(para, pos, col_pos, line_pos);
00317         QString lineNumber = QString("Line: %1 Col: %2")
00318                                      .arg(line_pos + 1).arg(col_pos + 1);
00319         textLabelLineCol->setText(lineNumber);
00320 }
00321 
00322 /*
00323         Following code to compute cursor row and col position is
00324         shameless taken from KEdit, indeed, it comes from
00325         http://websvn.kde.org/branches/KDE/3.4/kdelibs/kdeui/keditcl1.cpp
00326 */
00327 void CommitImpl::computePosition(int line, int col, int &col_pos, int &line_pos) {
00328 
00329         // line is expressed in paragraphs, we now need to convert to lines
00330         line_pos = 0;
00331         if (textEditMsg->wordWrap() == QTextEdit::NoWrap)
00332                 line_pos = line;
00333         else {
00334                 for (int i = 0; i < line; i++)
00335                         line_pos += textEditMsg->linesOfParagraph(i);
00336         }
00337         int line_offset = textEditMsg->lineOfChar(line, col);
00338         line_pos += line_offset;
00339 
00340         // We now calculate where the current line starts in the paragraph.
00341         const QString linetext(QString::number(line));
00342         int start_of_line = 0;
00343         if (line_offset > 0) {
00344                 start_of_line = col;
00345                 while (textEditMsg->lineOfChar(line, --start_of_line) == line_offset);
00346                         start_of_line++;
00347         }
00348         // O.K here is the deal: The function getCursorPositoin returns the character
00349         // position of the cursor, not the screenposition. I.e,. assume the line
00350         // consists of ab\tc then the character c will be on the screen on position 8
00351         // whereas getCursorPosition will return 3 if the cursors is on the character c.
00352         // Therefore we need to compute the screen position from the character position.
00353         // That's what all the following trouble is all about:
00354         int coltemp = col - start_of_line;
00355         int pos  = 0;
00356         int find = 0;
00357         int mem  = 0;
00358         bool found_one = false;
00359 
00360         // if you understand the following algorithm you are worthy to look at the
00361         // kedit+ sources -- if not, go away ;-)
00362         while (find >= 0 && find <= coltemp - 1) {
00363                 find = linetext.find('\t', find + start_of_line, true) - start_of_line;
00364                 if (find >=0 && find <= coltemp - 1) {
00365                         found_one = true;
00366                         pos = pos + find - mem;
00367                         pos = pos + 8 - pos % 8;
00368                         mem = find;
00369                         find ++;
00370                 }
00371         }
00372         // add the number of characters behind the last tab on the line.
00373         pos = pos + coltemp - mem;
00374         if (found_one)
00375                 pos = pos - 1;
00376 
00377         col_pos = pos;
00378 }

Generated on Fri Dec 7 21:57:36 2007 for QGit by  doxygen 1.5.3