Logo Search packages:      
Sourcecode: qgit version File versions  Download package

mainimpl.cpp

/*
      Description: qgit main view

      Author: Marco Costalba (C) 2005-2006

      Copyright: See COPYING file that comes with this distribution

*/
#include <qtimer.h>
#include <qlineedit.h>
#include <qtextbrowser.h>
#include <qlistview.h>
#include <qpushbutton.h>
#include <qtoolbutton.h>
#include <qpainter.h>
#include <qstringlist.h>
#include <qlistbox.h>
#include <qfontmetrics.h>
#include <qcombobox.h>
#include <qeventloop.h>
#include <qapplication.h>
#include <qwidgetlist.h>
#include <qmessagebox.h>
#include <qstatusbar.h>
#include <qheader.h>
#include <qpopupmenu.h>
#include <qcursor.h>
#include <qfiledialog.h>
#include <qsettings.h>
#include <qaction.h>
#include <qinputdialog.h>
#include <qaccel.h>
#include <qsplitter.h>
#include <qtabwidget.h>
#include <qobjectlist.h>
#include <qlayout.h>
#include "config.h" // defines PACKAGE_VERSION
#include "help.h"
#include "helpbase.h"
#include "consoleimpl.h"
#include "customactionimpl.h"
#include "settingsimpl.h"
#include "revbase.h"
#include "filebase.h"
#include "patchbase.h"
#include "common.h"
#include "git.h"
#include "listview.h"
#include "treeview.h"
#include "patchview.h"
#include "fileview.h"
#include "commitimpl.h"
#include "revsview.h"
#include "revdesc.h"
#include "mainimpl.h"

using namespace QGit;

// *************** QGit namespace definitions ****************************

QColor QGit::ODD_LINE_COL; // declared as extern in QGit
QColor QGit::EVEN_LINE_COL;

uint QGit::flags(SCRef group) {

      QSettings settings;
      return settings.readNumEntry(APP_KEY + group + FLAGS_KEY, FLAGS_DEF);
}

bool QGit::testFlag(uint f, SCRef group) { return (flags(group) & f); }

void QGit::setFlag(uint f, bool b, SCRef group) {

      QSettings settings;
      int flags = settings.readNumEntry(APP_KEY + group + FLAGS_KEY, FLAGS_DEF);
      flags = (b) ? flags | f : flags & ~f;
      settings.writeEntry(APP_KEY + group + FLAGS_KEY, flags);
}

void QGit::writeSetting(SCRef key, SCRef value, SCRef group) {

      QSettings settings;
      settings.writeEntry(APP_KEY + group + key, value);
}

// ******************************* MainImpl ******************************

MainImpl::MainImpl(const QString& cd, QWidget* p, const char *name) :
          MainBase(p, name, Qt::WDestructiveClose) {

      EM_INIT(exSetRepositoryCalled, "Reloading data");
      EM_INIT(exExiting, "Exiting");

      git = new Git(this);
      QAccel* accel = new QAccel(this);
      setupAccelerator(accel);
      qApp->installEventFilter(this);

      // init native types
       setRepositoryBusy = changesCommitted = false;

      // init filter match highlighters
      shortLogRE.setMinimal(true);
      shortLogRE.setCaseSensitive(false);
      longLogRE.setMinimal(true);
      longLogRE.setCaseSensitive(false);

      // set-up tab view
      delete tabWdg->currentPage(); // cannot be done in Qt Designer
      rv = new RevsView(this, git);
      pushButtonCloseTab->reparent(tabWdg, QPoint());
      tabWdg->setCornerWidget(pushButtonCloseTab);

      // set-up tree view
      treeView->hide();

      // set-up menu for recent visited repositories
      connect(File, SIGNAL(activated(int)), this, SLOT(on_openRecent_activated(int)));
      recentRepoMenuPos = 0;
      while (File->idAt(recentRepoMenuPos) != -1)
            recentRepoMenuPos++;
      doUpdateRecentRepoMenu("");

      // set-up menu for custom actions
      connect(Actions, SIGNAL(activated(int)), this, SLOT(on_customAction_activated(int)));
      QSettings set;
      QStringList sl = QStringList::split(",", set.readEntry(APP_KEY + MCR_LIST_KEY, ""));
      doUpdateCustomActionMenu(sl);

      // create light and dark colors for alternate background
      QColor l(rv->tab()->listViewLog->paletteBackgroundColor());
      QColor d(int(l.red() * 0.97), int(l.green() * 0.97), int(l.blue() * 0.97));
      QGit::ODD_LINE_COL = l;
      QGit::EVEN_LINE_COL = d;

      // manual adjust lineEditSHA width
      QString tmp;
      tmp.fill('8', 41);
      int wd = lineEditSHA->fontMetrics().boundingRect(tmp).width();
      lineEditSHA->setMinimumWidth(wd);

      pixmaps.setAutoDelete(true);
      adjustFontSize(0); // default is system-wide setting

      connect(git, SIGNAL(newRevsAdded(const FileHistory*, const QValueVector<QString>&)),
              this, SLOT(on_newRevsAdded(const FileHistory*, const QValueVector<QString>&)));

      // connect cross-domain update signals
      connect(rv->tab()->listViewLog, SIGNAL(doubleClicked(QListViewItem*)),
              this, SLOT(on_listViewLog_doubleClicked(QListViewItem*)));

      connect(rv->tab()->listBoxFiles, SIGNAL(doubleClicked(QListBoxItem*)),
              this, SLOT(on_fileList_doubleClicked(QListBoxItem*)));

      connect(treeView, SIGNAL(doubleClicked(QListViewItem*)),
              this, SLOT(on_treeView_doubleClicked(QListViewItem*)));

      // MainImpl c'tor is called before to enter event loop,
      // but some stuff requires event loop to init properly
      startUpDir = (cd.isEmpty()) ? QDir::current().absPath() : cd;
      QTimer::singleShot(10, this, SLOT(initWithEventLoopActive()));
}

void MainImpl::initWithEventLoopActive() {

      setRepository(startUpDir, false, false);
}

void MainImpl::lineEditSHA_returnPressed() {

      rv->st.setSha(lineEditSHA->text());
      UPDATE_DOMAIN(rv);
}

void MainImpl::ActBack_activated() {

      lineEditSHA->undo(); // first for insert(text)
      if (lineEditSHA->text().isEmpty())
            lineEditSHA->undo(); // double undo, see RevsView::updateLineEditSHA()

      lineEditSHA_returnPressed();
}

void MainImpl::ActForward_activated() {

      lineEditSHA->redo(); // redo skips empty fields so one is enough
      lineEditSHA_returnPressed();
}

// *************************** ExternalDiffViewer ***************************

void MainImpl::ActExternalDiff_activated() {

      QStringList args;
      getExternalDiffArgs(&args);
      ExternalDiffProc* externalDiff = new ExternalDiffProc(args, this);
      externalDiff->setWorkingDirectory(curDir);
      if (!externalDiff->start()) {
            QString text("Cannot start external viewer: ");
            text.append(externalDiff->arguments()[0]);
            QMessageBox::warning(this, "Error - QGit", text);
            delete externalDiff;
      }
}

