revsview.cpp

Go to the documentation of this file.
00001 /*
00002         Description: qgit revision list view
00003 
00004         Author: Marco Costalba (C) 2005-2006
00005 
00006         Copyright: See COPYING file that comes with this distribution
00007 
00008 */
00009 #include <qlineedit.h>
00010 #include <qtextbrowser.h>
00011 #include <qlistview.h>
00012 #include <qpainter.h>
00013 #include <qstringlist.h>
00014 #include <qlistbox.h>
00015 #include <qapplication.h>
00016 #include <qmessagebox.h>
00017 #include <qstatusbar.h>
00018 #include <qheader.h>
00019 #include <qpopupmenu.h>
00020 #include <qcursor.h>
00021 #include <qaction.h>
00022 #include <qdragobject.h>
00023 #include <qtoolbutton.h>
00024 #include <qtabwidget.h>
00025 #include <qaction.h>
00026 #include "common.h"
00027 #include "git.h"
00028 #include "domain.h"
00029 #include "treeview.h"
00030 #include "listview.h"
00031 #include "filelist.h"
00032 #include "revbase.h"
00033 #include "revdesc.h"
00034 #include "patchbase.h"
00035 #include "patchview.h"
00036 #include "mainimpl.h"
00037 #include "revsview.h"
00038 
00039 RevsView::RevsView(MainImpl* mi, Git* g) : Domain(mi, g) {
00040 
00041         revTab = new TabRev(m());
00042         m()->tabWdg->addTab(revTab, "&Rev list");
00043         tabPosition = m()->tabWdg->count() - 1;
00044 
00045         listViewLog = new ListView(this, g, tab()->listViewLog, &(g->revData), m()->listViewFont);
00046         tab()->textBrowserDesc->setDomain(this);
00047         listBoxFiles = new ListBoxFiles(this, git, tab()->listBoxFiles);
00048         treeView = new TreeView(this, git, m()->treeView);
00049 
00050         connect(git, SIGNAL(newRevsAdded(const FileHistory*, const QValueVector<QString>&)),
00051         listViewLog, SLOT(on_newRevsAdded(const FileHistory*, const QValueVector<QString>&)));
00052 
00053         connect(git, SIGNAL(loadCompleted(const FileHistory*, const QString&)),
00054                 this, SLOT(on_loadCompleted(const FileHistory*, const QString&)));
00055 
00056         connect(m(), SIGNAL(repaintListViews(const QFont&)),
00057                 listViewLog, SLOT(on_repaintListViews(const QFont&)));
00058 
00059         connect(m(), SIGNAL(updateRevDesc()), this, SLOT(on_updateRevDesc()));
00060 
00061         connect(listViewLog, SIGNAL(lanesContextMenuRequested(const QStringList&,
00062                 const QStringList&)), this, SLOT(on_lanesContextMenuRequested
00063                (const QStringList&, const QStringList&)));
00064 
00065         connect(listViewLog, SIGNAL(droppedRevisions(const QStringList&)),
00066                 this, SLOT(on_droppedRevisions(const QStringList&)));
00067 
00068         connect(listViewLog, SIGNAL(contextMenu(const QString&, int)),
00069                 this, SLOT(on_contextMenu(const QString&, int)));
00070 
00071         connect(treeView, SIGNAL(contextMenu(const QString&, int)),
00072                 this, SLOT(on_contextMenu(const QString&, int)));
00073 
00074         connect(listBoxFiles, SIGNAL(contextMenu(const QString&, int)),
00075                 this, SLOT(on_contextMenu(const QString&, int)));
00076 }
00077 
00078 RevsView::~RevsView() {
00079 
00080         if (!parent())
00081                 return;
00082 
00083         delete linkedPatchView;
00084         delete revTab;
00085 }
00086 
00087 void RevsView::clear(bool keepState) {
00088 
00089         if (!keepState)
00090                 st.clear();
00091 
00092         listViewLog->clear();
00093         tab()->textBrowserDesc->clear();
00094         listBoxFiles->clear();
00095         treeView->clear();
00096         updateLineEditSHA(true);
00097         if (linkedPatchView)
00098                 linkedPatchView->clear();
00099 }
00100 
00101 void RevsView::setEnabled(bool b) {
00102 
00103         revTab->setEnabled(b);
00104         if (linkedPatchView)
00105                 linkedPatchView->tab()->setEnabled(b);
00106 }
00107 
00108 void RevsView::viewPatch(bool newTab) {
00109 
00110         if (!newTab && linkedPatchView) {
00111                 m()->tabWdg->setCurrentPage(linkedPatchView->tabPos());
00112                 return;
00113         }
00114         PatchView* pv = new PatchView(m(), git);
00115         m()->tabWdg->setCurrentPage(pv->tabPos());
00116 
00117         if (!newTab) { // linkedPatchView == NULL
00118                 linkedPatchView = pv;
00119                 linkDomain(linkedPatchView);
00120 
00121                 connect(m(), SIGNAL(highlightPatch(const QString&, bool)),
00122                         linkedPatchView, SLOT(on_highlightPatch(const QString&, bool)));
00123 
00124                 connect(linkedPatchView->tab()->listBoxFiles, SIGNAL(doubleClicked(QListBoxItem*)),
00125                         m(), SLOT(on_fileList_doubleClicked(QListBoxItem*)));
00126         }
00127         connect(m(), SIGNAL(updateRevDesc()), pv, SLOT(on_updateRevDesc()));
00128         connect(m(), SIGNAL(closeAllTabs()), pv, SLOT(on_closeAllTabs()));
00129         pv->st = st;
00130         UPDATE_DM_MASTER(pv, false);
00131 }
00132 
00133 void RevsView::on_loadCompleted(const FileHistory* fh, const QString& stats) {
00134 
00135         if (!git->isMainHistory(fh))
00136                 return;
00137 
00138         if (st.sha().isEmpty()) { // point to first one in list
00139 
00140                 QListViewItem* item = tab()->listViewLog->firstChild();
00141                 if (item) {
00142                         st.setSha(((ListViewItem*)item)->sha());
00143                         st.setSelectItem(true);
00144                 }
00145         }
00146         UPDATE();
00147         QApplication::postEvent(this, new MessageEvent(stats));
00148 }
00149 
00150 void RevsView::on_updateRevDesc() {
00151 
00152         bool showHeader = m()->ActShowDescHeader->isOn();
00153         SCRef d(git->getDesc(st.sha(), m()->shortLogRE, m()->longLogRE, showHeader));
00154         tab()->textBrowserDesc->setText(d);
00155         tab()->textBrowserDesc->setCursorPosition(0, 0);
00156 }
00157 
00158 bool RevsView::doUpdate(bool force) {
00159 
00160         force = force || m()->lineEditSHA->text().isEmpty();
00161 
00162         bool found = listViewLog->update();
00163 
00164         if (!found && !st.sha().isEmpty()) {
00165 
00166                 const QString tmp("Sorry, revision " + st.sha() +
00167                                   " has not been found in main view");
00168                 m()->statusBar()->message(tmp);
00169 
00170         } else { // sha could be NULL
00171 
00172                 if (st.isChanged(StateInfo::SHA) || force) {
00173 
00174                         updateLineEditSHA();
00175                         on_updateRevDesc();
00176                         m()->statusBar()->message(git->getRevInfo(st.sha()));
00177                 }
00178                 const RevFile* files = NULL;
00179                 bool newFiles = false;
00180 
00181                 if (st.isChanged(StateInfo::ANY & ~StateInfo::FILE_NAME) || force) {
00182 
00183                         listBoxFiles->clear();
00184                         files = git->getFiles(st.sha(), st.diffToSha(), st.allMergeFiles());
00185                         newFiles = true;
00186                 }
00187                 // call always to allow a simple refresh
00188                 listBoxFiles->update(files, newFiles);
00189 
00190                 // update the tree at startup or when releasing a no-match toolbar search
00191                 if (m()->treeView->isVisible() || st.sha(false).isEmpty())
00192                         treeView->update(); // blocking call
00193 
00194                 if (st.selectItem()) {
00195                         bool isDir = treeView->isDir(st.fileName());
00196                         m()->updateContextActions(st.sha(), st.fileName(), isDir, found);
00197                 }
00198                 // at the end update diffs that is the slowest and must be
00199                 // run after update of file list for 'diff to sha' to work
00200                 if (linkedPatchView) {
00201                         linkedPatchView->st = st;
00202                         UPDATE_DM_MASTER(linkedPatchView, force); // async call
00203                 }
00204         }
00205         QListViewItem* item = tab()->listViewLog->currentItem();
00206 
00207         if (item && item->isVisible() && !found && force) {
00208                 // we are in an inconsistent state: list view current item is
00209                 // not selected and secondary panes are empty.
00210                 // This could happen as example after removing a tree filter.
00211                 // At least populate secondary panes
00212                 st.setSha(((ListViewItem*)item)->sha());
00213                 //st.setSelectItem(false); TODO remove selectItem from state properties
00214                 UpdateDomainEvent* e = new UpdateDomainEvent(false);
00215                 customEvent(e); // will be queued immediately
00216                 delete e;
00217         }
00218         return (found || st.sha().isEmpty());
00219 }
00220 
00221 void RevsView::updateLineEditSHA(bool clear) {
00222 
00223         QLineEdit* l = m()->lineEditSHA;
00224 
00225         if (clear)
00226                 l->setText(""); // clears history
00227 
00228         else if (l->text() != st.sha()) {
00229 
00230                 if (l->text().isEmpty())
00231                         l->setText(st.sha()); // first rev clears history
00232                 else {
00233                         // setText() clears undo/redo history so
00234                         // we use clear() + insert() instead
00235                         l->clear();
00236                         l->insert(st.sha());
00237                 }
00238         }
00239         m()->ActBack->setEnabled(l->isUndoAvailable());
00240         m()->ActForward->setEnabled(l->isRedoAvailable());
00241 }
00242 
00243 void RevsView::on_lanesContextMenuRequested(SCList parents, SCList childs) {
00244 
00245         QPopupMenu contextMenu;
00246         uint i = 0;
00247         FOREACH_SL (it, childs)
00248                 contextMenu.insertItem("Child: " + git->getShortLog(*it), i++);
00249 
00250         FOREACH_SL (it, parents) {
00251 
00252                 QString log(git->getShortLog(*it));
00253                 if (log.isEmpty())
00254                         log = *it;
00255 
00256                 contextMenu.insertItem("Parent: " + log, i++);
00257         }
00258         int id = contextMenu.exec(QCursor::pos()); // modal exec
00259         if (id == -1)
00260                 return;
00261 
00262         int cc = (int)childs.count();
00263         SCRef target(id < cc ? childs[id] : parents[id - cc]);
00264         st.setSha(target);
00265         UPDATE();
00266 }
00267 
00268 void RevsView::on_droppedRevisions(SCList remoteRevs) {
00269 // remoteRevs is already sanity checked to contain some possible valid data
00270 
00271         if (isDropping()) // avoid reentrancy
00272                 return;
00273 
00274         QDir dr(m()->curDir + QGit::PATCHES_DIR);
00275         if (dr.exists()) {
00276                 const QString tmp("Please remove stale import directory " + dr.absPath());
00277                 m()->statusBar()->message(tmp);
00278                 return;
00279         }
00280         bool workDirOnly, fold;
00281         if (!m()->askApplyPatchParameters(&workDirOnly, &fold))
00282                 return;
00283 
00284         // ok, let's go
00285         setDropping(true);
00286         dr.setFilter(QDir::Files);
00287         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
00288         m()->raise();
00289         EM_PROCESS_EVENTS;
00290 
00291         uint revNum = 0;
00292         QStringList::const_iterator it(remoteRevs.constEnd());
00293         do {
00294                 --it;
00295 
00296                 QString tmp("Importing revision %1 of %2");
00297                 m()->statusBar()->message(tmp.arg(++revNum).arg(remoteRevs.count()));
00298 
00299                 SCRef sha((*it).section('@', 0, 0));
00300                 SCRef remoteRepo((*it).section('@', 1));
00301 
00302                 if (!dr.exists(remoteRepo))
00303                         break;
00304 
00305                 // we create patches one by one
00306                 if (!git->formatPatch(QStringList(sha), dr.absPath(), remoteRepo))
00307                         break;
00308 
00309                 dr.refresh();
00310                 if (dr.count() != 1) {
00311                         qDebug("ASSERT in on_droppedRevisions: found %i files "
00312                                "in %s", dr.count(), QGit::PATCHES_DIR.latin1());
00313                         break;
00314                 }
00315                 SCRef fn(dr.absFilePath(dr[0]));
00316                 if (!git->applyPatchFile(fn, fold, Git::optDragDrop))
00317                         break;
00318 
00319                 dr.remove(fn);
00320 
00321         } while (it != remoteRevs.constBegin());
00322 
00323         if (it == remoteRevs.constBegin())
00324                 m()->statusBar()->clear();
00325         else
00326                 m()->statusBar()->message("Failed to import revision " + QString::number(revNum--));
00327 
00328         if (workDirOnly && (revNum > 0))
00329                 git->resetCommits(revNum);
00330 
00331         dr.rmdir(dr.absPath()); // 'dr' must be already empty
00332         QApplication::restoreOverrideCursor();
00333         setDropping(false);
00334         m()->refreshRepo();
00335 }

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