mainimpl.cpp

Go to the documentation of this file.
00001 /*
00002         Description: qgit main 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 <qpushbutton.h>
00013 #include <qtoolbutton.h>
00014 #include <qpainter.h>
00015 #include <qstringlist.h>
00016 #include <qlistbox.h>
00017 #include <qfontmetrics.h>
00018 #include <qcombobox.h>
00019 #include <qeventloop.h>
00020 #include <qapplication.h>
00021 #include <qwidgetlist.h>
00022 #include <qmessagebox.h>
00023 #include <qstatusbar.h>
00024 #include <qheader.h>
00025 #include <qpopupmenu.h>
00026 #include <qcursor.h>
00027 #include <qfiledialog.h>
00028 #include <qsettings.h>
00029 #include <qaction.h>
00030 #include <qinputdialog.h>
00031 #include <qaccel.h>
00032 #include <qsplitter.h>
00033 #include <qtabwidget.h>
00034 #include <qobjectlist.h>
00035 #include <qlayout.h>
00036 #include <qtooltip.h>
00037 #include "config.h" // defines PACKAGE_VERSION
00038 #include "help.h"
00039 #include "helpbase.h"
00040 #include "consoleimpl.h"
00041 #include "customactionimpl.h"
00042 #include "settingsimpl.h"
00043 #include "revbase.h"
00044 #include "filebase.h"
00045 #include "patchbase.h"
00046 #include "common.h"
00047 #include "git.h"
00048 #include "listview.h"
00049 #include "treeview.h"
00050 #include "patchview.h"
00051 #include "fileview.h"
00052 #include "commitimpl.h"
00053 #include "revsview.h"
00054 #include "revdesc.h"
00055 #include "mainimpl.h"
00056 
00057 using namespace QGit;
00058 
00059 class MyPushButton : public QPushButton {
00060 public:
00061         MyPushButton(const QIconSet& i, SCRef t, QWidget* p) : QPushButton(i, t, p) {}
00062 
00063         // we need this kludge to workaround 'close tab' button wrong width
00064         // under some themes as Bluecurve or Platinum. What happens is that
00065         // widget sizeHint() returns a wrong value that is used by Qt to paint
00066         // the button.
00067         // Because it is not possible to set sizeHint property directly,
00068         // we need to sub class QPushButton and override this puppy
00069         virtual QSize sizeHint() const {
00070 
00071                 return iconSet()->iconSize(QIconSet::Small);
00072         }
00073 };
00074 
00075 MainImpl::MainImpl(SCRef cd, QWidget* p) : MainBase(p, "", Qt::WDestructiveClose) {
00076 
00077         EM_INIT(exExiting, "Exiting");
00078 
00079         git = new Git(this);
00080         QAccel* accel = new QAccel(this);
00081         setupAccelerator(accel);
00082         qApp->installEventFilter(this);
00083 
00084         // init native types
00085         setRepositoryBusy = false;
00086 
00087         // init filter match highlighters
00088         shortLogRE.setMinimal(true);
00089         shortLogRE.setCaseSensitive(false);
00090         longLogRE.setMinimal(true);
00091         longLogRE.setCaseSensitive(false);
00092 
00093         // set-up typewriter (fixed width) font
00094         QSettings set;
00095         QString font(set.readEntry(APP_KEY + FONT_KEY));
00096         if (font.isEmpty()) { // choose a sensible default
00097                 QFont fnt = QApplication::font();
00098                 fnt.setStyleHint(QFont::TypeWriter, QFont::PreferDefault);
00099                 fnt.setFixedPitch(true);
00100                 fnt.setFamily(fnt.defaultFamily()); // the family corresponding
00101                 font = fnt.toString();              // to current style hint
00102         }
00103         QGit::TYPE_WRITER_FONT.fromString(font);
00104 
00105         // set-up tab view
00106         delete tabWdg->currentPage(); // cannot be done in Qt Designer
00107         rv = new RevsView(this, git);
00108 
00109         // set-up tab corner widget ('close tab' button)
00110         pbCloseTabBase->hide();
00111         MyPushButton* pb = new MyPushButton(*pbCloseTabBase->iconSet(), "", tabWdg);
00112         QToolTip::add(pb, "Close tab");
00113         tabWdg->setCornerWidget(pb);
00114         connect(pb, SIGNAL(clicked()), this, SLOT(pushButtonCloseTab_clicked()));
00115         connect(this, SIGNAL(closeTabButtonEnabled(bool)), pb, SLOT(setEnabled(bool)));
00116 
00117         // set-up tree view
00118         treeView->hide();
00119 
00120         // set-up menu for recent visited repositories
00121         connect(File, SIGNAL(activated(int)), this, SLOT(on_openRecent_activated(int)));
00122         recentRepoMenuPos = 0;
00123         while (File->idAt(recentRepoMenuPos) != -1)
00124                 recentRepoMenuPos++;
00125         doUpdateRecentRepoMenu("");
00126 
00127         // set-up menu for custom actions
00128         connect(Actions, SIGNAL(activated(int)), this, SLOT(on_customAction_activated(int)));
00129         QStringList sl(QStringList::split(",", set.readEntry(APP_KEY + MCR_LIST_KEY, "")));
00130         doUpdateCustomActionMenu(sl);
00131 
00132         // create light and dark colors for alternate background
00133         QColor l(rv->tab()->listViewLog->paletteBackgroundColor());
00134         QColor d(int(l.red() * 0.97), int(l.green() * 0.97), int(l.blue() * 0.97));
00135         QGit::ODD_LINE_COL = l;
00136         QGit::EVEN_LINE_COL = d;
00137 
00138         // manual adjust lineEditSHA width
00139         QString tmp;
00140         tmp.fill('8', 41);
00141         int wd = lineEditSHA->fontMetrics().boundingRect(tmp).width();
00142         lineEditSHA->setMinimumWidth(wd);
00143 
00144         connect(git, SIGNAL(newRevsAdded(const FileHistory*, const QValueVector<QString>&)),
00145                 this, SLOT(on_newRevsAdded(const FileHistory*, const QValueVector<QString>&)));
00146 
00147         // connect cross-domain update signals
00148         connect(rv->tab()->listViewLog, SIGNAL(doubleClicked(QListViewItem*)),
00149                 this, SLOT(on_listViewLog_doubleClicked(QListViewItem*)));
00150 
00151         connect(rv->tab()->listBoxFiles, SIGNAL(doubleClicked(QListBoxItem*)),
00152                 this, SLOT(on_fileList_doubleClicked(QListBoxItem*)));
00153 
00154         connect(treeView, SIGNAL(doubleClicked(QListViewItem*)),
00155                 this, SLOT(on_treeView_doubleClicked(QListViewItem*)));
00156 
00157         // MainImpl c'tor is called before to enter event loop,
00158         // but some stuff requires event loop to init properly
00159         startUpDir = (cd.isEmpty()) ? QDir::current().absPath() : cd;
00160         QTimer::singleShot(10, this, SLOT(initWithEventLoopActive()));
00161 }
00162 
00163 void MainImpl::initWithEventLoopActive() {
00164 
00165         git->checkEnvironment();
00166         setRepository(startUpDir, false, false);
00167         startUpDir = ""; // one shot
00168 }
00169 
00170 void MainImpl::lineEditSHA_returnPressed() {
00171 
00172         rv->st.setSha(lineEditSHA->text());
00173         UPDATE_DOMAIN(rv);
00174 }
00175 
00176 void MainImpl::ActBack_activated() {
00177 
00178         lineEditSHA->undo(); // first for insert(text)
00179         if (lineEditSHA->text().isEmpty())
00180                 lineEditSHA->undo(); // double undo, see RevsView::updateLineEditSHA()
00181 
00182         lineEditSHA_returnPressed();
00183 }
00184 
00185 void MainImpl::ActForward_activated() {
00186 
00187         lineEditSHA->redo(); // redo skips empty fields so one is enough
00188         lineEditSHA_returnPressed();
00189 }
00190 
00191 // *************************** ExternalDiffViewer ***************************
00192 
00193 void MainImpl::ActExternalDiff_activated() {
00194 
00195         QStringList args;
00196         getExternalDiffArgs(&args);
00197         ExternalDiffProc* externalDiff = new ExternalDiffProc(args, this);
00198         externalDiff->setWorkingDirectory(curDir);
00199         if (!externalDiff->start()) {
00200                 QString text("Cannot start external viewer: ");
00201                 text.append(externalDiff->arguments()[0]);
00202                 QMessageBox::warning(this, "Error - QGit", text);
00203                 delete externalDiff;
00204         }
00205 }
00206 
00207 void MainImpl::getExternalDiffArgs(QStringList* args) {
00208 
00209         // save files to diff in working directory,
00210         // will be removed by ExternalDiffProc on exit
00211         QFileInfo f(rv->st.fileName());
00212         QString prevRevSha(rv->st.diffToSha());
00213         if (prevRevSha.isEmpty()) { // default to first parent
00214                 const Rev* r = git->revLookup(rv->st.sha());
00215                 prevRevSha = (r && r->parentsCount() > 0) ? r->parent(0) : rv->st.sha();
00216         }
00217         QString fName1(curDir + "/" + rv->st.sha().left(6) + "_" + f.fileName());
00218         QString fName2(curDir + "/" + prevRevSha.left(6) + "_" + f.fileName());
00219 
00220         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
00221 
00222         QByteArray fileContent;
00223         git->getFile(rv->st.fileName(), rv->st.sha(), NULL, &fileContent);
00224         if (!writeToFile(fName1,  QString(fileContent)))
00225                 statusBar()->message("Unable to save " + fName1);
00226 
00227         git->getFile(rv->st.fileName(), prevRevSha, NULL, &fileContent);
00228         if (!writeToFile(fName2, QString(fileContent)))
00229                 statusBar()->message("Unable to save " + fName2);
00230 
00231         // get external diff viewer
00232         QSettings settings;
00233         SCRef extDiff(settings.readEntry(APP_KEY + EXT_DIFF_KEY, EXT_DIFF_DEF));
00234 
00235         QApplication::restoreOverrideCursor();
00236 
00237         // finally set process arguments
00238         args->append(extDiff);
00239         args->append(fName2);
00240         args->append(fName1);
00241 }
00242 
00243 // ********************** Repository open or changed *************************
00244 
00245 void MainImpl::setRepository(SCRef newDir, bool refresh, bool keepSelection,
00246                              QStringList* filterList) {
00247 
00248         // Git::stop() and Git::init() are not re-entrant and must not
00249         // be active at the same time. Because Git::init calls processEvents()
00250         // we need to guard against reentrancy here
00251         if (setRepositoryBusy)
00252                 return;
00253 
00254         setRepositoryBusy = true;
00255 
00256         // check for a refresh or open of a new repository while in filtered view
00257         if (ActFilterTree->isOn() && filterList == NULL)
00258                 // toggle() triggers a refresh and a following setRepository()
00259                 // call that is filtered out by setRepositoryBusy guard flag
00260                 ActFilterTree->toggle(); // triggers ActFilterTree_toggled()
00261 
00262         try {
00263                 EM_REGISTER(exExiting);
00264 
00265                 bool archiveChanged;
00266                 curDir = git->getBaseDir(&archiveChanged, newDir);
00267 
00268                 if (!git->stop(archiveChanged)) { // stop all pending processes, non blocking
00269 
00270                         // some process is still running, schedule a deferred
00271                         // call and return, waiting for end of activity
00272                         setRepositoryBusy = false;
00273                         EM_REMOVE(exExiting);
00274 
00275                         // this object will delete itself when done
00276                         new setRepoDelayed(this, newDir, refresh, keepSelection, filterList);
00277                         return;
00278                 }
00279                 if (archiveChanged && refresh)
00280                         dbs("ASSERT in setRepository: different dir with no range select");
00281 
00282                 // now we can clear all our data
00283                 setCaption(curDir + " - QGit");
00284                 rv->clear(refresh && keepSelection);
00285                 if (archiveChanged)
00286                         emit closeAllTabs();
00287 
00288                 // disable all actions
00289                 updateGlobalActions(false);
00290                 updateContextActions("", "", false, false);
00291                 ActCommit_setEnabled(false);
00292 
00293                 if (ActFilterTree->isOn())
00294                         setCaption(caption() + " - FILTER ON < " + filterList->join(" ") + " >");
00295 
00296                 // tree name should be set before init because in case of
00297                 // StGIT archives the first revs are sent before init returns
00298                 QString n(curDir);
00299                 rv->treeView->setTreeName(n.prepend('/').section('/', -1, -1));
00300 
00301                 bool quit;
00302                 bool ok = git->init(curDir, !refresh, filterList, &quit); // blocking call
00303                 if (quit)
00304                         goto exit;
00305 
00306                 updateCommitMenu(ok && git->isStGITStack());
00307                 ActCheckWorkDir->setOn(testFlag(DIFF_INDEX_F)); // could be changed in Git::init()
00308 
00309                 if (ok) {
00310                         updateGlobalActions(true);
00311                         if (archiveChanged)
00312                                 updateRecentRepoMenu(curDir);
00313                 } else
00314                         statusBar()->message("Not a git archive");
00315 
00316 exit:
00317                 setRepositoryBusy = false;
00318                 EM_REMOVE(exExiting);
00319 
00320                 if (quit && !startUpDir.isEmpty())
00321                         close();
00322 
00323         } catch (int i) {
00324                 EM_REMOVE(exExiting);
00325 
00326                 if (EM_MATCH(i, exExiting, "loading repository")) {
00327                         EM_THROW_PENDING;
00328                         return;
00329                 }
00330                 const QString info("Exception \'" + EM_DESC(i) + "\' not "
00331                                    "handled in setRepository...re-throw");
00332                 dbs(info);
00333                 throw;
00334         }
00335 }
00336 
00337 void MainImpl::updateGlobalActions(bool b) {
00338 
00339         ActRefresh->setEnabled(b);
00340         ActCheckWorkDir->setEnabled(b);
00341         ActViewRev->setEnabled(b);
00342         ActViewDiff->setEnabled(b);
00343         ActViewDiffNewTab->setEnabled(b && firstTab<PatchView>());
00344         ActShowTree->setEnabled(b);
00345         ActMailApplyPatch->setEnabled(b);
00346         ActMailFormatPatch->setEnabled(b);
00347 
00348         rv->setEnabled(b);
00349 }
00350 
00351 void MainImpl::updateContextActions(SCRef newRevSha, SCRef newFileName,
00352                                     bool isDir, bool found) {
00353 
00354         bool pathActionsEnabled = !newFileName.isEmpty();
00355         bool fileActionsEnabled = (pathActionsEnabled && !isDir);
00356 
00357         ActViewFile->setEnabled(fileActionsEnabled);
00358         ActViewFileNewTab->setEnabled(fileActionsEnabled && firstTab<FileView>());
00359         ActExternalDiff->setEnabled(fileActionsEnabled);
00360         ActSaveFile->setEnabled(fileActionsEnabled);
00361         ActFilterTree->setEnabled(pathActionsEnabled || ActFilterTree->isOn());
00362 
00363         bool isTag, isUnApplied, isApplied;
00364         isTag = isUnApplied = isApplied = false;
00365 
00366         if (found) {
00367                 const Rev* r = git->revLookup(newRevSha);
00368                 isTag = git->checkRef(newRevSha, Git::TAG);
00369                 isUnApplied = r->isUnApplied;
00370                 isApplied = r->isApplied;
00371         }
00372         ActTag->setEnabled(found && (newRevSha != ZERO_SHA) && !isUnApplied);
00373         ActTagDelete->setEnabled(found && isTag && (newRevSha != ZERO_SHA) && !isUnApplied);
00374         ActPush->setEnabled(found && isUnApplied && git->isNothingToCommit());
00375         ActPop->setEnabled(found && isApplied && git->isNothingToCommit());
00376 }
00377 
00378 // ************************* cross-domain update Actions ***************************
00379 
00380 void MainImpl::on_listViewLog_doubleClicked(QListViewItem* item) {
00381 
00382         if (item && ActViewDiff->isEnabled())
00383                 ActViewDiff->activate();
00384 }
00385 
00386 void MainImpl::on_histListView_doubleClicked(QListViewItem* item) {
00387 
00388         if (item && ActViewRev->isEnabled())
00389                 ActViewRev->activate();
00390 }
00391 
00392 void MainImpl::on_fileList_doubleClicked(QListBoxItem* item) {
00393 
00394         if (item && rv->st.isMerge() && item->prev() == 0)
00395                 return;
00396 
00397         if (item && item->listBox() == rv->tab()->listBoxFiles && ActViewDiff->isEnabled())
00398                 ActViewDiff->activate();
00399 
00400         if (item && item->listBox() != rv->tab()->listBoxFiles && ActViewFile->isEnabled())
00401                 ActViewFile->activate();
00402 }
00403 
00404 void MainImpl::on_treeView_doubleClicked(QListViewItem* item) {
00405 
00406         if (item && ActViewFile->isEnabled())
00407                 ActViewFile->activate();
00408 }
00409 
00410 void MainImpl::pushButtonCloseTab_clicked() {
00411 
00412         int curPos = tabWdg->currentPageIndex();
00413         Domain* t;
00414         switch (currentTabType(&t)) {
00415         case TAB_REV:
00416                 break;
00417         case TAB_PATCH:
00418                 t->deleteWhenDone();
00419                 emit tabClosed(curPos);
00420                 ActViewDiffNewTab->setEnabled(ActViewDiff->isEnabled() && firstTab<PatchView>());
00421                 break;
00422         case TAB_FILE:
00423                 t->deleteWhenDone();
00424                 emit tabClosed(curPos);
00425                 ActViewFileNewTab->setEnabled(ActViewFile->isEnabled() && firstTab<FileView>());
00426                 break;
00427         default:
00428                 dbs("ASSERT in pushButtonCloseTab_clicked: unknown current page");
00429                 break;
00430         }
00431 }
00432 
00433 void MainImpl::ActViewRev_activated() {
00434 
00435         Domain* t;
00436         if (currentTabType(&t) == TAB_FILE) {
00437                 rv->st = t->st;
00438                 UPDATE_DOMAIN(rv);
00439         }
00440         tabWdg->setCurrentPage(rv->tabPos());
00441 }
00442 
00443 void MainImpl::ActViewFile_activated() {
00444 
00445         openFileTab(firstTab<FileView>());
00446 }
00447 
00448 void MainImpl::ActViewFileNewTab_activated() {
00449 
00450         openFileTab();
00451 }
00452 
00453 void MainImpl::openFileTab(FileView* fv) {
00454 
00455         if (!fv) {
00456                 fv = new FileView(this, git);
00457 
00458                 connect(fv->tab()->histListView, SIGNAL(doubleClicked(QListViewItem*)),
00459                         this, SLOT(on_histListView_doubleClicked(QListViewItem*)));
00460 
00461                 connect(this, SIGNAL(closeAllTabs()), fv, SLOT(on_closeAllTabs()));
00462 
00463                 ActViewFileNewTab->setEnabled(ActViewFile->isEnabled());
00464         }
00465         tabWdg->setCurrentPage(fv->tabPos());
00466         fv->st = rv->st;
00467         UPDATE_DOMAIN(fv);
00468 }
00469 
00470 void MainImpl::ActViewDiff_activated() {
00471 
00472         Domain* t;
00473         if (currentTabType(&t) == TAB_FILE) {
00474                 rv->st = t->st;
00475                 UPDATE_DOMAIN(rv);
00476         }
00477         rv->viewPatch(false);
00478         ActViewDiffNewTab->setEnabled(true);
00479 
00480         if (toolButtonFilter->isOn() || toolButtonBold->isOn()) {
00481                 bool isRegExp = (cmbSearch->currentItem() == 6);
00482                 emit highlightPatch(lineEditFilter->text(), isRegExp);
00483         }
00484 }
00485 
00486 void MainImpl::ActViewDiffNewTab_activated() {
00487 
00488         rv->viewPatch(true);
00489 }
00490 
00491 bool MainImpl::eventFilter(QObject* obj, QEvent* ev) {
00492 
00493         if (ev->type() == QEvent::Wheel) {
00494 
00495                 QWheelEvent* e = static_cast<QWheelEvent*>(ev);
00496                 if (e->state() == Qt::AltButton) {
00497 
00498                         int idx = tabWdg->currentPageIndex();
00499                         if (e->delta() < 0)
00500                                 idx = (++idx == tabWdg->count()) ? 0 : idx;
00501                         else
00502                                 idx = (--idx < 0) ? tabWdg->count() - 1 : idx;
00503 
00504                         tabWdg->setCurrentPage(idx);
00505                         return true;
00506                 }
00507         }
00508         return MainBase::eventFilter(obj, ev);
00509 }
00510 
00511 // ******************************* Filter ******************************
00512 
00513 void MainImpl::on_newRevsAdded(const FileHistory* fh, const QValueVector<QString>&) {
00514 
00515         if (!git->isMainHistory(fh))
00516                 return;
00517 
00518         if (toolButtonFilter->isOn())
00519                 toolButtonFilter_toggled(true); // filter again on new arrived data
00520 
00521         if (toolButtonBold->isOn())
00522                 toolButtonBold_toggled(true); // filter again on new arrived data
00523 
00524         // first rev could be a StGIT unapplied patch so check more then once
00525         if (   (!git->isNothingToCommit() || git->isUnknownFiles())
00526             && !ActCommit->isEnabled()
00527             && !git->isCommittingMerge())
00528                 ActCommit_setEnabled(true);
00529 }
00530 
00531 void MainImpl::lineEditFilter_returnPressed() {
00532 
00533         toolButtonFilter->setOn(true);
00534 }
00535 
00536 void MainImpl::toolButtonFilter_toggled(bool isOn) {
00537 
00538         toolButtonBold->setEnabled(!isOn);
00539         toolButtonFilter->setEnabled(false);
00540         filterList(isOn, false); // blocking call
00541         toolButtonFilter->setEnabled(true);
00542 }
00543 
00544 void MainImpl::toolButtonBold_toggled(bool isOn) {
00545 
00546         toolButtonFilter->setEnabled(!isOn);
00547         toolButtonBold->setEnabled(false);
00548         filterList(isOn, true); // blocking call
00549         toolButtonBold->setEnabled(true);
00550 }
00551 
00552 void MainImpl::filterList(bool isOn, bool onlyHighlight) {
00553 
00554         lineEditFilter->setEnabled(!isOn);
00555         cmbSearch->setEnabled(!isOn);
00556 
00557         SCRef filter(lineEditFilter->text());
00558         if (filter.isEmpty())
00559                 return;
00560 
00561         QMap<QString, bool> shaMap;
00562         bool descNeedsUpdate, patchNeedsUpdate, isRegExp;
00563         descNeedsUpdate = patchNeedsUpdate = isRegExp = false;
00564         int idx = cmbSearch->currentItem(), colNum = 0;
00565         if (isOn) {
00566                 switch (idx) {
00567                 case 0:
00568                         colNum = LOG_COL;
00569                         shortLogRE.setPattern(filter);
00570                         descNeedsUpdate = true;
00571                         break;
00572                 case 1:
00573                         colNum = LOG_MSG_COL;
00574                         longLogRE.setPattern(filter);
00575                         descNeedsUpdate = true;
00576                         break;
00577                 case 2:
00578                         colNum = AUTH_COL;
00579                         break;
00580                 case 3:
00581                         colNum = COMMIT_COL;
00582                         break;
00583                 case 4:
00584                 case 5:
00585                 case 6:
00586                         colNum = SHA_MAP_COL;
00587                         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
00588                         EM_PROCESS_EVENTS; // to paint wait cursor
00589                         if (idx == 4)
00590                                 git->getFileFilter(filter, shaMap);
00591                         else {
00592                                 isRegExp = (idx == 6);
00593                                 if (!git->getPatchFilter(filter, isRegExp, shaMap)) {
00594                                         QApplication::restoreOverrideCursor();
00595                                         toolButtonFilter->toggle();
00596                                         return;
00597                                 }
00598                                 patchNeedsUpdate = (shaMap.count() > 0);
00599                         }
00600                         QApplication::restoreOverrideCursor();
00601                         break;
00602                 }
00603         } else {
00604                 patchNeedsUpdate = ((idx == 5) || (idx == 6));
00605                 descNeedsUpdate = !(shortLogRE.isEmpty() && longLogRE.isEmpty());
00606                 shortLogRE.setPattern("");
00607                 longLogRE.setPattern("");
00608         }
00609         bool evenLine = false;
00610         int visibleCnt = 0;
00611         QRegExp re(filter, false, true);
00612         QListViewItem* firstItem = NULL;
00613         QListView* lv = rv->tab()->listViewLog;
00614         QListViewItemIterator it(lv);
00615         while (it.current()) {
00616                 ListViewItem* item = static_cast<ListViewItem*>(it.current());
00617                 if (isOn) {
00618                         if (passFilter(item, re, colNum, shaMap)) {
00619                                 if (onlyHighlight)
00620                                         item->setHighlighted(true);
00621                                 else {
00622                                         item->setEven(evenLine);
00623                                         evenLine = !evenLine;
00624                                 }
00625                                 visibleCnt++;
00626                                 if (visibleCnt == 1) // only once
00627                                         firstItem = item;
00628 
00629                         } else if (!onlyHighlight)
00630                                 item->setVisible(false); // does not change listView current item
00631                 } else {
00632                         item->setHighlighted(false);
00633                         item->setEven(evenLine);
00634                         evenLine = !evenLine;
00635                         if (!item->isVisible())
00636                                 item->setVisible(true);
00637                 }
00638                 ++it;
00639         }
00640         lv->triggerUpdate(); // for onlyHighlight case
00641 
00642         // set new selection, could be NULL
00643         QListViewItem* curItem = lv->currentItem();
00644         QListViewItem* newItem = (curItem && curItem->isVisible()) ? curItem : firstItem;
00645         rv->st.setSha(newItem ? ((ListViewItem*)newItem)->sha() : "");
00646         UPDATE_DOMAIN(rv);
00647 
00648         // if current item does not change we need to force an update
00649         if (descNeedsUpdate && (newItem == curItem))
00650                 emit updateRevDesc();
00651 
00652         if (patchNeedsUpdate)
00653                 emit highlightPatch(isOn ? filter : "", isRegExp);
00654 
00655         QString msg;
00656         if (isOn)
00657                 msg = QString("Found %1 matches. Toggle filter/highlight "
00658                       "button to remove the filter").arg(visibleCnt);
00659 
00660         // deferred message, let update first
00661         QApplication::postEvent(rv, new MessageEvent(msg));
00662 }
00663 
00664 bool MainImpl::passFilter(ListViewItem* item, const QRegExp& filter, int colNum,
00665                           const QMap<QString, bool>& shaMap) {
00666 
00667         if (colNum == SHA_MAP_COL)
00668                 // in this case shaMap contains all good sha to search for
00669                 return shaMap.contains(item->sha());
00670 
00671         QString field;
00672         if (colNum != LOG_MSG_COL && colNum != COMMIT_COL) {
00673                 int adj = -1;
00674                 field = item->text(colNum + adj);
00675         }
00676         if (field.isEmpty()) { // still not setup or colNum is a dummy one
00677 
00678                 const Rev* c = git->revLookup(item->sha());
00679                 if (colNum == LOG_COL)
00680                         field = c->shortLog();
00681                 else if (colNum == AUTH_COL)
00682                         field = c->author();
00683                 else if (colNum == LOG_MSG_COL)
00684                         field = c->longLog();
00685                 else if (colNum == COMMIT_COL)
00686                         field = item->sha();
00687         }
00688         // wildcard search, case insensitive
00689         return (field.find(filter) != -1);
00690 }
00691 
00692 void MainImpl::customEvent(QCustomEvent* e) {
00693 
00694         BaseEvent* de = dynamic_cast<BaseEvent*>(e);
00695         if (de == NULL) {
00696                 MainBase::customEvent(e);
00697                 return;
00698         }
00699         SCRef data = de->myData();
00700 
00701         switch (e->type()) {
00702         case ERROR_EV: {
00703                 QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
00704                 EM_PROCESS_EVENTS;
00705                 MainExecErrorEvent* me = (MainExecErrorEvent*)e;
00706                 QString text("An error occurred while executing command:\n\n");
00707                 text.append(me->command() + "\n\nGit says: \n\n" + me->report());
00708                 QMessageBox::warning(this, "Error - QGit", text);
00709                 QApplication::restoreOverrideCursor(); }
00710                 break;
00711         case MSG_EV:
00712                 statusBar()->message(data);
00713                 break;
00714         case POPUP_LIST_EV:
00715                 doContexPopup(data);
00716                 break;
00717         case POPUP_FILE_EV:
00718         case POPUP_TREE_EV:
00719                 doFileContexPopup(data, e->type());
00720                 break;
00721         default:
00722                 dbp("ASSERT in MainImpl::customEvent unhandled event %1", e->type());
00723                 break;
00724         }
00725 }
00726 
00727 int MainImpl::currentTabType(Domain** t) {
00728 
00729         *t = NULL;
00730         int curPos = tabWdg->currentPageIndex();
00731         if (curPos == rv->tabPos()) {
00732                 *t = rv;
00733                 return TAB_REV;
00734         }
00735         QPtrList<PatchView>* l = getTabs<PatchView>(curPos);
00736         if (l->count() > 0) {
00737                 *t = l->first();
00738                 delete l;
00739                 return TAB_PATCH;
00740         }
00741         delete l;
00742         QPtrList<FileView>* l2 = getTabs<FileView>(curPos);
00743         if (l2->count() > 0) {
00744                 *t = l2->first();
00745                 delete l2;
00746                 return TAB_FILE;
00747         }
00748         if (l2->count() > 0)
00749                 dbs("ASSERT in tabType file not found");
00750 
00751         delete l2;
00752         return -1;
00753 }
00754 
00755 template<class X> QPtrList<X>* MainImpl::getTabs(int tabPos) {
00756 
00757         X dummy;
00758         QObjectList* l = this->queryList(dummy.className());
00759         QPtrList<X>* ret = new QPtrList<X>;
00760         for (QObject* item = l->first(); item; item = l->next()) {
00761 
00762                 X* x = static_cast<X*>(item);
00763                 if (tabPos == -1 || x->tabPos() == tabPos)
00764                         ret->append(x);
00765         }
00766         delete l;
00767         return ret; // 'ret' must be deleted by caller
00768 }
00769 
00770 template<class X> X* MainImpl::firstTab(int startPos) {
00771 
00772         int minVal = 99, firstVal = 99;
00773         X* min = NULL;
00774         X* first = NULL;
00775         QPtrList<X>* l = getTabs<X>();
00776         for (X* d = l->first(); d; d = l->next()) {
00777 
00778                 if (d->tabPos() < minVal) {
00779                         minVal = d->tabPos();
00780                         min = d;
00781                 }
00782                 if (d->tabPos() < firstVal && d->tabPos() > startPos) {
00783                         firstVal = d->tabPos();
00784                         first = d;
00785                 }
00786         }
00787         delete l;
00788         return first ? first : min;
00789 }
00790 
00791 void MainImpl::tabWdg_currentChanged(QWidget* w) {
00792 
00793         if (w == NULL)
00794                 return;
00795 
00796         // set correct focus for keyboard browsing
00797         Domain* t;
00798         switch (currentTabType(&t)) {
00799         case TAB_REV:
00800                 static_cast<RevsView*>(t)->tab()->listViewLog->setFocus();
00801                 emit closeTabButtonEnabled(false);
00802                 break;
00803         case TAB_PATCH:
00804                 static_cast<PatchView*>(t)->tab()->textEditDiff->setFocus();
00805                 emit closeTabButtonEnabled(true);
00806                 break;
00807         case TAB_FILE:
00808                 static_cast<FileView*>(t)->tab()->histListView->setFocus();
00809                 emit closeTabButtonEnabled(true);
00810                 break;
00811         default:
00812                 dbs("ASSERT in tabWdg_currentChanged: unknown current page");
00813                 break;
00814         }
00815 }
00816 
00817 void MainImpl::accelActivated(int id) {
00818 
00819         switch (id) {
00820         case KEY_UP:
00821                 scrollListView(-1);
00822                 break;
00823         case KEY_DOWN:
00824                 scrollListView(1);
00825                 break;
00826         case SHIFT_KEY_UP:
00827                 goMatch(-1);
00828                 break;
00829         case SHIFT_KEY_DOWN:
00830                 goMatch(1);
00831                 break;
00832         case KEY_LEFT:
00833                 ActBack_activated();
00834                 break;
00835         case KEY_RIGHT:
00836                 ActForward_activated();
00837                 break;
00838         case CTRL_PLUS:
00839                 adjustFontSize(1);
00840                 break;
00841         case CTRL_MINUS:
00842                 adjustFontSize(-1);
00843                 break;
00844         case KEY_U:
00845                 scrollTextEdit(-18);
00846                 break;
00847         case KEY_D:
00848                 scrollTextEdit(18);
00849                 break;
00850         case KEY_DELETE:
00851         case KEY_B:
00852         case KEY_BCKSPC:
00853                 scrollTextEdit(-1);
00854                 break;
00855         case KEY_SPACE:
00856                 scrollTextEdit(1);
00857                 break;
00858         case KEY_R:
00859                 tabWdg->setCurrentPage(rv->tabPos());
00860                 break;
00861         case KEY_P:
00862         case KEY_F:{
00863                 int cp = tabWdg->currentPageIndex();
00864                 Domain* d = (id == KEY_P) ? static_cast<Domain*>(firstTab<PatchView>(cp)) :
00865                                             static_cast<Domain*>(firstTab<FileView>(cp));
00866                 if (d)
00867                         tabWdg->setCurrentPage(d->tabPos()); }
00868                 break;
00869         }
00870 }
00871 
00872 void MainImpl::setupAccelerator(QAccel* accel) {
00873 
00874         accel->insertItem(Key_Up,         KEY_UP);
00875         accel->insertItem(Key_I,          KEY_UP);
00876         accel->insertItem(Key_Down,       KEY_DOWN);
00877         accel->insertItem(Key_N,          KEY_DOWN);
00878         accel->insertItem(Key_K,          KEY_DOWN);
00879         accel->insertItem(Key_Left,       KEY_LEFT);
00880         accel->insertItem(Key_Right,      KEY_RIGHT);
00881         accel->insertItem(SHIFT+Key_Up,   SHIFT_KEY_UP);
00882         accel->insertItem(SHIFT+Key_Down, SHIFT_KEY_DOWN);
00883         accel->insertItem(CTRL+Key_Plus,  CTRL_PLUS);
00884         accel->insertItem(CTRL+Key_Minus, CTRL_MINUS);
00885         accel->insertItem(Key_U,          KEY_U);
00886         accel->insertItem(Key_D,          KEY_D);
00887         accel->insertItem(Key_Delete,     KEY_DELETE);
00888         accel->insertItem(Key_B,          KEY_B);
00889         accel->insertItem(Key_Backspace,  KEY_BCKSPC);
00890         accel->insertItem(Key_Space,      KEY_SPACE);
00891         accel->insertItem(Key_R,          KEY_R);
00892         accel->insertItem(Key_P,          KEY_P);
00893         accel->insertItem(Key_F,          KEY_F);
00894 
00895         connect(accel, SIGNAL(activated(int)), this, SLOT(accelActivated(int)));
00896 }
00897 
00898 void MainImpl::goMatch(int delta) {
00899 
00900         if (!toolButtonBold->isOn())
00901                 return;
00902 
00903         QListViewItemIterator it(rv->tab()->listViewLog->currentItem());
00904         if (delta > 0)
00905                 ++it;
00906         else
00907                 --it;
00908 
00909         while (it.current()) {
00910                 ListViewItem* item = static_cast<ListViewItem*>(it.current());
00911                 if (item->highlighted()) {
00912                         QListView* lv = rv->tab()->listViewLog;
00913                         lv->clearSelection();
00914                         lv->setCurrentItem(item);
00915                         lv->ensureItemVisible(lv->currentItem());
00916                         return;
00917                 }
00918                 if (delta > 0)
00919                         ++it;
00920                 else
00921                         --it;
00922         }
00923 }
00924 
00925 QTextEdit* MainImpl::getCurrentTextEdit() {
00926 
00927         QTextEdit* te = NULL;
00928         Domain* t;
00929         switch (currentTabType(&t)) {
00930         case TAB_REV:
00931                 te = static_cast<RevsView*>(t)->tab()->textBrowserDesc;
00932                 break;
00933         case TAB_PATCH:
00934                 te = static_cast<PatchView*>(t)->tab()->textEditDiff;
00935                 break;
00936         case TAB_FILE:
00937                 te = static_cast<FileView*>(t)->tab()->textEditFile;
00938                 break;
00939         default:
00940                 break;
00941         }
00942         return te;
00943 }
00944 
00945 void MainImpl::scrollTextEdit(int delta) {
00946 
00947         QTextEdit* te = getCurrentTextEdit();
00948         if (!te)
00949                 return;
00950 
00951         int h = te->visibleHeight();
00952         int ls = te->fontMetrics().lineSpacing();
00953         if (delta == 1 || delta == -1) {
00954                 te->scrollBy(0, delta * (h - ls));
00955                 return;
00956         }
00957         te->scrollBy(0, delta * ls);
00958 }
00959 
00960 void MainImpl::scrollListView(int delta) {
00961 
00962         QWidget* lv = NULL;
00963         Domain* t;
00964         switch (currentTabType(&t)) {
00965         case TAB_REV:
00966                 lv = static_cast<RevsView*>(t)->tab()->listViewLog;
00967                 break;
00968         case TAB_FILE:
00969                 lv = static_cast<FileView*>(t)->tab()->histListView;
00970                 break;
00971         default:
00972                 lv = qApp->focusWidget();
00973                 break;
00974         }
00975         if (!lv)
00976                 return;
00977 
00978         int key = (delta == 1) ? Key_Down : Key_Up;
00979         QKeyEvent p(QEvent::KeyPress, key, 0, 0);
00980         QKeyEvent r(QEvent::KeyRelease, key, 0, 0);
00981         QApplication::sendEvent(lv, &p);
00982         QApplication::sendEvent(lv, &r);
00983 }
00984 
00985 void MainImpl::adjustFontSize(int delta) {
00986 // font size is changed on a 'per instance' base and only on list views
00987 
00988         int ps = listViewFont.pointSize() + delta;
00989         if (ps < 2)
00990                 return;
00991 
00992         listViewFont.setPointSize(ps);
00993         emit repaintListViews(listViewFont);
00994 }
00995 
00996 // ****************************** Menu *********************************
00997 
00998 void MainImpl::updateCommitMenu(bool isStGITStack) {
00999 
01000         int i = 0;
01001         bool found = false;
01002         while (!found && Edit->idAt(i) != -1) {
01003                 SCRef txt(Edit->text(Edit->idAt(i++)));
01004                 found = (txt == "&Commit..." || txt == "St&GIT patch...");
01005         }
01006         if (!found)
01007                 return;
01008 
01009         const QString newText(isStGITStack ? "St&GIT patch..." : "&Commit...");
01010         Edit->changeItem(Edit->idAt(--i), newText);
01011 }
01012 
01013 void MainImpl::updateRecentRepoMenu(SCRef newEntry) {
01014 
01015         // update menu of all windows
01016         QWidgetList* list = QApplication::topLevelWidgets();
01017         QWidgetListIt it(*list);
01018         while (it.current() != 0) {
01019                 MainImpl* w = dynamic_cast<MainImpl*>(it.current());
01020                 if (w)
01021                         w->doUpdateRecentRepoMenu(newEntry);
01022                 ++it;
01023         }
01024         delete list;
01025 }
01026 
01027 void MainImpl::doUpdateRecentRepoMenu(SCRef newEntry) {
01028 
01029         while (File->idAt(recentRepoMenuPos) != -1)
01030                 File->removeItemAt(recentRepoMenuPos); // removes also any separator
01031 
01032         QSettings settings;
01033         SCRef r(settings.readEntry(APP_KEY + REC_REP_KEY, ""));
01034         if (r.isEmpty() && newEntry.isEmpty())
01035                 return;
01036 
01037         QStringList recents(QStringList::split(',', r));
01038         QStringList::iterator it = recents.find(newEntry);
01039         if (it != recents.end())
01040                 recents.remove(it);
01041 
01042         if (!newEntry.isEmpty())
01043                 recents.prepend(newEntry);
01044 
01045         File->insertSeparator();
01046 
01047         QStringList::const_iterator it2 = recents.constBegin();
01048         for (int i = 1; it2 != recents.constEnd() && i <= MAX_RECENT_REPOS; ++it2, ++i)
01049                 File->insertItem(QString::number(i) + " " + *it2);
01050 
01051         for (int i = recents.count() - MAX_RECENT_REPOS; i > 0; i--)
01052                 recents.pop_back();
01053 
01054         settings.writeEntry(APP_KEY + REC_REP_KEY, recents.join(","));
01055 }
01056 
01057 void MainImpl::doContexPopup(SCRef sha) {
01058 
01059         // we need to use popup() to be non blocking and we need a
01060         // global scope because we use a signal/slot connection
01061         delete contextMenu;
01062         delete contextSubMenu;
01063         delete contextRmtMenu;
01064         contextMenu = new QPopupMenu(this);
01065         contextSubMenu = new QPopupMenu(this);
01066         contextRmtMenu = new QPopupMenu(this);
01067         connect(contextMenu, SIGNAL(activated(int)), this, SLOT(on_goRef_activated(int)));
01068         connect(contextSubMenu, SIGNAL(activated(int)), this, SLOT(on_goRef_activated(int)));
01069         connect(contextRmtMenu, SIGNAL(activated(int)), this, SLOT(on_goRef_activated(int)));
01070 
01071         Domain* t;
01072         int tt = currentTabType(&t);
01073         bool isRevPage = (tt == TAB_REV);
01074         bool isPatchPage = (tt == TAB_PATCH);
01075         bool isFilePage = (tt == TAB_FILE);
01076 
01077         if (!isFilePage && ActCheckWorkDir->isEnabled()) {
01078                 ActCheckWorkDir->addTo(contextMenu);
01079                 contextMenu->insertSeparator();
01080         }
01081         if (isFilePage && ActViewRev->isEnabled())
01082                 ActViewRev->addTo(contextMenu);
01083 
01084         if (!isPatchPage && ActViewDiff->isEnabled())
01085                 ActViewDiff->addTo(contextMenu);
01086 
01087         if (isRevPage && ActViewDiffNewTab->isEnabled())
01088                 ActViewDiffNewTab->addTo(contextMenu);
01089 
01090         if (!isFilePage && ActExternalDiff->isEnabled())
01091                 ActExternalDiff->addTo(contextMenu);
01092 
01093         if (isRevPage) {
01094                 if (ActCommit->isEnabled() && (sha == ZERO_SHA))
01095                         ActCommit->addTo(contextMenu);
01096                 if (ActTag->isEnabled())
01097                         ActTag->addTo(contextMenu);
01098                 if (ActTagDelete->isEnabled())
01099                         ActTagDelete->addTo(contextMenu);
01100                 if (ActMailFormatPatch->isEnabled())
01101                         ActMailFormatPatch->addTo(contextMenu);
01102                 if (ActPush->isEnabled())
01103                         ActPush->addTo(contextMenu);
01104                 if (ActPop->isEnabled())
01105                         ActPop->addTo(contextMenu);
01106 
01107                 const QStringList& bn(git->getAllRefNames(Git::BRANCH, Git::optOnlyLoaded));
01108                 const QStringList& rbn(git->getAllRefNames(Git::RMT_BRANCH, Git::optOnlyLoaded));
01109                 const QStringList& tn(git->getAllRefNames(Git::TAG, Git::optOnlyLoaded));
01110                 if (bn.empty() && rbn.empty() && tn.empty()) {
01111                         contextMenu->exec(QCursor::pos());
01112                         return;
01113                 }
01114                 int id = 1; // ref names have id > 0 to disambiguate from actions
01115                 if (!rbn.empty()) {
01116                         QStringList::const_iterator it = rbn.constBegin();
01117                         for ( ; it != rbn.constEnd(); ++it, id++)
01118                                 contextRmtMenu->insertItem(*it, id);
01119                 }
01120                 int rbnCnt = contextRmtMenu->count();
01121                 if (rbnCnt > 0)
01122                         contextMenu->insertItem("Remote branches", contextRmtMenu);
01123 
01124                 if (!bn.empty()) {
01125                         contextMenu->insertSeparator();
01126                         QStringList::const_iterator it = bn.constBegin();
01127                         for ( ; it != bn.constEnd(); ++it, id++) {
01128 
01129                                 if (id < MAX_MENU_ENTRIES + rbnCnt)
01130                                         contextMenu->insertItem(*it, id);
01131                                 else
01132                                         contextSubMenu->insertItem(*it, id);
01133                         }
01134                 }
01135                 if (!tn.empty()) {
01136                         contextMenu->insertSeparator();
01137                         QStringList::const_iterator it = tn.constBegin();
01138                         for ( ; it != tn.constEnd(); ++it, id++) {
01139 
01140                                 if (id < MAX_MENU_ENTRIES + rbnCnt)
01141                                         contextMenu->insertItem(*it, id);
01142                                 else
01143                                         contextSubMenu->insertItem(*it, id);
01144                         }
01145                 }
01146                 if (contextSubMenu->count() > 0)
01147                         contextMenu->insertItem("More...", contextSubMenu);
01148         }
01149         contextMenu->popup(QCursor::pos());
01150 }
01151 
01152 void MainImpl::doFileContexPopup(SCRef fileName, int type) {
01153 
01154         QPopupMenu contextMenu;
01155 
01156         Domain* t;
01157         int tt = currentTabType(&t);
01158         bool isRevPage = (tt == TAB_REV);
01159         bool isPatchPage = (tt == TAB_PATCH);
01160         bool isDir = rv->treeView->isDir(fileName);
01161 
01162         if (type == POPUP_FILE_EV)
01163                 if (!isPatchPage && ActViewDiff->isEnabled())
01164                         ActViewDiff->addTo(&contextMenu);
01165 
01166         if (!isDir && ActViewFile->isEnabled())
01167                 ActViewFile->addTo(&contextMenu);
01168 
01169         if (!isDir && ActViewFileNewTab->isEnabled())
01170                 ActViewFileNewTab->addTo(&contextMenu);
01171 
01172         if (!isRevPage && (type == POPUP_FILE_EV) && ActViewRev->isEnabled())
01173                 ActViewRev->addTo(&contextMenu);
01174 
01175         if (ActFilterTree->isEnabled())
01176                 ActFilterTree->addTo(&contextMenu);
01177 
01178         if (!isDir) {
01179                 if (ActSaveFile->isEnabled())
01180                         ActSaveFile->addTo(&contextMenu);
01181                 if ((type == POPUP_FILE_EV) && ActExternalDiff->isEnabled())
01182                         ActExternalDiff->addTo(&contextMenu);
01183         }
01184         contextMenu.exec(QCursor::pos());
01185 }
01186 
01187 void MainImpl::on_goRef_activated(int id) {
01188 
01189         if (id <= 0) // not a tag name entry
01190                 return;
01191 
01192         SCRef refSha(git->getRefSha(contextMenu->text(id)));
01193         rv->st.setSha(refSha);
01194         UPDATE_DOMAIN(rv);
01195 }
01196 
01197 void MainImpl::ActSplitView_activated() {
01198 
01199         Domain* t;
01200         switch (currentTabType(&t)) {
01201         case TAB_REV: {
01202                 RevsView* rv = static_cast<RevsView*>(t);
01203                 QWidget* w = rv->tab()->textBrowserDesc;
01204                 QSplitter* sp = static_cast<QSplitter*>(w->parent());
01205                 sp->setHidden(w->isVisible()); }
01206                 break;
01207         case TAB_PATCH: {
01208                 PatchView* pv = static_cast<PatchView*>(t);
01209                 QWidget* w = pv->tab()->textBrowserDesc;
01210                 w->setHidden(w->isVisible()); }
01211                 break;
01212         case TAB_FILE: {
01213                 FileView* fv = static_cast<FileView*>(t);
01214                 QWidget* w = fv->tab()->histListView;
01215                 w->setHidden(w->isVisible()); }
01216                 break;
01217         default:
01218                 dbs("ASSERT in ActSplitView_activated: unknown current page");
01219                 break;
01220         }
01221 }
01222 
01223 void MainImpl::ActShowDescHeader_toggled(bool) {
01224 
01225         emit updateRevDesc();
01226 }
01227 
01228 void MainImpl::ActShowTree_toggled(bool b) {
01229 
01230         if (b) {
01231                 treeView->show();
01232                 UPDATE_DOMAIN(rv);
01233         } else
01234                 treeView->hide();
01235 }
01236 
01237 void MainImpl::ActSaveFile_activated() {
01238 
01239         QFileInfo f(rv->st.fileName());
01240         const QString fileName(QFileDialog::getSaveFileName(f.fileName(), "",
01241                                this, "save file dialog", "Save file as"));
01242 
01243         if (fileName.isEmpty())
01244                 return;
01245 
01246         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
01247 
01248         if (!git->saveFile(rv->st.fileName(), rv->st.sha(), fileName))
01249                 statusBar()->message("Unable to save " + fileName);
01250 
01251         QApplication::restoreOverrideCursor();
01252 }
01253 
01254 void MainImpl::on_openRecent_activated(int id) {
01255 
01256         bool ok;
01257         File->text(id).left(1).toInt(&ok);
01258         if (!ok) // only recent repos entries have a number in first char
01259                 return;
01260 
01261         const QString workDir(File->text(id).section(' ', 1));
01262         if (!workDir.isEmpty())
01263                 setRepository(workDir, false, false);
01264 }
01265 
01266 void MainImpl::ActOpenRepo_activated() {
01267 
01268         const QString dirName(QFileDialog::getExistingDirectory(curDir,
01269                               this, "", "Choose a directory"));
01270 
01271         if (!dirName.isEmpty()) {
01272                 QDir d(dirName);
01273                 setRepository(d.absPath(), false, false);
01274         }
01275 }
01276 
01277 void MainImpl::ActOpenRepoNewWindow_activated() {
01278 
01279         const QString dirName(QFileDialog::getExistingDirectory(curDir,
01280                               this, "", "Choose a directory"));
01281 
01282         if (!dirName.isEmpty()) {
01283                 QDir d(dirName);
01284                 MainImpl* newWin = new MainImpl(d.absPath());
01285                 newWin->show();
01286         }
01287 }
01288 
01289 void MainImpl::refreshRepo(bool b) {
01290 
01291         setRepository(curDir, true, b);
01292 }
01293 
01294 void MainImpl::ActRefresh_activated() {
01295 
01296         refreshRepo(true);
01297 }
01298 
01299 void MainImpl::ActMailFormatPatch_activated() {
01300 
01301         if (rv->tab()->listViewLog->childCount() == 0)
01302                 return;
01303 
01304         if (rv->tab()->listViewLog->currentItem() == NULL) {
01305                 statusBar()->message("At least one selected revision needed");
01306                 return;
01307         }
01308         QStringList selectedItems;
01309         rv->listViewLog->getSelectedItems(selectedItems);
01310         if (selectedItems.contains(ZERO_SHA)) {
01311                 statusBar()->message("Unable to format patch for not committed content");
01312                 return;
01313         }
01314         QSettings settings;
01315         QString outDir(settings.readEntry(APP_KEY + FP_DIR_KEY, curDir));
01316         QString dirPath(QFileDialog::getExistingDirectory(outDir, this, "",
01317                         "Choose destination directory - Format Patch"));
01318         if (dirPath.isEmpty())
01319                 return;
01320 
01321         QDir d(dirPath);
01322         settings.writeEntry(APP_KEY + FP_DIR_KEY, d.absPath());
01323         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
01324         git->formatPatch(selectedItems, d.absPath());
01325         QApplication::restoreOverrideCursor();
01326 }
01327 
01328 bool MainImpl::askApplyPatchParameters(bool* workDirOnly, bool* fold) {
01329 
01330         int ret = 0;
01331         if (!git->isStGITStack()) {
01332                 ret = QMessageBox::question(this, "Apply Patch",
01333                       "Do you want to commit or just to apply changes to "
01334                       "working directory?", "&Cancel", "&Working dir", "&Commit", 0, 0);
01335                 *workDirOnly = (ret == 1);
01336                 *fold = false;
01337         } else {
01338                 ret = QMessageBox::question(this, "Apply Patch", "Do you want to "
01339                       "import or fold the patch?", "&Cancel", "&Fold", "&Import", 0, 0);
01340                 *workDirOnly = false;
01341                 *fold = (ret == 1);
01342         }
01343         return (ret != 0);
01344 }
01345 
01346 void MainImpl::ActMailApplyPatch_activated() {
01347 
01348         QSettings settings;
01349         QString outDir(settings.readEntry(APP_KEY + FP_DIR_KEY, curDir));
01350         QString patchName(QFileDialog::getOpenFileName(outDir, NULL, this,
01351                           "", "Choose the patch file - Apply Patch"));
01352         if (patchName.isEmpty())
01353                 return;
01354 
01355         QFileInfo f(patchName);
01356         settings.writeEntry(APP_KEY + FP_DIR_KEY, f.dirPath(true));
01357 
01358         bool workDirOnly, fold;
01359         if (!askApplyPatchParameters(&workDirOnly, &fold))
01360                 return;
01361 
01362         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
01363 
01364         bool ok = git->applyPatchFile(f.absFilePath(), fold, !Git::optDragDrop);
01365         if (workDirOnly && ok)
01366                 git->resetCommits(1);
01367 
01368         QApplication::restoreOverrideCursor();
01369         refreshRepo(false);
01370 }
01371 
01372 void MainImpl::ActCheckWorkDir_toggled(bool b) {
01373 
01374         if (!ActCheckWorkDir->isEnabled()) // to avoid looping with setOn()
01375                 return;
01376 
01377         setFlag(DIFF_INDEX_F, b);
01378         bool keepSelection = (rv->st.sha() != ZERO_SHA);
01379         refreshRepo(keepSelection);
01380 }
01381 
01382 void MainImpl::ActSettings_activated() {
01383 
01384         SettingsImpl* setView = new SettingsImpl(this, git);
01385         setView->exec(); // modal exec, has Qt::WDestructiveClose
01386 
01387         // update ActCheckWorkDir if necessary
01388         if (ActCheckWorkDir->isOn() != testFlag(DIFF_INDEX_F))
01389                 ActCheckWorkDir->toggle();
01390 }
01391 
01392 void MainImpl::ActCustomActionSetup_activated() {
01393 
01394         CustomActionImpl* ca = new CustomActionImpl(); // has Qt::WDestructiveClose
01395 
01396         connect(this, SIGNAL(closeAllWindows()), ca, SLOT(close()));
01397 
01398         connect(ca, SIGNAL(listChanged(const QStringList&)),
01399                 this, SLOT(on_customActionListChanged(const QStringList&)));
01400 
01401         ca->show();
01402 }
01403 
01404 void MainImpl::on_customActionListChanged(const QStringList& list) {
01405 
01406         // update menu of all windows
01407         QWidgetList* l = QApplication::topLevelWidgets();
01408         QWidgetListIt it(*l);
01409         while (it.current() != 0) {
01410                 MainImpl* w = dynamic_cast<MainImpl*>(it.current());
01411                 if (w)
01412                         w->doUpdateCustomActionMenu(list);
01413                 ++it;
01414         }
01415         delete l; // needs to be deleted, see docs
01416 }
01417 
01418 void MainImpl::doUpdateCustomActionMenu(const QStringList& list) {
01419 
01420         while (Actions->idAt(1) != -1) // clear menu
01421                 Actions->removeItemAt(1);
01422 
01423         if (list.isEmpty())
01424                 return;
01425 
01426         Actions->insertSeparator();
01427         FOREACH_SL (it, list)
01428                 Actions->insertItem(*it);
01429 }
01430 
01431 void MainImpl::on_customAction_activated(int id) {
01432 
01433         const QString name(Actions->text(id));
01434         if (name == "Setup actions...")
01435                 return;
01436 
01437         QSettings set;
01438         const QStringList sl(QStringList::split(",", set.readEntry(APP_KEY + MCR_LIST_KEY, "")));
01439 
01440         if (sl.findIndex(name) == -1) {
01441                 dbp("ASSERT in on_customAction_activated, action %1 not found", name);
01442                 return;
01443         }
01444         const QString header("Macro " + name + "/");
01445         QString cmdArgs;
01446 
01447         if (testFlag(MCR_CMD_LINE_F, header)) {
01448                 bool ok;
01449                 cmdArgs = QInputDialog::getText("Run action - QGit", "Enter command line "
01450                           "arguments for '" + name + "'", QLineEdit::Normal, "", &ok, this);
01451                 cmdArgs.prepend(' ');
01452                 if (!ok)
01453                         return;
01454         }
01455         SCRef cmd = set.readEntry(APP_KEY + header + MCR_TEXT_KEY, "");
01456         if (cmd.isEmpty())
01457                 return;
01458 
01459         ConsoleImpl* c = new ConsoleImpl(name, git); // has Qt::WDestructiveClose
01460 
01461         connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
01462 
01463         connect(c, SIGNAL(customAction_exited(const QString&)),
01464                 this, SLOT(on_customAction_exited(const QString&)));
01465 
01466         QStringList selectedItems, selectedItemsReversed;
01467         rv->listViewLog->getSelectedItems(selectedItems);
01468 
01469         FOREACH_SL(it, selectedItems)
01470                 selectedItemsReversed.prepend(*it);
01471 
01472         /* setup a bunch of environment variables to be
01473          * freely used by the launched custom action
01474          */
01475         QStringList env;
01476         env.append("QGIT_CURRENT_REVISION=" + lineEditSHA->text());
01477         env.append("QGIT_SELECTED_REVISIONS=" + selectedItems.join(" "));
01478         env.append("QGIT_SELECTED_REVISIONS_REVERSE=" + selectedItemsReversed.join(" "));
01479 
01480         if (c->start(cmd, cmdArgs, &env))
01481                 c->show();
01482 }
01483 
01484 void MainImpl::on_customAction_exited(const QString& name) {
01485 
01486         const QString header("Macro " + name + "/");
01487         if (testFlag(MCR_REFRESH_F, header))
01488                 QTimer::singleShot(10, this, SLOT(refreshRepo())); // outside of event handler
01489 }
01490 
01491 void MainImpl::ActCommit_activated() {
01492 
01493         CommitImpl* c = new CommitImpl(git); // has WDestructiveClose
01494 
01495         connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
01496         connect(c, SIGNAL(changesCommitted(bool)), this, SLOT(on_changesCommitted(bool)));
01497         c->show();
01498 }
01499 
01500 void MainImpl::on_changesCommitted(bool ok) {
01501 
01502         if (ok)
01503                 refreshRepo(false);
01504         else
01505                 statusBar()->message("Failed to commit changes");
01506 }
01507 
01508 void MainImpl::ActCommit_setEnabled(bool b) {
01509 
01510         // pop and push commands fail if there are local changes,
01511         // so in this case we disable ActPop and ActPush
01512         if (b) {
01513                 ActPush->setEnabled(false);
01514                 ActPop->setEnabled(false);
01515         }
01516         ActCommit->setEnabled(b);
01517 }
01518 
01519 void MainImpl::ActTag_activated() {
01520 
01521         int adj = -1; // hack to correctly map col numbers in main view
01522         QString tag(rv->tab()->listViewLog->currentItem()->text(LOG_COL + adj));
01523         bool ok;
01524         tag = QInputDialog::getText("Make tag - QGit", "Enter tag name:",
01525                                     QLineEdit::Normal, tag, &ok, this);
01526         if (!ok || tag.isEmpty())
01527                 return;
01528 
01529         QString tmp(tag.simplifyWhiteSpace());
01530         if (tag != tmp.remove(' ')) {
01531                 QMessageBox::warning(this, "Make tag - QGit",
01532                              "Sorry, control characters or spaces\n"
01533                              "are not allowed in tag name.");
01534                 return;
01535         }
01536         if (!git->getRefSha(tag, Git::TAG, false).isEmpty()) {
01537                 QMessageBox::warning(this, "Make tag - QGit",
01538                              "Sorry, tag name already exists.\n"
01539                              "Please choose a different name.");
01540                 return;
01541         }
01542         QString msg(QInputDialog::getText("Make tag - QGit",
01543                 "Enter tag message, if any:", QLineEdit::Normal, "", &ok, this));
01544 
01545         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
01546         ok = git->makeTag(lineEditSHA->text(), tag, msg);
01547         QApplication::restoreOverrideCursor();
01548         if (ok)
01549                 refreshRepo(true);
01550         else
01551                 statusBar()->message("Sorry, unable to tag the revision");
01552 }
01553 
01554 void MainImpl::ActTagDelete_activated() {
01555 
01556         if (QMessageBox::question(this, "Delete tag - QGit",
01557                          "Do you want to un-tag selected revision?",
01558                          "&Yes", "&No", QString::null, 0, 1) == 1)
01559                 return;
01560 
01561         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
01562         bool ok = git->deleteTag(lineEditSHA->text());
01563         QApplication::restoreOverrideCursor();
01564         if (ok)
01565                 refreshRepo(true);
01566         else
01567                 statusBar()->message("Sorry, unable to un-tag the revision");
01568 }
01569 
01570 void MainImpl::ActPush_activated() {
01571 
01572         QStringList selectedItems;
01573         rv->listViewLog->getSelectedItems(selectedItems);
01574         for (uint i = 0; i < selectedItems.count(); i++) {
01575                 if (!git->checkRef(selectedItems[i], Git::UN_APPLIED)) {
01576                         statusBar()->message("Please, select only unapplied patches");
01577                         return;
01578                 }
01579         }
01580         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
01581         bool ok = true;
01582         for (uint i = 0; i < selectedItems.count(); i++) {
01583                 const QString tmp(QString("Pushing patch %1 of %2")
01584                                   .arg(i+1).arg(selectedItems.count()));
01585                 statusBar()->message(tmp);
01586                 SCRef sha = selectedItems[selectedItems.count() - i - 1];
01587                 if (!git->stgPush(sha)) {
01588                         statusBar()->message("Failed to push patch " + sha);
01589                         ok = false;
01590                         break;
01591                 }
01592         }
01593         if (ok)
01594                 statusBar()->clear();
01595 
01596         QApplication::restoreOverrideCursor();
01597         refreshRepo(false);
01598 }
01599 
01600 void MainImpl::ActPop_activated() {
01601 
01602         QStringList selectedItems;
01603         rv->listViewLog->getSelectedItems(selectedItems);
01604         if (selectedItems.count() > 1) {
01605                 statusBar()->message("Please, select one revision only");
01606                 return;
01607         }
01608         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
01609         git->stgPop(selectedItems[0]);
01610         QApplication::restoreOverrideCursor();
01611         refreshRepo(false);
01612 }
01613 
01614 void MainImpl::ActFilterTree_toggled(bool b) {
01615 
01616         if (!ActFilterTree->isEnabled()) {
01617                 dbs("ASSERT ActFilterTree_toggled while disabled");
01618                 return;
01619         }
01620         if (b) {
01621                 QStringList selectedItems;
01622                 if (!treeView->isVisible())
01623                         rv->treeView->update(); // force tree updating
01624 
01625                 rv->treeView->getTreeSelectedItems(selectedItems);
01626                 if (selectedItems.count() == 0) {
01627                         dbs("ASSERT tree filter action activated with no selected items");
01628                         return;
01629                 }
01630                 statusBar()->message("Filter view on " + selectedItems.join(" "));
01631                 setRepository(curDir, true, true, &selectedItems);
01632         } else
01633                 refreshRepo(true);
01634 }
01635 
01636 void MainImpl::ActFindNext_activated() {
01637 
01638         QTextEdit* te = getCurrentTextEdit();
01639         if (!te || textToFind.isEmpty())
01640                 return;
01641 
01642         bool endOfDocument = false;
01643         while (true) {
01644                 if (te->find(textToFind, false, false))
01645                         return;
01646 
01647                 if (endOfDocument) {
01648                         QMessageBox::warning(this, "Find text - QGit", "Text \"" +
01649                                      textToFind + "\" not found!", QMessageBox::Ok, 0);
01650                         return;
01651                 }
01652                 if (QMessageBox::question(this, "Find text - QGit", "End of document "
01653                     "reached\n\nDo you want to continue from beginning?", QMessageBox::Yes,
01654                     QMessageBox::No | QMessageBox::Escape) == QMessageBox::No)
01655                         return;
01656 
01657                 endOfDocument = true;
01658                 te->setCursorPosition(0, 0);
01659         }
01660 }
01661 
01662 void MainImpl::ActFind_activated() {
01663 
01664         QTextEdit* te = getCurrentTextEdit();
01665         if (!te)
01666                 return;
01667 
01668         QString def(textToFind);
01669         if (te->hasSelectedText()) {
01670                 TextFormat tf = te->textFormat();
01671                 te->setTextFormat(Qt::PlainText); // we want text without formatting tags
01672                 def = te->selectedText().section('\n', 0, 0);
01673                 te->setTextFormat(tf);
01674         }
01675         bool ok;
01676         QString str(QInputDialog::getText("Find text - QGit", "Text to find:",
01677                                           QLineEdit::Normal, def, &ok, this));
01678         if (!ok || str.isEmpty())
01679                 return;
01680 
01681         textToFind = str; // update with valid data only
01682         ActFindNext_activated();
01683 }
01684 
01685 void MainImpl::ActHelp_activated() {
01686 
01687         // helpInfo is defined in help.h
01688         HelpBase* helpDlg = new HelpBase(NULL, 0, Qt::WDestructiveClose);
01689 
01690         connect(this, SIGNAL(closeAllWindows()), helpDlg, SLOT(close()));
01691 
01692         helpDlg->textEditHelp->setText(QString::fromLatin1(helpInfo));
01693         helpDlg->show();
01694         helpDlg->raise();
01695 }
01696 
01697 void MainImpl::ActAbout_activated() {
01698 
01699         static const char* aboutMsg =
01700         "<center><p><b>QGit version " PACKAGE_VERSION "</b></p><br>"
01701         "<p>Copyright (c) 2005, 2006 Marco Costalba</p>"
01702         "<p>Use and redistribute under the "
01703         "terms of the GNU General Public License</p></center>";
01704         QMessageBox::about(this, "About QGit", QString::fromLatin1(aboutMsg));
01705 }
01706 
01707 void MainImpl::closeEvent(QCloseEvent* ce) {
01708 
01709         // lastWindowClosed() signal is emitted by close(), after sending
01710         // closeEvent(), so we need to close _here_ all secondary windows before
01711         // the close() method checks for lastWindowClosed flag to avoid missing
01712         // the signal and stay in the main loop forever, because lastWindowClosed()
01713         // signal is connected to qApp->quit()
01714         //
01715         // note that we cannot rely on setting 'this' parent in secondary windows
01716         // because when close() is called children are still alive and, finally,
01717         // when children are deleted, d'tor do not call close() anymore. So we miss
01718         // lastWindowClosed() signal in this case.
01719         emit closeAllWindows();
01720         hide();
01721 
01722         EM_RAISE(exExiting);
01723 
01724         if (!git->stop(Git::optSaveCache)) {
01725                 // if not all processes have been deleted, there is
01726                 // still some run() call not returned somewhere, it is
01727                 // not safe to delete run() callers objects now
01728                 QTimer::singleShot(100, this, SLOT(ActClose_activated()));
01729                 ce->ignore(); // just for sanity, already ignored by default
01730                 return;
01731         }
01732         emit closeAllTabs();
01733         delete rv;
01734         MainBase::closeEvent(ce);
01735 }
01736 
01737 void MainImpl::ActClose_activated() {
01738 
01739         close();
01740 }
01741 
01742 void MainImpl::ActExit_activated() {
01743 
01744         qApp->closeAllWindows();
01745 }

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