void MainImpl::getExternalDiffArgs(QStringList* args) {

      // save files to diff in working directory,
      // will be removed by ExternalDiffProc on exit
      QFileInfo f(rv->st.fileName());
      QString prevRevSha(rv->st.diffToSha());
      if (prevRevSha.isEmpty()) { // default to first parent
            const Rev* r = git->revLookup(rv->st.sha());
            prevRevSha = (r && r->parentsCount() > 0) ? r->parent(0) : rv->st.sha();
      }
      QString fName1(curDir + "/" + rv->st.sha().left(6) + "_" + f.baseName(true));
      QString fName2(curDir + "/" + prevRevSha.left(6) + "_" + f.baseName(true));

      QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

      QString fileContent;
      git->getFile(rv->st.fileName(), rv->st.sha(), NULL, &fileContent);
      if (!git->writeToFile(fName1, fileContent))
            statusBar()->message("Unable to save " + fName1);

      git->getFile(rv->st.fileName(), prevRevSha, NULL, &fileContent);
      if (!git->writeToFile(fName2, fileContent))
            statusBar()->message("Unable to save " + fName2);

      // get external diff viewer
      QSettings settings;
      SCRef extDiff(settings.readEntry(APP_KEY + EXT_DIFF_KEY, EXT_DIFF_DEF));

      QApplication::restoreOverrideCursor();

      // finally set process arguments
      args->append(extDiff);
      args->append(fName2);
      args->append(fName1);
}

// *************************** Repo open or changed *************************

void MainImpl::setRepository(SCRef newDir, bool refresh, bool keepSelection,
                             QStringList* filterList) {

      if (refresh && ActFilterTree->isOn() && filterList == NULL) {
            // refresh called while in filtered view
            ActFilterTree->toggle(); // just remove the filter and reload
            return;
      }
      // Git::stop() and Git::init() are not re-entrant and must not
      // be active at the same time. Because Git::init calls processEvents()
      // we need to guard against reentrancy here.
      if (setRepositoryBusy)
            return;

      setRepositoryBusy = true;

      EM_RAISE(exSetRepositoryCalled);

      try {
            EM_REGISTER(exExiting);

            bool archiveChanged;
            curDir = git->getBaseDir(&archiveChanged, newDir);

            git->stop(archiveChanged); // this stops all pending processing

            if (archiveChanged && refresh)
                  dbs("ASSERT in setRepository: different dir with no range select");

            // now we can clear all our data
            setCaption(curDir + " - QGit");
            rv->clear(refresh && keepSelection);
            if (archiveChanged)
                  emit closeAllTabs();

            // disable all actions
            updateGlobalActions(false);
            updateContextActions("", "", false, false);
            ActCommit_setEnabled(false);

            int adj = -1; // hack to correctly map col numbers in main view

            if (testFlag(REL_DATE_F)) {
                  secs = QDateTime::currentDateTime().toTime_t();
                  rv->tab()->listViewLog->setColumnText(TIME_COL + adj, "Last Change");
            } else {
                  secs = 0;
                  rv->tab()->listViewLog->setColumnText(TIME_COL + adj, "Author Date");
            }
            if (ActFilterTree->isOn())
                  setCaption(caption() + " - FILTER ON < " +
                             filterList->join(" ") + " >");

            // tree name should be set before init because in case of
            // StGIT archives the first revs are sent before init returns
            QString n(curDir);
            rv->treeView->setTreeName(n.prepend('/').section('/', -1, -1));

            bool quit;
            bool ret = git->init(curDir, !refresh, filterList, &quit);
            if (quit)
                  goto exit;

            if (!ret)
                  statusBar()->message("Not a git archive");

            if (ret && archiveChanged)
                  updateRecentRepoMenu(curDir);

            updateCommitMenu(ret && git->isStGITStack());

            // re-enable global actions
            ActCheckWorkDir->setOn(testFlag(DIFF_INDEX_F));
            updateGlobalActions(ret);

exit:
            setRepositoryBusy = false;
            EM_REMOVE(exExiting);

            if (quit && !startUpDir.isEmpty())
                  ActClose->activate();

            startUpDir = ""; // one shot

      } catch (int i) {
            EM_REMOVE(exExiting);

            if (EM_MATCH(i, exExiting, "loading repository")) {
                  EM_CHECK_PENDING;
                  return;
            }
            const QString info("Exception \'" + EM_DESC(i) + "\' "
                               "not handled in setRepository...re-throw");
            dbp("%1", info.latin1());
            throw i;
      }
}

void MainImpl::updateGlobalActions(bool b) {

      ActRefresh->setEnabled(b);
      ActCheckWorkDir->setEnabled(b);
      ActViewRev->setEnabled(b);
      ActViewDiff->setEnabled(b);
      ActViewDiffNewTab->setEnabled(b && firstTab<PatchView>());
      ActShowTree->setEnabled(b);
      ActMailApplyPatch->setEnabled(b);
      ActMailFormatPatch->setEnabled(b);

      rv->setEnabled(b);
}

void MainImpl::updateContextActions(SCRef newRevSha, SCRef newFileName,
                                    bool isDir, bool found) {

      bool pathActionsEnabled = !newFileName.isEmpty();
      bool fileActionsEnabled = (pathActionsEnabled && !isDir);

      ActViewFile->setEnabled(fileActionsEnabled);
      ActViewFileNewTab->setEnabled(fileActionsEnabled && firstTab<FileView>());
      ActExternalDiff->setEnabled(fileActionsEnabled);
      ActSaveFile->setEnabled(fileActionsEnabled);
      ActFilterTree->setEnabled(pathActionsEnabled || ActFilterTree->isOn());

      bool isTag, isUnApplied, isApplied;
      isTag = isUnApplied = isApplied = false;

      if (found) {
            const Rev* r = git->revLookup(newRevSha);
            isTag = r->isTag;
            isUnApplied = r->isUnApplied;
            isApplied = r->isApplied;
      }
      ActTag->setEnabled(found && !isTag && (newRevSha != ZERO_SHA) && !isUnApplied);
      ActTagDelete->setEnabled(found && isTag && (newRevSha != ZERO_SHA) && !isUnApplied);
      ActPush->setEnabled(found && isUnApplied && git->isNothingToCommit());
      ActPop->setEnabled(found && isApplied && git->isNothingToCommit());
}

// ************************* cross-domain update Actions ***************************

void MainImpl::on_listViewLog_doubleClicked(QListViewItem* item) {

      if (item && ActViewDiff->isEnabled())
            ActViewDiff->activate();
}

void MainImpl::on_histListView_doubleClicked(QListViewItem* item) {

      if (item && ActViewRev->isEnabled())
            ActViewRev->activate();
}

void MainImpl::on_fileList_doubleClicked(QListBoxItem* item) {

      if (item && rv->st.isMerge() && item->prev() == 0)
            return;

      if (item && item->listBox() == rv->tab()->listBoxFiles && ActViewDiff->isEnabled())
            ActViewDiff->activate();

      if (item && item->listBox() != rv->tab()->listBoxFiles && ActViewFile->isEnabled())
            ActViewFile->activate();
}

void MainImpl::on_treeView_doubleClicked(QListViewItem* item) {

      if (item && ActViewFile->isEnabled())
            ActViewFile->activate();
}

void MainImpl::pushButtonCloseTab_clicked() {

      int curPos = tabWdg->currentPageIndex();
      QObject* t;
      switch (currentTabType(&t)) {
      case TAB_REV:
            break;
      case TAB_PATCH:
            delete t;
            emit tabClosed(curPos);
            ActViewDiffNewTab->setEnabled(ActViewDiff->isEnabled() && firstTab<PatchView>());
            break;
      case TAB_FILE:
            delete t;
            emit tabClosed(curPos);
            ActViewFileNewTab->setEnabled(ActViewFile->isEnabled() && firstTab<FileView>());
            break;
      default:
            dbs("ASSERT in pushButtonCloseTab_clicked: unknown current page");
            break;
      }
}

void MainImpl::ActViewRev_activated() {

      QObject* t;
      if (currentTabType(&t) == TAB_FILE) {
            rv->st = (static_cast<FileView*>(t))->st;
            UPDATE_DOMAIN(rv);
      }
      tabWdg->setCurrentPage(rv->tabPos());
}

void MainImpl::ActViewFile_activated() {

      openFileTab(firstTab<FileView>());
}

void MainImpl::ActViewFileNewTab_activated() {

      openFileTab();
}

void MainImpl::openFileTab(FileView* fv) {

      if (!fv) {
            fv = new FileView(this, git);

            connect(fv->tab()->histListView, SIGNAL(doubleClicked(QListViewItem*)),
                    this, SLOT(on_histListView_doubleClicked(QListViewItem*)));

            connect(this, SIGNAL(closeAllTabs()), fv, SLOT(on_closeAllTabs()));

            ActViewFileNewTab->setEnabled(ActViewFile->isEnabled());
      }
      tabWdg->setCurrentPage(fv->tabPos());
      fv->st = rv->st;
      UPDATE_DOMAIN(fv);
}

void MainImpl::ActViewDiff_activated() {

      QObject* t;
      if (currentTabType(&t) == TAB_FILE) {
            rv->st = (static_cast<FileView*>(t))->st;
            UPDATE_DOMAIN(rv);
      }
      rv->viewPatch(false);
      ActViewDiffNewTab->setEnabled(true);
}

void MainImpl::ActViewDiffNewTab_activated() {

      rv->viewPatch(true);
}

bool MainImpl::eventFilter(QObject* obj, QEvent* ev) {

      if (ev->type() == QEvent::Wheel) {
            QWheelEvent* e = static_cast<QWheelEvent*>(ev);
            if (e->state() == Qt::AltButton) {

                  int idx = tabWdg->currentPageIndex();
                  if (e->delta() < 0)
                        idx = (++idx == tabWdg->count()) ? 0 : idx;
                  else
                        idx = (--idx < 0) ? tabWdg->count() - 1 : idx;

                  tabWdg->setCurrentPage(idx);
                  return true;
            }
      }
      return MainBase::eventFilter(obj, ev);
}

// ******************************* Filter ******************************

void MainImpl::on_newRevsAdded(const FileHistory* fh, const QValueVector<QString>&) {

      if (fh)
            return;

      if (toolButtonFilter->isOn())
            toolButtonFilter_toggled(true); // filter again on new arrived data

      if (toolButtonBold->isOn())
            toolButtonBold_toggled(true); // filter again on new arrived data

      // first rev could be a StGIT unapplied patch so check more then once
      if (   (!git->isNothingToCommit() || git->isUnknownFiles())
          && !ActCommit->isEnabled() && !git->isCommittingMerge())
            ActCommit_setEnabled(true);
}

void MainImpl::lineEditFilter_returnPressed() {

      toolButtonFilter->setOn(true);
}

void MainImpl::toolButtonFilter_toggled(bool isOn) {

      toolButtonBold->setEnabled(!isOn);
      filterList(isOn, false);
}

void MainImpl::toolButtonBold_toggled(bool isOn) {

      toolButtonFilter->setEnabled(!isOn);
      filterList(isOn, true);
}

void MainImpl::filterList(bool isOn, bool onlyHighlight) {

      lineEditFilter->setEnabled(!isOn);
      cmbSearch->setEnabled(!isOn);

      SCRef filter(lineEditFilter->text());
      if (filter.isEmpty())
            return;

      QMap<QString, bool> shaMap;
      bool descNeedsUpdate, patchNeedsUpdate, isRegExp;
      descNeedsUpdate = patchNeedsUpdate = isRegExp = false;
      int idx = cmbSearch->currentItem(), colNum = 0;
      if (isOn) {
            switch (idx) {
            case 0:
                  colNum = LOG_COL;
                  shortLogRE.setPattern(filter);
                  descNeedsUpdate = true;
                  break;
            case 1:
                  colNum = LOG_MSG_COL;
                  longLogRE.setPattern(filter);
                  descNeedsUpdate = true;
                  break;
            case 2:
                  colNum = AUTH_COL;
                  break;
            case 3:
                  colNum = COMMIT_COL;
                  break;
            case 4:
            case 5:
            case 6:
                  colNum = SHA_MAP_COL;
                  QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
                  EM_PROCESS_EVENTS; // to let paint wait cursor
                  if (idx == 4)
                        git->getFileFilter(filter, shaMap);
                  else {
                        isRegExp = (idx == 6);
                        if (!git->getPatchFilter(filter, isRegExp, shaMap)) {
                              QApplication::restoreOverrideCursor();
                              toolButtonFilter->toggle();
                              return;
                        }
                        patchNeedsUpdate = (shaMap.count() > 0);
                  }
                  QApplication::restoreOverrideCursor();
                  break;
            }
      } else {
            patchNeedsUpdate = ((idx == 5) || (idx == 6));
            descNeedsUpdate = (!shortLogRE.isEmpty() || !longLogRE.isEmpty());
            shortLogRE.setPattern("");
            longLogRE.setPattern("");
      }
      bool evenLine = false;
      int visibleCnt = 0;
      QListViewItemIterator it(rv->tab()->listViewLog);
      while (it.current()) {
            ListViewItem* item = static_cast<ListViewItem*>(it.current());
            if (isOn) {
                  if (passFilter(item, filter, colNum, shaMap)) {
                        if (onlyHighlight)
                              item->setHighlighted(true);
                        else {
                              item->setEven(evenLine);
                              evenLine = !evenLine;
                        }
                        visibleCnt++;
                  } else if (!onlyHighlight)
                        item->setVisible(false);
            } else {
                  item->setHighlighted(false);
                  item->setEven(evenLine);
                  evenLine = !evenLine;
                  if (!item->isVisible())
                        item->setVisible(true);
            }
            ++it;
      }
      if (descNeedsUpdate) {
            SCRef d(git->getDesc(rv->st.sha(), shortLogRE, longLogRE));
            rv->tab()->textBrowserDesc->setText(d);
      }
      if (patchNeedsUpdate)
            emit highlightPatch(isOn ? filter : "", isRegExp);

      if (isOn) {
            const QString tmp(QString("Found %1 matches. Toggle filter/highlight "
                                      "button to remove the filter").arg(visibleCnt));
            statusBar()->message(tmp);
      } else {
            QListView* lv = rv->tab()->listViewLog;
            lv->ensureItemVisible(lv->currentItem());
            statusBar()->clear();
      }
      scrollListView(1); // hack to force a repaint
      scrollListView(-1);
}

bool MainImpl::passFilter(ListViewItem* item, SCRef filter, int colNum,
                          const QMap<QString, bool>& shaMap) {

      if (colNum == SHA_MAP_COL)
            // in this case shaMap contains all good sha to search for
            return shaMap.contains(item->text(COMMIT_COL));

      QString field;
      if (colNum != LOG_MSG_COL) {
            int adj = (colNum != COMMIT_COL) ? -1 : 0;
            field = item->text(colNum + adj);
      }
      if (field.isEmpty()) { // still not setup or colNum == LOG_MSG_COL
            const Rev& c = *git->revLookup(item->text(COMMIT_COL));
            if (colNum == LOG_COL)
                  field = c.shortLog();
            else if (colNum == AUTH_COL)
                  field = c.author();
            else if (colNum == LOG_MSG_COL)
                  field = c.longLog();
      }
      // wildcard search, case insensitive
      return (field.find(QRegExp(filter, false, true)) != -1);
}

void MainImpl::customEvent(QCustomEvent* e) {

      BaseEvent* de = dynamic_cast<BaseEvent*>(e);
      if (de == NULL) {
            MainBase::customEvent(e);
            return;
      }
      SCRef data = de->data();

      switch (e->type()) {
      case ERROR_EV: {
            QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
            EM_PROCESS_EVENTS;
            MainExecErrorEvent* me = (MainExecErrorEvent*)e;
            QString text("An error occurred while executing command:\n\n");
            text.append(me->command() + "\n\nGit says: \n\n" + me->report());
            QMessageBox::warning(this, "Error - QGit", text);
            QApplication::restoreOverrideCursor(); }
            break;
      case MSG_EV:
            statusBar()->message(data);
            break;
      case POPUP_LIST_EV:
            doContexPopup(data);
            break;
      case POPUP_FILE_EV:
      case POPUP_TREE_EV:
            doFileContexPopup(data, e->type());
            break;
      default:
            qDebug("ASSERT in MainImpl::customEvent unhandled event %i", e->type());
            break;
      }
}

int MainImpl::currentTabType(QObject** t) {

      *t = NULL;
      int curPos = tabWdg->currentPageIndex();
      if (curPos == rv->tabPos()) {
            *t = rv;
            return TAB_REV;
      }
      QPtrList<PatchView>* l = getTabs<PatchView>(curPos);
      if (l->count() > 0) {
            *t = l->first();
            delete l;
            return TAB_PATCH;
      }
      delete l;
      QPtrList<FileView>* l2 = getTabs<FileView>(curPos);
      if (l2->count() > 0) {
            *t = l2->first();
            delete l2;
            return TAB_FILE;
      }
      if (l2->count() > 0)
            dbs("ASSERT in tabType file not found");

      delete l2;
      return -1;
}

template<class X> QPtrList<X>* MainImpl::getTabs(int tabPos) {

      X dummy;
      QObjectList* l = this->queryList(dummy.className());
      QPtrList<X>* ret = new QPtrList<X>;
      for (QObject* item = l->first(); item; item = l->next()) {

            X* x = static_cast<X*>(item);
            if (tabPos == -1 || x->tabPos() == tabPos)
                  ret->append(x);
      }
      delete l;
      return ret; // 'ret' must be deleted by caller
}

template<class X> X* MainImpl::firstTab(int startPos) {

      int minVal = 99, firstVal = 99;
      X* min = NULL;
      X* first = NULL;
      QPtrList<X>* l = getTabs<X>();
      for (X* d = l->first(); d; d = l->next()) {

            if (d->tabPos() < minVal) {
                  minVal = d->tabPos();
                  min = d;
            }
            if (d->tabPos() < firstVal && d->tabPos() > startPos) {
                  firstVal = d->tabPos();
                  first = d;
            }
      }
      delete l;
      return first ? first : min;
}

void MainImpl::tabWdg_currentChanged(QWidget* w) {

      if (w == NULL)
            return;

      // set correct focus for keyboard browsing
      QObject* t;
      switch (currentTabType(&t)) {
      case TAB_REV:
            static_cast<RevsView*>(t)->tab()->listViewLog->setFocus();
            pushButtonCloseTab->setEnabled(false);
            break;
      case TAB_PATCH:
            static_cast<PatchView*>(t)->tab()->textEditDiff->setFocus();
            pushButtonCloseTab->setEnabled(true);
            break;
      case TAB_FILE:
            static_cast<FileView*>(t)->tab()->histListView->setFocus();
            pushButtonCloseTab->setEnabled(true);
            break;
      default:
            dbs("ASSERT in tabWdg_currentChanged: unknown current page");
            break;
      }
}

void MainImpl::accelActivated(int id) {

      switch (id) {
      case KEY_UP:
            scrollListView(-1);
            break;
      case KEY_DOWN:
            scrollListView(1);
            break;
      case SHIFT_KEY_UP:
            goMatch(-1);
            break;
      case SHIFT_KEY_DOWN:
            goMatch(1);
            break;
      case KEY_LEFT:
            ActBack_activated();
            break;
      case KEY_RIGHT:
            ActForward_activated();
            break;
      case CTRL_PLUS:
            adjustFontSize(1);
            break;
      case CTRL_MINUS:
            adjustFontSize(-1);
            break;
      case KEY_U:
            scrollTextEdit(-18);
            break;
      case KEY_D:
            scrollTextEdit(18);
            break;
      case KEY_DELETE:
      case KEY_B:
      case KEY_BCKSPC:
            scrollTextEdit(-1);
            break;
      case KEY_SPACE:
            scrollTextEdit(1);
            break;
      case KEY_R:
            tabWdg->setCurrentPage(rv->tabPos());
            break;
      case KEY_P:
      case KEY_F:{
            int cp = tabWdg->currentPageIndex();
            Domain* d = (id == KEY_P) ? static_cast<Domain*>(firstTab<PatchView>(cp)) :
                                        static_cast<Domain*>(firstTab<FileView>(cp));
            if (d)
                  tabWdg->setCurrentPage(d->tabPos()); }
            break;
      }
}

void MainImpl::setupAccelerator(QAccel* accel) {

      accel->insertItem(Key_Up,         KEY_UP);
      accel->insertItem(Key_I,          KEY_UP);
      accel->insertItem(Key_Down,       KEY_DOWN);
      accel->insertItem(Key_N,          KEY_DOWN);
      accel->insertItem(Key_K,          KEY_DOWN);
      accel->insertItem(Key_Left,       KEY_LEFT);
      accel->insertItem(Key_Right,      KEY_RIGHT);
      accel->insertItem(SHIFT+Key_Up,   SHIFT_KEY_UP);
      accel->insertItem(SHIFT+Key_Down, SHIFT_KEY_DOWN);
      accel->insertItem(CTRL+Key_Plus,  CTRL_PLUS);
      accel->insertItem(CTRL+Key_Minus, CTRL_MINUS);
      accel->insertItem(Key_U,          KEY_U);
      accel->insertItem(Key_D,          KEY_D);
      accel->insertItem(Key_Delete,     KEY_DELETE);
      accel->insertItem(Key_B,          KEY_B);
      accel->insertItem(Key_Backspace,  KEY_BCKSPC);
      accel->insertItem(Key_Space,      KEY_SPACE);
      accel->insertItem(Key_R,          KEY_R);
      accel->insertItem(Key_P,          KEY_P);
      accel->insertItem(Key_F,          KEY_F);

      connect(accel, SIGNAL(activated(int)), this, SLOT(accelActivated(int)));
}

void MainImpl::goMatch(int delta) {

      if (!toolButtonBold->isOn())
            return;

      QListViewItemIterator it(rv->tab()->listViewLog->currentItem());
      if (delta > 0)
            ++it;
      else
            --it;

      while (it.current()) {
            ListViewItem* item = static_cast<ListViewItem*>(it.current());
            if (item->highlighted()) {
                  QListView* lv = rv->tab()->listViewLog;
                  lv->clearSelection();
                  lv->setCurrentItem(item);
                  lv->ensureItemVisible(lv->currentItem());
                  return;
            }
            if (delta > 0)
                  ++it;
            else
                  --it;
      }
}

QTextEdit* MainImpl::getCurrentTextEdit() {

      QTextEdit* te = NULL;
      QObject* t;
      switch (currentTabType(&t)) {
      case TAB_REV:
            te = static_cast<RevsView*>(t)->tab()->textBrowserDesc;
            break;
      case TAB_PATCH:
            te = static_cast<PatchView*>(t)->tab()->textEditDiff;
            break;
      case TAB_FILE:
            te = static_cast<FileView*>(t)->tab()->textEditFile;
            break;
      default:
            break;
      }
      return te;
}

void MainImpl::scrollTextEdit(int delta) {

      QTextEdit* te = getCurrentTextEdit();
      if (!te)
            return;

      int h = te->visibleHeight();
      int ls = te->fontMetrics().lineSpacing();
      if (delta == 1 || delta == -1) {
            te->scrollBy(0, delta * (h - ls));
            return;
      }
      te->scrollBy(0, delta * ls);
}

void MainImpl::scrollListView(int delta) {

      QWidget* lv = NULL;
      QObject* t;
      switch (currentTabType(&t)) {
      case TAB_REV:
            lv = static_cast<RevsView*>(t)->tab()->listViewLog;
            break;
      case TAB_FILE:
            lv = static_cast<FileView*>(t)->tab()->histListView;
            break;
      default:
            lv = qApp->focusWidget();
            break;
      }
      if (!lv)
            return;

      int key = (delta == 1) ? Key_Down : Key_Up;
      QKeyEvent p(QEvent::KeyPress, key, 0, 0);
      QKeyEvent r(QEvent::KeyRelease, key, 0, 0);
      QApplication::sendEvent(lv, &p);
      QApplication::sendEvent(lv, &r);
}

void MainImpl::adjustFontSize(int delta) {
// font size is adjusted on a 'per instance' base and only on list views

      QListView* lv = rv->tab()->listViewLog; // we cannot use a different list view. Wrong sizes!
      QFont f(lv->font());
      f.setPointSize(f.pointSize() + delta);

      // little hack to read new item height
      lv->setFont(f);
      QListViewItem* item = new QListViewItem(lv);
      int h = item->height();
      delete item;

      setupPixmaps(h); // (re)create the pixmaps

      // clear main view graphs
      rv->listViewLog->repaintAll(f);
      QPtrList<FileView>* l = getTabs<FileView>();
      for (FileView* item = l->first(); item; item = l->next())
            item->histListView->repaintAll(f);
      delete l;
}

// ****************************** Menu *********************************

void MainImpl::updateCommitMenu(bool isStGITStack) {

      int i = 0;
      bool found = false;
      while (!found && Edit->idAt(i) != -1) {
            SCRef txt(Edit->text(Edit->idAt(i++)));
            found = (txt == "&Commit..." || txt == "St&GIT patch...");
      }
      if (!found)
            return;

      const QString newText(isStGITStack ? "St&GIT patch..." : "&Commit...");
      Edit->changeItem(Edit->idAt(--i), newText);
}

void MainImpl::updateRecentRepoMenu(SCRef newEntry) {

      // update menu of all windows
      QWidgetList* list = QApplication::topLevelWidgets();
      QWidgetListIt it(*list);
      while (it.current() != 0) {
            MainImpl* w = dynamic_cast<MainImpl*>(it.current());
            if (w)
                  w->doUpdateRecentRepoMenu(newEntry);
            ++it;
      }
      delete list;
}

void MainImpl::doUpdateRecentRepoMenu(SCRef newEntry) {

      while (File->idAt(recentRepoMenuPos) != -1)
            File->removeItemAt(recentRepoMenuPos); // removes also any separator

      QSettings settings;
      SCRef r(settings.readEntry(APP_KEY + REC_REP_KEY, ""));
      if (r.isEmpty() && newEntry.isEmpty())
            return;

      QStringList recents(QStringList::split(',', r));
      QStringList::iterator it = recents.find(newEntry);
      if (it != recents.end())
            recents.remove(it);

      if (!newEntry.isEmpty())
            recents.prepend(newEntry);

      File->insertSeparator();

      QStringList::const_iterator it2 = recents.constBegin();
      for (int i = 1 ; it2 != recents.constEnd() && i <= MAX_RECENT_REPOS; ++it2, ++i)
            File->insertItem(QString::number(i) + " " + *it2);

      for (int i = recents.count() - MAX_RECENT_REPOS; i > 0; i--)
            recents.pop_back();

      settings.writeEntry(APP_KEY + REC_REP_KEY, recents.join(","));
}

void MainImpl::doContexPopup(SCRef sha) {

      // we need to use popup() to be non blocking and we need a
      // global scope because we use a signal/slot connection
      delete contextMenu;
      delete contextSubMenu;
      contextMenu = new QPopupMenu(this);
      contextSubMenu = new QPopupMenu(this);
      connect(contextMenu, SIGNAL(activated(int)), this, SLOT(on_goRef_activated(int)));
      connect(contextSubMenu, SIGNAL(activated(int)), this, SLOT(on_goRef_activated(int)));

      QObject* t;
      int tt = currentTabType(&t);
      bool isRevPage = (tt == TAB_REV);
      bool isPatchPage = (tt == TAB_PATCH);
      bool isFilePage = (tt == TAB_FILE);

      if (!isFilePage && ActCheckWorkDir->isEnabled())
            ActCheckWorkDir->addTo(contextMenu);

      contextMenu->insertSeparator();

      if (!isPatchPage && ActViewDiff->isEnabled())
            ActViewDiff->addTo(contextMenu);

      if (isRevPage && ActViewDiffNewTab->isEnabled())
            ActViewDiffNewTab->addTo(contextMenu);

      if (isFilePage && ActViewRev->isEnabled())
            ActViewRev->addTo(contextMenu);

      if (!isFilePage && ActExternalDiff->isEnabled())
            ActExternalDiff->addTo(contextMenu);

      if (isRevPage) {
            if (ActCommit->isEnabled() && (sha == ZERO_SHA))
                  ActCommit->addTo(contextMenu);
            if (ActTag->isEnabled())
                  ActTag->addTo(contextMenu);
            if (ActTagDelete->isEnabled())
                  ActTagDelete->addTo(contextMenu);
            if (ActMailFormatPatch->isEnabled())
                  ActMailFormatPatch->addTo(contextMenu);
            if (ActPush->isEnabled())
                  ActPush->addTo(contextMenu);
            if (ActPop->isEnabled())
                  ActPop->addTo(contextMenu);

            const QStringList& bn(git->getBranchNames());
            const QStringList& tn(git->getTagNames(Git::optOnlyLoaded));
            if (bn.empty() && tn.empty()) {
                  contextMenu->exec(QCursor::pos());
                  return;
            }
            int id = 1;
            if (!bn.empty()) {
                  contextMenu->insertSeparator();
                  QStringList::const_iterator it = bn.constBegin();
                  for ( ; it != bn.constEnd(); ++it, id++) {
                        // branch names have id > 0 to disambiguate them from actions,
                        // Qt assigns negative id as default
                        if (id < MAX_MENU_ENTRIES)
                              contextMenu->insertItem(*it, id);
                        else
                              contextSubMenu->insertItem(*it, id);
                  }
            }
            if (!tn.empty()) {
                  contextMenu->insertSeparator();
                  QStringList::const_iterator it = tn.constBegin();
                  for ( ; it != tn.constEnd(); ++it, id++) {
                        // tag names have id > 0 to disambiguate them from actions,
                        // Qt assigns negative id as default
                        if (id < MAX_MENU_ENTRIES)
                              contextMenu->insertItem(*it, id);
                        else
                              contextSubMenu->insertItem(*it, id);
                  }
            }
            if (contextSubMenu->count() > 0)
                  contextMenu->insertItem("More...", contextSubMenu);
      }
      contextMenu->popup(QCursor::pos());
}

void MainImpl::doFileContexPopup(SCRef fileName, int type) {

      QPopupMenu contextMenu;

      QObject* t;
      int tt = currentTabType(&t);
      bool isRevPage = (tt == TAB_REV);
      bool isPatchPage = (tt == TAB_PATCH);
      bool isDir = rv->treeView->isDir(fileName);

      if (type == POPUP_FILE_EV)
            if (!isPatchPage && ActViewDiff->isEnabled())
                  ActViewDiff->addTo(&contextMenu);

      if (!isDir && ActViewFile->isEnabled())
            ActViewFile->addTo(&contextMenu);

      if (!isDir && ActViewFileNewTab->isEnabled())
            ActViewFileNewTab->addTo(&contextMenu);

      if (!isRevPage && (type == POPUP_FILE_EV) && ActViewRev->isEnabled())
            ActViewRev->addTo(&contextMenu);

      if (ActFilterTree->isEnabled())
            ActFilterTree->addTo(&contextMenu);

      if (!isDir) {
            if (ActSaveFile->isEnabled())
                  ActSaveFile->addTo(&contextMenu);
            if ((type == POPUP_FILE_EV) && ActExternalDiff->isEnabled())
                  ActExternalDiff->addTo(&contextMenu);
      }
      contextMenu.exec(QCursor::pos());
}

void MainImpl::on_goRef_activated(int id) {

      if (id <= 0) // not a tag name entry
            return;

      SCRef refSha(git->getRefSha(contextMenu->text(id)));
      rv->st.setSha(refSha);
      UPDATE_DOMAIN(rv);
}

void MainImpl::ActSplitView_activated() {

      bool hide;
      QObject* t;
      switch (currentTabType(&t)) {
      case TAB_REV: {
            RevsView* rv = static_cast<RevsView*>(t);
            hide = rv->tab()->textBrowserDesc->isVisible();
            rv->tab()->textBrowserDesc->setHidden(hide);
            rv->tab()->listBoxFiles->setHidden(hide); }
            break;
      case TAB_PATCH: {
            PatchView* pv = static_cast<PatchView*>(t);
            hide = pv->tab()->textBrowserDesc->isVisible();
            pv->tab()->textBrowserDesc->setHidden(hide); }
            break;
      case TAB_FILE: {
            FileView* fv = static_cast<FileView*>(t);
            hide = fv->tab()->histListView->isVisible();
            fv->tab()->histListView->setHidden(hide); }
            break;
      default:
            dbs("ASSERT in ActSplitView_activated: unknown current page");
            break;
      }
}

void MainImpl::ActShowTree_toggled(bool b) {

      if (b) {
            treeView->show();
            UPDATE_DOMAIN(rv);
      } else
            treeView->hide();
}

void MainImpl::ActSaveFile_activated() {

      QFileInfo f(rv->st.fileName());
      const QString fileName(QFileDialog::getSaveFileName(f.fileName(), "",
                             this, "save file dialog", "Save file as"));

      if (fileName.isEmpty())
            return;

      QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

      if (!git->saveFile(rv->st.fileName(), rv->st.sha(), fileName))
            statusBar()->message("Unable to save " + fileName);

      QApplication::restoreOverrideCursor();
}

void MainImpl::on_openRecent_activated(int id) {

      bool ok;
      File->text(id).left(1).toInt(&ok);
      if (!ok) // only recent repos entries have a number in first char
            return;

      const QString workDir(File->text(id).section(' ', 1));
      if (!workDir.isEmpty())
            setRepository(workDir, false, false);
}

void MainImpl::ActOpenRepo_activated() {

      const QString dirName(QFileDialog::getExistingDirectory(curDir,
                            this, "", "Choose a directory"));

      if (!dirName.isEmpty()) {
            QDir d(dirName);
            setRepository(d.absPath(), false, false);
      }
}

void MainImpl::ActOpenRepoNewWindow_activated() {

      const QString dirName(QFileDialog::getExistingDirectory(curDir,
                            this, "", "Choose a directory"));

      if (!dirName.isEmpty()) {
            QDir d(dirName);
            MainImpl* newWin = new MainImpl(d.absPath());
            newWin->show();
      }
}

void MainImpl::refreshRepo(bool b) {

      setRepository(curDir, true, b);
}

void MainImpl::ActRefresh_activated() {

      refreshRepo(true);
}

void MainImpl::ActMailFormatPatch_activated() {

      if (rv->tab()->listViewLog->childCount() == 0)
            return;

      if (rv->tab()->listViewLog->currentItem() == NULL) {
            statusBar()->message("At least one selected revision needed");
            return;
      }
      QStringList selectedItems;
      rv->listViewLog->getSelectedItems(selectedItems);
      if (selectedItems.contains(ZERO_SHA)) {
            statusBar()->message("Unable to format patch for not committed content");
            return;
      }
      QSettings settings;
      QString outDir(settings.readEntry(APP_KEY + FP_DIR_KEY, curDir));
      QString dirPath(QFileDialog::getExistingDirectory(outDir, this, "",
                      "Choose destination directory - Format Patch"));
      if (dirPath.isEmpty())
            return;

      QDir d(dirPath);
      settings.writeEntry(APP_KEY + FP_DIR_KEY, d.absPath());
      QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
      git->formatPatch(selectedItems, d.absPath());
      QApplication::restoreOverrideCursor();
}

bool MainImpl::askApplyPatchParameters(bool* commit, bool* fold) {

      int ret = QMessageBox::question(this, "Apply Patch",
                "Do you want to commit or just to apply changes to "
                "working directory?", "&Cancel", "&Working dir", "&Commit", 0, 0);
      if (ret == 0)
            return false;

      *commit = (ret == 2);
      *fold = false;
      if (*commit && git->isStGITStack()) {
            ret = QMessageBox::question(this, "Apply Patch", "Do you want to "
                  "import or fold the patch?", "&Cancel", "&Fold", "&Import", 0, 0);
            if (ret == 0)
                  return false;

            *fold = (ret == 1);
      }
      return true;
}

void MainImpl::ActMailApplyPatch_activated() {

      QSettings settings;
      QString outDir(settings.readEntry(APP_KEY + FP_DIR_KEY, curDir));
      QString patchName(QFileDialog::getOpenFileName(outDir, NULL, this,
                        "", "Choose the patch file - Apply Patch"));
      if (patchName.isEmpty())
            return;

      QFileInfo f(patchName);
      settings.writeEntry(APP_KEY + FP_DIR_KEY, f.dirPath(true));

      bool commit, fold;
      if (!askApplyPatchParameters(&commit, &fold))
            return;

      QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

      if (git->applyPatchFile(f.absFilePath(), commit, fold, Git::optSign) && !commit)
            git->resetCommits(1);

      QApplication::restoreOverrideCursor();
      refreshRepo(false);
}

void MainImpl::ActCheckWorkDir_toggled(bool b) {

      if (!ActCheckWorkDir->isEnabled()) // to avoid looping with setOn()
            return;

      setFlag(DIFF_INDEX_F, b);
      bool keepSelection = (rv->st.sha() != ZERO_SHA);
      refreshRepo(keepSelection);
}

void MainImpl::ActSettings_activated() {

      SettingsImpl* setView = new SettingsImpl(this, git);
      setView->exec(); // modal exec

      // update ActCheckWorkDir if necessary
      if (ActCheckWorkDir->isOn() != testFlag(DIFF_INDEX_F))
            ActCheckWorkDir->toggle();
}

void MainImpl::ActCustomActionSetup_activated() {

      CustomActionImpl* ca = new CustomActionImpl(); // has WDestructiveClose
      connect(this, SIGNAL(closeAllWindows()), ca, SLOT(close()));
      connect(ca, SIGNAL(listChanged(const QStringList&)),
              this, SLOT(on_customActionListChanged(const QStringList&)));

      ca->show();
}

void MainImpl::on_customActionListChanged(const QStringList& list) {

      // update menu of all windows
      QWidgetList* l = QApplication::topLevelWidgets();
      QWidgetListIt it(*l);
      while (it.current() != 0) {
            MainImpl* w = dynamic_cast<MainImpl*>(it.current());
            if (w)
                  w->doUpdateCustomActionMenu(list);
            ++it;
      }
      delete l; // list is created using new by topLevelWidgets()
}

void MainImpl::doUpdateCustomActionMenu(const QStringList& list) {

      while (Actions->idAt(1) != -1) // clear menu
            Actions->removeItemAt(1);

      if (list.isEmpty())
            return;

      Actions->insertSeparator();
      loopList(it, list)
            Actions->insertItem(*it);
}

void MainImpl::on_customAction_activated(int id) {

      const QString name(Actions->text(id));
      if (name == "Setup actions...")
            return;

      QSettings set;
      QStringList sl = QStringList::split(",", set.readEntry(APP_KEY + MCR_LIST_KEY, ""));

      if (sl.findIndex(name) == -1) {
            dbp("ASSERT in on_customAction_activated, action %1 not found", name);
            return;
      }
      const QString header("Macro " + name + "/");
      QString cmdArgs;

      if (testFlag(MCR_CMD_LINE_F, header)) {
            bool ok;
            cmdArgs = QInputDialog::getText("Run action - QGit", "Enter command line "
                      "arguments for '" + name + "'", QLineEdit::Normal, "", &ok, this);
            cmdArgs.prepend(' ');
            if (!ok)
                  return;
      }
      SCRef cmd = set.readEntry(APP_KEY + header + MCR_TEXT_KEY, "");
      if (cmd.isEmpty())
            return;

      ConsoleImpl* c = new ConsoleImpl(name, git); // has WDestructiveClose
      connect(this, SIGNAL(closeAllWindows()), c, SLOT(close()));
      connect(c, SIGNAL(customAction_exited(const QString&)),
            this, SLOT(on_customAction_exited(const QString&)));

      if (c->start(cmd, cmdArgs))
            c->show();
}

void MainImpl::on_customAction_exited(const QString& name) {

      const QString header("Macro " + name + "/");
      if (testFlag(MCR_REFRESH_F, header))
            QTimer::singleShot(10, this, SLOT(refreshRepo())); // outside of event handler
}

void MainImpl::ActCommit_activated() {

      ActCommit_setEnabled(false);
      CommitImpl* commitViewer = new CommitImpl(git, &changesCommitted); // has WDestructiveClose
      connect(this, SIGNAL(closeAllWindows()), commitViewer, SLOT(close()));
      connect(commitViewer, SIGNAL(destroyed()), this, SLOT(slotCommitViewerClosed()));
      commitViewer->show();
}

void MainImpl::ActCommit_setEnabled(bool b) {

      // pop and push commands fail if there are local changes,
      // so in this case we disable ActPop and ActPush
      if (b) {
            ActPush->setEnabled(false);
            ActPop->setEnabled(false);
      }
      ActCommit->setEnabled(b);
}

void MainImpl::slotCommitViewerClosed() {

      if (changesCommitted) {
            changesCommitted = false;
            refreshRepo(false);
      } else {
            QListViewItem* item = rv->tab()->listViewLog->findItem(ZERO_SHA, COMMIT_COL);
            ActCommit_setEnabled(item != NULL && !git->isCommittingMerge());
            statusBar()->message("Failed to commit changes");
      }
}

void MainImpl::ActTag_activated() {

      int adj = -1; // hack to correctly map col numbers in main view
      QString tag(rv->tab()->listViewLog->currentItem()->text(LOG_COL + adj));
      bool ok;
      tag = QInputDialog::getText("Make tag - QGit", "Enter tag name:",
                                  QLineEdit::Normal, tag, &ok, this);
      if (!ok || tag.isEmpty())
            return;

      QString tmp(tag.simplifyWhiteSpace());
      if (tag != tmp.remove(' ')) {
            QMessageBox::warning(this, "Make tag - QGit",
                         "Sorry, control characters or spaces\n"
                         "are not allowed in tag name.");
            return;
      }
      if (git->isTagName(tag)) {
            QMessageBox::warning(this, "Make tag - QGit",
                         "Sorry, tag name already exists.\n"
                         "Please choose a different name.");
            return;
      }
      QString msg(QInputDialog::getText("Make tag - QGit",
              "Enter tag message, if any:", QLineEdit::Normal, "", &ok, this));

      QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
      ok = git->makeTag(lineEditSHA->text(), tag, msg);
      QApplication::restoreOverrideCursor();
      if (ok)
            refreshRepo(true);
      else
            statusBar()->message("Sorry, unable to tag the revision");
}

void MainImpl::ActTagDelete_activated() {

      if (QMessageBox::question(this, "Delete tag - QGit",
                       "Do you want to un-tag selected revision?",
                       "&Yes", "&No", QString::null, 0, 1) == 1)
            return;

      QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
      bool ok = git->deleteTag(lineEditSHA->text());
      QApplication::restoreOverrideCursor();
      if (ok)
            refreshRepo(true);
      else
            statusBar()->message("Sorry, unable to un-tag the revision");
}

void MainImpl::ActPush_activated() {

      QStringList selectedItems;
      rv->listViewLog->getSelectedItems(selectedItems);
      for (uint i = 0; i < selectedItems.count(); i++) {
            if (!git->isUnapplied(selectedItems[i])) {
                  statusBar()->message("Please, select only unapplied patches");
                  return;
            }
      }
      QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
      bool ok = true;
      for (uint i = 0; i < selectedItems.count(); i++) {
            const QString tmp(QString("Pushing patch %1 of %2")
                              .arg(i+1).arg(selectedItems.count()));
            statusBar()->message(tmp);
            SCRef sha = selectedItems[selectedItems.count() - i - 1];
            if (!git->stgPush(sha)) {
                  statusBar()->message("Failed to push patch " + sha);
                  ok = false;
                  break;
            }
      }
      if (ok)
            statusBar()->clear();

      QApplication::restoreOverrideCursor();
      refreshRepo(false);
}

void MainImpl::ActPop_activated() {

      QStringList selectedItems;
      rv->listViewLog->getSelectedItems(selectedItems);
      if (selectedItems.count() > 1) {
            statusBar()->message("Please, select one revision only");
            return;
      }
      QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
      git->stgPop(selectedItems[0]);
      QApplication::restoreOverrideCursor();
      refreshRepo(false);
}

void MainImpl::ActFilterTree_toggled(bool b) {

      if (!ActFilterTree->isEnabled()) {
            dbs("ASSERT ActFilterTree_toggled while disabled");
            return;
      }
      if (b) {
            QStringList selectedItems;
            if (!treeView->isVisible())
                  rv->treeView->update(); // force tree updating

            rv->treeView->getTreeSelectedItems(selectedItems);
            if (selectedItems.count() == 0) {
                  dbs("ASSERT tree filter action activated with no selected items");
                  return;
            }
            statusBar()->message("Filter view on " + selectedItems.join(" "));
            setRepository(curDir, true, true, &selectedItems);
      } else
            refreshRepo(true);
}

void MainImpl::ActFindNext_activated() {

      QTextEdit* te = getCurrentTextEdit();
      if (!te || textToFind.isEmpty())
            return;

      bool endOfDocument = false;
      while (true) {
            if (te->find(textToFind, false, false))
                  return;

            if (endOfDocument) {
                  QMessageBox::warning(this, "Find text - QGit", "Text \"" +
                               textToFind + "\" not found!", QMessageBox::Ok, 0);
                  return;
            }
            if (QMessageBox::question(this, "Find text - QGit", "End of document "
                "reached\n\nDo you want to continue from beginning?", QMessageBox::Yes,
                QMessageBox::No | QMessageBox::Escape) == QMessageBox::No)
                  return;

            endOfDocument = true;
            te->setCursorPosition(0, 0);
      }
}

void MainImpl::ActFind_activated() {

      QTextEdit* te = getCurrentTextEdit();
      if (!te)
            return;

      QString def(textToFind);
      if (te->hasSelectedText()) {
            TextFormat tf = te->textFormat();
            te->setTextFormat(Qt::PlainText); // we want text without formatting tags
            def = te->selectedText().section('\n', 0, 0);
            te->setTextFormat(tf);
      }
      bool ok;
      QString str(QInputDialog::getText("Find text - QGit", "Text to find:",
                                        QLineEdit::Normal, def, &ok, this));
      if (!ok || str.isEmpty())
            return;

      textToFind = str; // update with valid data only
      ActFindNext_activated();
}

void MainImpl::ActHelp_activated() {

      // helpInfo is defined in help.h
      HelpBase* helpDlg = new HelpBase(NULL, 0, Qt::WDestructiveClose);
      connect(this, SIGNAL(closeAllWindows()), helpDlg, SLOT(close()));
      helpDlg->textEditHelp->setText(QString::fromLatin1(helpInfo));
      helpDlg->show();
      helpDlg->raise();
}

void MainImpl::ActAbout_activated() {

      static const char* aboutMsg =
      "<center><p><b>QGit version " PACKAGE_VERSION "</b></p><br>"
      "<p>Copyright  2005, 2006 Marco Costalba</p>"
      "<p>Use and redistribute under the "
      "terms of the GNU General Public License</p></center>";
      QMessageBox::about(this, "About QGit", QString::fromLatin1(aboutMsg));
}

void MainImpl::closeEvent(QCloseEvent* ce) {

      // lastWindowClosed() signal is emitted by close(), after sending
      // closeEvent(), so we need to close _here_ all secondary windows before
      // the close() method checks for lastWindowClosed flag to avoid missing
      // the signal and stay in the main loop forever, because lastWindowClosed()
      // signal is connected to qApp->quit()
      //
      // note that we cannot rely on setting 'this' parent in secondary windows
      // because when close() is called childern are still alive and, finally,
      // when childern are deleted, d'tor do not call close() anymore. So we miss
      // lastWindowClosed() signal in this case.
      emit closeAllWindows();

      hide();

      EM_RAISE(exExiting);
      if (!git->stop(Git::optSaveCache)) {
            // not all processes have been deleted, there is
            // still some run() not returned somewhere, it is
            // not safe to delete run() callers objects now
            QTimer::singleShot(100, this, SLOT(ActClose_activated()));
            return;
      }
      emit closeAllTabs();
      delete rv;
      MainBase::closeEvent(ce);
}

void MainImpl::ActClose_activated() {

      close();
}

void MainImpl::ActExit_activated() {

      qApp->closeAllWindows();
}

Generated by  Doxygen 1.6.0   Back to index