# HG changeset patch # User Chris Cannam # Date 1300962471 0 # Node ID b9c153e00e84f63ccbd0b630c4d74601c208ccc2 # Parent 19cce6d2c4701eafcd2982d05342154af60fda40 Move source files to src/ diff -r 19cce6d2c470 -r b9c153e00e84 annotatedialog.cpp --- a/annotatedialog.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "annotatedialog.h" -#include "common.h" -#include "colourset.h" -#include "debug.h" - -#include -#include -#include -#include -#include - -AnnotateDialog::AnnotateDialog(QWidget *w, QString text) : - QDialog(w) -{ - setMinimumWidth(800); - setMinimumHeight(500); - - text.replace("\r\n", "\n"); - QStringList lines = text.split("\n"); - - QGridLayout *layout = new QGridLayout; - QTableWidget *table = new QTableWidget; - - QRegExp annotateLineRE = QRegExp("^([^:]+) ([a-z0-9]{12}) ([0-9-]+): (.*)$"); - - table->setRowCount(lines.size()); - table->setColumnCount(4); - table->horizontalHeader()->setStretchLastSection(true); - table->verticalHeader()->setDefaultSectionSize - (table->verticalHeader()->fontMetrics().height() + 2); - - QStringList labels; - labels << tr("User") << tr("Revision") << tr("Date") << tr("Content"); - table->setHorizontalHeaderLabels(labels); - - table->setShowGrid(false); - - QFont monofont("Monospace"); - monofont.setStyleHint(QFont::TypeWriter); - - int row = 0; - - foreach (QString line, lines) { - if (annotateLineRE.indexIn(line) == 0) { - QStringList items = annotateLineRE.capturedTexts(); - QString id = items[2]; - QColor colour = ColourSet::instance()->getColourFor(id); - QColor bg = QColor::fromHsv(colour.hue(), - 30, - 230); - // note items[0] is the whole match, so we want 1-4 - for (int col = 0; col+1 < items.size(); ++col) { - QString item = items[col+1]; - if (col == 0) item = item.trimmed(); - QTableWidgetItem *wi = new QTableWidgetItem(item); - wi->setFlags(Qt::ItemIsEnabled); - wi->setBackground(bg); - if (col == 3) { // id, content - wi->setFont(monofont); - } - table->setItem(row, col, wi); - } - } else { - DEBUG << "AnnotateDialog: Failed to match RE in line: " << line << " at row " << row << endl; - } - ++row; - } - - QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); - connect(bb, SIGNAL(accepted()), this, SLOT(accept())); - - layout->addWidget(table, 0, 0); - layout->addWidget(bb, 1, 0); - - setLayout(layout); -} - diff -r 19cce6d2c470 -r b9c153e00e84 annotatedialog.h --- a/annotatedialog.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef ANNOTATE_DIALOG_H -#define ANNOTATE_DIALOG_H - -#include - -class AnnotateDialog : public QDialog -{ - Q_OBJECT - -public: - AnnotateDialog(QWidget *parent, QString output); -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 changeset.cpp --- a/changeset.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "changeset.h" -#include "common.h" - -#include - -Changeset::Changeset(const LogEntry &e) -{ - foreach (QString key, e.keys()) { - if (key == "parents") { - QStringList parents = e.value(key).split - (" ", QString::SkipEmptyParts); - setParents(parents); - } else if (key == "tag") { - QStringList tags = e.value(key).split - (" ", QString::SkipEmptyParts); - setTags(tags); - } else if (key == "timestamp") { - setTimestamp(e.value(key).split(" ")[0].toULongLong()); - } else if (key == "changeset") { - setId(e.value(key)); - } else { - setProperty(key.toLocal8Bit().data(), e.value(key)); - } - } -} - -QString Changeset::getLogTemplate() -{ - return "id: {rev}:{node|short}\\nauthor: {author}\\nbranch: {branches}\\ntag: {tags}\\ndatetime: {date|isodate}\\ntimestamp: {date|hgdate}\\nage: {date|age}\\nparents: {parents}\\ncomment: {desc|json}\\n\\n"; -} - -QString Changeset::formatHtml() -{ - QString description; - QString rowTemplate = "%1 %2"; - - description = ""; - - QString c = comment().trimmed(); - c = c.replace(QRegExp("^\""), ""); - c = c.replace(QRegExp("\"$"), ""); - c = c.replace("\\\"", "\""); - c = xmlEncode(c); - c = c.replace("\\n", "
"); - - QStringList propNames, propTexts; - - propNames << "id" - << "author" - << "datetime" - << "branch" - << "tags" - << "comment"; - - propTexts << QObject::tr("Identifier:") - << QObject::tr("Author:") - << QObject::tr("Date:") - << QObject::tr("Branch:") - << QObject::tr("Tag:") - << QObject::tr("Comment:"); - - for (int i = 0; i < propNames.size(); ++i) { - QString prop = propNames[i]; - QString value; - if (prop == "id") { - value = hashOf(id()); - } else if (prop == "comment") { - value = c; - } else if (prop == "tags") { - value = tags().join(" "); - } else { - value = xmlEncode(property(prop.toLocal8Bit().data()).toString()); - } - if (value != "") { - description += rowTemplate - .arg(xmlEncode(propTexts[i])) - .arg(value); - } - } - - description += "
"; - - return description; -} diff -r 19cce6d2c470 -r b9c153e00e84 changeset.h --- a/changeset.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,152 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef CHANGESET_H -#define CHANGESET_H - -#include -#include -#include -#include -#include - -#include "logparser.h" - -class Changeset; - -typedef QList Changesets; - -class Changeset : public QObject -{ - Q_OBJECT - - Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged STORED true); - Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged STORED true); - Q_PROPERTY(QString branch READ branch WRITE setBranch NOTIFY branchChanged STORED true); - Q_PROPERTY(QStringList tags READ tags WRITE setTags NOTIFY tagsChanged STORED true); - Q_PROPERTY(QString datetime READ datetime WRITE setDatetime NOTIFY datetimeChanged STORED true); - Q_PROPERTY(qulonglong timestamp READ timestamp WRITE setTimestamp NOTIFY timestampChanged STORED true); - Q_PROPERTY(QString age READ age WRITE setAge NOTIFY ageChanged STORED true); - Q_PROPERTY(QStringList parents READ parents WRITE setParents NOTIFY parentsChanged STORED true); - Q_PROPERTY(QStringList children READ children WRITE setChildren NOTIFY childrenChanged STORED true); - Q_PROPERTY(QString comment READ comment WRITE setComment NOTIFY commentChanged STORED true); - -public: - Changeset() : QObject() { } - explicit Changeset(const LogEntry &e); - - QString id() const { return m_id; } - QString author() const { return m_author; } - QString branch() const { return m_branch; } - QStringList tags() const { return m_tags; } - QString datetime() const { return m_datetime; } - qulonglong timestamp() const { return m_timestamp; } - QString age() const { return m_age; } - QStringList parents() const { return m_parents; } - QString comment() const { return m_comment; } - - /** - * The children property is not obtained from Hg, but set in - * Grapher::layout() based on reported parents - */ - QStringList children() const { return m_children; } - - int number() const { - return id().split(':')[0].toInt(); - } - - QString authorName() const { - QString a = author(); - return a.replace(QRegExp("\\s*<[^>]*>"), ""); - } - - QString date() const { - return datetime().split(' ')[0]; - } - - bool isOnBranch(QString branch) { - QString b = m_branch; - if (branch == "") branch = "default"; - if (b == "") b = "default"; - if (branch == b) return true; - return false; - } - - static QString hashOf(QString id) { - return id.split(':')[1]; - } - - static QStringList getIds(Changesets csets) { - QStringList ids; - foreach (Changeset *cs, csets) ids.push_back(cs->id()); - return ids; - } - - static Changesets parseChangesets(QString logText) { - Changesets csets; - LogList log = LogParser(logText).parse(); - foreach (LogEntry e, log) { - csets.push_back(new Changeset(e)); - } - return csets; - } - - static QString getLogTemplate(); - - QString formatHtml(); - -signals: - void idChanged(QString id); - void authorChanged(QString author); - void branchChanged(QString branch); - void tagsChanged(QStringList tags); - void datetimeChanged(QString datetime); - void timestampChanged(qulonglong timestamp); - void ageChanged(QString age); - void parentsChanged(QStringList parents); - void childrenChanged(QStringList children); - void commentChanged(QString comment); - -public slots: - void setId(QString id) { m_id = id; emit idChanged(id); } - void setAuthor(QString author) { m_author = author; emit authorChanged(author); } - void setBranch(QString branch) { m_branch = branch; emit branchChanged(branch); } - void setTags(QStringList tags) { m_tags = tags; emit tagsChanged(tags); } - void addTag(QString tag) { m_tags.push_back(tag); emit tagsChanged(m_tags); } - void setDatetime(QString datetime) { m_datetime = datetime; emit datetimeChanged(datetime); } - void setTimestamp(qulonglong timestamp) { m_timestamp = timestamp; emit timestampChanged(timestamp); } - void setAge(QString age) { m_age = age; emit ageChanged(age); } - void setParents(QStringList parents) { m_parents = parents; emit parentsChanged(parents); } - void setChildren(QStringList children) { m_children = children; emit childrenChanged(m_children); } - void addChild(QString child) { m_children.push_back(child); emit childrenChanged(m_children); } - void setComment(QString comment) { m_comment = comment; emit commentChanged(comment); } - -private: - QString m_id; - QString m_author; - QString m_branch; - QStringList m_tags; - QString m_datetime; - qulonglong m_timestamp; - QString m_age; - QStringList m_parents; - QStringList m_children; - QString m_comment; -}; - - -#endif // CHANGESET_H diff -r 19cce6d2c470 -r b9c153e00e84 changesetdetailitem.cpp --- a/changesetdetailitem.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "changesetdetailitem.h" -#include "changeset.h" -#include "textabbrev.h" -#include "colourset.h" -#include "debug.h" -#include "common.h" - -#include -#include - -ChangesetDetailItem::ChangesetDetailItem(Changeset *cs) : - m_changeset(cs), m_doc(0) -{ - m_font = QFont(); - m_font.setPixelSize(11); - m_font.setBold(false); - m_font.setItalic(false); - - makeDocument(); -} - -ChangesetDetailItem::~ChangesetDetailItem() -{ - delete m_doc; -} - -QRectF -ChangesetDetailItem::boundingRect() const -{ - int w = 350; - m_doc->setTextWidth(w); - return QRectF(-10, -10, w + 10, m_doc->size().height() + 10); -} - -void -ChangesetDetailItem::paint(QPainter *paint, - const QStyleOptionGraphicsItem *option, - QWidget *w) -{ - paint->save(); - - ColourSet *colourSet = ColourSet::instance(); - QColor branchColour = colourSet->getColourFor(m_changeset->branch()); - QColor userColour = colourSet->getColourFor(m_changeset->author()); - - QFont f(m_font); - - QTransform t = paint->worldTransform(); - float scale = std::min(t.m11(), t.m22()); - if (scale > 1.0) { - int ps = int((f.pixelSize() / scale) + 0.5); - if (ps < 8) ps = 8; - f.setPixelSize(ps); - } - - if (scale < 0.1) { - paint->setPen(QPen(branchColour, 0)); - } else { - paint->setPen(QPen(branchColour, 2)); - } - - paint->setFont(f); - QFontMetrics fm(f); - int fh = fm.height(); - - int width = 350; - m_doc->setTextWidth(width); - int height = m_doc->size().height(); - - QRectF r(0.5, 0.5, width - 1, height - 1); - paint->setBrush(Qt::white); - paint->drawRect(r); - - if (scale < 0.1) { - paint->restore(); - return; - } - - // little triangle connecting to its "owning" changeset item - paint->setBrush(branchColour); - QVector pts; - pts.push_back(QPointF(0, height/3 - 5)); - pts.push_back(QPointF(0, height/3 + 5)); - pts.push_back(QPointF(-10, height/3)); - pts.push_back(QPointF(0, height/3 - 5)); - paint->drawPolygon(QPolygonF(pts)); - -/* - paint->setBrush(branchColour); - QVector pts; - pts.push_back(QPointF(width/2 - 5, 0)); - pts.push_back(QPointF(width/2 + 5, 0)); - pts.push_back(QPointF(width/2, -10)); - pts.push_back(QPointF(width/2 - 5, 0)); - paint->drawPolygon(QPolygonF(pts)); -*/ - m_doc->drawContents(paint, r); - - paint->restore(); -} - -void -ChangesetDetailItem::makeDocument() -{ - delete m_doc; - m_doc = new QTextDocument; - m_doc->setHtml(m_changeset->formatHtml()); -} - diff -r 19cce6d2c470 -r b9c153e00e84 changesetdetailitem.h --- a/changesetdetailitem.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef CHANGESETDETAILITEM_H -#define CHANGESETDETAILITEM_H - -#include -#include - -class Changeset; - -class ChangesetDetailItem : public QGraphicsItem -{ -public: - ChangesetDetailItem(Changeset *cs); - virtual ~ChangesetDetailItem(); - - virtual QRectF boundingRect() const; - virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); - - Changeset *getChangeset() { return m_changeset; } - -private: - QFont m_font; - Changeset *m_changeset; - QTextDocument *m_doc; - - void makeDocument(); -}; - -#endif // CHANGESETDETAILITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 changesetitem.cpp --- a/changesetitem.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,383 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "changesetitem.h" -#include "changesetscene.h" -#include "changesetdetailitem.h" -#include "changeset.h" -#include "textabbrev.h" -#include "colourset.h" -#include "debug.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -ChangesetItem::ChangesetItem(Changeset *cs) : - m_changeset(cs), m_detail(0), - m_showBranch(false), m_column(0), m_row(0), m_wide(false), - m_current(false), m_new(false) -{ - m_font = QFont(); - m_font.setPixelSize(11); - m_font.setBold(false); - m_font.setItalic(false); - setCursor(Qt::ArrowCursor); -} - -QString -ChangesetItem::getId() -{ - return m_changeset->id(); -} - -QRectF -ChangesetItem::boundingRect() const -{ - int w = 100; - if (m_wide) w = 180; - return QRectF(-((w-50)/2 - 1), -30, w - 3, 90); -} - -void -ChangesetItem::showDetail() -{ - if (m_detail) return; - m_detail = new ChangesetDetailItem(m_changeset); - m_detail->setZValue(zValue() + 1); - scene()->addItem(m_detail); - int w = 100; - if (m_wide) w = 180; - int h = 80; -// m_detail->moveBy(x() - (m_detail->boundingRect().width() - 50) / 2, -// y() + 60); - m_detail->moveBy(x() + (w + 50) / 2 + 10 + 0.5, - y() - (m_detail->boundingRect().height() - h) / 2 + 0.5); - emit detailShown(); -} - -void -ChangesetItem::hideDetail() -{ - if (!m_detail) return; - scene()->removeItem(m_detail); - delete m_detail; - m_detail = 0; - emit detailHidden(); -} - -void -ChangesetItem::mousePressEvent(QGraphicsSceneMouseEvent *e) -{ - DEBUG << "ChangesetItem::mousePressEvent" << endl; - if (e->button() == Qt::LeftButton) { - if (m_detail) { - hideDetail(); - } else { - showDetail(); - } - } else if (e->button() == Qt::RightButton) { - if (m_detail) { - hideDetail(); - } - activateMenu(); - } -} - -void -ChangesetItem::activateMenu() -{ - m_parentDiffActions.clear(); - m_summaryActions.clear(); - - QMenu *menu = new QMenu; - QLabel *label = new QLabel(tr(" Revision: %1") - .arg(Changeset::hashOf(m_changeset->id()))); - QWidgetAction *wa = new QWidgetAction(menu); - wa->setDefaultWidget(label); - menu->addAction(wa); - menu->addSeparator(); - - QAction *copyId = menu->addAction(tr("Copy identifier to clipboard")); - connect(copyId, SIGNAL(triggered()), this, SLOT(copyIdActivated())); - - QAction *stat = menu->addAction(tr("Summarise changes")); - connect(stat, SIGNAL(triggered()), this, SLOT(showSummaryActivated())); - - menu->addSeparator(); - - QStringList parents = m_changeset->parents(); - - QString leftId, rightId; - bool havePositions = false; - - if (parents.size() > 1) { - ChangesetScene *cs = dynamic_cast(scene()); - if (cs && parents.size() == 2) { - ChangesetItem *i0 = cs->getItemById(parents[0]); - ChangesetItem *i1 = cs->getItemById(parents[1]); - if (i0 && i1) { - if (i0->x() < i1->x()) { - leftId = parents[0]; - rightId = parents[1]; - } else { - leftId = parents[1]; - rightId = parents[0]; - } - havePositions = true; - } - } - } - - if (parents.size() > 1) { - if (havePositions) { - - QAction *diff = menu->addAction(tr("Diff to left parent")); - connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); - m_parentDiffActions[diff] = leftId; - - diff = menu->addAction(tr("Diff to right parent")); - connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); - m_parentDiffActions[diff] = rightId; - - } else { - - foreach (QString parentId, parents) { - QString text = tr("Diff to parent %1").arg(Changeset::hashOf(parentId)); - QAction *diff = menu->addAction(text); - connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); - m_parentDiffActions[diff] = parentId; - } - } - - } else { - - QAction *diff = menu->addAction(tr("Diff to parent")); - connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); - } - - QAction *diffCurrent = menu->addAction(tr("Diff to current working folder")); - connect(diffCurrent, SIGNAL(triggered()), this, SLOT(diffToCurrentActivated())); - - menu->addSeparator(); - - QAction *update = menu->addAction(tr("Update to this revision")); - connect(update, SIGNAL(triggered()), this, SLOT(updateActivated())); - - QAction *merge = menu->addAction(tr("Merge from here to current")); - connect(merge, SIGNAL(triggered()), this, SLOT(mergeActivated())); - - menu->addSeparator(); - - QAction *branch = menu->addAction(tr("Start new branch...")); - branch->setEnabled(m_current); - connect(branch, SIGNAL(triggered()), this, SLOT(newBranchActivated())); - - QAction *tag = menu->addAction(tr("Add tag...")); - connect(tag, SIGNAL(triggered()), this, SLOT(tagActivated())); - - menu->exec(QCursor::pos()); - - ungrabMouse(); -} - -void -ChangesetItem::copyIdActivated() -{ - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(Changeset::hashOf(m_changeset->id())); -} - -void ChangesetItem::diffToParentActivated() -{ - QAction *a = qobject_cast(sender()); - QString parentId; - if (m_parentDiffActions.contains(a)) { - parentId = m_parentDiffActions[a]; - DEBUG << "ChangesetItem::diffToParentActivated: specific parent " - << parentId << " selected" << endl; - } else { - parentId = m_changeset->parents()[0]; - DEBUG << "ChangesetItem::diffToParentActivated: " - << "no specific parent selected, using first parent " - << parentId << endl; - } - - emit diffToParent(getId(), parentId); -} - -void ChangesetItem::showSummaryActivated() -{ - emit showSummary(m_changeset); -} - -void ChangesetItem::updateActivated() { emit updateTo(getId()); } -void ChangesetItem::diffToCurrentActivated() { emit diffToCurrent(getId()); } -void ChangesetItem::mergeActivated() { emit mergeFrom(getId()); } -void ChangesetItem::tagActivated() { emit tag(getId()); } -void ChangesetItem::newBranchActivated() { emit newBranch(getId()); } - -void -ChangesetItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *) -{ - paint->save(); - - ColourSet *colourSet = ColourSet::instance(); - QColor branchColour = colourSet->getColourFor(m_changeset->branch()); - QColor userColour = colourSet->getColourFor(m_changeset->author()); - - QFont f(m_font); - - QTransform t = paint->worldTransform(); - float scale = std::min(t.m11(), t.m22()); - if (scale > 1.0) { - int ps = int((f.pixelSize() / scale) + 0.5); - if (ps < 8) ps = 8; - f.setPixelSize(ps); - } - - bool showText = (scale >= 0.2); - bool showProperLines = (scale >= 0.1); - - if (!showProperLines) { - paint->setPen(QPen(branchColour, 0)); - } else { - paint->setPen(QPen(branchColour, 2)); - } - - paint->setFont(f); - QFontMetrics fm(f); - int fh = fm.height(); - - int width = 100; - if (m_wide) width = 180; - int x0 = -((width - 50) / 2 - 1); - - int textwid = width - 7; - - QString comment; - QStringList lines; - int lineCount = 3; - - if (showText) { - - comment = m_changeset->comment().trimmed(); - comment = comment.replace("\\n", " "); - comment = comment.replace(QRegExp("^\"\\s*\\**\\s*"), ""); - comment = comment.replace(QRegExp("\"$"), ""); - comment = comment.replace("\\\"", "\""); - - comment = TextAbbrev::abbreviate(comment, fm, textwid, - TextAbbrev::ElideEnd, "...", 3); - // abbreviate() changes this (ouch!), restore it - textwid = width - 5; - - lines = comment.split('\n'); - lineCount = lines.size(); - - if (lineCount < 2) lineCount = 2; - } - - int height = (lineCount + 1) * fh + 2; - QRectF r(x0, 0, width - 3, height); - - if (showProperLines) { - - paint->setBrush(Qt::white); - - if (m_current) { - paint->drawRect(QRectF(x0 - 4, -4, width + 5, height + 8)); - } - - if (m_new) { - paint->save(); - paint->setPen(Qt::yellow); - paint->setBrush(Qt::NoBrush); - paint->drawRect(QRectF(x0 - 2, -2, width + 1, height + 4)); - paint->restore(); - } - } - - paint->drawRect(r); - - if (!showText) { - paint->restore(); - return; - } - - paint->fillRect(QRectF(x0 + 0.5, 0.5, width - 4, fh - 0.5), - QBrush(userColour)); - - paint->setPen(QPen(Qt::white)); - - QString person = TextAbbrev::abbreviate(m_changeset->authorName(), - fm, textwid); - paint->drawText(x0 + 3, fm.ascent(), person); - - paint->setPen(QPen(Qt::black)); - - QStringList tags = m_changeset->tags(); - if (!tags.empty()) { - QStringList nonTipTags; - foreach (QString t, tags) { - // I'm not convinced that showing the tip tag really - // works; I think perhaps it confuses as much as it - // illuminates. But also, our current implementation - // doesn't interact well with it because it moves -- it's - // the one thing that can actually (in normal use) change - // inside an existing changeset record even during an - // incremental update - if (t != "tip") nonTipTags.push_back(t); - } - if (!nonTipTags.empty()) { - QString tagText = nonTipTags.join(" ").trimmed(); - int tw = fm.width(tagText); - paint->fillRect(QRectF(x0 + width - 8 - tw, 1, tw + 4, fh - 1), - QBrush(Qt::yellow)); - paint->drawText(x0 + width - 6 - tw, fm.ascent(), tagText); - } - } - - if (m_showBranch) { - // write branch name - paint->save(); - f.setBold(true); - paint->setFont(f); - paint->setPen(QPen(branchColour)); - QString branch = m_changeset->branch(); - if (branch == "") branch = "default"; - int wid = width - 3; - branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid); - paint->drawText(x0, -fh + fm.ascent() - 4, branch); - f.setBold(false); - paint->restore(); - } - - paint->setFont(f); - - for (int i = 0; i < lines.size(); ++i) { - paint->drawText(x0 + 3, i * fh + fh + fm.ascent(), lines[i].trimmed()); - } - - paint->restore(); -} diff -r 19cce6d2c470 -r b9c153e00e84 changesetitem.h --- a/changesetitem.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef CHANGESETITEM_H -#define CHANGESETITEM_H - -#include -#include - -class Changeset; -class ChangesetDetailItem; - -class QAction; - -class ChangesetItem : public QGraphicsObject -{ - Q_OBJECT - -public: - ChangesetItem(Changeset *cs); - - virtual QRectF boundingRect() const; - virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); - - Changeset *getChangeset() { return m_changeset; } - QString getId(); - - int column() const { return m_column; } - int row() const { return m_row; } - void setColumn(int c) { m_column = c; setX(c * 100); } - void setRow(int r) { m_row = r; setY(r * 90); } - - bool isWide() const { return m_wide; } - void setWide(bool w) { m_wide = w; } - - bool isCurrent() const { return m_current; } - void setCurrent(bool c) { m_current = c; } - - bool isNew() const { return m_new; } - void setNew(bool n) { m_new = n; } - - bool showBranch() const { return m_showBranch; } - void setShowBranch(bool s) { m_showBranch = s; } - -signals: - void detailShown(); - void detailHidden(); - - void updateTo(QString); - void diffToCurrent(QString); - void diffToParent(QString child, QString parent); - void showSummary(Changeset *); - void mergeFrom(QString); - void newBranch(QString); - void tag(QString); - -public slots: - void showDetail(); - void hideDetail(); - -private slots: - void copyIdActivated(); - void updateActivated(); - void diffToParentActivated(); - void showSummaryActivated(); - void diffToCurrentActivated(); - void mergeActivated(); - void tagActivated(); - void newBranchActivated(); - -protected: - virtual void mousePressEvent(QGraphicsSceneMouseEvent *); - -private: - void activateMenu(); - - QFont m_font; - Changeset *m_changeset; - ChangesetDetailItem *m_detail; - bool m_showBranch; - int m_column; - int m_row; - bool m_wide; - bool m_current; - bool m_new; - - QMap m_parentDiffActions; - QMap m_summaryActions; -}; - -#endif // CHANGESETITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 changesetscene.cpp --- a/changesetscene.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "changesetscene.h" -#include "changesetitem.h" -#include "uncommitteditem.h" -#include "dateitem.h" - -ChangesetScene::ChangesetScene() - : QGraphicsScene(), m_detailShown(0) -{ -} - -void -ChangesetScene::addChangesetItem(ChangesetItem *item) -{ - addItem(item); - - connect(item, SIGNAL(detailShown()), - this, SLOT(changesetDetailShown())); - - connect(item, SIGNAL(detailHidden()), - this, SLOT(changesetDetailHidden())); - - connect(item, SIGNAL(updateTo(QString)), - this, SIGNAL(updateTo(QString))); - - connect(item, SIGNAL(diffToCurrent(QString)), - this, SIGNAL(diffToCurrent(QString))); - - connect(item, SIGNAL(diffToParent(QString, QString)), - this, SIGNAL(diffToParent(QString, QString))); - - connect(item, SIGNAL(showSummary(Changeset *)), - this, SIGNAL(showSummary(Changeset *))); - - connect(item, SIGNAL(mergeFrom(QString)), - this, SIGNAL(mergeFrom(QString))); - - connect(item, SIGNAL(newBranch(QString)), - this, SIGNAL(newBranch(QString))); - - connect(item, SIGNAL(tag(QString)), - this, SIGNAL(tag(QString))); -} - -void -ChangesetScene::addUncommittedItem(UncommittedItem *item) -{ - addItem(item); - - connect(item, SIGNAL(commit()), - this, SIGNAL(commit())); - - connect(item, SIGNAL(revert()), - this, SIGNAL(revert())); - - connect(item, SIGNAL(diff()), - this, SIGNAL(diffWorkingFolder())); - - connect(item, SIGNAL(showSummary()), - this, SIGNAL(showSummary())); - - connect(item, SIGNAL(showWork()), - this, SIGNAL(showWork())); - - connect(item, SIGNAL(newBranch()), - this, SIGNAL(newBranch())); - - connect(item, SIGNAL(noBranch()), - this, SIGNAL(noBranch())); - -} - -void -ChangesetScene::addDateItem(DateItem *item) -{ - addItem(item); - - connect(item, SIGNAL(clicked()), - this, SLOT(dateItemClicked())); -} - -void -ChangesetScene::changesetDetailShown() -{ - ChangesetItem *csi = qobject_cast(sender()); - if (!csi) return; - - if (m_detailShown && m_detailShown != csi) { - m_detailShown->hideDetail(); - } - m_detailShown = csi; -} - -void -ChangesetScene::changesetDetailHidden() -{ - m_detailShown = 0; -} - -void -ChangesetScene::dateItemClicked() -{ - if (m_detailShown) { - m_detailShown->hideDetail(); - } -} - -ChangesetItem * -ChangesetScene::getItemById(QString id) -{ - foreach (QGraphicsItem *it, items()) { - ChangesetItem *csit = dynamic_cast(it); - if (csit && csit->getId() == id) return csit; - } - return 0; -} - - diff -r 19cce6d2c470 -r b9c153e00e84 changesetscene.h --- a/changesetscene.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef CHANGESETSCENE_H -#define CHANGESETSCENE_H - -#include - -class ChangesetItem; -class Changeset; -class UncommittedItem; -class DateItem; - -class ChangesetScene : public QGraphicsScene -{ - Q_OBJECT - -public: - ChangesetScene(); - - void addChangesetItem(ChangesetItem *item); - void addUncommittedItem(UncommittedItem *item); - void addDateItem(DateItem *item); - - ChangesetItem *getItemById(QString id); // Slow: traversal required - -signals: - void commit(); - void revert(); - void diffWorkingFolder(); - void showSummary(); - void showWork(); - void newBranch(); - void noBranch(); - - void updateTo(QString id); - void diffToParent(QString id, QString parent); - void showSummary(Changeset *); - void diffToCurrent(QString id); - void mergeFrom(QString id); - void newBranch(QString id); - void tag(QString id); - -private slots: - void changesetDetailShown(); - void changesetDetailHidden(); - void dateItemClicked(); - -private: - ChangesetItem *m_detailShown; -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 clickablelabel.h --- a/clickablelabel.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef _CLICKABLE_LABEL_H_ -#define _CLICKABLE_LABEL_H_ - -#include - -class ClickableLabel : public QLabel -{ - Q_OBJECT - - Q_PROPERTY(bool mouseUnderline READ mouseUnderline WRITE setMouseUnderline) - -public: - ClickableLabel(const QString &text, QWidget *parent = 0) : - QLabel(text, parent), - m_naturalText(text) - { } - - ClickableLabel(QWidget *parent = 0) : - QLabel(parent) - { } - - ~ClickableLabel() - { } - - void setText(const QString &t) { - m_naturalText = t; - QLabel::setText(t); - } - - bool mouseUnderline() const { - return m_mouseUnderline; - } - - void setMouseUnderline(bool mu) { - m_mouseUnderline = mu; - if (mu) { - setTextFormat(Qt::RichText); - setCursor(Qt::PointingHandCursor); - } - } - -signals: - void clicked(); - -protected: - virtual void enterEvent(QEvent *) { - if (m_mouseUnderline) { - QLabel::setText(tr("%1").arg(m_naturalText)); - } - } - - virtual void leaveEvent(QEvent *) { - if (m_mouseUnderline) { - QLabel::setText(m_naturalText); - } - } - - virtual void mousePressEvent(QMouseEvent *) { - emit clicked(); - } - -private: - bool m_mouseUnderline; - QString m_naturalText; -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 colourset.cpp --- a/colourset.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - - -#include "colourset.h" - -ColourSet -ColourSet::m_instance; - -ColourSet::ColourSet() { } - -ColourSet * -ColourSet::instance() -{ - return &m_instance; -} - -QColor -ColourSet::getColourFor(QString n) -{ - if (m_defaultNames.contains(n)) return Qt::black; - if (m_colours.contains(n)) return m_colours[n]; - - QColor c; - - if (m_colours.empty()) { - c = QColor::fromHsv(0, 200, 100); - } else { - c = QColor::fromHsv((m_lastColour.hue() + 70) % 360, 200, 100); - } - - m_colours[n] = c; - m_lastColour = c; - return c; -} - - - diff -r 19cce6d2c470 -r b9c153e00e84 colourset.h --- a/colourset.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef _COLOURSET_H_ -#define _COLOURSET_H_ - -#include -#include -#include -#include - -class ColourSet -{ -public: - void clearDefaultNames() { m_defaultNames.clear(); } - void addDefaultName(QString n) { m_defaultNames.insert(n); } - - QColor getColourFor(QString n); - - static ColourSet *instance(); - -private: - ColourSet(); - QSet m_defaultNames; - QMap m_colours; - QColor m_lastColour; - - static ColourSet m_instance; -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 common.cpp --- a/common.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,259 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "common.h" -#include "debug.h" - -#include -#include -#include -#include -#include - -#include - -#ifdef Q_OS_WIN32 -#define _WIN32_WINNT 0x0500 -#define SECURITY_WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#include -#include -#include -#endif - -QString findInPath(QString name, QString installPath, bool executableRequired) -{ - bool found = false; - if (name != "") { - if (name[0] != '/' -#ifdef Q_OS_WIN32 - && (QRegExp("^\\w:").indexIn(name) != 0) -#endif - ) { -#ifdef Q_OS_WIN32 - QChar pathSep = ';'; -#else - QChar pathSep = ':'; -#endif - name = QFileInfo(name).fileName(); - QString path = - QProcessEnvironment::systemEnvironment().value("PATH"); - DEBUG << "findInPath: seeking location for binary " << name - << ": system path is " << path << endl; - if (installPath != "") { - DEBUG << "findInPath: install path is " << installPath - << ", adding to system path" << endl; - //!!! path = path + pathSep + installPath; - path = installPath + pathSep + path; - } -#ifndef Q_OS_WIN32 - //!!! - path = path + ":/usr/local/bin"; - DEBUG << "... adding /usr/local/bin just in case (fix and add settings dlg please)" - << endl; -#endif - QStringList elements = path.split(pathSep, QString::SkipEmptyParts); - foreach (QString element, elements) { - QString full = QDir(element).filePath(name); - QFileInfo fi(full); - DEBUG << "findInPath: looking at " << full << endl; - if (fi.exists() && fi.isFile()) { - DEBUG << "findInPath: it's a file" << endl; - if (!executableRequired || fi.isExecutable()) { - name = full; - DEBUG << "findInPath: found at " << name << endl; - found = true; - break; - } - } - } - } else { - // absolute path given - QFileInfo fi(name); - DEBUG << "findInPath: looking at absolute path " << name << endl; - if (fi.exists() && fi.isFile()) { - DEBUG << "findInPath: it's a file" << endl; - if (!executableRequired || fi.isExecutable()) { - DEBUG << "findInPath: found at " << name << endl; - found = true; - } - } - } - } -#ifdef Q_OS_WIN32 - if (!found) { - if (!name.endsWith(".exe")) { - return findInPath(name + ".exe", installPath, executableRequired); - } - } -#endif - if (found) { - return name; - } else { - return ""; - } -} - -#ifdef Q_OS_WIN32 -QString getUserRealName() -{ - TCHAR buf[1024]; - long unsigned int maxlen = 1000; - LPTSTR info = buf; - - if (!GetUserNameEx(NameDisplay, info, &maxlen)) { - DEBUG << "GetUserNameEx failed: " << GetLastError() << endl; - return ""; - } - -#ifdef UNICODE - return QString::fromUtf16((const unsigned short *)info); -#else - return QString::fromLocal8Bit(info); -#endif -} -#else -#ifdef Q_OS_MAC -// Nothing here: definition is in common_osx.mm -#else -QString getUserRealName() -{ - const int maxlen = 1023; - char buf[maxlen + 2]; - - if (getlogin_r(buf, maxlen)) return ""; - - struct passwd *p = getpwnam(buf); - if (!p) return ""; - - QString s(p->pw_gecos); - if (s != "") s = s.split(',')[0]; - return s; -} -#endif -#endif - -void loseControllingTerminal() -{ -#ifndef Q_OS_WIN32 - - if (!isatty(0)) { - DEBUG << "stdin is not a terminal" << endl; - } else { - DEBUG << "stdin is a terminal, detaching from it" << endl; - if (ioctl(0, TIOCNOTTY, NULL) < 0) { - perror("ioctl failed"); - DEBUG << "ioctl for TIOCNOTTY on stdin failed (errno = " << errno << ")" << endl; - } else { - DEBUG << "ioctl for TIOCNOTTY on stdin succeeded" << endl; - return; - } - } - - int ttyfd = open("/dev/tty", O_RDWR); - if (ttyfd < 0) { - DEBUG << "failed to open controlling terminal" << endl; - } else { - if (ioctl(ttyfd, TIOCNOTTY, NULL) < 0) { - perror("ioctl failed"); - DEBUG << "ioctl for TIOCNOTTY on controlling terminal failed (errno = " << errno << ")" << endl; - } else { - DEBUG << "ioctl for TIOCNOTTY on controlling terminal succeeded" << endl; - return; - } - } - -#endif -} - -void installSignalHandlers() -{ -#ifndef Q_OS_WIN32 - sigset_t sgnals; - sigemptyset (&sgnals); - sigaddset(&sgnals, SIGHUP); - sigaddset(&sgnals, SIGCONT); - pthread_sigmask(SIG_BLOCK, &sgnals, 0); -#endif -} - -FolderStatus getFolderStatus(QString path) -{ - if (path != "/" && path.endsWith("/")) { - path = path.left(path.length()-1); - } - DEBUG << "getFolderStatus: " << path << endl; - QFileInfo fi(path); - if (fi.exists()) { - DEBUG << "exists" << endl; - QDir dir(path); - if (!dir.exists()) { // returns false for files - DEBUG << "not directory" << endl; - return FolderIsFile; - } - if (QDir(dir.filePath(".hg")).exists()) { - DEBUG << "has repo" << endl; - return FolderHasRepo; - } - return FolderExists; - } else { - QDir parent = fi.dir(); - if (parent.exists()) { - DEBUG << "parent exists" << endl; - return FolderParentExists; - } - return FolderUnknown; - } -} - -QString getContainingRepoFolder(QString path) -{ - if (getFolderStatus(path) == FolderHasRepo) return ""; - - QFileInfo me(path); - QFileInfo parent(me.dir().absolutePath()); - - while (me != parent) { - QString parentPath = parent.filePath(); - if (getFolderStatus(parentPath) == FolderHasRepo) { - return parentPath; - } - me = parent; - parent = me.dir().absolutePath(); - } - - return ""; -} - -QString xmlEncode(QString s) -{ - s - .replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("\"", """) - .replace("'", "'"); - - return s; -} diff -r 19cce6d2c470 -r b9c153e00e84 common.h --- a/common.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef COMMON_H -#define COMMON_H - -#include - -#define MY_ICON_SIZE 32 -//!!!: -#define REPOMENU_TITLE "Repository actions" -#define WORKFOLDERMENU_TITLE "Workfolder actions" - -extern QString findInPath(QString name, QString installPath, bool executable); - -extern QString getSystem(); -extern QString getHgDirName(); - -extern QString getUserRealName(); - -extern void loseControllingTerminal(); - -void installSignalHandlers(); - -/** - * Status used in testing whether a folder argument (received from the - * user) is valid for particular uses. - */ -enum FolderStatus { - FolderUnknown, /// Neither the folder nor its parent exists - FolderParentExists, /// The folder is absent, but its parent exists - FolderExists, /// The folder exists and has no .hg repo in it - FolderHasRepo, /// The folder exists and has an .hg repo in it - FolderIsFile /// The "folder" is actually a file -}; - -FolderStatus getFolderStatus(QString path); - -/** - * If the given path is somewhere within an existing repository, - * return the path of the root directory of the repository (i.e. the - * one with .hg in it). - * - * If the given path is _not_ in a repository, or the given path _is_ - * the root directory of a repository, return QString(). Use - * getFolderStatus to distinguish between these cases. - */ -QString getContainingRepoFolder(QString path); - -QString xmlEncode(QString); - - -#endif //COMMON_H - - diff -r 19cce6d2c470 -r b9c153e00e84 confirmcommentdialog.cpp --- a/confirmcommentdialog.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,257 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "confirmcommentdialog.h" -#include "common.h" - -#include -#include -#include -#include -#include -#include - -ConfirmCommentDialog::ConfirmCommentDialog(QWidget *parent, - QString title, - QString introText, - QString initialComment, - QString okButtonText) : - QDialog(parent) -{ - setWindowTitle(title); - - QGridLayout *layout = new QGridLayout; - setLayout(layout); - QLabel *label = new QLabel(introText); - label->setWordWrap(true); - layout->addWidget(label, 0, 0); - - m_textEdit = new QTextEdit; - m_textEdit->setAcceptRichText(false); - m_textEdit->document()->setPlainText(initialComment); - m_textEdit->setMinimumWidth(360); - connect(m_textEdit, SIGNAL(textChanged()), this, SLOT(commentChanged())); - layout->addWidget(m_textEdit, 1, 0); - - QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok | - QDialogButtonBox::Cancel); - layout->addWidget(bbox, 2, 0); - m_ok = bbox->button(QDialogButtonBox::Ok); - m_ok->setDefault(true); - m_ok->setEnabled(initialComment != ""); - m_ok->setText(okButtonText); - bbox->button(QDialogButtonBox::Cancel)->setAutoDefault(false); - - connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); - connect(bbox, SIGNAL(rejected()), this, SLOT(reject())); -} - -void ConfirmCommentDialog::commentChanged() -{ - m_ok->setEnabled(getComment() != ""); -} - -QString ConfirmCommentDialog::getComment() const -{ - return m_textEdit->document()->toPlainText(); -} - -QString ConfirmCommentDialog::buildFilesText(QString intro, QStringList files) -{ - QString text; - - if (intro == "") text = ""; - else text = "" + intro + "

"; - - text += ""; - foreach (QString file, files) { - text += "   " + xmlEncode(file) + "
"; - } - text += "
"; - - return text; -} - -bool ConfirmCommentDialog::confirm(QWidget *parent, - QString title, - QString head, - QString text, - QString okButtonText) -{ - QMessageBox box(QMessageBox::Question, - title, - head, - QMessageBox::Cancel, - parent); - - box.setInformativeText(text); - - QPushButton *ok = box.addButton(QMessageBox::Ok); - ok->setText(okButtonText); - box.setDefaultButton(QMessageBox::Ok); - if (box.exec() == -1) return false; - return box.standardButton(box.clickedButton()) == QMessageBox::Ok; -} - -bool ConfirmCommentDialog::confirmDangerous(QWidget *parent, - QString title, - QString head, - QString text, - QString okButtonText) -{ - QMessageBox box(QMessageBox::Warning, - title, - head, - QMessageBox::Cancel, - parent); - - box.setInformativeText(text); - - QPushButton *ok = box.addButton(QMessageBox::Ok); - ok->setText(okButtonText); - box.setDefaultButton(QMessageBox::Cancel); - if (box.exec() == -1) return false; - return box.standardButton(box.clickedButton()) == QMessageBox::Ok; -} - -bool ConfirmCommentDialog::confirmFilesAction(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString okButtonText) -{ - QString text; - if (files.size() <= 10) { - text = buildFilesText(introText, files); - } else { - text = "" + introTextWithCount + ""; - } - return confirm(parent, title, text, "", okButtonText); -} - -bool ConfirmCommentDialog::confirmDangerousFilesAction(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString okButtonText) -{ - QString text; - if (files.size() <= 10) { - text = buildFilesText(introText, files); - } else { - text = "" + introTextWithCount + ""; - } - return confirmDangerous(parent, title, text, "", okButtonText); -} - -bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString &comment, - QString okButtonText) -{ - return confirmAndComment(parent, title, introText, - introTextWithCount, files, comment, false, - okButtonText); -} - -bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString &comment, - QString okButtonText) -{ - return confirmAndComment(parent, title, introText, - introTextWithCount, files, comment, true, - okButtonText); -} - -bool ConfirmCommentDialog::confirmAndComment(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString &comment, - bool longComment, - QString okButtonText) -{ - QString text; - if (files.size() <= 10) { - text = buildFilesText(introText, files); - } else { - text = "" + introTextWithCount; - } - text += tr("

Please enter your comment:"); - return confirmAndComment(parent, title, text, comment, longComment, - okButtonText); -} - -bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent, - QString title, - QString introText, - QString &comment, - QString okButtonText) -{ - return confirmAndComment(parent, title, introText, comment, false, - okButtonText); -} - -bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent, - QString title, - QString introText, - QString &comment, - QString okButtonText) -{ - return confirmAndComment(parent, title, introText, comment, true, - okButtonText); -} - -bool ConfirmCommentDialog::confirmAndComment(QWidget *parent, - QString title, - QString introText, - QString &comment, - bool longComment, - QString okButtonText) -{ - bool ok = false; - if (!longComment) { - QInputDialog d(parent); - d.setWindowTitle(title); - d.setLabelText(introText); - d.setTextValue(comment); - d.setOkButtonText(okButtonText); - d.setTextEchoMode(QLineEdit::Normal); - if (d.exec() == QDialog::Accepted) { - comment = d.textValue(); - ok = true; - } - } else { - ConfirmCommentDialog d(parent, title, introText, comment, okButtonText); - if (d.exec() == QDialog::Accepted) { - comment = d.getComment(); - ok = true; - } - } - - return ok; -} diff -r 19cce6d2c470 -r b9c153e00e84 confirmcommentdialog.h --- a/confirmcommentdialog.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef CONFIRMCOMMENTDIALOG_H -#define CONFIRMCOMMENTDIALOG_H - -#include -#include -#include -#include -#include -#include - -class ConfirmCommentDialog : public QDialog -{ - Q_OBJECT - -public: - static bool confirm(QWidget *parent, - QString title, - QString head, - QString text, - QString okButtonText); - - static bool confirmDangerous(QWidget *parent, - QString title, - QString head, - QString text, - QString okButtonText); - - static bool confirmFilesAction(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString okButtonText); - - static bool confirmDangerousFilesAction(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString okButtonText); - - static bool confirmAndGetShortComment(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString &comment, - QString okButtonText); - - static bool confirmAndGetLongComment(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString &comment, - QString okButtonText); - - static bool confirmAndGetShortComment(QWidget *parent, - QString title, - QString introText, - QString &comment, - QString okButtonText); - - static bool confirmAndGetLongComment(QWidget *parent, - QString title, - QString introText, - QString &comment, - QString okButtonText); - -private slots: - void commentChanged(); - -private: - ConfirmCommentDialog(QWidget *parent, - QString title, - QString introText, - QString initialComment, - QString okButtonText); - - static bool confirmAndComment(QWidget *parent, - QString title, - QString introText, - QString introTextWithCount, - QStringList files, - QString &comment, - bool longComment, - QString okButtonText); - - static bool confirmAndComment(QWidget *parent, - QString title, - QString introText, - QString &comment, - bool longComment, - QString okButtonText); - - static QString buildFilesText(QString intro, QStringList files); - - QString getComment() const; - - QTextEdit *m_textEdit; - QPushButton *m_ok; -}; - -#endif // CONFIRMCOMMENTDIALOG_H diff -r 19cce6d2c470 -r b9c153e00e84 connectionitem.cpp --- a/connectionitem.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,132 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "connectionitem.h" -#include "uncommitteditem.h" - -#include "changesetitem.h" -#include "changeset.h" -#include "colourset.h" - -#include - -QRectF -ConnectionItem::boundingRect() const -{ - if (!m_parent || !(m_child || m_uncommitted)) return QRectF(); - float xscale = 100; - float yscale = 90; - float size = 50; - - int p_col = m_parent->column(), p_row = m_parent->row(); - int c_col, c_row; - if (m_child) { - c_col = m_child->column(); c_row = m_child->row(); - } else { - c_col = m_uncommitted->column(); c_row = m_uncommitted->row(); - } - - return QRectF(xscale * c_col + size/2 - 2, - yscale * c_row + size - 2, - xscale * p_col - xscale * c_col + 4, - yscale * p_row - yscale * c_row - size + 4) - .normalized(); -} - -void -ConnectionItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *) -{ - if (!m_parent || !(m_child || m_uncommitted)) return; - QPainterPath p; - - paint->save(); - - ColourSet *colourSet = ColourSet::instance(); - QString branch; - if (m_child) branch = m_child->getChangeset()->branch(); - else branch = m_uncommitted->branch(); - QColor branchColour = colourSet->getColourFor(branch); - - Qt::PenStyle ls = Qt::SolidLine; - if (!m_child) ls = Qt::DashLine; - - QTransform t = paint->worldTransform(); - float scale = std::min(t.m11(), t.m22()); - if (scale < 0.2) { - paint->setPen(QPen(branchColour, 0, ls)); - } else { - paint->setPen(QPen(branchColour, 2, ls)); - } - - float xscale = 100; - - float yscale = 90; - float size = 50; - float ygap = yscale - size - 2; - - int p_col = m_parent->column(), p_row = m_parent->row(); - int c_col, c_row; - if (m_child) { - c_col = m_child->column(); c_row = m_child->row(); - } else { - c_col = m_uncommitted->column(); c_row = m_uncommitted->row(); - } - - float c_x = xscale * c_col + size/2; - float p_x = xscale * p_col + size/2; - - // ensure line reaches the box, even if it's in a small height -- - // doesn't matter if we overshoot as the box is opaque and has a - // greater Z value - p.moveTo(c_x, yscale * c_row + size - 20); - - p.lineTo(c_x, yscale * c_row + size); - - if (p_col == c_col) { - - p.lineTo(p_x, yscale * p_row); - - } else if (m_type == Split || m_type == Normal) { - - // place the bulk of the line on the child (i.e. branch) row - - if (abs(p_row - c_row) > 1) { - p.lineTo(c_x, yscale * p_row - ygap); - } - - p.cubicTo(c_x, yscale * p_row, - p_x, yscale * p_row - ygap, - p_x, yscale * p_row); - - } else if (m_type == Merge) { - - // place bulk of the line on the parent row - - p.cubicTo(c_x, yscale * c_row + size + ygap, - p_x, yscale * c_row + size, - p_x, yscale * c_row + size + ygap); - - if (abs(p_row - c_row) > 1) { - p.lineTo(p_x, yscale * p_row); - } - } - - paint->drawPath(p); - paint->restore(); -} - - diff -r 19cce6d2c470 -r b9c153e00e84 connectionitem.h --- a/connectionitem.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef CONNECTIONITEM_H -#define CONNECTIONITEM_H - -#include - -class Connection; - -class ChangesetItem; -class UncommittedItem; - -class ConnectionItem : public QGraphicsItem -{ -public: - enum Type { - Normal, - Split, - Merge - }; - - ConnectionItem() : m_type(Normal), m_parent(0), m_child(0) { } - - virtual QRectF boundingRect() const; - virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); - - Type connectionType() const { return m_type; } - void setConnectionType(Type t) { m_type = t; } - - //!!! deletion signals from parent/child? - - ChangesetItem *parent() { return m_parent; } - ChangesetItem *child() { return m_child; } - - void setParent(ChangesetItem *p) { m_parent = p; } - void setChild(ChangesetItem *c) { m_child = c; } - void setChild(UncommittedItem *u) { m_uncommitted = u; } - -private: - Type m_type; - ChangesetItem *m_parent; - ChangesetItem *m_child; - UncommittedItem *m_uncommitted; -}; - -#endif // CONNECTIONITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 dateitem.cpp --- a/dateitem.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "dateitem.h" - -#include "debug.h" - -#include -#include -#include -#include - -DateItem::DateItem() : - m_minrow(0), m_maxrow(0), - m_mincol(0), m_maxcol(0), - m_even(false) -{ -} - -void -DateItem::setRows(int minrow, int n) -{ - m_minrow = minrow; - m_maxrow = minrow + n - 1; - setY(m_minrow * 90); -} - -void -DateItem::setCols(int mincol, int n) -{ - m_mincol = mincol; - m_maxcol = mincol + n - 1; - setX(m_mincol * 100); -} - -void -DateItem::mousePressEvent(QGraphicsSceneMouseEvent *e) -{ - DEBUG << "DateItem::mousePressEvent" << endl; - if (e->button() == Qt::LeftButton) { - emit clicked(); - } - e->ignore(); -} - -QRectF -DateItem::boundingRect() const -{ - return QRectF(-75, -25, - (m_maxcol - m_mincol + 1) * 100 + 100, - (m_maxrow - m_minrow + 1) * 90).normalized(); -} - -void -DateItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *opt, QWidget *w) -{ - QBrush brush; - - if (m_even) { - QColor c(QColor::fromRgb(240, 240, 240)); - brush = QBrush(c); - } else { - QColor c(QColor::fromRgb(250, 250, 250)); - brush = QBrush(c); - } - - paint->fillRect(boundingRect(), brush); - - paint->save(); - QFont f(paint->font()); - f.setBold(true); - paint->setFont(f); - paint->drawText(-70, -10, m_dateString); - paint->restore(); -} - - diff -r 19cce6d2c470 -r b9c153e00e84 dateitem.h --- a/dateitem.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef DATEITEM_H -#define DATEITEM_H - -#include - -class DateItem : public QGraphicsObject -{ - Q_OBJECT - -public: - DateItem(); - - virtual QRectF boundingRect() const; - virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); - - void setRows(int minrow, int n); - void setCols(int mincol, int n); - - void setEven(bool e) { m_even = e; } - - QString dateString() const { return m_dateString; } - void setDateString(QString s) { m_dateString = s; } - -signals: - void clicked(); - -protected: - virtual void mousePressEvent(QGraphicsSceneMouseEvent *); - -private: - QString m_dateString; - int m_minrow; - int m_maxrow; - int m_mincol; - int m_maxcol; - bool m_even; -}; - -#endif // DATEITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 debug.cpp --- a/debug.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "debug.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -QDebug & -getEasyHgDebug() -{ - static QFile *logFile = 0; - static QDebug *debug = 0; - static QMutex mutex; - static char *prefix; - mutex.lock(); - if (!debug) { - prefix = new char[20]; - sprintf(prefix, "[%lu]", (unsigned long)QCoreApplication::applicationPid()); - logFile = new QFile(QDir::homePath() + "/.easyhg.log"); - if (logFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) { - QDebug(QtDebugMsg) << (const char *)prefix - << "Opened debug log file " - << logFile->fileName(); - debug = new QDebug(logFile); - } else { - QDebug(QtWarningMsg) << (const char *)prefix - << "Failed to open debug log file " - << logFile->fileName() - << " for writing, using console debug instead"; - debug = new QDebug(QtDebugMsg); - delete logFile; - logFile = 0; - } - *debug << endl << (const char *)prefix << "Log started at " - << QDateTime::currentDateTime().toString(); - } - mutex.unlock(); - - QDebug &dref = *debug; - return dref << endl << (const char *)prefix; -} - -QDebug & -operator<<(QDebug &dbg, const std::string &s) -{ - dbg << QString::fromUtf8(s.c_str()); - return dbg; -} - -std::ostream & -operator<<(std::ostream &target, const QString &str) -{ - return target << str.toLocal8Bit().data(); -} - -std::ostream & -operator<<(std::ostream &target, const QUrl &u) -{ - return target << "<" << u.toString() << ">"; -} - diff -r 19cce6d2c470 -r b9c153e00e84 debug.h --- a/debug.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef _DEBUG_H_ -#define _DEBUG_H_ - -#include -#include -#include -#include - -class QString; -class QUrl; - -QDebug &operator<<(QDebug &, const std::string &); -std::ostream &operator<<(std::ostream &, const QString &); -std::ostream &operator<<(std::ostream &, const QUrl &); - -#ifndef NDEBUG - -extern QDebug &getEasyHgDebug(); - -#define DEBUG getEasyHgDebug() - -template -inline QDebug &operator<<(QDebug &d, const T &t) { - QString s; - QTextStream ts(&s); - ts << t; - d << s; - return d; -} - -#else - -class NoDebug -{ -public: - inline NoDebug() {} - inline ~NoDebug(){} - - template - inline NoDebug &operator<<(const T &) { return *this; } - - inline NoDebug &operator<<(QTextStreamFunction) { return *this; } -}; - -#define DEBUG NoDebug() - -#endif /* !NDEBUG */ - -#endif /* !_DEBUG_H_ */ - diff -r 19cce6d2c470 -r b9c153e00e84 easyhg.pro --- a/easyhg.pro Thu Mar 24 10:20:47 2011 +0000 +++ b/easyhg.pro Thu Mar 24 10:27:51 2011 +0000 @@ -27,76 +27,78 @@ OBJECTS_DIR = o MOC_DIR = o -HEADERS = mainwindow.h \ - hgtabwidget.h \ - common.h \ - grapher.h \ - hgrunner.h \ - changeset.h \ - changesetitem.h \ - changesetdetailitem.h \ - logparser.h \ - panner.h \ - panned.h \ - connectionitem.h \ - textabbrev.h \ - dateitem.h \ - colourset.h \ - debug.h \ - recentfiles.h \ - startupdialog.h \ - repositorydialog.h \ - multichoicedialog.h \ - selectablelabel.h \ - filestates.h \ - filestatuswidget.h \ - confirmcommentdialog.h \ - hgaction.h \ - historywidget.h \ - changesetscene.h \ - incomingdialog.h \ - uncommitteditem.h \ - settingsdialog.h \ - clickablelabel.h \ - workstatuswidget.h \ - moreinformationdialog.h \ - annotatedialog.h -SOURCES = main.cpp \ - mainwindow.cpp \ - hgtabwidget.cpp \ - hgrunner.cpp \ - grapher.cpp \ - common.cpp \ - changeset.cpp \ - changesetdetailitem.cpp \ - changesetitem.cpp \ - logparser.cpp \ - panner.cpp \ - panned.cpp \ - connectionitem.cpp \ - textabbrev.cpp \ - dateitem.cpp \ - colourset.cpp \ - debug.cpp \ - recentfiles.cpp \ - startupdialog.cpp \ - repositorydialog.cpp \ - multichoicedialog.cpp \ - selectablelabel.cpp \ - filestates.cpp \ - filestatuswidget.cpp \ - confirmcommentdialog.cpp \ - historywidget.cpp \ - changesetscene.cpp \ - incomingdialog.cpp \ - uncommitteditem.cpp \ - settingsdialog.cpp \ - workstatuswidget.cpp \ - moreinformationdialog.cpp \ - annotatedialog.cpp +HEADERS = \ + src/mainwindow.h \ + src/hgtabwidget.h \ + src/common.h \ + src/grapher.h \ + src/hgrunner.h \ + src/changeset.h \ + src/changesetitem.h \ + src/changesetdetailitem.h \ + src/logparser.h \ + src/panner.h \ + src/panned.h \ + src/connectionitem.h \ + src/textabbrev.h \ + src/dateitem.h \ + src/colourset.h \ + src/debug.h \ + src/recentfiles.h \ + src/startupdialog.h \ + src/repositorydialog.h \ + src/multichoicedialog.h \ + src/selectablelabel.h \ + src/filestates.h \ + src/filestatuswidget.h \ + src/confirmcommentdialog.h \ + src/hgaction.h \ + src/historywidget.h \ + src/changesetscene.h \ + src/incomingdialog.h \ + src/uncommitteditem.h \ + src/settingsdialog.h \ + src/clickablelabel.h \ + src/workstatuswidget.h \ + src/moreinformationdialog.h \ + src/annotatedialog.h +SOURCES = \ + src/main.cpp \ + src/mainwindow.cpp \ + src/hgtabwidget.cpp \ + src/hgrunner.cpp \ + src/grapher.cpp \ + src/common.cpp \ + src/changeset.cpp \ + src/changesetdetailitem.cpp \ + src/changesetitem.cpp \ + src/logparser.cpp \ + src/panner.cpp \ + src/panned.cpp \ + src/connectionitem.cpp \ + src/textabbrev.cpp \ + src/dateitem.cpp \ + src/colourset.cpp \ + src/debug.cpp \ + src/recentfiles.cpp \ + src/startupdialog.cpp \ + src/repositorydialog.cpp \ + src/multichoicedialog.cpp \ + src/selectablelabel.cpp \ + src/filestates.cpp \ + src/filestatuswidget.cpp \ + src/confirmcommentdialog.cpp \ + src/historywidget.cpp \ + src/changesetscene.cpp \ + src/incomingdialog.cpp \ + src/uncommitteditem.cpp \ + src/settingsdialog.cpp \ + src/workstatuswidget.cpp \ + src/moreinformationdialog.cpp \ + src/annotatedialog.cpp macx-* { - SOURCES += common_osx.mm + SOURCES += src/common_osx.mm LIBS += -framework Foundation ICON = easyhg.icns } diff -r 19cce6d2c470 -r b9c153e00e84 filestates.cpp --- a/filestates.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,221 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "filestates.h" - -#include "debug.h" - -#include - -FileStates::FileStates() -{ -} - -void FileStates::clearBuckets() -{ - m_clean.clear(); - m_modified.clear(); - m_added.clear(); - m_removed.clear(); - m_missing.clear(); - m_inConflict.clear(); - m_unknown.clear(); - m_ignored.clear(); -} - -FileStates::State FileStates::charToState(QChar c, bool *ok) -{ - // Note that InConflict does not correspond to a stat char -- it's - // reported separately, by resolve --list, which shows U for - // Unresolved -- stat reports files in conflict as M, which means - // they will appear in more than one bin if we handle them - // naively. 'u' is also used by stat as the command option for - // Unknown, but the stat output uses ? for these so there's no - // ambiguity in parsing. - - if (ok) *ok = true; - if (c == 'M') return Modified; - if (c == 'A') return Added; - if (c == 'R') return Removed; - if (c == '!') return Missing; - if (c == 'U') return InConflict; - if (c == '?') return Unknown; - if (c == 'C') return Clean; - if (c == 'I') return Ignored; - if (ok) *ok = false; - return Unknown; -} - -QStringList *FileStates::stateToBucket(State s) -{ - switch (s) { - case Clean: return &m_clean; - case Modified: return &m_modified; - case Added: return &m_added; - case Unknown: return &m_unknown; - case Removed: return &m_removed; - case Missing: return &m_missing; - case InConflict: return &m_inConflict; - case Ignored: return &m_ignored; - - default: return &m_clean; - } -} - -void FileStates::parseStates(QString text) -{ - text.replace("\r\n", "\n"); - - clearBuckets(); - m_stateMap.clear(); - - QStringList lines = text.split("\n", QString::SkipEmptyParts); - - foreach (QString line, lines) { - - if (line.length() < 3 || line[1] != ' ') { - continue; - } - - QChar c = line[0]; - bool ok = false; - State s = charToState(c, &ok); - if (!ok) continue; - - QString file = line.right(line.length() - 2); - m_stateMap[file] = s; - } - - foreach (QString file, m_stateMap.keys()) { - QStringList *bucket = stateToBucket(m_stateMap[file]); - if (bucket) bucket->push_back(file); - } - - DEBUG << "FileStates: " - << m_modified.size() << " modified, " << m_added.size() - << " added, " << m_removed.size() << " removed, " << m_missing.size() - << " missing, " << m_inConflict.size() << " in conflict, " - << m_unknown.size() << " unknown" << endl; -} - -QStringList FileStates::filesInState(State s) const -{ - QStringList *sl = const_cast(this)->stateToBucket(s); - if (sl) return *sl; - else return QStringList(); -} - -bool FileStates::isInState(QString file, State s) const -{ - return filesInState(s).contains(file); -} - -FileStates::State FileStates::stateOf(QString file) const -{ - if (m_stateMap.contains(file)) { - return m_stateMap[file]; - } - DEBUG << "FileStates: WARNING: getStateOfFile: file " - << file << " is unknown to us: returning Unknown state, " - << "but unknown to us is not supposed to be the same " - << "thing as unknown state..." - << endl; - return Unknown; -} - -FileStates::Activities FileStates::activitiesSupportedBy(State s) -{ - Activities a; - - switch (s) { - - case Modified: - a << Annotate << Diff << Commit << Revert << Rename << Copy << Remove; - break; - - case Added: - a << Commit << Revert << Rename << Copy << Remove; - break; - - case Removed: - a << Commit << Revert << Add; - break; - - case InConflict: - a << Annotate << Diff << RedoMerge << MarkResolved << Revert; - break; - - case Missing: - a << Revert << Remove; - break; - - case Unknown: - a << Add << Ignore; - break; - - case Clean: - a << Annotate << Rename << Copy << Remove; - break; - - case Ignored: - a << UnIgnore; - break; - } - - return a; -} - -bool FileStates::supportsActivity(State s, Activity a) -{ - return activitiesSupportedBy(s).contains(a); -} - -int FileStates::activityGroup(Activity a) -{ - switch (a) { - case Annotate: case Diff: return 0; - case Commit: case Revert: return 1; - case Rename: case Copy: return 2; - case Add: case Remove: return 3; - case RedoMerge: case MarkResolved: return 4; - case Ignore: case UnIgnore: return 5; - } - return 0; -} - -bool FileStates::supportsActivity(QString file, Activity a) const -{ - return supportsActivity(stateOf(file), a); -} - -QStringList FileStates::filesSupportingActivity(Activity a) const -{ - QStringList f; - for (int i = int(FirstState); i <= int(LastState); ++i) { - State s = (State)i; - if (supportsActivity(s, a)) { - f << filesInState(s); - } - } - return f; -} - -FileStates::Activities FileStates::activitiesSupportedBy(QString file) const -{ - return activitiesSupportedBy(stateOf(file)); -} - diff -r 19cce6d2c470 -r b9c153e00e84 filestates.h --- a/filestates.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef FILESTATES_H -#define FILESTATES_H - -#include -#include -#include - -class FileStates -{ -public: - FileStates(); - - enum State { - - // These are in the order in which they want to be listed in - // the interface - - Modified, - Added, - Removed, - InConflict, - Missing, - Unknown, - Clean, - Ignored, - - FirstState = Modified, - LastState = Ignored - }; - - void parseStates(QString text); - - bool isInState(QString file, State s) const; - QStringList filesInState(State s) const; - State stateOf(QString file) const; - - enum Activity { - - // These are in the order in which they want to be listed in - // the context menu - - Diff, - Annotate, - - Commit, - Revert, - - Rename, - Copy, - - Add, - Remove, - - RedoMerge, - MarkResolved, - - Ignore, - UnIgnore, - - FirstActivity = Diff, - LastActivity = UnIgnore - }; - - typedef QList Activities; - - static bool supportsActivity(State s, Activity a); - static Activities activitiesSupportedBy(State s); - static int activityGroup(Activity a); - - bool supportsActivity(QString file, Activity a) const; - QStringList filesSupportingActivity(Activity) const; - Activities activitiesSupportedBy(QString file) const; - -private: - QStringList m_modified; - QStringList m_added; - QStringList m_unknown; - QStringList m_removed; - QStringList m_missing; - QStringList m_inConflict; - QStringList m_clean; - QStringList m_ignored; - QMap m_stateMap; - - void clearBuckets(); - - State charToState(QChar, bool * = 0); - QStringList *stateToBucket(State); -}; - -#endif // FILESTATES_H diff -r 19cce6d2c470 -r b9c153e00e84 filestatuswidget.cpp --- a/filestatuswidget.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,538 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "filestatuswidget.h" -#include "debug.h" -#include "multichoicedialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -FileStatusWidget::FileStatusWidget(QWidget *parent) : - QWidget(parent), - m_dateReference(0) -{ - QGridLayout *layout = new QGridLayout; - layout->setMargin(10); - setLayout(layout); - - int row = 0; - - m_noModificationsLabel = new QLabel; - setNoModificationsLabelText(); - layout->addWidget(m_noModificationsLabel, row, 0); - m_noModificationsLabel->hide(); - - m_simpleLabels[FileStates::Clean] = tr("Unmodified:"); - m_simpleLabels[FileStates::Modified] = tr("Modified:"); - m_simpleLabels[FileStates::Added] = tr("Added:"); - m_simpleLabels[FileStates::Removed] = tr("Removed:"); - m_simpleLabels[FileStates::Missing] = tr("Missing:"); - m_simpleLabels[FileStates::InConflict] = tr("In Conflict:"); - m_simpleLabels[FileStates::Unknown] = tr("Untracked:"); - m_simpleLabels[FileStates::Ignored] = tr("Ignored:"); - - m_actionLabels[FileStates::Annotate] = tr("Show annotated version"); - m_actionLabels[FileStates::Diff] = tr("Diff to parent"); - m_actionLabels[FileStates::Commit] = tr("Commit..."); - m_actionLabels[FileStates::Revert] = tr("Revert to last committed state"); - m_actionLabels[FileStates::Rename] = tr("Rename..."); - m_actionLabels[FileStates::Copy] = tr("Copy..."); - m_actionLabels[FileStates::Add] = tr("Add to version control"); - m_actionLabels[FileStates::Remove] = tr("Remove from version control"); - m_actionLabels[FileStates::RedoMerge] = tr("Redo merge"); - m_actionLabels[FileStates::MarkResolved] = tr("Mark conflict as resolved"); - m_actionLabels[FileStates::Ignore] = tr("Ignore"); - m_actionLabels[FileStates::UnIgnore] = tr("Stop ignoring"); - - m_descriptions[FileStates::Clean] = tr("You have not changed these files."); - m_descriptions[FileStates::Modified] = tr("You have changed these files since you last committed them."); - m_descriptions[FileStates::Added] = tr("These files will be added to version control next time you commit them."); - m_descriptions[FileStates::Removed] = tr("These files will be removed from version control next time you commit them.
" - "They will not be deleted from the local folder."); - m_descriptions[FileStates::Missing] = tr("These files are recorded in the version control, but absent from your working folder.
" - "If you intended to delete them, select them and use Remove to tell the version control system about it.
" - "If you deleted them by accident, select them and use Revert to restore their previous contents."); - m_descriptions[FileStates::InConflict] = tr("These files are unresolved following an incomplete merge.
Select a file and use Merge to try to resolve the merge again."); - m_descriptions[FileStates::Unknown] = tr("These files are in your working folder but are not under version control.
" -// "Select a file and use Add to place it under version control or Ignore to remove it from this list."); - "Select a file and use Add to place it under version control."); - m_descriptions[FileStates::Ignored] = tr("These files have names that match entries in the working folder's .hgignore file,
" - "and so will be ignored by the version control system."); - - m_highlightExplanation = tr("Files highlighted in red " - "have appeared since your most recent commit or update."); - - m_boxesParent = new QWidget(this); - layout->addWidget(m_boxesParent, ++row, 0); - - QGridLayout *boxesLayout = new QGridLayout; - boxesLayout->setMargin(0); - m_boxesParent->setLayout(boxesLayout); - int boxRow = 0; - - for (int i = int(FileStates::FirstState); - i <= int(FileStates::LastState); ++i) { - - FileStates::State s = FileStates::State(i); - - QWidget *box = new QWidget(m_boxesParent); - QGridLayout *boxlayout = new QGridLayout; - boxlayout->setMargin(0); - box->setLayout(boxlayout); - - boxlayout->addItem(new QSpacerItem(3, 3), 0, 0); - - QLabel *label = new QLabel(labelFor(s)); - label->setWordWrap(true); - boxlayout->addWidget(label, 1, 0); - - QListWidget *w = new QListWidget; - m_stateListMap[s] = w; - w->setSelectionMode(QListWidget::ExtendedSelection); - boxlayout->addWidget(w, 2, 0); - - connect(w, SIGNAL(itemSelectionChanged()), - this, SLOT(itemSelectionChanged())); - connect(w, SIGNAL(itemDoubleClicked(QListWidgetItem *)), - this, SLOT(itemDoubleClicked(QListWidgetItem *))); - - FileStates::Activities activities = m_fileStates.activitiesSupportedBy(s); - int prevGroup = -1; - foreach (FileStates::Activity a, activities) { - // Skip activities which are not yet implemented - if (a == FileStates::Ignore || - a == FileStates::UnIgnore) { - continue; - } - int group = FileStates::activityGroup(a); - if (group != prevGroup && prevGroup != -1) { - QAction *sep = new QAction("", w); - sep->setSeparator(true); - w->insertAction(0, sep); - } - prevGroup = group; - QAction *act = new QAction(m_actionLabels[a], w); - act->setProperty("state", s); - act->setProperty("activity", a); - connect(act, SIGNAL(triggered()), this, SLOT(menuActionActivated())); - w->insertAction(0, act); - } - w->setContextMenuPolicy(Qt::ActionsContextMenu); - - boxlayout->addItem(new QSpacerItem(2, 2), 3, 0); - - boxesLayout->addWidget(box, ++boxRow, 0); - m_boxes.push_back(box); - box->hide(); - } - - m_gridlyLayout = false; - - layout->setRowStretch(++row, 20); - - layout->addItem(new QSpacerItem(8, 8), ++row, 0); - - m_showAllFiles = new QCheckBox(tr("Show all files"), this); - m_showAllFiles->setEnabled(false); - layout->addWidget(m_showAllFiles, ++row, 0, Qt::AlignLeft); - connect(m_showAllFiles, SIGNAL(toggled(bool)), - this, SIGNAL(showAllChanged(bool))); -} - -FileStatusWidget::~FileStatusWidget() -{ - delete m_dateReference; -} - -QString FileStatusWidget::labelFor(FileStates::State s, bool addHighlightExplanation) -{ - QSettings settings; - settings.beginGroup("Presentation"); - if (settings.value("showhelpfultext", true).toBool()) { - if (addHighlightExplanation) { - return QString("%1
%2
%3
") - .arg(m_simpleLabels[s]) - .arg(m_descriptions[s]) - .arg(m_highlightExplanation); - } else { - return QString("%1
%2
") - .arg(m_simpleLabels[s]) - .arg(m_descriptions[s]); - } - } else { - return QString("%1") - .arg(m_simpleLabels[s]); - } - settings.endGroup(); -} - -void FileStatusWidget::setNoModificationsLabelText() -{ - QSettings settings; - settings.beginGroup("Presentation"); - if (settings.value("showhelpfultext", true).toBool()) { - m_noModificationsLabel->setText - (tr("This area will list files in your working folder that you have changed.

At the moment you have no uncommitted changes.

To see changes previously made to the repository,
switch to the History tab.

%1
") -#if defined Q_OS_MAC - .arg(tr("To open the working folder in Finder,
click on the “Local” folder path shown above.")) -#elif defined Q_OS_WIN32 - .arg(tr("To open the working folder in Windows Explorer,
click on the “Local” folder path shown above.")) -#else - .arg(tr("To open the working folder in your system file manager,
click the “Local” folder path shown above.")) -#endif - ); - } else { - m_noModificationsLabel->setText - (tr("You have no uncommitted changes.")); - } -} - - -void FileStatusWidget::menuActionActivated() -{ - QAction *act = qobject_cast(sender()); - if (!act) return; - - FileStates::State state = (FileStates::State) - act->property("state").toUInt(); - FileStates::Activity activity = (FileStates::Activity) - act->property("activity").toUInt(); - - DEBUG << "menuActionActivated: state = " << state << ", activity = " - << activity << endl; - - if (!FileStates::supportsActivity(state, activity)) { - std::cerr << "WARNING: FileStatusWidget::menuActionActivated: " - << "Action state " << state << " does not support activity " - << activity << std::endl; - return; - } - - QStringList files = getSelectedFilesInState(state); - - switch (activity) { - case FileStates::Annotate: emit annotateFiles(files); break; - case FileStates::Diff: emit diffFiles(files); break; - case FileStates::Commit: emit commitFiles(files); break; - case FileStates::Revert: emit revertFiles(files); break; - case FileStates::Rename: emit renameFiles(files); break; - case FileStates::Copy: emit copyFiles(files); break; - case FileStates::Add: emit addFiles(files); break; - case FileStates::Remove: emit removeFiles(files); break; - case FileStates::RedoMerge: emit redoFileMerges(files); break; - case FileStates::MarkResolved: emit markFilesResolved(files); break; - case FileStates::Ignore: emit ignoreFiles(files); break; - case FileStates::UnIgnore: emit unIgnoreFiles(files); break; - } -} - -void FileStatusWidget::itemDoubleClicked(QListWidgetItem *item) -{ - QStringList files; - QString file = item->text(); - files << file; - - switch (m_fileStates.stateOf(file)) { - - case FileStates::Modified: - case FileStates::InConflict: - emit diffFiles(files); - break; - - case FileStates::Clean: - case FileStates::Missing: - emit annotateFiles(files); - break; - } -} - -void FileStatusWidget::itemSelectionChanged() -{ - DEBUG << "FileStatusWidget::itemSelectionChanged" << endl; - - QListWidget *list = qobject_cast(sender()); - - if (list) { - foreach (QListWidget *w, m_stateListMap) { - if (w != list) { - w->blockSignals(true); - w->clearSelection(); - w->blockSignals(false); - } - } - } - - m_selectedFiles.clear(); - - foreach (QListWidget *w, m_stateListMap) { - QList sel = w->selectedItems(); - foreach (QListWidgetItem *i, sel) { - m_selectedFiles.push_back(i->text()); - DEBUG << "file " << i->text() << " is selected" << endl; - } - } - - emit selectionChanged(); -} - -void FileStatusWidget::clearSelections() -{ - m_selectedFiles.clear(); - foreach (QListWidget *w, m_stateListMap) { - w->clearSelection(); - } -} - -bool FileStatusWidget::haveChangesToCommit() const -{ - return !getAllCommittableFiles().empty(); -} - -bool FileStatusWidget::haveSelection() const -{ - return !m_selectedFiles.empty(); -} - -QStringList FileStatusWidget::getSelectedFilesInState(FileStates::State s) const -{ - QStringList files; - foreach (QString f, m_selectedFiles) { - if (m_fileStates.stateOf(f) == s) files.push_back(f); - } - return files; -} - -QStringList FileStatusWidget::getSelectedFilesSupportingActivity(FileStates::Activity a) const -{ - QStringList files; - foreach (QString f, m_selectedFiles) { - if (m_fileStates.supportsActivity(f, a)) files.push_back(f); - } - return files; -} - -QStringList FileStatusWidget::getAllCommittableFiles() const -{ - return m_fileStates.filesSupportingActivity(FileStates::Commit); -} - -QStringList FileStatusWidget::getAllRevertableFiles() const -{ - return m_fileStates.filesSupportingActivity(FileStates::Revert); -} - -QStringList FileStatusWidget::getAllUnresolvedFiles() const -{ - return m_fileStates.filesInState(FileStates::InConflict); -} - -QStringList FileStatusWidget::getSelectedAddableFiles() const -{ - return getSelectedFilesSupportingActivity(FileStates::Add); -} - -QStringList FileStatusWidget::getSelectedRemovableFiles() const -{ - return getSelectedFilesSupportingActivity(FileStates::Remove); -} - -QString -FileStatusWidget::localPath() const -{ - return m_localPath; -} - -void -FileStatusWidget::setLocalPath(QString p) -{ - m_localPath = p; - delete m_dateReference; - m_dateReference = new QFileInfo(p + "/.hg/dirstate"); - if (!m_dateReference->exists() || - !m_dateReference->isFile() || - !m_dateReference->isReadable()) { - DEBUG << "FileStatusWidget::setLocalPath: date reference file " - << m_dateReference->absoluteFilePath() - << " does not exist, is not a file, or cannot be read" - << endl; - delete m_dateReference; - m_dateReference = 0; - m_showAllFiles->setEnabled(false); - } else { - m_showAllFiles->setEnabled(true); - } -} - -void -FileStatusWidget::setFileStates(FileStates p) -{ - m_fileStates = p; - updateWidgets(); -} - -void -FileStatusWidget::updateWidgets() -{ - QDateTime lastInteractionTime; - if (m_dateReference) { - lastInteractionTime = m_dateReference->lastModified(); - DEBUG << "reference time: " << lastInteractionTime << endl; - } - - QSet selectedFiles; - foreach (QString f, m_selectedFiles) selectedFiles.insert(f); - - int visibleCount = 0; - - foreach (FileStates::State s, m_stateListMap.keys()) { - - QListWidget *w = m_stateListMap[s]; - w->clear(); - QStringList files = m_fileStates.filesInState(s); - - QStringList highPriority, lowPriority; - - foreach (QString file, files) { - - bool highlighted = false; - - if (s == FileStates::Unknown) { - // We want to highlight untracked files that have appeared - // since the last interaction with the repo - QString fn(m_localPath + "/" + file); - DEBUG << "comparing with " << fn << endl; - QFileInfo fi(fn); - if (fi.exists() && fi.created() > lastInteractionTime) { - DEBUG << "file " << fn << " is newer (" << fi.lastModified() - << ") than reference" << endl; - highlighted = true; - } - } - - if (highlighted) { - highPriority.push_back(file); - } else { - lowPriority.push_back(file); - } - } - - foreach (QString file, highPriority) { - QListWidgetItem *item = new QListWidgetItem(file); - w->addItem(item); - item->setForeground(QColor("#d40000")); - item->setSelected(selectedFiles.contains(file)); - } - - foreach (QString file, lowPriority) { - QListWidgetItem *item = new QListWidgetItem(file); - w->addItem(item); - item->setSelected(selectedFiles.contains(file)); - } - - setLabelFor(w, s, !highPriority.empty()); - - if (files.empty()) { - w->parentWidget()->hide(); - } else { - w->parentWidget()->show(); - ++visibleCount; - } - } - - m_noModificationsLabel->setVisible(visibleCount == 0); - - if (visibleCount > 3) { - layoutBoxesGridly(visibleCount); - } else { - layoutBoxesLinearly(); - } - - setNoModificationsLabelText(); -} - -void FileStatusWidget::layoutBoxesGridly(int visibleCount) -{ - if (m_gridlyLayout && m_lastGridlyCount == visibleCount) return; - - delete m_boxesParent->layout(); - - QGridLayout *layout = new QGridLayout; - layout->setMargin(0); - m_boxesParent->setLayout(layout); - - int row = 0; - int col = 0; - - DEBUG << "FileStatusWidget::layoutBoxesGridly: visibleCount = " - << visibleCount << endl; - - for (int i = 0; i < m_boxes.size(); ++i) { - - if (!m_boxes[i]->isVisible()) continue; - - if (col == 0 && row >= (visibleCount+1)/2) { - layout->addItem(new QSpacerItem(10, 5), 0, 1); - col = 2; - row = 0; - } - - layout->addWidget(m_boxes[i], row, col); - - ++row; - } - - m_gridlyLayout = true; - m_lastGridlyCount = visibleCount; -} - -void FileStatusWidget::layoutBoxesLinearly() -{ - if (!m_gridlyLayout) return; - - delete m_boxesParent->layout(); - - QGridLayout *layout = new QGridLayout; - layout->setMargin(0); - m_boxesParent->setLayout(layout); - - for (int i = 0; i < m_boxes.size(); ++i) { - layout->addWidget(m_boxes[i], i, 0); - } - - m_gridlyLayout = false; -} - -void FileStatusWidget::setLabelFor(QWidget *w, FileStates::State s, bool addHighlight) -{ - QString text = labelFor(s, addHighlight); - QWidget *p = w->parentWidget(); - QList ql = p->findChildren(); - if (!ql.empty()) ql[0]->setText(text); -} - diff -r 19cce6d2c470 -r b9c153e00e84 filestatuswidget.h --- a/filestatuswidget.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef FILESTATUSWIDGET_H -#define FILESTATUSWIDGET_H - -#include "filestates.h" - -#include -#include - -class QLabel; -class QListWidget; -class QListWidgetItem; -class QPushButton; -class QFileInfo; -class QCheckBox; - -class FileStatusWidget : public QWidget -{ - Q_OBJECT - -public: - FileStatusWidget(QWidget *parent = 0); - ~FileStatusWidget(); - - QString localPath() const; - void setLocalPath(QString p); - - FileStates fileStates() const; - void setFileStates(FileStates sp); - - bool haveChangesToCommit() const; - bool haveSelection() const; - - QStringList getAllCommittableFiles() const; - QStringList getAllRevertableFiles() const; - QStringList getAllUnresolvedFiles() const; - - QStringList getSelectedAddableFiles() const; - QStringList getSelectedRemovableFiles() const; - -signals: - void selectionChanged(); - void showAllChanged(bool); - - void annotateFiles(QStringList); - void diffFiles(QStringList); - void commitFiles(QStringList); - void revertFiles(QStringList); - void renameFiles(QStringList); - void copyFiles(QStringList); - void addFiles(QStringList); - void removeFiles(QStringList); - void redoFileMerges(QStringList); - void markFilesResolved(QStringList); - void ignoreFiles(QStringList); - void unIgnoreFiles(QStringList); - -public slots: - void clearSelections(); - void updateWidgets(); - -private slots: - void menuActionActivated(); - void itemSelectionChanged(); - void itemDoubleClicked(QListWidgetItem *); - -private: - QString m_localPath; - QLabel *m_noModificationsLabel; - - QCheckBox *m_showAllFiles; - - FileStates m_fileStates; - QMap m_simpleLabels; - QMap m_descriptions; - QMap m_stateListMap; - QMap m_actionLabels; - QString m_highlightExplanation; - - QFileInfo *m_dateReference; - QStringList m_selectedFiles; - - bool m_gridlyLayout; - int m_lastGridlyCount; - QList m_boxes; - QWidget *m_boxesParent; - - void layoutBoxesGridly(int count); - void layoutBoxesLinearly(); - void setNoModificationsLabelText(); - QString labelFor(FileStates::State, bool addHighlightExplanation = false); - void setLabelFor(QWidget *w, FileStates::State, bool addHighlightExplanation); - - QStringList getSelectedFilesInState(FileStates::State s) const; - QStringList getSelectedFilesSupportingActivity(FileStates::Activity) const; -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 grapher.cpp --- a/grapher.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,603 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "grapher.h" -#include "connectionitem.h" -#include "dateitem.h" -#include "debug.h" -#include "changesetscene.h" - -#include - -#include - -Grapher::Grapher(ChangesetScene *scene) : - m_scene(scene) -{ - QSettings settings; - settings.beginGroup("Presentation"); - m_showDates = (settings.value("dateformat", 0) == 1); -} - -int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol) -{ - int col = parent; - if (preferParentCol) { - if (isAvailable(row, col)) return col; - } - while (col > 0) { - if (isAvailable(row, --col)) return col; - } - while (col < 0) { - if (isAvailable(row, ++col)) return col; - } - col = parent; - int sign = (col < 0 ? -1 : 1); - while (1) { - col += sign; - if (isAvailable(row, col)) return col; - } -} - -bool Grapher::isAvailable(int row, int col) -{ - if (m_alloc.contains(row) && m_alloc[row].contains(col)) return false; - if (!m_haveAllocatedUncommittedColumn) return true; - if (!m_uncommitted) return true; - return !(row <= m_uncommittedParentRow && col == m_uncommitted->column()); -} - -void Grapher::layoutRow(QString id) -{ - if (m_handled.contains(id)) { - return; - } - if (!m_changesets.contains(id)) { - throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); - } - if (!m_items.contains(id)) { - throw LayoutException(QString("Changeset %1 not in item map").arg(id)); - } - Changeset *cs = m_changesets[id]; - ChangesetItem *item = m_items[id]; - DEBUG << "layoutRow: Looking at " << id.toStdString() << endl; - - int row = 0; - int nparents = cs->parents().size(); - - if (nparents > 0) { - bool haveRow = false; - foreach (QString parentId, cs->parents()) { - - if (!m_changesets.contains(parentId)) continue; - if (!m_items.contains(parentId)) continue; - - if (!m_handled.contains(parentId)) { - layoutRow(parentId); - } - - ChangesetItem *parentItem = m_items[parentId]; - if (!haveRow || parentItem->row() < row) { - row = parentItem->row(); - haveRow = true; - } - } - row = row - 1; - } - - // row is now an upper bound on our eventual row (because we want - // to be above all parents). But we also want to ensure we are - // above all nodes that have earlier dates (to the nearest day). - // m_rowDates maps each row to a date: use that. - - QString date; - if (m_showDates) { - date = cs->date(); - } else { - date = cs->age(); - } - while (m_rowDates.contains(row) && m_rowDates[row] != date) { - --row; - } - - // We have already laid out all nodes that have earlier timestamps - // than this one, so we know (among other things) that we can - // safely fill in this row has having this date, if it isn't in - // the map yet (it cannot have an earlier date) - - if (!m_rowDates.contains(row)) { - m_rowDates[row] = date; - } - - // If we're the parent of the uncommitted item, make a note of our - // row (we need it later, to avoid overwriting the connecting line) - if (!m_uncommittedParents.empty() && m_uncommittedParents[0] == id) { - m_uncommittedParentRow = row; - } - - DEBUG << "putting " << cs->id().toStdString() << " at row " << row - << endl; - - item->setRow(row); - m_handled.insert(id); -} - -void Grapher::layoutCol(QString id) -{ - if (m_handled.contains(id)) { - DEBUG << "Already looked at " << id.toStdString() << endl; - return; - } - if (!m_changesets.contains(id)) { - throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); - } - if (!m_items.contains(id)) { - throw LayoutException(QString("Changeset %1 not in item map").arg(id)); - } - - Changeset *cs = m_changesets[id]; - DEBUG << "layoutCol: Looking at " << id.toStdString() << endl; - - ChangesetItem *item = m_items[id]; - - int col = 0; - int row = item->row(); - QString branch = cs->branch(); - - int nparents = cs->parents().size(); - QString parentId; - int parentsOnSameBranch = 0; - - switch (nparents) { - - case 0: - col = m_branchHomes[cs->branch()]; - col = findAvailableColumn(row, col, true); - break; - - case 1: - parentId = cs->parents()[0]; - - if (!m_changesets.contains(parentId) || - !m_changesets[parentId]->isOnBranch(branch)) { - // new branch - col = m_branchHomes[branch]; - } else { - col = m_items[parentId]->column(); - } - - col = findAvailableColumn(row, col, true); - break; - - case 2: - // a merge: behave differently if parents are both on the same - // branch (we also want to behave differently for nodes that - // have multiple children on the same branch -- spreading them - // out rather than having affinity to a specific branch) - - foreach (QString parentId, cs->parents()) { - if (!m_changesets.contains(parentId)) continue; - if (m_changesets[parentId]->isOnBranch(branch)) { - ChangesetItem *parentItem = m_items[parentId]; - col += parentItem->column(); - parentsOnSameBranch++; - } - } - - if (parentsOnSameBranch > 0) { - col /= parentsOnSameBranch; - col = findAvailableColumn(item->row(), col, true); - } else { - col = findAvailableColumn(item->row(), col, false); - } - break; - } - - DEBUG << "putting " << cs->id().toStdString() << " at col " << col << endl; - - m_alloc[row].insert(col); - item->setColumn(col); - m_handled.insert(id); - - // If we're the first parent of the uncommitted item, it should be - // given the same column as us (we already noted that its - // connecting line would end at our row) - - if (m_uncommittedParents.contains(id)) { - if (m_uncommittedParents[0] == id) { - int ucol = findAvailableColumn(row-1, col, true); - m_uncommitted->setColumn(ucol); - m_haveAllocatedUncommittedColumn = true; - } - // also, if the uncommitted item has a different branch from - // any of its parents, tell it to show the branch - if (!cs->isOnBranch(m_uncommitted->branch())) { - DEBUG << "Uncommitted branch " << m_uncommitted->branch() - << " differs from my branch " << cs->branch() - << ", asking it to show branch" << endl; - m_uncommitted->setShowBranch(true); - } - } - - - // Normally the children will lay out themselves, but we can do - // a better job in some special cases: - - int nchildren = cs->children().size(); - - // look for merging children and children distant from us but in a - // straight line, and make sure nobody is going to overwrite their - // connection lines - - foreach (QString childId, cs->children()) { - DEBUG << "reserving connection line space" << endl; - if (!m_changesets.contains(childId)) continue; - Changeset *child = m_changesets[childId]; - int childRow = m_items[childId]->row(); - if (child->parents().size() > 1 || - child->isOnBranch(cs->branch())) { - for (int r = row-1; r > childRow; --r) { - m_alloc[r].insert(col); - } - } - } - - // look for the case where exactly two children have the same - // branch as us: split them to a little either side of our position - - if (nchildren > 1) { - QList special; - foreach (QString childId, cs->children()) { - if (!m_changesets.contains(childId)) continue; - Changeset *child = m_changesets[childId]; - if (child->isOnBranch(branch) && - child->parents().size() == 1) { - special.push_back(childId); - } - } - if (special.size() == 2) { - DEBUG << "handling split-in-two for children " << special[0] << " and " << special[1] << endl; - for (int i = 0; i < 2; ++i) { - int off = i * 2 - 1; // 0 -> -1, 1 -> 1 - ChangesetItem *it = m_items[special[i]]; - it->setColumn(findAvailableColumn(it->row(), col + off, true)); - for (int r = row-1; r >= it->row(); --r) { - m_alloc[r].insert(it->column()); - } - m_handled.insert(special[i]); - } - } - } -} - -bool Grapher::rangesConflict(const Range &r1, const Range &r2) -{ - // allow some additional space at edges. we really ought also to - // permit space at the end of a branch up to the point where the - // merge happens - int a1 = r1.first - 2, b1 = r1.second + 2; - int a2 = r2.first - 2, b2 = r2.second + 2; - if (a1 > b2 || b1 < a2) return false; - if (a2 > b1 || b2 < a1) return false; - return true; -} - -void Grapher::allocateBranchHomes(Changesets csets) -{ - foreach (Changeset *cs, csets) { - QString branch = cs->branch(); - ChangesetItem *item = m_items[cs->id()]; - if (!item) continue; - int row = item->row(); - if (!m_branchRanges.contains(branch)) { - m_branchRanges[branch] = Range(row, row); - } else { - Range p = m_branchRanges[branch]; - if (row < p.first) p.first = row; - if (row > p.second) p.second = row; - m_branchRanges[branch] = p; - } - } - - m_branchHomes[""] = 0; - m_branchHomes["default"] = 0; - - foreach (QString branch, m_branchRanges.keys()) { - if (branch == "") continue; - QSet taken; - taken.insert(0); - Range myRange = m_branchRanges[branch]; - foreach (QString other, m_branchRanges.keys()) { - if (other == branch || other == "") continue; - Range otherRange = m_branchRanges[other]; - if (rangesConflict(myRange, otherRange)) { - if (m_branchHomes.contains(other)) { - taken.insert(m_branchHomes[other]); - } - } - } - int home = 2; - while (taken.contains(home)) { - if (home > 0) { - if (home % 2 == 1) { - home = -home; - } else { - home = home + 1; - } - } else { - if ((-home) % 2 == 1) { - home = home + 1; - } else { - home = -(home-2); - } - } - } - m_branchHomes[branch] = home; - } - - foreach (QString branch, m_branchRanges.keys()) { - DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl; - } -} - -static bool -compareChangesetsByDate(Changeset *const &a, Changeset *const &b) -{ - return a->timestamp() < b->timestamp(); -} - -ChangesetItem * -Grapher::getItemFor(Changeset *cs) -{ - if (!cs || !m_items.contains(cs->id())) return 0; - return m_items[cs->id()]; -} - -void Grapher::layout(Changesets csets, - QStringList uncommittedParents, - QString uncommittedBranch) -{ - m_changesets.clear(); - m_items.clear(); - m_alloc.clear(); - m_branchHomes.clear(); - - m_uncommittedParents = uncommittedParents; - m_haveAllocatedUncommittedColumn = false; - m_uncommittedParentRow = 0; - m_uncommitted = 0; - - DEBUG << "Grapher::layout: Have " << csets.size() << " changesets" << endl; - - if (csets.empty()) return; - - // Create (but don't yet position) the changeset items - - foreach (Changeset *cs, csets) { - - QString id = cs->id(); - - if (id == "") { - throw LayoutException("Changeset has no ID"); - } - if (m_changesets.contains(id)) { - DEBUG << "Duplicate changeset ID " << id - << " in Grapher::layout()" << endl; - throw LayoutException(QString("Duplicate changeset ID %1").arg(id)); - } - - m_changesets[id] = cs; - - ChangesetItem *item = new ChangesetItem(cs); - item->setX(0); - item->setY(0); - item->setZValue(0); - m_items[id] = item; - m_scene->addChangesetItem(item); - } - - // Add the connecting lines - - foreach (Changeset *cs, csets) { - QString id = cs->id(); - ChangesetItem *item = m_items[id]; - bool merge = (cs->parents().size() > 1); - foreach (QString parentId, cs->parents()) { - if (!m_changesets.contains(parentId)) continue; - Changeset *parent = m_changesets[parentId]; - parent->addChild(id); - ConnectionItem *conn = new ConnectionItem(); - if (merge) conn->setConnectionType(ConnectionItem::Merge); - conn->setChild(item); - conn->setParent(m_items[parentId]); - conn->setZValue(-1); - m_scene->addItem(conn); - } - } - - // Add uncommitted item and connecting line as necessary - - if (!m_uncommittedParents.empty()) { - - m_uncommitted = new UncommittedItem(); - m_uncommitted->setBranch(uncommittedBranch); - m_uncommitted->setZValue(10); - m_scene->addUncommittedItem(m_uncommitted); - - bool haveParentOnBranch = false; - foreach (QString p, m_uncommittedParents) { - ConnectionItem *conn = new ConnectionItem(); - conn->setConnectionType(ConnectionItem::Merge); - ChangesetItem *pitem = m_items[p]; - conn->setParent(pitem); - conn->setChild(m_uncommitted); - conn->setZValue(0); - m_scene->addItem(conn); - if (pitem) { - if (pitem->getChangeset()->branch() == uncommittedBranch) { - haveParentOnBranch = true; - } - } - } - - // If the uncommitted item has no parents on the same branch, - // tell it it has a new branch (the "show branch" flag is set - // elsewhere for this item) - m_uncommitted->setIsNewBranch(!haveParentOnBranch); - } - - // Add the branch labels - - foreach (Changeset *cs, csets) { - QString id = cs->id(); - ChangesetItem *item = m_items[id]; - bool haveChildOnSameBranch = false; - foreach (QString childId, cs->children()) { - Changeset *child = m_changesets[childId]; - if (child->branch() == cs->branch()) { - haveChildOnSameBranch = true; - break; - } - } - item->setShowBranch(!haveChildOnSameBranch); - } - - // We need to lay out the changesets in forward chronological - // order. We have no guarantees about the order in which - // changesets appear in the list -- in a simple repository they - // will generally be reverse chronological, but that's far from - // guaranteed. So, sort explicitly using the date comparator - // above - - qStableSort(csets.begin(), csets.end(), compareChangesetsByDate); - - foreach (Changeset *cs, csets) { - DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp() - << ", date " << cs->datetime().toStdString() << endl; - } - - m_handled.clear(); - foreach (Changeset *cs, csets) { - layoutRow(cs->id()); - } - - allocateBranchHomes(csets); - - m_handled.clear(); - foreach (Changeset *cs, csets) { - foreach (QString parentId, cs->parents()) { - if (!m_handled.contains(parentId) && - m_changesets.contains(parentId)) { - layoutCol(parentId); - } - } - layoutCol(cs->id()); - } - - // Find row and column extents. We know that 0 is an upper bound - // on row, and that mincol must be <= 0 and maxcol >= 0, so these - // initial values are good - - int minrow = 0, maxrow = 0; - int mincol = 0, maxcol = 0; - - foreach (int r, m_alloc.keys()) { - if (r < minrow) minrow = r; - if (r > maxrow) maxrow = r; - ColumnSet &c = m_alloc[r]; - foreach (int i, c) { - if (i < mincol) mincol = i; - if (i > maxcol) maxcol = i; - } - } - - int datemincol = mincol, datemaxcol = maxcol; - - if (mincol == maxcol) { - --datemincol; - ++datemaxcol; - } else if (m_alloc[minrow].contains(mincol)) { - --datemincol; - } - - // We've given the uncommitted item a column, but not a row yet -- - // it always goes at the top - - if (m_uncommitted) { - --minrow; - DEBUG << "putting uncommitted item at row " << minrow << endl; - m_uncommitted->setRow(minrow); - } - - // Changeset items that have nothing to either side of them can be - // made double-width - - foreach (Changeset *cs, csets) { - ChangesetItem *item = m_items[cs->id()]; - if (isAvailable(item->row(), item->column()-1) && - isAvailable(item->row(), item->column()+1)) { - item->setWide(true); - } - } - - if (m_uncommitted) { - if (isAvailable(m_uncommitted->row(), m_uncommitted->column()-1) && - isAvailable(m_uncommitted->row(), m_uncommitted->column()+1)) { - m_uncommitted->setWide(true); - } - } - - QString prevDate; - int changeRow = 0; - - bool even = false; - int n = 0; - - for (int row = minrow; row <= maxrow; ++row) { - - QString date = m_rowDates[row]; - n++; - - if (date != prevDate) { - if (prevDate != "") { - DateItem *item = new DateItem(); - item->setDateString(prevDate); - item->setCols(datemincol, datemaxcol - datemincol + 1); - item->setRows(changeRow, n); - item->setEven(even); - item->setZValue(-2); - m_scene->addDateItem(item); - even = !even; - } - prevDate = date; - changeRow = row; - n = 0; - } - } - - if (n > 0) { - DateItem *item = new DateItem(); - item->setDateString(prevDate); - item->setCols(datemincol, datemaxcol - datemincol + 1); - item->setRows(changeRow, n+1); - item->setEven(even); - item->setZValue(-2); - m_scene->addDateItem(item); - even = !even; - } -} - diff -r 19cce6d2c470 -r b9c153e00e84 grapher.h --- a/grapher.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef GRAPHER_H -#define GRAPHER_H - -#include "changeset.h" -#include "changesetitem.h" -#include "uncommitteditem.h" -#include "changesetscene.h" - -#include -#include -#include - -#include - -class Grapher -{ -public: - Grapher(ChangesetScene *scene); - - void layout(Changesets csets, - QStringList uncommittedParents, - QString uncommittedBranch); - - ChangesetItem *getItemFor(Changeset *cs); - - UncommittedItem *getUncommittedItem() { return m_uncommitted; } - - class LayoutException : public std::exception { - public: - LayoutException(QString message) throw() : m_message(message) { } - virtual ~LayoutException() throw() { } - virtual const char *what() const throw() { - return m_message.toLocal8Bit().data(); - } - protected: - QString m_message; - }; - -private: - ChangesetScene *m_scene; - - typedef QMap IdChangesetMap; - IdChangesetMap m_changesets; - - typedef QMap IdItemMap; - IdItemMap m_items; - - typedef QSet ColumnSet; - typedef QMap GridAlloc; - GridAlloc m_alloc; - - typedef QPair Range; - typedef QMap BranchRangeMap; - BranchRangeMap m_branchRanges; - - typedef QMap BranchColumnMap; - BranchColumnMap m_branchHomes; - - typedef QSet IdSet; - IdSet m_handled; - - typedef QMap RowDateMap; - RowDateMap m_rowDates; - - bool m_showDates; - - QStringList m_uncommittedParents; - int m_uncommittedParentRow; - UncommittedItem *m_uncommitted; - bool m_haveAllocatedUncommittedColumn; - - void layoutRow(QString id); - void layoutCol(QString id); - void allocateBranchHomes(Changesets csets); - bool rangesConflict(const Range &r1, const Range &r2); - int findAvailableColumn(int row, int parent, bool preferParentCol); - bool isAvailable(int row, int col); -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 hgaction.h --- a/hgaction.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,120 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef HGACTION_H -#define HGACTION_H - -#include -#include - -enum HGACTIONS -{ - ACT_NONE, - ACT_TEST_HG, - ACT_TEST_HG_EXT, - ACT_QUERY_PATHS, - ACT_QUERY_BRANCH, - ACT_STAT, - ACT_RESOLVE_LIST, - ACT_QUERY_HEADS, - ACT_QUERY_PARENTS, - ACT_LOG, - ACT_LOG_INCREMENTAL, - ACT_REMOVE, - ACT_ADD, - ACT_INCOMING, - ACT_PUSH, - ACT_PULL, - ACT_CLONEFROMREMOTE, - ACT_INIT, - ACT_COMMIT, - ACT_ANNOTATE, - ACT_UNCOMMITTED_SUMMARY, - ACT_DIFF_SUMMARY, - ACT_FOLDERDIFF, - ACT_CHGSETDIFF, - ACT_UPDATE, - ACT_REVERT, - ACT_MERGE, - ACT_SERVE, - ACT_RESOLVE_MARK, - ACT_RETRY_MERGE, - ACT_TAG, - ACT_NEW_BRANCH, - ACT_HG_IGNORE, - ACT_COPY_FILE, - ACT_RENAME_FILE -}; - -struct HgAction -{ - HGACTIONS action; - QString workingDir; - QStringList params; - QString executable; // empty for normal Hg, but gets filled in by hgrunner - void *extraData; - - HgAction() : action(ACT_NONE) { } - - HgAction(HGACTIONS _action, QString _wd, QStringList _params) : - action(_action), workingDir(_wd), params(_params), extraData(0) { } - - HgAction(HGACTIONS _action, QString _wd, QStringList _params, void *_d) : - action(_action), workingDir(_wd), params(_params), extraData(_d) { } - - bool operator==(const HgAction &a) { - return (a.action == action && a.workingDir == workingDir && - a.params == params && a.executable == executable && - a.extraData == extraData); - } - - bool shouldBeFast() const { - switch (action) { - case ACT_NONE: - case ACT_TEST_HG: - case ACT_TEST_HG_EXT: - case ACT_QUERY_PATHS: - case ACT_QUERY_BRANCH: - case ACT_STAT: - case ACT_RESOLVE_LIST: - case ACT_QUERY_HEADS: - case ACT_QUERY_PARENTS: - case ACT_LOG_INCREMENTAL: - return true; - default: - return false; - } - } - - bool mayBeInteractive() const { - switch (action) { - case ACT_TEST_HG_EXT: // so we force the module load to be tested - case ACT_INCOMING: - case ACT_PUSH: - case ACT_PULL: - case ACT_CLONEFROMREMOTE: - case ACT_FOLDERDIFF: - case ACT_CHGSETDIFF: - case ACT_SERVE: - return true; - default: - return false; - } - } -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 hgrunner.cpp --- a/hgrunner.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,544 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "hgrunner.h" -#include "common.h" -#include "debug.h" -#include "settingsdialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#ifndef Q_OS_WIN32 -#include -#include -#include -#endif - -HgRunner::HgRunner(QString myDirPath, QWidget * parent) : - QProgressBar(parent), - m_myDirPath(myDirPath) -{ - m_proc = 0; - - // Always unbundle the extension: even if it already exists (in - // case we're upgrading) and even if we're not going to use it (so - // that it's available in case someone wants to use it later, - // e.g. to fix a malfunctioning setup). But the path we actually - // prefer is the one in the settings first, if it exists; then the - // unbundled one; then anything in the path if for some reason - // unbundling failed - unbundleExtension(); - - setTextVisible(false); - setVisible(false); - m_isRunning = false; -} - -HgRunner::~HgRunner() -{ - closeTerminal(); - if (m_proc) { - m_proc->kill(); - m_proc->deleteLater(); - } -} - -QString HgRunner::getUnbundledFileName() -{ - return SettingsDialog::getUnbundledExtensionFileName(); -} - -QString HgRunner::unbundleExtension() -{ - // Pull out the bundled Python file into a temporary file, and - // copy it to our known extension location, replacing the magic - // text NO_EASYHG_IMPORT_PATH with our installation location - - QString bundled = ":easyhg.py"; - QString unbundled = getUnbundledFileName(); - - QString target = QFileInfo(unbundled).path(); - if (!QDir().mkpath(target)) { - DEBUG << "Failed to make unbundle path " << target << endl; - std::cerr << "Failed to make unbundle path " << target << std::endl; - return ""; - } - - QFile bf(bundled); - DEBUG << "unbundle: bundled file will be " << bundled << endl; - if (!bf.exists() || !bf.open(QIODevice::ReadOnly)) { - DEBUG << "Bundled extension is missing!" << endl; - return ""; - } - - QTemporaryFile tmpfile(QString("%1/easyhg.py.XXXXXX").arg(target)); - tmpfile.setAutoRemove(false); - DEBUG << "unbundle: temp file will be " << tmpfile.fileName() << endl; - if (!tmpfile.open()) { - DEBUG << "Failed to open temporary file " << tmpfile.fileName() << endl; - std::cerr << "Failed to open temporary file " << tmpfile.fileName() << std::endl; - return ""; - } - - QString all = QString::fromUtf8(bf.readAll()); - all.replace("NO_EASYHG_IMPORT_PATH", m_myDirPath); - tmpfile.write(all.toUtf8()); - DEBUG << "unbundle: wrote " << all.length() << " characters" << endl; - - tmpfile.close(); - - QFile ef(unbundled); - if (ef.exists()) { - DEBUG << "unbundle: removing old file " << unbundled << endl; - ef.remove(); - } - DEBUG << "unbundle: renaming " << tmpfile.fileName() << " to " << unbundled << endl; - if (!tmpfile.rename(unbundled)) { - DEBUG << "Failed to move temporary file to target file " << unbundled << endl; - std::cerr << "Failed to move temporary file to target file " << unbundled << std::endl; - return ""; - } - - DEBUG << "Unbundled extension to " << unbundled << endl; - return unbundled; -} - -void HgRunner::requestAction(HgAction action) -{ - DEBUG << "requestAction " << action.action << endl; - bool pushIt = true; - if (m_queue.empty()) { - if (action == m_currentAction) { - // this request is identical to the thing we're executing - DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl; - pushIt = false; - } - } else { - HgAction last = m_queue.back(); - if (action == last) { - // this request is identical to the previous thing we - // queued which we haven't executed yet - DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl; - pushIt = false; - } - } - if (pushIt) m_queue.push_back(action); - checkQueue(); -} - -QString HgRunner::getHgBinaryName() -{ - QSettings settings; - settings.beginGroup("Locations"); - return settings.value("hgbinary", "").toString(); -} - -QString HgRunner::getExtensionLocation() -{ - QSettings settings; - settings.beginGroup("Locations"); - QString extpath = settings.value("extensionpath", "").toString(); - if (extpath != "" && QFile(extpath).exists()) return extpath; - return ""; -} - -void HgRunner::started() -{ - DEBUG << "started" << endl; - /* - m_proc->write("blah\n"); - m_proc->write("blah\n"); - m_proc -> closeWriteChannel(); - */ -} - -void HgRunner::noteUsername(QString name) -{ - m_userName = name; -} - -void HgRunner::noteRealm(QString realm) -{ - m_realm = realm; -} - -void HgRunner::getUsername() -{ - if (m_ptyFile) { - bool ok = false; - QString prompt = tr("User name:"); - if (m_realm != "") { - prompt = tr("User name for \"%1\":").arg(m_realm); - } - QString pwd = QInputDialog::getText - (qobject_cast(parent()), - tr("Enter user name"), prompt, - QLineEdit::Normal, QString(), &ok); - if (ok) { - m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8()); - m_ptyFile->flush(); - return; - } else { - DEBUG << "HgRunner::getUsername: user cancelled" << endl; - killCurrentCommand(); - return; - } - } - // user cancelled or something went wrong - DEBUG << "HgRunner::getUsername: something went wrong" << endl; - killCurrentCommand(); -} - -void HgRunner::getPassword() -{ - if (m_ptyFile) { - bool ok = false; - QString prompt = tr("Password:"); - if (m_userName != "") { - if (m_realm != "") { - prompt = tr("Password for \"%1\" at \"%2\":") - .arg(m_userName).arg(m_realm); - } else { - prompt = tr("Password for user \"%1\":") - .arg(m_userName); - } - } - QString pwd = QInputDialog::getText - (qobject_cast(parent()), - tr("Enter password"), prompt, - QLineEdit::Password, QString(), &ok); - if (ok) { - m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8()); - m_ptyFile->flush(); - return; - } else { - DEBUG << "HgRunner::getPassword: user cancelled" << endl; - killCurrentCommand(); - return; - } - } - // user cancelled or something went wrong - DEBUG << "HgRunner::getPassword: something went wrong" << endl; - killCurrentCommand(); -} - -bool HgRunner::checkPrompts(QString chunk) -{ - //DEBUG << "checkPrompts: " << chunk << endl; - - if (!m_currentAction.mayBeInteractive()) return false; - - QString text = chunk.trimmed(); - QString lower = text.toLower(); - if (lower.endsWith("password:")) { - getPassword(); - return true; - } - if (lower.endsWith("user:") || lower.endsWith("username:")) { - getUsername(); - return true; - } - QRegExp userRe("\\buser(name)?:\\s*([^\\s]+)"); - if (userRe.indexIn(text) >= 0) { - noteUsername(userRe.cap(2)); - } - QRegExp realmRe("\\brealmr:\\s*([^\\s]+)"); - if (realmRe.indexIn(text) >= 0) { - noteRealm(realmRe.cap(1)); - } - return false; -} - -void HgRunner::dataReadyStdout() -{ - DEBUG << "dataReadyStdout" << endl; - QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput()); - if (!checkPrompts(chunk)) { - m_stdout += chunk; - } -} - -void HgRunner::dataReadyStderr() -{ - DEBUG << "dataReadyStderr" << endl; - QString chunk = QString::fromUtf8(m_proc->readAllStandardError()); - DEBUG << chunk; - if (!checkPrompts(chunk)) { - m_stderr += chunk; - } -} - -void HgRunner::dataReadyPty() -{ - DEBUG << "dataReadyPty" << endl; - QString chunk = QString::fromUtf8(m_ptyFile->readAll()); - DEBUG << "chunk of " << chunk.length() << " chars" << endl; - if (!checkPrompts(chunk)) { - m_stdout += chunk; - } -} - -void HgRunner::error(QProcess::ProcessError) -{ - finished(-1, QProcess::CrashExit); -} - -void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus) -{ - // Save the current action and reset m_currentAction before we - // emit a signal to mark the completion; otherwise we may be - // resetting the action after a slot has already tried to set it - // to something else to start a new action - - HgAction completedAction = m_currentAction; - - m_isRunning = false; - m_currentAction = HgAction(); - - //closeProcInput(); - m_proc->deleteLater(); - m_proc = 0; - - if (completedAction.action == ACT_NONE) { - DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl; - } else { - if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) { - DEBUG << "HgRunner::finished: Command completed successfully" - << endl; -// DEBUG << "stdout is " << m_stdout << endl; - emit commandCompleted(completedAction, m_stdout); - } else { - DEBUG << "HgRunner::finished: Command failed, exit code " - << procExitCode << ", exit status " << procExitStatus - << ", stderr follows" << endl; - DEBUG << m_stderr << endl; - emit commandFailed(completedAction, m_stderr); - } - } - - checkQueue(); -} - -void HgRunner::killCurrentActions() -{ - m_queue.clear(); - killCurrentCommand(); -} - -void HgRunner::killCurrentCommand() -{ - if (m_isRunning) { - m_currentAction.action = ACT_NONE; // so that we don't bother to notify - m_proc->kill(); - } -} - -void HgRunner::checkQueue() -{ - if (m_isRunning) { - return; - } - if (m_queue.empty()) { - hide(); - return; - } - HgAction toRun = m_queue.front(); - m_queue.pop_front(); - DEBUG << "checkQueue: have action: running " << toRun.action << endl; - startCommand(toRun); -} - -void HgRunner::startCommand(HgAction action) -{ - QString executable = action.executable; - bool interactive = false; - QStringList params = action.params; - - if (action.workingDir.isEmpty()) { - // We require a working directory, never just operate in pwd - emit commandFailed(action, "EasyMercurial: No working directory supplied, will not run Mercurial command without one"); - return; - } - - QSettings settings; - settings.beginGroup("General"); - - if (executable == "") { - // This is a Hg command - executable = getHgBinaryName(); - - if (action.mayBeInteractive()) { - params.push_front("ui.interactive=true"); - params.push_front("--config"); - - if (settings.value("useextension", true).toBool()) { - QString extpath = getExtensionLocation(); - params.push_front(QString("extensions.easyhg=%1").arg(extpath)); - params.push_front("--config"); - } - interactive = true; - } - - //!!! want an option to use the mercurial_keyring extension as well - } - - m_isRunning = true; - setRange(0, 0); - if (!action.shouldBeFast()) show(); - m_stdout.clear(); - m_stderr.clear(); - m_realm = ""; - m_userName = ""; - - m_proc = new QProcess; - - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - -#ifdef Q_OS_WIN32 - // On Win32 we like to bundle Hg and other executables with EasyHg - if (m_myDirPath != "") { - env.insert("PATH", m_myDirPath + ";" + env.value("PATH")); - } -#endif - -#ifdef Q_OS_MAC - if (settings.value("python32", false).toBool()) { - env.insert("VERSIONER_PYTHON_PREFER_32_BIT", "1"); - } -#endif - - env.insert("LANG", "en_US.utf8"); - env.insert("LC_ALL", "en_US.utf8"); - env.insert("HGPLAIN", "1"); - m_proc->setProcessEnvironment(env); - - connect(m_proc, SIGNAL(started()), this, SLOT(started())); - connect(m_proc, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(error(QProcess::ProcessError))); - connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), - this, SLOT(finished(int, QProcess::ExitStatus))); - connect(m_proc, SIGNAL(readyReadStandardOutput()), - this, SLOT(dataReadyStdout())); - connect(m_proc, SIGNAL(readyReadStandardError()), - this, SLOT(dataReadyStderr())); - - m_proc->setWorkingDirectory(action.workingDir); - - if (interactive) { - openTerminal(); - if (m_ptySlaveFilename != "") { - DEBUG << "HgRunner: connecting to pseudoterminal" << endl; - m_proc->setStandardInputFile(m_ptySlaveFilename); -// m_proc->setStandardOutputFile(m_ptySlaveFilename); -// m_proc->setStandardErrorFile(m_ptySlaveFilename); - } - } - - QString cmdline = executable; - foreach (QString param, params) cmdline += " " + param; - DEBUG << "HgRunner: starting: " << cmdline << " with cwd " - << action.workingDir << endl; - - m_currentAction = action; - - // fill these out with what we actually ran - m_currentAction.executable = executable; - m_currentAction.params = params; - - DEBUG << "set current action to " << m_currentAction.action << endl; - - emit commandStarting(action); - - m_proc->start(executable, params); -} - -void HgRunner::closeProcInput() -{ - DEBUG << "closeProcInput" << endl; - - m_proc->closeWriteChannel(); -} - -void HgRunner::openTerminal() -{ -#ifndef Q_OS_WIN32 - if (m_ptySlaveFilename != "") return; // already open - DEBUG << "HgRunner::openTerminal: trying to open new pty" << endl; - int master = posix_openpt(O_RDWR | O_NOCTTY); - if (master < 0) { - DEBUG << "openpt failed" << endl; - perror("openpt failed"); - return; - } - struct termios t; - if (tcgetattr(master, &t)) { - DEBUG << "tcgetattr failed" << endl; - perror("tcgetattr failed"); - } - cfmakeraw(&t); - if (tcsetattr(master, TCSANOW, &t)) { - DEBUG << "tcsetattr failed" << endl; - perror("tcsetattr failed"); - } - if (grantpt(master)) { - perror("grantpt failed"); - } - if (unlockpt(master)) { - perror("unlockpt failed"); - } - char *slave = ptsname(master); - if (!slave) { - perror("ptsname failed"); - ::close(master); - return; - } - m_ptyMasterFd = master; - m_ptyFile = new QFile(); - connect(m_ptyFile, SIGNAL(readyRead()), this, SLOT(dataReadyPty())); - if (!m_ptyFile->open(m_ptyMasterFd, QFile::ReadWrite)) { - DEBUG << "HgRunner::openTerminal: Failed to open QFile on master fd" << endl; - } - m_ptySlaveFilename = slave; - DEBUG << "HgRunner::openTerminal: succeeded, slave is " - << m_ptySlaveFilename << endl; -#endif -} - -void HgRunner::closeTerminal() -{ -#ifndef Q_OS_WIN32 - if (m_ptySlaveFilename != "") { - delete m_ptyFile; - m_ptyFile = 0; - ::close(m_ptyMasterFd); - m_ptySlaveFilename = ""; - } -#endif -} diff -r 19cce6d2c470 -r b9c153e00e84 hgrunner.h --- a/hgrunner.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef HGRUNNER_H -#define HGRUNNER_H - -#include "hgaction.h" - -#include -#include -#include -#include -#include - -#include - -class HgRunner : public QProgressBar -{ - Q_OBJECT - -public: - HgRunner(QString myDirPath, QWidget * parent = 0); - ~HgRunner(); - - void requestAction(HgAction action); - void killCurrentActions(); // kill anything running; clear the queue - -signals: - void commandStarting(HgAction action); - void commandCompleted(HgAction action, QString stdOut); - void commandFailed(HgAction action, QString stdErr); - -private slots: - void started(); - void error(QProcess::ProcessError); - void finished(int procExitCode, QProcess::ExitStatus procExitStatus); - void dataReadyStdout(); - void dataReadyStderr(); - void dataReadyPty(); - -private: - void checkQueue(); - void startCommand(HgAction action); - void closeProcInput(); - void killCurrentCommand(); - - void noteUsername(QString); - void noteRealm(QString); - void getUsername(); - void getPassword(); - bool checkPrompts(QString); - - void openTerminal(); - void closeTerminal(); - - QString getHgBinaryName(); - QString getExtensionLocation(); - - QString getUnbundledFileName(); - QString unbundleExtension(); - - int m_ptyMasterFd; - int m_ptySlaveFd; - QString m_ptySlaveFilename; - QFile *m_ptyFile; - - bool m_isRunning; - QProcess *m_proc; - QString m_stdout; - QString m_stderr; - - QString m_userName; - QString m_realm; - - QString m_myDirPath; - QString m_extensionPath; - - typedef std::deque ActionQueue; - ActionQueue m_queue; - HgAction m_currentAction; -}; - -#endif // HGRUNNER_H diff -r 19cce6d2c470 -r b9c153e00e84 hgtabwidget.cpp --- a/hgtabwidget.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,265 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "hgtabwidget.h" -#include "common.h" -#include "filestatuswidget.h" -#include "historywidget.h" - -#include -#include -#include - -#include - -HgTabWidget::HgTabWidget(QWidget *parent, - QString workFolderPath) : - QTabWidget(parent) -{ - // Work tab - m_fileStatusWidget = new FileStatusWidget; - m_fileStatusWidget->setLocalPath(workFolderPath); - - connect(m_fileStatusWidget, SIGNAL(selectionChanged()), - this, SIGNAL(selectionChanged())); - - connect(m_fileStatusWidget, SIGNAL(showAllChanged(bool)), - this, SIGNAL(showAllChanged(bool))); - - connect(m_fileStatusWidget, SIGNAL(annotateFiles(QStringList)), - this, SIGNAL(annotateFiles(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(diffFiles(QStringList)), - this, SIGNAL(diffFiles(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(commitFiles(QStringList)), - this, SIGNAL(commitFiles(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(revertFiles(QStringList)), - this, SIGNAL(revertFiles(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(renameFiles(QStringList)), - this, SIGNAL(renameFiles(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(copyFiles(QStringList)), - this, SIGNAL(copyFiles(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(addFiles(QStringList)), - this, SIGNAL(addFiles(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(removeFiles(QStringList)), - this, SIGNAL(removeFiles(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(redoFileMerges(QStringList)), - this, SIGNAL(redoFileMerges(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(markFilesResolved(QStringList)), - this, SIGNAL(markFilesResolved(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(ignoreFiles(QStringList)), - this, SIGNAL(ignoreFiles(QStringList))); - - connect(m_fileStatusWidget, SIGNAL(unIgnoreFiles(QStringList)), - this, SIGNAL(unIgnoreFiles(QStringList))); - - addTab(m_fileStatusWidget, tr("My work")); - - // History graph tab - m_historyWidget = new HistoryWidget; - addTab(m_historyWidget, tr("History")); - - connect(m_historyWidget, SIGNAL(commit()), - this, SIGNAL(commit())); - - connect(m_historyWidget, SIGNAL(revert()), - this, SIGNAL(revert())); - - connect(m_historyWidget, SIGNAL(showSummary()), - this, SIGNAL(showSummary())); - - connect(m_historyWidget, SIGNAL(newBranch()), - this, SIGNAL(newBranch())); - - connect(m_historyWidget, SIGNAL(noBranch()), - this, SIGNAL(noBranch())); - - connect(m_historyWidget, SIGNAL(diffWorkingFolder()), - this, SIGNAL(diffWorkingFolder())); - - connect(m_historyWidget, SIGNAL(showWork()), - this, SLOT(showWorkTab())); - - connect(m_historyWidget, SIGNAL(updateTo(QString)), - this, SIGNAL(updateTo(QString))); - - connect(m_historyWidget, SIGNAL(diffToCurrent(QString)), - this, SIGNAL(diffToCurrent(QString))); - - connect(m_historyWidget, SIGNAL(diffToParent(QString, QString)), - this, SIGNAL(diffToParent(QString, QString))); - - connect(m_historyWidget, SIGNAL(showSummary(Changeset *)), - this, SIGNAL(showSummary(Changeset *))); - - connect(m_historyWidget, SIGNAL(mergeFrom(QString)), - this, SIGNAL(mergeFrom(QString))); - - connect(m_historyWidget, SIGNAL(newBranch(QString)), - this, SIGNAL(newBranch(QString))); - - connect(m_historyWidget, SIGNAL(tag(QString)), - this, SIGNAL(tag(QString))); -} - -void HgTabWidget::clearSelections() -{ - m_fileStatusWidget->clearSelections(); -} - -void HgTabWidget::setCurrent(QStringList ids, QString branch) -{ - bool showUncommitted = haveChangesToCommit(); - m_historyWidget->setCurrent(ids, branch, showUncommitted); -} - -void HgTabWidget::updateFileStates() -{ - m_fileStatusWidget->updateWidgets(); -} - -void HgTabWidget::updateHistory() -{ - m_historyWidget->update(); -} - -bool HgTabWidget::canDiff() const -{ - return canRevert(); -} - -bool HgTabWidget::canCommit() const -{ - if (!m_fileStatusWidget->haveChangesToCommit()) return false; - if (!m_fileStatusWidget->getAllUnresolvedFiles().empty()) return false; - return true; -} - -bool HgTabWidget::canRevert() const -{ - // Not the same as canCommit() -- we can revert (and diff) - // unresolved files, but we can't commit them - if (!m_fileStatusWidget->haveChangesToCommit() && - m_fileStatusWidget->getAllUnresolvedFiles().empty()) return false; - return true; -} - -bool HgTabWidget::canAdd() const -{ - // Permit this only when work tab is visible - if (currentIndex() != 0) return false; - - QStringList addable = m_fileStatusWidget->getSelectedAddableFiles(); - if (addable.empty()) return false; - - QStringList removable = m_fileStatusWidget->getSelectedRemovableFiles(); - if (!removable.empty()) return false; - - return true; -} - -bool HgTabWidget::canRemove() const -{ - // Permit this only when work tab is visible - if (currentIndex() != 0) return false; - - if (m_fileStatusWidget->getSelectedRemovableFiles().empty()) return false; - if (!m_fileStatusWidget->getSelectedAddableFiles().empty()) return false; - return true; -} - -bool HgTabWidget::canResolve() const -{ - return !m_fileStatusWidget->getAllUnresolvedFiles().empty(); -} - -bool HgTabWidget::haveChangesToCommit() const -{ - return m_fileStatusWidget->haveChangesToCommit(); -} - -QStringList HgTabWidget::getAllCommittableFiles() const -{ - return m_fileStatusWidget->getAllCommittableFiles(); -} - -QStringList HgTabWidget::getAllRevertableFiles() const -{ - return m_fileStatusWidget->getAllRevertableFiles(); -} - -QStringList HgTabWidget::getSelectedAddableFiles() const -{ - return m_fileStatusWidget->getSelectedAddableFiles(); -} - -QStringList HgTabWidget::getSelectedRemovableFiles() const -{ - return m_fileStatusWidget->getSelectedRemovableFiles(); -} - -QStringList HgTabWidget::getAllUnresolvedFiles() const -{ - return m_fileStatusWidget->getAllUnresolvedFiles(); -} - -void HgTabWidget::updateWorkFolderFileList(QString fileList) -{ - m_fileStates.parseStates(fileList); - m_fileStatusWidget->setFileStates(m_fileStates); -} - -void HgTabWidget::setNewLog(QString hgLogList) -{ - m_historyWidget->parseNewLog(hgLogList); - if (m_historyWidget->haveNewItems()) { - showHistoryTab(); - } -} - -void HgTabWidget::addIncrementalLog(QString hgLogList) -{ - m_historyWidget->parseIncrementalLog(hgLogList); - if (m_historyWidget->haveNewItems()) { - showHistoryTab(); - } -} - -void HgTabWidget::setLocalPath(QString workFolderPath) -{ - m_fileStatusWidget->setLocalPath(workFolderPath); -} - -void HgTabWidget::showWorkTab() -{ - setCurrentWidget(m_fileStatusWidget); -} - -void HgTabWidget::showHistoryTab() -{ - setCurrentWidget(m_historyWidget); -} - diff -r 19cce6d2c470 -r b9c153e00e84 hgtabwidget.h --- a/hgtabwidget.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef HGTABWIDGET_H -#define HGTABWIDGET_H - -#include "changeset.h" -#include "common.h" -#include "filestates.h" - -#include -#include -#include -#include -#include -#include -#include - -class FileStatusWidget; -class HistoryWidget; - -class HgTabWidget: public QTabWidget -{ - Q_OBJECT - -public: - HgTabWidget(QWidget *parent, QString workFolderPath); - - void updateWorkFolderFileList(QString fileList); - - void setNewLog(QString hgLogList); - void addIncrementalLog(QString hgLogList); - - void setLocalPath(QString workFolderPath); - - void setCurrent(QStringList ids, QString branch); - - void updateFileStates(); - void updateHistory(); - - FileStates getFileStates() { return m_fileStates; } - - bool canDiff() const; - bool canCommit() const; - bool canRevert() const; - bool canAdd() const; - bool canRemove() const; - bool canResolve() const; - bool haveChangesToCommit() const; - - QStringList getAllCommittableFiles() const; - QStringList getAllRevertableFiles() const; - QStringList getAllUnresolvedFiles() const; - - QStringList getSelectedAddableFiles() const; - QStringList getSelectedRemovableFiles() const; - -signals: - void selectionChanged(); - void showAllChanged(bool); - - void commit(); - void revert(); - void diffWorkingFolder(); - void showSummary(); - void newBranch(); - void noBranch(); - - void updateTo(QString id); - void diffToParent(QString id, QString parent); - void showSummary(Changeset *); - void diffToCurrent(QString id); - void mergeFrom(QString id); - void newBranch(QString id); - void tag(QString id); - - void annotateFiles(QStringList); - void diffFiles(QStringList); - void commitFiles(QStringList); - void revertFiles(QStringList); - void renameFiles(QStringList); - void copyFiles(QStringList); - void addFiles(QStringList); - void removeFiles(QStringList); - void redoFileMerges(QStringList); - void markFilesResolved(QStringList); - void ignoreFiles(QStringList); - void unIgnoreFiles(QStringList); - -public slots: - void clearSelections(); - void showWorkTab(); - void showHistoryTab(); - -private: - FileStatusWidget *m_fileStatusWidget; - HistoryWidget *m_historyWidget; - FileStates m_fileStates; - - Changesets parseChangeSets(QString changeSetsStr); -}; - -#endif // HGTABWIDGET_H diff -r 19cce6d2c470 -r b9c153e00e84 historywidget.cpp --- a/historywidget.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,298 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "historywidget.h" - -#include "changesetscene.h" -#include "panned.h" -#include "panner.h" -#include "grapher.h" -#include "debug.h" -#include "uncommitteditem.h" - -#include - -#include - -HistoryWidget::HistoryWidget() : - m_showUncommitted(false), - m_refreshNeeded(false) -{ - m_panned = new Panned; - m_panner = new Panner; - - m_panned->setDragMode(QGraphicsView::ScrollHandDrag); - m_panned->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); - - QGridLayout *layout = new QGridLayout; - layout->addWidget(m_panned, 0, 0); - layout->addWidget(m_panner, 0, 1); - m_panner->setMaximumWidth(80); - m_panner->connectToPanned(m_panned); - - setLayout(layout); -} - -HistoryWidget::~HistoryWidget() -{ - clearChangesets(); -} - -QGraphicsScene *HistoryWidget::scene() -{ - return m_panned->scene(); -} - -void HistoryWidget::clearChangesets() -{ - foreach (Changeset *cs, m_changesets) delete cs; - m_changesets.clear(); -} - -void HistoryWidget::setCurrent(QStringList ids, QString branch, - bool showUncommitted) -{ - if (m_currentIds == ids && - m_currentBranch == branch && - m_showUncommitted == showUncommitted) return; - - DEBUG << "HistoryWidget::setCurrent: " << ids.size() << " ids, " - << "showUncommitted: " << showUncommitted << endl; - - m_currentIds.clear(); - m_currentBranch = branch; - m_showUncommitted = showUncommitted; - - if (ids.empty()) return; - - foreach (QString id, ids) { - m_currentIds.push_back(id); - } - - m_refreshNeeded = true; -} - -void HistoryWidget::parseNewLog(QString log) -{ - DEBUG << "HistoryWidget::parseNewLog: log has " << log.length() << " chars" << endl; - Changesets csets = Changeset::parseChangesets(log); - DEBUG << "HistoryWidget::parseNewLog: log has " << csets.size() << " changesets" << endl; - replaceChangesets(csets); - m_refreshNeeded = true; -} - -void HistoryWidget::parseIncrementalLog(QString log) -{ - DEBUG << "HistoryWidget::parseIncrementalLog: log has " << log.length() << " chars" << endl; - Changesets csets = Changeset::parseChangesets(log); - DEBUG << "HistoryWidget::parseIncrementalLog: log has " << csets.size() << " changesets" << endl; - if (!csets.empty()) { - addChangesets(csets); - } - m_refreshNeeded = true; -} - -void HistoryWidget::replaceChangesets(Changesets csets) -{ - QSet oldIds; - foreach (Changeset *cs, m_changesets) { - oldIds.insert(cs->id()); - } - - QSet newIds; - foreach (Changeset *cs, csets) { - if (!oldIds.contains(cs->id())) { - newIds.insert(cs->id()); - } - } - - if (newIds.size() == csets.size()) { - // completely new set, unrelated to the old: don't mark new - m_newIds.clear(); - } else { - m_newIds = newIds; - } - - clearChangesets(); - m_changesets = csets; -} - -void HistoryWidget::addChangesets(Changesets csets) -{ - m_newIds.clear(); - - if (csets.empty()) return; - - foreach (Changeset *cs, csets) { - m_newIds.insert(cs->id()); - } - - DEBUG << "addChangesets: " << csets.size() << " new changesets have (" - << m_changesets.size() << " already)" << endl; - - csets << m_changesets; - m_changesets = csets; -} - -void HistoryWidget::update() -{ - if (m_refreshNeeded) { - layoutAll(); - } -} - -void HistoryWidget::layoutAll() -{ - m_refreshNeeded = false; - - setChangesetParents(); - - ChangesetScene *scene = new ChangesetScene(); - ChangesetItem *tipItem = 0; - - QGraphicsScene *oldScene = m_panned->scene(); - - m_panned->setScene(0); - m_panner->setScene(0); - - delete oldScene; - - QGraphicsItem *toFocus = 0; - - if (!m_changesets.empty()) { - Grapher g(scene); - try { - g.layout(m_changesets, - m_showUncommitted ? m_currentIds : QStringList(), - m_currentBranch); - } catch (std::string s) { - std::cerr << "Internal error: Layout failed: " << s << std::endl; - } - toFocus = g.getUncommittedItem(); - if (!toFocus) { - toFocus = g.getItemFor(m_changesets[0]); - } - } - - m_panned->setScene(scene); - m_panner->setScene(scene); - - updateNewAndCurrentItems(); - - if (toFocus) { - toFocus->ensureVisible(); - } - - connectSceneSignals(); -} - -void HistoryWidget::setChangesetParents() -{ - for (int i = 0; i < m_changesets.size(); ++i) { - Changeset *cs = m_changesets[i]; - // Need to reset this, as Grapher::layout will recalculate it - // and we don't want to end up with twice the children for - // each parent... - cs->setChildren(QStringList()); - } - for (int i = 0; i+1 < m_changesets.size(); ++i) { - Changeset *cs = m_changesets[i]; - if (cs->parents().empty()) { - QStringList list; - list.push_back(m_changesets[i+1]->id()); - cs->setParents(list); - } - } -} - -void HistoryWidget::updateNewAndCurrentItems() -{ - QGraphicsScene *scene = m_panned->scene(); - if (!scene) return; - - QList items = scene->items(); - foreach (QGraphicsItem *it, items) { - - ChangesetItem *csit = dynamic_cast(it); - if (!csit) continue; - - QString id = csit->getChangeset()->id(); - - bool current = m_currentIds.contains(id); - if (current) { - DEBUG << "id " << id << " is current" << endl; - } - bool newid = m_newIds.contains(id); - if (newid) { - DEBUG << "id " << id << " is new" << endl; - } - - if (csit->isCurrent() != current || csit->isNew() != newid) { - csit->setCurrent(current); - csit->setNew(newid); - csit->update(); - } - } -} - -void HistoryWidget::connectSceneSignals() -{ - ChangesetScene *scene = qobject_cast(m_panned->scene()); - if (!scene) return; - - connect(scene, SIGNAL(commit()), - this, SIGNAL(commit())); - - connect(scene, SIGNAL(revert()), - this, SIGNAL(revert())); - - connect(scene, SIGNAL(diffWorkingFolder()), - this, SIGNAL(diffWorkingFolder())); - - connect(scene, SIGNAL(showSummary()), - this, SIGNAL(showSummary())); - - connect(scene, SIGNAL(showWork()), - this, SIGNAL(showWork())); - - connect(scene, SIGNAL(newBranch()), - this, SIGNAL(newBranch())); - - connect(scene, SIGNAL(noBranch()), - this, SIGNAL(noBranch())); - - connect(scene, SIGNAL(updateTo(QString)), - this, SIGNAL(updateTo(QString))); - - connect(scene, SIGNAL(diffToCurrent(QString)), - this, SIGNAL(diffToCurrent(QString))); - - connect(scene, SIGNAL(diffToParent(QString, QString)), - this, SIGNAL(diffToParent(QString, QString))); - - connect(scene, SIGNAL(showSummary(Changeset *)), - this, SIGNAL(showSummary(Changeset *))); - - connect(scene, SIGNAL(mergeFrom(QString)), - this, SIGNAL(mergeFrom(QString))); - - connect(scene, SIGNAL(newBranch(QString)), - this, SIGNAL(newBranch(QString))); - - connect(scene, SIGNAL(tag(QString)), - this, SIGNAL(tag(QString))); -} diff -r 19cce6d2c470 -r b9c153e00e84 historywidget.h --- a/historywidget.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef HISTORYWIDGET_H -#define HISTORYWIDGET_H - -#include "changeset.h" - -#include -#include - -class Panned; -class Panner; -class UncommittedItem; -class QGraphicsScene; - -class HistoryWidget : public QWidget -{ - Q_OBJECT - -public: - HistoryWidget(); - virtual ~HistoryWidget(); - - void setCurrent(QStringList ids, QString branch, bool showUncommitted); - - void parseNewLog(QString log); - void parseIncrementalLog(QString log); - - bool haveNewItems() const { return !m_newIds.empty(); } - - void update(); - -signals: - void commit(); - void revert(); - void diffWorkingFolder(); - void showSummary(); - void showWork(); - void newBranch(); - void noBranch(); - - void updateTo(QString id); - void diffToParent(QString id, QString parent); - void showSummary(Changeset *); - void diffToCurrent(QString id); - void mergeFrom(QString id); - void newBranch(QString id); - void tag(QString id); - -private: - Changesets m_changesets; - QStringList m_currentIds; - QString m_currentBranch; - QSet m_newIds; - bool m_showUncommitted; - bool m_refreshNeeded; - - Panned *m_panned; - Panner *m_panner; - - QGraphicsScene *scene(); - void clearChangesets(); - void replaceChangesets(Changesets); - void addChangesets(Changesets); - void layoutAll(); - void setChangesetParents(); - void updateNewAndCurrentItems(); - void connectSceneSignals(); -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 incomingdialog.cpp --- a/incomingdialog.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "incomingdialog.h" -#include "changeset.h" -#include "common.h" - -#include -#include -#include -#include -#include -#include - -IncomingDialog::IncomingDialog(QWidget *w, QString text) : - QDialog(w) -{ - QString head; - QString body; - bool scroll; - - Changesets csets = Changeset::parseChangesets(text); - if (csets.empty()) { - head = tr("No changes waiting to pull"); - if (text.trimmed() != "") { - body = QString("

%1

%2") - .arg(tr("The command output was:")) - .arg(xmlEncode(text).replace("\n", "
")); - } else { - body = tr("Your local repository already contains all changes found in the remote repository."); - } - scroll = false; - } else { - head = tr("There are %n change(s) ready to pull", "", csets.size()); - foreach (Changeset *cs, csets) { - body += cs->formatHtml() + "

"; - delete cs; - } - scroll = true; - } - - QGridLayout *layout = new QGridLayout; - setLayout(layout); - - QLabel *info = new QLabel; - QStyle *style = qApp->style(); - int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this); - info->setPixmap(style->standardIcon(QStyle::SP_MessageBoxInformation, 0, this) - .pixmap(iconSize, iconSize)); - layout->addWidget(info, 0, 0, 2, 1); - - QLabel *headLabel = new QLabel(QString("

%1

").arg(head)); - layout->addWidget(headLabel, 0, 1); - - QLabel *textLabel = new QLabel(body); - if (csets.empty()) textLabel->setWordWrap(true); - - if (scroll) { - QScrollArea *sa = new QScrollArea; - layout->addWidget(sa, 1, 1); - layout->setRowStretch(1, 20); - sa->setWidget(textLabel); - } else { - layout->addWidget(textLabel, 1, 1); - } - - QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); - connect(bb, SIGNAL(accepted()), this, SLOT(accept())); - layout->addWidget(bb, 2, 0, 1, 2); - - layout->setColumnStretch(1, 20); - setMinimumWidth(400); -} - - diff -r 19cce6d2c470 -r b9c153e00e84 incomingdialog.h --- a/incomingdialog.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef INCOMING_DIALOG_H -#define INCOMING_DIALOG_H - -#include - -class IncomingDialog : public QDialog -{ - Q_OBJECT - -public: - IncomingDialog(QWidget *parent, QString incoming); -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 logparser.cpp --- a/logparser.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "logparser.h" - -#include "debug.h" - -#include -#include - -LogParser::LogParser(QString text, QString separator) : - m_text(text), m_sep(separator) -{ - m_text.replace("\r\n", "\n"); -} - -QStringList LogParser::split() -{ - return m_text.split("\n\n", QString::SkipEmptyParts); -} - -LogList LogParser::parse() -{ - LogList results; - QRegExp re(QString("^(\\w+)\\s*%1\\s+([^\\s].*)$").arg(m_sep)); - QStringList entries = split(); - foreach (QString entry, entries) { - LogEntry dictionary; - QStringList lines = entry.split('\n'); - foreach (QString line, lines) { - if (re.indexIn(line) == 0) { - QString key = re.cap(1); - QString value = re.cap(2); - dictionary[key.trimmed()] = value; - } - } - results.push_back(dictionary); - } - return results; -} diff -r 19cce6d2c470 -r b9c153e00e84 logparser.h --- a/logparser.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef LOGPARSER_H -#define LOGPARSER_H - -#include -#include -#include -#include - -typedef QMap LogEntry; -typedef QList LogList; - -class LogParser : public QObject -{ -public: - LogParser(QString text, QString separator = ":"); - - QStringList split(); - LogList parse(); - -private: - QString m_text; - QString m_sep; -}; - -#endif // LOGPARSER_H diff -r 19cce6d2c470 -r b9c153e00e84 main.cpp --- a/main.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "mainwindow.h" -#include "common.h" -#include "debug.h" - -#include -#include -#include - -int main(int argc, char *argv[]) -{ - QApplication app(argc, argv); - - QApplication::setOrganizationName("easymercurial"); - QApplication::setOrganizationDomain("easymercurial.org"); - QApplication::setApplicationName(QApplication::tr("EasyMercurial")); - - // Lose our controlling terminal (so we can provide a new pty to - // capture password requests) - loseControllingTerminal(); - - installSignalHandlers(); - - QTranslator translator; - QString language = QLocale::system().name(); - QString trname = QString("easyhg_%1").arg(language); - translator.load(trname, ":"); - app.installTranslator(&translator); - - QStringList args = app.arguments(); - - QString myDirPath = QFileInfo(QDir::current().absoluteFilePath(args[0])) - .canonicalPath(); - - MainWindow mainWin(myDirPath); - mainWin.show(); - - if (args.size() == 2) { - QString path = args[1]; - DEBUG << "Opening " << args[1] << endl; - if (QDir(path).exists()) { - path = QDir(path).canonicalPath(); - mainWin.open(path); - } - } - - return app.exec(); -} diff -r 19cce6d2c470 -r b9c153e00e84 mainwindow.cpp --- a/mainwindow.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2839 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mainwindow.h" -#include "multichoicedialog.h" -#include "startupdialog.h" -#include "colourset.h" -#include "debug.h" -#include "logparser.h" -#include "confirmcommentdialog.h" -#include "incomingdialog.h" -#include "settingsdialog.h" -#include "moreinformationdialog.h" -#include "annotatedialog.h" -#include "version.h" -#include "workstatuswidget.h" - - -MainWindow::MainWindow(QString myDirPath) : - m_myDirPath(myDirPath), - m_fsWatcherGeneralTimer(0), - m_fsWatcherRestoreTimer(0), - m_fsWatcherSuspended(false) -{ - setWindowIcon(QIcon(":images/easyhg-icon.png")); - - QString wndTitle; - - m_showAllFiles = false; - - m_fsWatcher = 0; - m_commitsSincePush = 0; - m_shouldHgStat = true; - - createActions(); - createMenus(); - createToolBars(); - createStatusBar(); - - m_runner = new HgRunner(m_myDirPath, this); - connect(m_runner, SIGNAL(commandStarting(HgAction)), - this, SLOT(commandStarting(HgAction))); - connect(m_runner, SIGNAL(commandCompleted(HgAction, QString)), - this, SLOT(commandCompleted(HgAction, QString))); - connect(m_runner, SIGNAL(commandFailed(HgAction, QString)), - this, SLOT(commandFailed(HgAction, QString))); - statusBar()->addPermanentWidget(m_runner); - - setWindowTitle(tr("EasyMercurial")); - - m_remoteRepoPath = ""; - m_workFolderPath = ""; - - readSettings(); - - m_justMerged = false; - - QWidget *central = new QWidget(this); - setCentralWidget(central); - - QGridLayout *cl = new QGridLayout(central); - int row = 0; - -#ifndef Q_OS_MAC - cl->setMargin(0); -#endif - - m_workStatus = new WorkStatusWidget(this); - cl->addWidget(m_workStatus, row++, 0); - - m_hgTabs = new HgTabWidget(central, m_workFolderPath); - connectTabsSignals(); - - cl->addWidget(m_hgTabs, row++, 0); - - connect(m_hgTabs, SIGNAL(selectionChanged()), - this, SLOT(enableDisableActions())); - connect(m_hgTabs, SIGNAL(showAllChanged(bool)), - this, SLOT(showAllChanged(bool))); - - setUnifiedTitleAndToolBarOnMac(true); - connectActions(); - clearState(); - enableDisableActions(); - - if (m_firstStart) { - startupDialog(); - } - - SettingsDialog::findDefaultLocations(m_myDirPath); - - ColourSet *cs = ColourSet::instance(); - cs->clearDefaultNames(); - cs->addDefaultName(""); - cs->addDefaultName("default"); - cs->addDefaultName(getUserInfo()); - - hgTest(); - updateRecentMenu(); -} - - -void MainWindow::closeEvent(QCloseEvent *) -{ - writeSettings(); - delete m_fsWatcher; -} - - -QString MainWindow::getUserInfo() const -{ - QSettings settings; - settings.beginGroup("User Information"); - QString name = settings.value("name", getUserRealName()).toString(); - QString email = settings.value("email", "").toString(); - - QString identifier; - - if (email != "") { - identifier = QString("%1 <%2>").arg(name).arg(email); - } else { - identifier = name; - } - - return identifier; -} - -void MainWindow::about() -{ - QMessageBox::about(this, tr("About EasyMercurial"), - tr("

EasyMercurial v%1

" -#ifdef Q_OS_MAC - "" -#endif - "

EasyMercurial is a simple user interface for the " - "Mercurial version control system.

" - "

Credits and Copyright

" - "

Development carried out by Chris Cannam for " - "SoundSoftware.ac.uk at the Centre for Digital Music, " - "Queen Mary, University of London.

" - "

EasyMercurial is based on HgExplorer by " - "Jari Korhonen, with thanks.

" - "

" - "Copyright © 2011 Queen Mary, University of London.
" - "Copyright © 2010 Jari Korhonen.
" - "Copyright © 2011 Chris Cannam." - "

" - "

" - "This program requires Mercurial, by Matt Mackall and others.
" - "This program uses Qt by Nokia.
" - "This program uses Nuvola icons by David Vignoni.
" - "This program may use KDiff3 by Joachim Eibl.
" - "This program may use PyQt by River Bank Computing.
" - "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others." - "

" - "

License

" - "

This program is free software; you can redistribute it and/or " - "modify it under the terms of the GNU General Public License as " - "published by the Free Software Foundation; either version 2 of the " - "License, or (at your option) any later version. See the file " - "COPYING included with this distribution for more information.

" -#ifdef Q_OS_MAC - "
" -#endif - ).arg(EASYHG_VERSION)); -} - -void MainWindow::clearSelections() -{ - m_hgTabs->clearSelections(); -} - -void MainWindow::showAllChanged(bool s) -{ - m_showAllFiles = s; - hgQueryPaths(); -} - -void MainWindow::hgRefresh() -{ - clearState(); - hgQueryPaths(); -} - -void MainWindow::hgTest() -{ - QStringList params; - //!!! should we test version output? Really we want at least 1.7.x - //!!! for options such as merge --tool - params << "--version"; - m_runner->requestAction(HgAction(ACT_TEST_HG, m_myDirPath, params)); -} - -void MainWindow::hgTestExtension() -{ - QStringList params; - params << "--version"; - m_runner->requestAction(HgAction(ACT_TEST_HG_EXT, m_myDirPath, params)); -} - -void MainWindow::hgStat() -{ - QStringList params; - - if (m_showAllFiles) { - params << "stat" << "-A"; - } else { - params << "stat" << "-ardum"; - } - - m_lastStatOutput = ""; - - m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params)); -} - -void MainWindow::hgQueryPaths() -{ - // Quickest is to just read the file - - QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc"); - - QString path; - - if (hgrc.exists()) { - QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat); - s.beginGroup("paths"); - path = s.value("default").toString(); - } - - m_remoteRepoPath = path; - - // We have to do this here, because commandCompleted won't be called - MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); - MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); - updateWorkFolderAndRepoNames(); - - hgQueryBranch(); - return; - -/* The classic method! - - QStringList params; - params << "paths"; - m_runner->requestAction(HgAction(ACT_QUERY_PATHS, m_workFolderPath, params)); -*/ -} - -void MainWindow::hgQueryBranch() -{ - // Quickest is to just read the file - - QFile hgbr(m_workFolderPath + "/.hg/branch"); - - QString br = "default"; - - if (hgbr.exists() && hgbr.open(QFile::ReadOnly)) { - QByteArray ba = hgbr.readLine(); - br = QString::fromUtf8(ba).trimmed(); - } - - m_currentBranch = br; - - // We have to do this here, because commandCompleted won't be called - hgStat(); - return; - -/* The classic method! - - QStringList params; - params << "branch"; - m_runner->requestAction(HgAction(ACT_QUERY_BRANCH, m_workFolderPath, params)); -*/ -} - -void MainWindow::hgQueryHeads() -{ - QStringList params; - // On empty repos, "hg heads" will fail -- we don't care about - // that. Use --closed option so as to include closed branches; - // otherwise we'll be stuck if the user updates into one, and our - // incremental log will end up with spurious stuff in it because - // we won't be pruning at the ends of closed branches - params << "heads" << "--closed"; - m_runner->requestAction(HgAction(ACT_QUERY_HEADS, m_workFolderPath, params)); -} - -void MainWindow::hgLog() -{ - QStringList params; - params << "log"; - params << "--template"; - params << Changeset::getLogTemplate(); - - m_runner->requestAction(HgAction(ACT_LOG, m_workFolderPath, params)); -} - -void MainWindow::hgLogIncremental(QStringList prune) -{ - // Sometimes we can be called with prune empty -- it represents - // the current heads, but if we have none already and for some - // reason are being prompted for an incremental update, we may run - // into trouble. In that case, make this a full log instead - - if (prune.empty()) { - hgLog(); - return; - } - - QStringList params; - params << "log"; - - foreach (QString p, prune) { - params << "--prune" << Changeset::hashOf(p); - } - - params << "--template"; - params << Changeset::getLogTemplate(); - - m_runner->requestAction(HgAction(ACT_LOG_INCREMENTAL, m_workFolderPath, params)); -} - -void MainWindow::hgQueryParents() -{ - QStringList params; - params << "parents"; - m_runner->requestAction(HgAction(ACT_QUERY_PARENTS, m_workFolderPath, params)); -} - -void MainWindow::hgAnnotateFiles(QStringList files) -{ - QStringList params; - - if (!files.isEmpty()) { - params << "annotate" << "-udqc" << "--" << files; - m_runner->requestAction(HgAction(ACT_ANNOTATE, m_workFolderPath, params)); - } -} - -void MainWindow::hgResolveList() -{ - QStringList params; - - params << "resolve" << "--list"; - m_runner->requestAction(HgAction(ACT_RESOLVE_LIST, m_workFolderPath, params)); -} - -void MainWindow::hgAdd() -{ - // hgExplorer permitted adding "all" files -- I'm not sure - // that one is a good idea, let's require the user to select - - hgAddFiles(m_hgTabs->getSelectedAddableFiles()); -} - -void MainWindow::hgAddFiles(QStringList files) -{ - QStringList params; - - if (!files.empty()) { - params << "add" << "--" << files; - m_runner->requestAction(HgAction(ACT_ADD, m_workFolderPath, params)); - } -} - -void MainWindow::hgRemove() -{ - hgRemoveFiles(m_hgTabs->getSelectedRemovableFiles()); -} - -void MainWindow::hgRemoveFiles(QStringList files) -{ - QStringList params; - - if (!files.empty()) { - params << "remove" << "--after" << "--force" << "--" << files; - m_runner->requestAction(HgAction(ACT_REMOVE, m_workFolderPath, params)); - } -} - -void MainWindow::hgCommit() -{ - hgCommitFiles(QStringList()); -} - -void MainWindow::hgCommitFiles(QStringList files) -{ - QStringList params; - QString comment; - - if (m_justMerged) { - comment = m_mergeCommitComment; - } - - QStringList allFiles = m_hgTabs->getAllCommittableFiles(); - QStringList reportFiles = files; - if (reportFiles.empty()) { - reportFiles = allFiles; - } - - QString subsetNote; - if (reportFiles != allFiles) { - subsetNote = tr("

Note: you are committing only the files you have selected, not all of the files that have been changed!"); - } - - QString cf(tr("Commit files")); - - QString branchText; - if (m_currentBranch == "" || m_currentBranch == "default") { - branchText = tr("the default branch"); - } else { - branchText = tr("branch \"%1\"").arg(m_currentBranch); - } - - if (ConfirmCommentDialog::confirmAndGetLongComment - (this, - cf, - tr("

%1

%2%3").arg(cf) - .arg(tr("You are about to commit the following files to %1:").arg(branchText)) - .arg(subsetNote), - tr("

%1

%2%3").arg(cf) - .arg(tr("You are about to commit %n file(s) to %1.", "", reportFiles.size()).arg(branchText)) - .arg(subsetNote), - reportFiles, - comment, - tr("Co&mmit"))) { - - if (!m_justMerged && !files.empty()) { - // User wants to commit selected file(s) (and this is not - // merge commit, which would fail if we selected files) - params << "commit" << "--message" << comment - << "--user" << getUserInfo() << "--" << files; - } else { - // Commit all changes - params << "commit" << "--message" << comment - << "--user" << getUserInfo(); - } - - m_runner->requestAction(HgAction(ACT_COMMIT, m_workFolderPath, params)); - m_mergeCommitComment = ""; - } -} - -QString MainWindow::filterTag(QString tag) -{ - for(int i = 0; i < tag.size(); i++) { - if (tag[i].isLower() || tag[i].isUpper() || - tag[i].isDigit() || (tag[i] == QChar('.'))) { - //ok - } else { - tag[i] = QChar('_'); - } - } - return tag; -} - - -void MainWindow::hgNewBranch() -{ - QStringList params; - QString branch; - - if (ConfirmCommentDialog::confirmAndGetShortComment - (this, - tr("New Branch"), - tr("Enter new branch name:"), - branch, - tr("Start &Branch"))) { - if (!branch.isEmpty()) {//!!! do something better if it is empty - - params << "branch" << filterTag(branch); - m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params)); - } - } -} - -void MainWindow::hgNoBranch() -{ - if (m_currentParents.empty()) return; - - QString parentBranch = m_currentParents[0]->branch(); - if (parentBranch == "") parentBranch = "default"; - - QStringList params; - params << "branch" << parentBranch; - m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params)); -} - -void MainWindow::hgTag(QString id) -{ - QStringList params; - QString tag; - - if (ConfirmCommentDialog::confirmAndGetShortComment - (this, - tr("Tag"), - tr("Enter tag:"), - tag, - tr("Add &Tag"))) { - if (!tag.isEmpty()) {//!!! do something better if it is empty - - params << "tag" << "--user" << getUserInfo(); - params << "--rev" << Changeset::hashOf(id) << filterTag(tag); - - m_runner->requestAction(HgAction(ACT_TAG, m_workFolderPath, params)); - } - } -} - -void MainWindow::hgIgnore() -{ - QString hgIgnorePath; - QStringList params; - - hgIgnorePath = m_workFolderPath; - hgIgnorePath += "/.hgignore"; - - if (!QDir(m_workFolderPath).exists()) return; - QFile f(hgIgnorePath); - if (!f.exists()) { - f.open(QFile::WriteOnly); - QTextStream *ts = new QTextStream(&f); - *ts << "syntax: glob\n"; - delete ts; - f.close(); - } - - params << hgIgnorePath; - - QString editor = getEditorBinaryName(); - - if (editor == "") { - DEBUG << "Failed to find a text editor" << endl; - //!!! visible error! - return; - } - - HgAction action(ACT_HG_IGNORE, m_workFolderPath, params); - action.executable = editor; - - m_runner->requestAction(action); -} - -void MainWindow::hgIgnoreFiles(QStringList files) -{ - //!!! not implemented yet - DEBUG << "MainWindow::hgIgnoreFiles: Not implemented" << endl; -} - -void MainWindow::hgUnIgnoreFiles(QStringList files) -{ - //!!! not implemented yet - DEBUG << "MainWindow::hgUnIgnoreFiles: Not implemented" << endl; -} - -QString MainWindow::getDiffBinaryName() -{ - QSettings settings; - settings.beginGroup("Locations"); - return settings.value("extdiffbinary", "").toString(); -} - -QString MainWindow::getMergeBinaryName() -{ - QSettings settings; - settings.beginGroup("Locations"); - return settings.value("mergebinary", "").toString(); -} - -QString MainWindow::getEditorBinaryName() -{ - QSettings settings; - settings.beginGroup("Locations"); - return settings.value("editorbinary", "").toString(); -} - -void MainWindow::hgShowSummary() -{ - QStringList params; - - params << "diff" << "--stat"; - - m_runner->requestAction(HgAction(ACT_UNCOMMITTED_SUMMARY, m_workFolderPath, params)); -} - -void MainWindow::hgFolderDiff() -{ - hgDiffFiles(QStringList()); -} - -void MainWindow::hgDiffFiles(QStringList files) -{ - QString diff = getDiffBinaryName(); - if (diff == "") return; - - QStringList params; - - // Diff parent against working folder (folder diff) - - params << "--config" << "extensions.extdiff=" << "extdiff"; - params << "--program" << diff; - - params << "--" << files; // may be none: whole dir - - m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params)); -} - -void MainWindow::hgDiffToCurrent(QString id) -{ - QString diff = getDiffBinaryName(); - if (diff == "") return; - - QStringList params; - - // Diff given revision against working folder - - params << "--config" << "extensions.extdiff=" << "extdiff"; - params << "--program" << diff; - params << "--rev" << Changeset::hashOf(id); - - m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params)); -} - -void MainWindow::hgDiffToParent(QString child, QString parent) -{ - QString diff = getDiffBinaryName(); - if (diff == "") return; - - QStringList params; - - // Diff given revision against parent revision - - params << "--config" << "extensions.extdiff=" << "extdiff"; - params << "--program" << diff; - params << "--rev" << Changeset::hashOf(parent) - << "--rev" << Changeset::hashOf(child); - - m_runner->requestAction(HgAction(ACT_CHGSETDIFF, m_workFolderPath, params)); -} - - -void MainWindow::hgShowSummaryFor(Changeset *cs) -{ - QStringList params; - - // This will pick a default parent if there is more than one - // (whereas with diff we need to supply one). But it does need a - // bit more parsing - params << "log" << "--stat" << "--rev" << Changeset::hashOf(cs->id()); - - m_runner->requestAction(HgAction(ACT_DIFF_SUMMARY, m_workFolderPath, - params, cs)); -} - - -void MainWindow::hgUpdate() -{ - QStringList params; - - params << "update"; - - m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params)); -} - - -void MainWindow::hgUpdateToRev(QString id) -{ - QStringList params; - - params << "update" << "--rev" << Changeset::hashOf(id) << "--check"; - - m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params)); -} - - -void MainWindow::hgRevert() -{ - hgRevertFiles(QStringList()); -} - -void MainWindow::hgRevertFiles(QStringList files) -{ - QStringList params; - QString comment; - bool all = false; - - QStringList allFiles = m_hgTabs->getAllRevertableFiles(); - if (files.empty() || files == allFiles) { - files = allFiles; - all = true; - } - - QString subsetNote; - if (!all) { - subsetNote = tr("

Note: you are reverting only the files you have selected, not all of the files that have been changed!"); - } - - QString rf(tr("Revert files")); - - // Set up params before asking for confirmation, because there is - // a failure case here that we would need to report on early - - DEBUG << "hgRevert: m_justMerged = " << m_justMerged << ", m_mergeTargetRevision = " << m_mergeTargetRevision << endl; - - if (m_justMerged) { - - // This is a little fiddly. The proper way to "revert" the - // whole of an uncommitted merge is with "hg update --clean ." - // But if the user has selected only some files, we're sort of - // promising to revert only those, which means we need to - // specify which parent to revert to. We can only do that if - // we have a record of it, which we do if you just did the - // merge from within easyhg but don't if you've exited and - // restarted, or changed repository, since then. Hmmm. - - if (all) { - params << "update" << "--clean" << "."; - } else { - if (m_mergeTargetRevision != "") { - params << "revert" << "--rev" - << Changeset::hashOf(m_mergeTargetRevision) - << "--" << files; - } else { - QMessageBox::information - (this, tr("Unable to revert"), - tr("Sorry, unable to revert these files

EasyMercurial can only revert a subset of files during a merge if it still has a record of which parent was the original merge target; that information is no longer available.

This is a limitation of EasyMercurial. Consider reverting all files, or using hg revert with a specific revision at the command-line instead.
")); - return; - } - } - } else { - params << "revert" << "--" << files; - } - - if (ConfirmCommentDialog::confirmDangerousFilesAction - (this, - rf, - tr("

%1

%2%3").arg(rf) - .arg(tr("You are about to revert the following files to their previous committed state.

This will throw away any changes that you have made to these files but have not committed.")) - .arg(subsetNote), - tr("

%1

%2%3").arg(rf) - .arg(tr("You are about to revert %n file(s).

This will throw away any changes that you have made to these files but have not committed.", "", files.size())) - .arg(subsetNote), - files, - tr("Re&vert"))) { - - m_lastRevertedFiles = files; - - m_runner->requestAction(HgAction(ACT_REVERT, m_workFolderPath, params)); - } -} - - -void MainWindow::hgRenameFiles(QStringList files) -{ - QString renameTo; - - QString file; - if (files.empty()) return; - file = files[0]; - - if (ConfirmCommentDialog::confirmAndGetShortComment - (this, - tr("Rename"), - tr("Rename %1 to:").arg(xmlEncode(file)), - renameTo, - tr("Re&name"))) { - - if (renameTo != "" && renameTo != file) { - - QStringList params; - - params << "rename" << "--" << file << renameTo; - - m_runner->requestAction(HgAction(ACT_RENAME_FILE, m_workFolderPath, params)); - } - } -} - - -void MainWindow::hgCopyFiles(QStringList files) -{ - QString copyTo; - - QString file; - if (files.empty()) return; - file = files[0]; - - if (ConfirmCommentDialog::confirmAndGetShortComment - (this, - tr("Copy"), - tr("Copy %1 to:").arg(xmlEncode(file)), - copyTo, - tr("Co&py"))) { - - if (copyTo != "" && copyTo != file) { - - QStringList params; - - params << "copy" << "--" << file << copyTo; - - m_runner->requestAction(HgAction(ACT_COPY_FILE, m_workFolderPath, params)); - } - } -} - - -void MainWindow::hgMarkFilesResolved(QStringList files) -{ - QStringList params; - - params << "resolve" << "--mark"; - - if (files.empty()) { - params << "--all"; - } else { - params << "--" << files; - } - - m_runner->requestAction(HgAction(ACT_RESOLVE_MARK, m_workFolderPath, params)); -} - - -void MainWindow::hgRedoMerge() -{ - hgRedoFileMerges(QStringList()); -} - - -void MainWindow::hgRedoFileMerges(QStringList files) -{ - QStringList params; - - params << "resolve"; - - QString merge = getMergeBinaryName(); - if (merge != "") { - params << "--tool" << merge; - } - - if (files.empty()) { - params << "--all"; - } else { - params << "--" << files; - } - - if (m_currentParents.size() == 1) { - m_mergeTargetRevision = m_currentParents[0]->id(); - } - - m_runner->requestAction(HgAction(ACT_RETRY_MERGE, m_workFolderPath, params)); - - m_mergeCommitComment = tr("Merge"); -} - - -void MainWindow::hgMerge() -{ - if (m_hgTabs->canResolve()) { - hgRedoMerge(); - return; - } - - QStringList params; - - params << "merge"; - - QString merge = getMergeBinaryName(); - if (merge != "") { - params << "--tool" << merge; - } - - if (m_currentParents.size() == 1) { - m_mergeTargetRevision = m_currentParents[0]->id(); - } - - m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params)); - - m_mergeCommitComment = tr("Merge"); -} - - -void MainWindow::hgMergeFrom(QString id) -{ - QStringList params; - - params << "merge"; - params << "--rev" << Changeset::hashOf(id); - - QString merge = getMergeBinaryName(); - if (merge != "") { - params << "--tool" << merge; - } - - if (m_currentParents.size() == 1) { - m_mergeTargetRevision = m_currentParents[0]->id(); - } - - m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params)); - - m_mergeCommitComment = ""; - - foreach (Changeset *cs, m_currentHeads) { - if (cs->id() == id && !cs->isOnBranch(m_currentBranch)) { - if (cs->branch() == "" || cs->branch() == "default") { - m_mergeCommitComment = tr("Merge from the default branch"); - } else { - m_mergeCommitComment = tr("Merge from branch \"%1\"").arg(cs->branch()); - } - } - } - - if (m_mergeCommitComment == "") { - m_mergeCommitComment = tr("Merge from %1").arg(id); - } -} - - -void MainWindow::hgCloneFromRemote() -{ - QStringList params; - - if (!QDir(m_workFolderPath).exists()) { - if (!QDir().mkpath(m_workFolderPath)) { - DEBUG << "hgCloneFromRemote: Failed to create target path " - << m_workFolderPath << endl; - //!!! report error - return; - } - } - - params << "clone" << m_remoteRepoPath << m_workFolderPath; - - updateWorkFolderAndRepoNames(); - m_hgTabs->updateWorkFolderFileList(""); - - m_runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, m_workFolderPath, params)); -} - -void MainWindow::hgInit() -{ - QStringList params; - - params << "init"; - params << m_workFolderPath; - - m_runner->requestAction(HgAction(ACT_INIT, m_workFolderPath, params)); -} - -void MainWindow::hgIncoming() -{ - QStringList params; - - params << "incoming" << "--newest-first" << m_remoteRepoPath; - params << "--template" << Changeset::getLogTemplate(); - - m_runner->requestAction(HgAction(ACT_INCOMING, m_workFolderPath, params)); -} - -void MainWindow::hgPull() -{ - if (ConfirmCommentDialog::confirm - (this, tr("Confirm pull"), - tr("

Pull from remote repository?

"), - tr("

You are about to pull changes from the remote repository at %1.

").arg(xmlEncode(m_remoteRepoPath)), - tr("&Pull"))) { - - QStringList params; - params << "pull" << m_remoteRepoPath; - m_runner->requestAction(HgAction(ACT_PULL, m_workFolderPath, params)); - } -} - -void MainWindow::hgPush() -{ - if (m_remoteRepoPath.isEmpty()) { - changeRemoteRepo(true); - if (m_remoteRepoPath.isEmpty()) return; - } - - QString uncommittedNote; - if (m_hgTabs->canCommit()) { - uncommittedNote = tr("

Note: You have uncommitted changes. If you want to push these changes to the remote repository, you need to commit them first."); - } - - if (ConfirmCommentDialog::confirm - (this, tr("Confirm push"), - tr("

Push to remote repository?

"), - tr("

You are about to push your commits to the remote repository at %1.

%2
").arg(xmlEncode(m_remoteRepoPath)).arg(uncommittedNote), - tr("&Push"))) { - - QStringList params; - params << "push" << "--new-branch" << m_remoteRepoPath; - m_runner->requestAction(HgAction(ACT_PUSH, m_workFolderPath, params)); - } -} - -QStringList MainWindow::listAllUpIpV4Addresses() -{ - QStringList ret; - QList ifaces = QNetworkInterface::allInterfaces(); - - for (int i = 0; i < ifaces.count(); i++) { - QNetworkInterface iface = ifaces.at(i); - if (iface.flags().testFlag(QNetworkInterface::IsUp) - && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) { - for (int j=0; j

%1

") - .arg(tr("Running temporary server at %n address(es):", "", addrs.size())); - foreach (QString addr, addrs) { - ts << QString("
  http://%1:8000
").arg(xmlEncode(addr)); - } - ts << tr("

Press Close to stop the server and return.

"); - ts.flush(); - - params << "serve"; - - m_runner->requestAction(HgAction(ACT_SERVE, m_workFolderPath, params)); - - QMessageBox::information(this, tr("Serve"), msg, QMessageBox::Close); - - m_runner->killCurrentActions(); -} - -void MainWindow::startupDialog() -{ - StartupDialog *dlg = new StartupDialog(this); - if (dlg->exec()) m_firstStart = false; - else exit(0); -} - -void MainWindow::open() -{ - bool done = false; - - while (!done) { - - MultiChoiceDialog *d = new MultiChoiceDialog - (tr("Open Repository"), - tr("What would you like to open?"), - this); - - d->addChoice("remote", - tr("

Remote repository
"), - tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."), - MultiChoiceDialog::UrlToDirectoryArg); - - d->addChoice("local", - tr("

Local repository
"), - tr("Open an existing local Mercurial repository."), - MultiChoiceDialog::DirectoryArg); - - d->addChoice("init", - tr("

File folder
"), - tr("Open a local folder, by creating a Mercurial repository in it."), - MultiChoiceDialog::DirectoryArg); - - QSettings settings; - settings.beginGroup("General"); - QString lastChoice = settings.value("lastopentype", "remote").toString(); - if (lastChoice != "local" && - lastChoice != "remote" && - lastChoice != "init") { - lastChoice = "remote"; - } - - d->setCurrentChoice(lastChoice); - - if (d->exec() == QDialog::Accepted) { - - QString choice = d->getCurrentChoice(); - settings.setValue("lastopentype", choice); - - QString arg = d->getArgument().trimmed(); - - bool result = false; - - if (choice == "local") { - result = openLocal(arg); - } else if (choice == "remote") { - result = openRemote(arg, d->getAdditionalArgument().trimmed()); - } else if (choice == "init") { - result = openInit(arg); - } - - if (result) { - enableDisableActions(); - clearState(); - hgQueryPaths(); - done = true; - } - - } else { - - // cancelled - done = true; - } - - delete d; - } -} - -void MainWindow::recentMenuActivated() -{ - QAction *a = qobject_cast(sender()); - if (!a) return; - QString local = a->text(); - open(local); -} - -void MainWindow::changeRemoteRepo() -{ - changeRemoteRepo(false); -} - -void MainWindow::changeRemoteRepo(bool initial) -{ - // This will involve rewriting the local .hgrc - - QDir hgDir(m_workFolderPath + "/.hg"); - if (!hgDir.exists()) { - //!!! visible error! - return; - } - - QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc"); - if (hgrc.exists() && !hgrc.isWritable()) { - //!!! visible error! - return; - } - - MultiChoiceDialog *d = new MultiChoiceDialog - (tr("Set Remote Location"), - tr("Set the remote location"), - this); - - QString explanation; - if (initial) { - explanation = tr("Provide a URL to use for push and pull actions from the current local repository.
This will be the default for subsequent pushes and pulls.
You can change it using “Set Remote Location” on the File menu."); - } else { - explanation = tr("Provide a new URL to use for push and pull actions from the current local repository."); - } - - d->addChoice("remote", - tr("

Remote repository
"), - explanation, - MultiChoiceDialog::UrlArg); - - if (d->exec() == QDialog::Accepted) { - - // New block to ensure QSettings is deleted before - // hgQueryPaths called. NB use of absoluteFilePath instead of - // canonicalFilePath, which would fail if the file did not yet - // exist - - { - QSettings s(hgrc.absoluteFilePath(), QSettings::IniFormat); - s.beginGroup("paths"); - s.setValue("default", d->getArgument()); - } - - m_stateUnknown = true; - hgQueryPaths(); - } - - delete d; -} - -void MainWindow::open(QString local) -{ - if (openLocal(local)) { - enableDisableActions(); - clearState(); - hgQueryPaths(); - } -} - -bool MainWindow::complainAboutFilePath(QString arg) -{ - QMessageBox::critical - (this, tr("File chosen"), - tr("Folder required

You asked to open \"%1\".
This is a file; to open a repository, you need to choose a folder.
").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::askAboutUnknownFolder(QString arg) -{ - bool result = (QMessageBox::question - (this, tr("Path does not exist"), - tr("Path does not exist: create it?

You asked to open a remote repository by cloning it to \"%1\". This folder does not exist, and neither does its parent.

Would you like to create the parent folder as well?
").arg(xmlEncode(arg)), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Cancel) - == QMessageBox::Ok); - if (result) { - QDir dir(arg); - dir.cdUp(); - if (!dir.mkpath(dir.absolutePath())) { - QMessageBox::critical - (this, tr("Failed to create folder"), - tr("Failed to create folder

Sorry, the path for the parent folder \"%1\" could not be created.
").arg(dir.absolutePath())); - return false; - } - return true; - } - return false; -} - -bool MainWindow::complainAboutUnknownFolder(QString arg) -{ - QMessageBox::critical - (this, tr("Folder does not exist"), - tr("Folder does not exist

You asked to open \"%1\".
This folder does not exist, and it cannot be created because its parent does not exist either.
").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::complainAboutInitInRepo(QString arg) -{ - QMessageBox::critical - (this, tr("Path is in existing repository"), - tr("Path is in an existing repository

You asked to initialise a repository at \"%1\".
This path is already inside an existing repository.
").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::complainAboutInitFile(QString arg) -{ - QMessageBox::critical - (this, tr("Path is a file"), - tr("Path is a file

You asked to initialise a repository at \"%1\".
This is an existing file; it is only possible to initialise in folders.
").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::complainAboutCloneToExisting(QString arg) -{ - QMessageBox::critical - (this, tr("Path is in existing repository"), - tr("Local path is in an existing repository

You asked to open a remote repository by cloning it to the local path \"%1\".
This path is already inside an existing repository.
Please provide a different folder name for the local repository.
").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::complainAboutCloneToFile(QString arg) -{ - QMessageBox::critical - (this, tr("Path is a file"), - tr("Local path is a file

You asked to open a remote repository by cloning it to the local path \"%1\".
This path is an existing file.
Please provide a new folder name for the local repository.
").arg(xmlEncode(arg))); - return false; -} - -QString MainWindow::complainAboutCloneToExistingFolder(QString arg, QString remote) -{ - // If the directory "arg" exists but is empty, then we accept it. - - // If the directory "arg" exists and is non-empty, but "arg" plus - // the last path component of "remote" does not exist, then offer - // the latter as an alternative path. - - QString offer; - - QDir d(arg); - - if (d.exists()) { - - if (d.entryList(QDir::Dirs | QDir::Files | - QDir::NoDotAndDotDot | - QDir::Hidden | QDir::System).empty()) { - // directory is empty; accept it - return arg; - } - - if (QRegExp("^\\w+://").indexIn(remote) >= 0) { - QString rpath = QUrl(remote).path(); - if (rpath != "") { - rpath = QDir(rpath).dirName(); - if (rpath != "" && !d.exists(rpath)) { - offer = d.filePath(rpath); - } - } - } - } - - if (offer != "") { - bool result = (QMessageBox::question - (this, tr("Folder exists"), - tr("Local folder already exists

You asked to open a remote repository by cloning it to \"%1\", but this folder already exists and so cannot be cloned to.

Would you like to create the new folder \"%2\" instead?
") - .arg(xmlEncode(arg)).arg(xmlEncode(offer)), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Cancel) - == QMessageBox::Ok); - if (result) return offer; - else return ""; - } - - QMessageBox::critical - (this, tr("Folder exists"), - tr("Local folder already exists

You asked to open a remote repository by cloning it to \"%1\", but this file or folder already exists and so cannot be cloned to.
Please provide a different folder name for the local repository.
").arg(xmlEncode(arg))); - return ""; -} - -bool MainWindow::askToOpenParentRepo(QString arg, QString parent) -{ - return (QMessageBox::question - (this, tr("Path is inside a repository"), - tr("Open the repository that contains this path?

You asked to open \"%1\".
This is not the root folder of a repository.
But it is inside a repository, whose root is at \"%2\".

Would you like to open that repository instead?
") - .arg(xmlEncode(arg)).arg(xmlEncode(parent)), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Ok) - == QMessageBox::Ok); -} - -bool MainWindow::askToInitExisting(QString arg) -{ - return (QMessageBox::question - (this, tr("Folder has no repository"), - tr("Initialise a repository here?

You asked to open \"%1\".
This folder is not a Mercurial working copy.

Would you like to initialise a repository here?
") - .arg(xmlEncode(arg)), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Ok) - == QMessageBox::Ok); -} - -bool MainWindow::askToInitNew(QString arg) -{ - return (QMessageBox::question - (this, tr("Folder does not exist"), - tr("Initialise a new repository?

You asked to open \"%1\".
This folder does not yet exist.

Would you like to create the folder and initialise a new empty repository in it?
") - .arg(xmlEncode(arg)), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Ok) - == QMessageBox::Ok); -} - -bool MainWindow::askToOpenInsteadOfInit(QString arg) -{ - return (QMessageBox::question - (this, tr("Repository exists"), - tr("Open existing repository?

You asked to initialise a new repository at \"%1\".
This folder already contains a repository. Would you like to open it?
") - .arg(xmlEncode(arg)), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Ok) - == QMessageBox::Ok); -} - -bool MainWindow::openLocal(QString local) -{ - DEBUG << "open " << local << endl; - - FolderStatus status = getFolderStatus(local); - QString containing = getContainingRepoFolder(local); - - switch (status) { - - case FolderHasRepo: - // fine - break; - - case FolderExists: - if (containing != "") { - if (!askToOpenParentRepo(local, containing)) return false; - local = containing; - } else { - //!!! No -- this is likely to happen far more by accident - // than because the user actually wanted to init something. - // Don't ask, just politely reject. - if (!askToInitExisting(local)) return false; - return openInit(local); - } - break; - - case FolderParentExists: - if (containing != "") { - if (!askToOpenParentRepo(local, containing)) return false; - local = containing; - } else { - if (!askToInitNew(local)) return false; - return openInit(local); - } - break; - - case FolderUnknown: - if (containing != "") { - if (!askToOpenParentRepo(local, containing)) return false; - local = containing; - } else { - return complainAboutUnknownFolder(local); - } - break; - - case FolderIsFile: - return complainAboutFilePath(local); - } - - m_workFolderPath = local; - m_remoteRepoPath = ""; - return true; -} - -bool MainWindow::openRemote(QString remote, QString local) -{ - DEBUG << "clone " << remote << " to " << local << endl; - - FolderStatus status = getFolderStatus(local); - QString containing = getContainingRepoFolder(local); - - DEBUG << "status = " << status << ", containing = " << containing << endl; - - if (status == FolderHasRepo || containing != "") { - return complainAboutCloneToExisting(local); - } - - if (status == FolderIsFile) { - return complainAboutCloneToFile(local); - } - - if (status == FolderUnknown) { - if (!askAboutUnknownFolder(local)) { - return false; - } - } - - if (status == FolderExists) { - local = complainAboutCloneToExistingFolder(local, remote); - if (local == "") return false; - } - - m_workFolderPath = local; - m_remoteRepoPath = remote; - hgCloneFromRemote(); - - return true; -} - -bool MainWindow::openInit(QString local) -{ - DEBUG << "openInit " << local << endl; - - FolderStatus status = getFolderStatus(local); - QString containing = getContainingRepoFolder(local); - - DEBUG << "status = " << status << ", containing = " << containing << endl; - - if (status == FolderHasRepo) { - if (!askToOpenInsteadOfInit(local)) return false; - } - - if (containing != "") { - return complainAboutInitInRepo(local); - } - - if (status == FolderIsFile) { - return complainAboutInitFile(local); - } - - if (status == FolderUnknown) { - return complainAboutUnknownFolder(local); - } - - m_workFolderPath = local; - m_remoteRepoPath = ""; - hgInit(); - return true; -} - -void MainWindow::settings() -{ - SettingsDialog *settingsDlg = new SettingsDialog(this); - settingsDlg->exec(); - - if (settingsDlg->presentationChanged()) { - m_hgTabs->updateFileStates(); - updateToolBarStyle(); - hgRefresh(); - } -} - -void MainWindow::updateFileSystemWatcher() -{ - bool justCreated = false; - if (!m_fsWatcher) { - m_fsWatcher = new QFileSystemWatcher(); - justCreated = true; - } - - // QFileSystemWatcher will refuse to add a file or directory to - // its watch list that it is already watching -- fine, that's what - // we want -- but it prints a warning when this happens, which is - // annoying because it would be the normal case for us. So we'll - // check for duplicates ourselves. - QSet alreadyWatched; - QStringList dl(m_fsWatcher->directories()); - foreach (QString d, dl) alreadyWatched.insert(d); - - std::deque pending; - pending.push_back(m_workFolderPath); - - while (!pending.empty()) { - - QString path = pending.front(); - pending.pop_front(); - if (!alreadyWatched.contains(path)) { - m_fsWatcher->addPath(path); - DEBUG << "Added to file system watcher: " << path << endl; - } - - QDir d(path); - if (d.exists()) { - d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | - QDir::Readable | QDir::NoSymLinks); - foreach (QString entry, d.entryList()) { - if (entry.startsWith('.')) continue; - QString entryPath = d.absoluteFilePath(entry); - pending.push_back(entryPath); - } - } - } - - // The general timer isn't really related to the fs watcher - // object, it just does something similar -- every now and then we - // do a refresh just to update the history dates etc - - m_fsWatcherGeneralTimer = new QTimer(this); - connect(m_fsWatcherGeneralTimer, SIGNAL(timeout()), - this, SLOT(checkFilesystem())); - m_fsWatcherGeneralTimer->setInterval(30 * 60 * 1000); // half an hour - m_fsWatcherGeneralTimer->start(); - - if (justCreated) { - connect(m_fsWatcher, SIGNAL(directoryChanged(QString)), - this, SLOT(fsDirectoryChanged(QString))); - connect(m_fsWatcher, SIGNAL(fileChanged(QString)), - this, SLOT(fsFileChanged(QString))); - } -} - -void MainWindow::suspendFileSystemWatcher() -{ - DEBUG << "MainWindow::suspendFileSystemWatcher" << endl; - if (m_fsWatcher) { - m_fsWatcherSuspended = true; - if (m_fsWatcherRestoreTimer) { - delete m_fsWatcherRestoreTimer; - m_fsWatcherRestoreTimer = 0; - } - m_fsWatcherGeneralTimer->stop(); - } -} - -void MainWindow::restoreFileSystemWatcher() -{ - DEBUG << "MainWindow::restoreFileSystemWatcher" << endl; - if (m_fsWatcherRestoreTimer) delete m_fsWatcherRestoreTimer; - - // The restore timer is used to leave a polite interval between - // being asked to restore the watcher and actually doing so. It's - // a single shot timer each time it's used, but we don't use - // QTimer::singleShot because we want to stop the previous one if - // it's running (via deleting it) - - m_fsWatcherRestoreTimer = new QTimer(this); - connect(m_fsWatcherRestoreTimer, SIGNAL(timeout()), - this, SLOT(actuallyRestoreFileSystemWatcher())); - m_fsWatcherRestoreTimer->setInterval(1000); - m_fsWatcherRestoreTimer->setSingleShot(true); - m_fsWatcherRestoreTimer->start(); -} - -void MainWindow::actuallyRestoreFileSystemWatcher() -{ - DEBUG << "MainWindow::actuallyRestoreFileSystemWatcher" << endl; - if (m_fsWatcher) { - m_fsWatcherSuspended = false; - m_fsWatcherGeneralTimer->start(); - } -} - -void MainWindow::checkFilesystem() -{ - DEBUG << "MainWindow::checkFilesystem" << endl; - hgRefresh(); -} - -void MainWindow::fsDirectoryChanged(QString d) -{ - DEBUG << "MainWindow::fsDirectoryChanged " << d << endl; - if (!m_fsWatcherSuspended) { - hgStat(); - } -} - -void MainWindow::fsFileChanged(QString f) -{ - DEBUG << "MainWindow::fsFileChanged " << f << endl; - if (!m_fsWatcherSuspended) { - hgStat(); - } -} - -QString MainWindow::format1(QString head) -{ - return QString("

%1

").arg(head); -} - -QString MainWindow::format3(QString head, QString intro, QString code) -{ - code = xmlEncode(code).replace("\n", "
") -#ifndef Q_OS_WIN32 - // The hard hyphen comes out funny on Windows - .replace("-", "‑") -#endif - .replace(" ", " "); - if (intro == "") { - return QString("

%1

%2

") - .arg(head).arg(code); - } else if (code == "") { - return QString("

%1

%2

") - .arg(head).arg(intro); - } else { - return QString("

%1

%2

%3

") - .arg(head).arg(intro).arg(code); - } -} - -void MainWindow::showIncoming(QString output) -{ - m_runner->hide(); - IncomingDialog *d = new IncomingDialog(this, output); - d->exec(); - delete d; -} - -int MainWindow::extractChangeCount(QString text) -{ - QRegExp re("added (\\d+) ch\\w+ with (\\d+) ch\\w+ to (\\d+) f\\w+"); - if (re.indexIn(text) >= 0) { - return re.cap(1).toInt(); - } else if (text.contains("no changes")) { - return 0; - } else { - return -1; // unknown - } -} - -void MainWindow::showPushResult(QString output) -{ - QString head; - QString report; - int n = extractChangeCount(output); - if (n > 0) { - head = tr("Pushed %n changeset(s)", "", n); - report = tr("Successfully pushed to the remote repository at %1.").arg(xmlEncode(m_remoteRepoPath)); - } else if (n == 0) { - head = tr("No changes to push"); - report = tr("The remote repository already contains all changes that have been committed locally."); - if (m_hgTabs->canCommit()) { - report = tr("%1

You do have some uncommitted changes. If you wish to push those to the remote repository, commit them locally first.").arg(report); - } - } else { - head = tr("Push complete"); - } - m_runner->hide(); - - MoreInformationDialog::information(this, tr("Push complete"), - head, report, output); -} - -void MainWindow::showPullResult(QString output) -{ - QString head; - QString report; - int n = extractChangeCount(output); - if (n > 0) { - head = tr("Pulled %n changeset(s)", "", n); - report = tr("New changes will be highlighted in yellow in the history."); - } else if (n == 0) { - head = tr("No changes to pull"); - report = tr("Your local repository already contains all changes found in the remote repository."); - } else { - head = tr("Pull complete"); - } - m_runner->hide(); - - MoreInformationDialog::information(this, tr("Pull complete"), - head, report, output); -} - -void MainWindow::reportNewRemoteHeads(QString output) -{ - bool headsAreLocal = false; - - if (m_currentParents.size() == 1) { - int m_currentBranchHeads = 0; - bool parentIsHead = false; - Changeset *parent = m_currentParents[0]; - foreach (Changeset *head, m_currentHeads) { - if (head->isOnBranch(m_currentBranch)) { - ++m_currentBranchHeads; - } - if (parent->id() == head->id()) { - parentIsHead = true; - } - } - if (m_currentBranchHeads == 2 && parentIsHead) { - headsAreLocal = true; - } - } - - if (headsAreLocal) { - MoreInformationDialog::warning - (this, - tr("Push failed"), - tr("Push failed"), - tr("Your local repository could not be pushed to the remote repository.

You may need to merge the changes locally first."), - output); - } else if (m_hgTabs->canCommit() && m_currentParents.size() > 1) { - MoreInformationDialog::warning - (this, - tr("Push failed"), - tr("Push failed"), - tr("Your local repository could not be pushed to the remote repository.

You have an uncommitted merge in your local folder. You probably need to commit it before you push."), - output); - } else { - MoreInformationDialog::warning - (this, - tr("Push failed"), - tr("Push failed"), - tr("Your local repository could not be pushed to the remote repository.

The remote repository may have been changed by someone else since you last pushed. Try pulling and merging their changes into your local repository first."), - output); - } -} - -void MainWindow::reportAuthFailed(QString output) -{ - MoreInformationDialog::warning - (this, - tr("Authorization failed"), - tr("Authorization failed"), - tr("You may have entered an incorrect user name or password, or the remote URL may be wrong.

Or you may lack the necessary permissions on the remote repository.

Check with the administrator of your remote repository if necessary."), - output); -} - -void MainWindow::commandStarting(HgAction action) -{ - // Annoyingly, hg stat actually modifies the working directory -- - // it creates files called hg-checklink and hg-checkexec to test - // properties of the filesystem. For safety's sake, suspend the - // fs watcher while running commands, and restore it shortly after - // a command has finished. - - if (action.action == ACT_STAT) { - suspendFileSystemWatcher(); - } -} - -void MainWindow::commandFailed(HgAction action, QString output) -{ - DEBUG << "MainWindow::commandFailed" << endl; - restoreFileSystemWatcher(); - - QString setstr; -#ifdef Q_OS_MAC - setstr = tr("Preferences"); -#else - setstr = tr("Settings"); -#endif - - // Some commands we just have to ignore bad return values from, - // and some output gets special treatment. - - // Note our fallback case should always be to report a - // non-specific error and show the text -- in case output scraping - // fails (as it surely will). Note also that we must force the - // locale in order to ensure the output is scrapable; this happens - // in HgRunner and may break some system encodings. - - switch(action.action) { - case ACT_NONE: - // uh huh - return; - case ACT_TEST_HG: - MoreInformationDialog::warning - (this, - tr("Failed to run Mercurial"), - tr("Failed to run Mercurial"), - tr("The Mercurial program either could not be found or failed to run.
Check that the Mercurial program path is correct in %1.").arg(setstr), - output); - settings(); - return; - case ACT_TEST_HG_EXT: - MoreInformationDialog::warning - (this, - tr("Failed to run Mercurial"), - tr("Failed to run Mercurial with extension enabled"), - tr("The Mercurial program failed to run with the EasyMercurial interaction extension enabled.
This may indicate an installation problem.

You may be able to continue working if you switch off “Use EasyHg Mercurial Extension” in %1. Note that remote repositories that require authentication might not work if you do this.").arg(setstr), - output); - settings(); - return; - case ACT_CLONEFROMREMOTE: - // if clone fails, we have no repo - m_workFolderPath = ""; - enableDisableActions(); - break; // go on to default report - case ACT_INCOMING: - if (output.contains("authorization failed")) { - reportAuthFailed(output); - return; - } else if (output.contains("entry cancelled")) { - // ignore this, user cancelled username or password dialog - return; - } else { - // Incoming returns non-zero code and no output if the - // check was successful but there are no changes - // pending. This is the only case where we need to remove - // warning messages, because it's the only case where a - // non-zero code can be returned even though the command - // has for our purposes succeeded - QString replaced = output; - while (1) { - QString r1 = replaced; - r1.replace(QRegExp("warning: [^\\n]*"), ""); - if (r1 == replaced) break; - replaced = r1.trimmed(); - } - if (replaced == "") { - showIncoming(""); - return; - } - } - break; // go on to default report - case ACT_PULL: - if (output.contains("authorization failed")) { - reportAuthFailed(output); - return; - } else if (output.contains("entry cancelled")) { - // ignore this, user cancelled username or password dialog - return; - } - break; // go on to default report - case ACT_PUSH: - if (output.contains("creates new remote heads")) { - reportNewRemoteHeads(output); - return; - } else if (output.contains("authorization failed")) { - reportAuthFailed(output); - return; - } else if (output.contains("entry cancelled")) { - // ignore this, user cancelled username or password dialog - return; - } - break; // go on to default report - case ACT_QUERY_HEADS: - // fails if repo is empty; we don't care (if there's a genuine - // problem, something else will fail too). Pretend it - // succeeded, so that any further actions that are contingent - // on the success of the heads query get carried out properly. - commandCompleted(action, ""); - return; - case ACT_FOLDERDIFF: - case ACT_CHGSETDIFF: - // external program, unlikely to be anything useful in stderr - // and some return with failure codes when something as basic - // as the user closing the window via the wm happens - return; - case ACT_MERGE: - case ACT_RETRY_MERGE: - MoreInformationDialog::information - (this, tr("Merge"), tr("Merge failed"), - tr("Some files were not merged successfully.

You can Merge again to repeat the interactive merge; use Revert to abandon the merge entirely; or edit the files that are in conflict in an editor and, when you are happy with them, choose Mark Resolved in each file's right-button menu."), - output); - return; - case ACT_STAT: - break; // go on to default report - default: - break; - } - - QString command = action.executable; - if (command == "") command = "hg"; - foreach (QString arg, action.params) { - command += " " + arg; - } - - MoreInformationDialog::warning - (this, - tr("Command failed"), - tr("Command failed"), - tr("A Mercurial command failed to run correctly. This may indicate an installation problem or some other problem with EasyMercurial.

See “More Details” for the command output."), - output); -} - -void MainWindow::commandCompleted(HgAction completedAction, QString output) -{ - restoreFileSystemWatcher(); - HGACTIONS action = completedAction.action; - - if (action == ACT_NONE) return; - - bool headsChanged = false; - QStringList oldHeadIds; - - switch (action) { - - case ACT_TEST_HG: - break; - - case ACT_TEST_HG_EXT: - break; - - case ACT_QUERY_PATHS: - { - DEBUG << "stdout is " << output << endl; - LogParser lp(output, "="); - LogList ll = lp.parse(); - DEBUG << ll.size() << " results" << endl; - if (!ll.empty()) { - m_remoteRepoPath = lp.parse()[0]["default"].trimmed(); - DEBUG << "Set remote path to " << m_remoteRepoPath << endl; - } else { - m_remoteRepoPath = ""; - } - MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); - MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); - updateWorkFolderAndRepoNames(); - break; - } - - case ACT_QUERY_BRANCH: - m_currentBranch = output.trimmed(); - break; - - case ACT_STAT: - m_lastStatOutput = output; - updateFileSystemWatcher(); - break; - - case ACT_RESOLVE_LIST: - if (output != "") { - // Remove lines beginning with R (they are resolved, - // and the file stat parser treats R as removed) - QStringList outList = output.split('\n'); - QStringList winnowed; - foreach (QString line, outList) { - if (!line.startsWith("R ")) winnowed.push_back(line); - } - output = winnowed.join("\n"); - } - DEBUG << "m_lastStatOutput = " << m_lastStatOutput << endl; - DEBUG << "resolve output = " << output << endl; - m_hgTabs->updateWorkFolderFileList(m_lastStatOutput + output); - break; - - case ACT_RESOLVE_MARK: - m_shouldHgStat = true; - break; - - case ACT_INCOMING: - showIncoming(output); - break; - - case ACT_ANNOTATE: - { - AnnotateDialog dialog(this, output); - dialog.exec(); - m_shouldHgStat = true; - break; - } - - case ACT_PULL: - showPullResult(output); - m_shouldHgStat = true; - break; - - case ACT_PUSH: - showPushResult(output); - break; - - case ACT_INIT: - MultiChoiceDialog::addRecentArgument("init", m_workFolderPath); - MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); - enableDisableActions(); - m_shouldHgStat = true; - break; - - case ACT_CLONEFROMREMOTE: - MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); - MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); - MultiChoiceDialog::addRecentArgument("remote", m_workFolderPath, true); - MoreInformationDialog::information - (this, - tr("Clone"), - tr("Clone successful"), - tr("The remote repository was successfully cloned to the local folder %1.").arg(xmlEncode(m_workFolderPath)), - output); - enableDisableActions(); - m_shouldHgStat = true; - break; - - case ACT_LOG: - m_hgTabs->setNewLog(output); - m_needNewLog = false; - break; - - case ACT_LOG_INCREMENTAL: - m_hgTabs->addIncrementalLog(output); - break; - - case ACT_QUERY_PARENTS: - { - foreach (Changeset *cs, m_currentParents) delete cs; - m_currentParents = Changeset::parseChangesets(output); - QStringList parentIds = Changeset::getIds(m_currentParents); - m_hgTabs->setCurrent(parentIds, m_currentBranch); - } - break; - - case ACT_QUERY_HEADS: - { - oldHeadIds = Changeset::getIds(m_currentHeads); - Changesets newHeads = Changeset::parseChangesets(output); - QStringList newHeadIds = Changeset::getIds(newHeads); - if (oldHeadIds != newHeadIds) { - DEBUG << "Heads changed, will prompt an incremental log if appropriate" << endl; - DEBUG << "Old heads: " << oldHeadIds.join(",") << endl; - DEBUG << "New heads: " << newHeadIds.join(",") << endl; - headsChanged = true; - foreach (Changeset *cs, m_currentHeads) delete cs; - m_currentHeads = newHeads; - } - } - break; - - case ACT_COMMIT: - if (m_currentParents.empty()) { - // first commit to empty repo - m_needNewLog = true; - } - m_hgTabs->clearSelections(); - m_justMerged = false; - m_shouldHgStat = true; - break; - - case ACT_REVERT: - hgMarkFilesResolved(m_lastRevertedFiles); - m_justMerged = false; - break; - - case ACT_REMOVE: - case ACT_ADD: - m_hgTabs->clearSelections(); - m_shouldHgStat = true; - break; - - case ACT_TAG: - m_needNewLog = true; - m_shouldHgStat = true; - break; - - case ACT_NEW_BRANCH: - m_shouldHgStat = true; - break; - - case ACT_UNCOMMITTED_SUMMARY: - QMessageBox::information(this, tr("Change summary"), - format3(tr("Summary of uncommitted changes"), - "", - output)); - break; - - case ACT_DIFF_SUMMARY: - { - // Output has log info first, diff following after a blank line - output.replace("\r\n", "\n"); - QStringList olist = output.split("\n\n", QString::SkipEmptyParts); - if (olist.size() > 1) output = olist[1]; - - Changeset *cs = (Changeset *)completedAction.extraData; - if (cs) { - QMessageBox::information - (this, tr("Change summary"), - format3(tr("Summary of changes"), - cs->formatHtml(), - output)); - } else if (output == "") { - // Can happen, for a merge commit (depending on parent) - QMessageBox::information(this, tr("Change summary"), - format3(tr("Summary of changes"), - tr("No changes"), - output)); - } else { - QMessageBox::information(this, tr("Change summary"), - format3(tr("Summary of changes"), - "", - output)); - } - break; - } - - case ACT_FOLDERDIFF: - case ACT_CHGSETDIFF: - case ACT_SERVE: - case ACT_HG_IGNORE: - m_shouldHgStat = true; - break; - - case ACT_UPDATE: - QMessageBox::information(this, tr("Update"), tr("

Update successful

%1

").arg(xmlEncode(output))); - m_shouldHgStat = true; - break; - - case ACT_MERGE: - MoreInformationDialog::information - (this, tr("Merge"), tr("Merge successful"), - tr("Remember to test and commit the result before making any further changes."), - output); - m_shouldHgStat = true; - m_justMerged = true; - break; - - case ACT_RETRY_MERGE: - QMessageBox::information(this, tr("Resolved"), - tr("

Merge resolved

Merge resolved successfully.
Remember to test and commit the result before making any further changes.

")); - m_shouldHgStat = true; - m_justMerged = true; - break; - - default: - break; - } - - // Sequence when no full log required: - // paths -> branch -> stat -> resolve-list -> heads -> - // incremental-log (only if heads changed) -> parents - // - // Sequence when full log required: - // paths -> branch -> stat -> resolve-list -> heads -> parents -> log - // - // Note we want to call enableDisableActions only once, at the end - // of whichever sequence is in use. - - bool noMore = false; - - switch (action) { - - case ACT_TEST_HG: - { - QSettings settings; - settings.beginGroup("General"); - if (settings.value("useextension", true).toBool()) { - hgTestExtension(); - } else if (m_workFolderPath == "") { - open(); - } else { - hgQueryPaths(); - } - break; - } - - case ACT_TEST_HG_EXT: - if (m_workFolderPath == "") { - open(); - } else{ - hgQueryPaths(); - } - break; - - case ACT_QUERY_PATHS: - hgQueryBranch(); - break; - - case ACT_QUERY_BRANCH: - hgStat(); - break; - - case ACT_STAT: - hgResolveList(); - break; - - case ACT_RESOLVE_LIST: - hgQueryHeads(); - break; - - case ACT_QUERY_HEADS: - if (headsChanged && !m_needNewLog) { - hgLogIncremental(oldHeadIds); - } else { - hgQueryParents(); - } - break; - - case ACT_LOG_INCREMENTAL: - hgQueryParents(); - break; - - case ACT_QUERY_PARENTS: - if (m_needNewLog) { - hgLog(); - } else { - // we're done - noMore = true; - } - break; - - case ACT_LOG: - // we're done - noMore = true; - break; - - default: - if (m_shouldHgStat) { - m_shouldHgStat = false; - hgQueryPaths(); - } else { - noMore = true; - } - break; - } - - if (noMore) { - m_stateUnknown = false; - enableDisableActions(); - m_hgTabs->updateHistory(); - updateRecentMenu(); - } -} - -void MainWindow::connectActions() -{ - connect(m_exitAct, SIGNAL(triggered()), this, SLOT(close())); - connect(m_aboutAct, SIGNAL(triggered()), this, SLOT(about())); - - connect(m_hgRefreshAct, SIGNAL(triggered()), this, SLOT(hgRefresh())); - connect(m_hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove())); - connect(m_hgAddAct, SIGNAL(triggered()), this, SLOT(hgAdd())); - connect(m_hgCommitAct, SIGNAL(triggered()), this, SLOT(hgCommit())); - connect(m_hgFolderDiffAct, SIGNAL(triggered()), this, SLOT(hgFolderDiff())); - connect(m_hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate())); - connect(m_hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert())); - connect(m_hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge())); - connect(m_hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore())); - - connect(m_settingsAct, SIGNAL(triggered()), this, SLOT(settings())); - connect(m_openAct, SIGNAL(triggered()), this, SLOT(open())); - connect(m_changeRemoteRepoAct, SIGNAL(triggered()), this, SLOT(changeRemoteRepo())); - - connect(m_hgIncomingAct, SIGNAL(triggered()), this, SLOT(hgIncoming())); - connect(m_hgPullAct, SIGNAL(triggered()), this, SLOT(hgPull())); - connect(m_hgPushAct, SIGNAL(triggered()), this, SLOT(hgPush())); - - connect(m_hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe())); -} - -void MainWindow::connectTabsSignals() -{ - connect(m_hgTabs, SIGNAL(currentChanged(int)), - this, SLOT(enableDisableActions())); - - connect(m_hgTabs, SIGNAL(commit()), - this, SLOT(hgCommit())); - - connect(m_hgTabs, SIGNAL(revert()), - this, SLOT(hgRevert())); - - connect(m_hgTabs, SIGNAL(diffWorkingFolder()), - this, SLOT(hgFolderDiff())); - - connect(m_hgTabs, SIGNAL(showSummary()), - this, SLOT(hgShowSummary())); - - connect(m_hgTabs, SIGNAL(newBranch()), - this, SLOT(hgNewBranch())); - - connect(m_hgTabs, SIGNAL(noBranch()), - this, SLOT(hgNoBranch())); - - connect(m_hgTabs, SIGNAL(updateTo(QString)), - this, SLOT(hgUpdateToRev(QString))); - - connect(m_hgTabs, SIGNAL(diffToCurrent(QString)), - this, SLOT(hgDiffToCurrent(QString))); - - connect(m_hgTabs, SIGNAL(diffToParent(QString, QString)), - this, SLOT(hgDiffToParent(QString, QString))); - - connect(m_hgTabs, SIGNAL(showSummary(Changeset *)), - this, SLOT(hgShowSummaryFor(Changeset *))); - - connect(m_hgTabs, SIGNAL(mergeFrom(QString)), - this, SLOT(hgMergeFrom(QString))); - - connect(m_hgTabs, SIGNAL(newBranch(QString)), - this, SLOT(hgNewBranch())); - - connect(m_hgTabs, SIGNAL(tag(QString)), - this, SLOT(hgTag(QString))); - - connect(m_hgTabs, SIGNAL(annotateFiles(QStringList)), - this, SLOT(hgAnnotateFiles(QStringList))); - - connect(m_hgTabs, SIGNAL(diffFiles(QStringList)), - this, SLOT(hgDiffFiles(QStringList))); - - connect(m_hgTabs, SIGNAL(commitFiles(QStringList)), - this, SLOT(hgCommitFiles(QStringList))); - - connect(m_hgTabs, SIGNAL(revertFiles(QStringList)), - this, SLOT(hgRevertFiles(QStringList))); - - connect(m_hgTabs, SIGNAL(renameFiles(QStringList)), - this, SLOT(hgRenameFiles(QStringList))); - - connect(m_hgTabs, SIGNAL(copyFiles(QStringList)), - this, SLOT(hgCopyFiles(QStringList))); - - connect(m_hgTabs, SIGNAL(addFiles(QStringList)), - this, SLOT(hgAddFiles(QStringList))); - - connect(m_hgTabs, SIGNAL(removeFiles(QStringList)), - this, SLOT(hgRemoveFiles(QStringList))); - - connect(m_hgTabs, SIGNAL(redoFileMerges(QStringList)), - this, SLOT(hgRedoFileMerges(QStringList))); - - connect(m_hgTabs, SIGNAL(markFilesResolved(QStringList)), - this, SLOT(hgMarkFilesResolved(QStringList))); - - connect(m_hgTabs, SIGNAL(ignoreFiles(QStringList)), - this, SLOT(hgIgnoreFiles(QStringList))); - - connect(m_hgTabs, SIGNAL(unIgnoreFiles(QStringList)), - this, SLOT(hgUnIgnoreFiles(QStringList))); -} - -void MainWindow::enableDisableActions() -{ - DEBUG << "MainWindow::enableDisableActions" << endl; - - QString dirname = QDir(m_workFolderPath).dirName(); - - if (m_workFolderPath != "") { // dirname of "" is ".", so test path instead - setWindowTitle(tr("EasyMercurial: %1").arg(dirname)); - } else { - setWindowTitle(tr("EasyMercurial")); - } - - //!!! should also do things like set the status texts for the - //!!! actions appropriately by context - - QDir localRepoDir; - QDir workFolderDir; - bool workFolderExist = true; - bool localRepoExist = true; - - m_remoteRepoActionsEnabled = true; - if (m_remoteRepoPath.isEmpty()) { - m_remoteRepoActionsEnabled = false; - } - - m_localRepoActionsEnabled = true; - if (m_workFolderPath.isEmpty()) { - m_localRepoActionsEnabled = false; - workFolderExist = false; - } - - if (m_workFolderPath == "" || !workFolderDir.exists(m_workFolderPath)) { - m_localRepoActionsEnabled = false; - workFolderExist = false; - } else { - workFolderExist = true; - } - - if (!localRepoDir.exists(m_workFolderPath + "/.hg")) { - m_localRepoActionsEnabled = false; - localRepoExist = false; - } - - bool haveDiff = false; - QSettings settings; - settings.beginGroup("Locations"); - if (settings.value("extdiffbinary", "").toString() != "") { - haveDiff = true; - } - settings.endGroup(); - - m_hgRefreshAct->setEnabled(m_localRepoActionsEnabled); - m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && haveDiff); - m_hgRevertAct->setEnabled(m_localRepoActionsEnabled); - m_hgAddAct->setEnabled(m_localRepoActionsEnabled); - m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled); - m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled); - m_hgCommitAct->setEnabled(m_localRepoActionsEnabled); - m_hgMergeAct->setEnabled(m_localRepoActionsEnabled); - m_hgServeAct->setEnabled(m_localRepoActionsEnabled); - m_hgIgnoreAct->setEnabled(m_localRepoActionsEnabled); - - DEBUG << "m_localRepoActionsEnabled = " << m_localRepoActionsEnabled << endl; - DEBUG << "canCommit = " << m_hgTabs->canCommit() << endl; - - m_hgAddAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canAdd()); - m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRemove()); - m_hgCommitAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canCommit()); - m_hgRevertAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRevert()); - m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canDiff()); - - // A default merge makes sense if: - // * there is only one parent (if there are two, we have an uncommitted merge) and - // * there are exactly two heads that have the same branch as the current branch and - // * our parent is one of those heads - // - // A default update makes sense if: - // * there is only one parent and - // * the parent is not one of the current heads - - bool canMerge = false; - bool canUpdate = false; - bool haveMerge = false; - bool emptyRepo = false; - bool noWorkingCopy = false; - bool newBranch = false; - int m_currentBranchHeads = 0; - - if (m_currentParents.size() == 1) { - bool parentIsHead = false; - Changeset *parent = m_currentParents[0]; - foreach (Changeset *head, m_currentHeads) { - DEBUG << "head branch " << head->branch() << ", current branch " << m_currentBranch << endl; - if (head->isOnBranch(m_currentBranch)) { - ++m_currentBranchHeads; - } - if (parent->id() == head->id()) { - parentIsHead = true; - } - } - if (m_currentBranchHeads == 2 && parentIsHead) { - canMerge = true; - } - if (m_currentBranchHeads == 0 && parentIsHead) { - // Just created a new branch - newBranch = true; - } - if (!parentIsHead) { - canUpdate = true; - DEBUG << "parent id = " << parent->id() << endl; - DEBUG << " head ids "<setState(tr("No repository open")); - } else { - m_workStatus->setState(tr("(Examining repository)")); - } - } else if (emptyRepo) { - m_workStatus->setState(tr("Nothing committed to this repository yet")); - } else if (noWorkingCopy) { - m_workStatus->setState(tr("No working copy yet: consider updating")); - } else if (canMerge) { - m_workStatus->setState(tr("Awaiting merge on %1").arg(branchText)); - } else if (!m_hgTabs->getAllUnresolvedFiles().empty()) { - m_workStatus->setState(tr("Have unresolved files following merge on %1").arg(branchText)); - } else if (haveMerge) { - m_workStatus->setState(tr("Have merged but not yet committed on %1").arg(branchText)); - } else if (newBranch) { - m_workStatus->setState(tr("On %1. New branch: has not yet been committed").arg(branchText)); - } else if (canUpdate) { - if (m_hgTabs->haveChangesToCommit()) { - // have uncommitted changes - m_workStatus->setState(tr("On %1. Not at the head of the branch").arg(branchText)); - } else { - // no uncommitted changes - m_workStatus->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText)); - } - } else if (m_currentBranchHeads > 1) { - m_workStatus->setState(tr("At one of %n heads of %1", "", m_currentBranchHeads).arg(branchText)); - } else { - m_workStatus->setState(tr("At the head of %1").arg(branchText)); - } -} - - -void MainWindow::updateRecentMenu() -{ - m_recentMenu->clear(); - RecentFiles rf("Recent-local"); - QStringList recent = rf.getRecent(); - if (recent.empty()) { - QLabel *label = new QLabel(tr("No recent local repositories")); - QWidgetAction *wa = new QWidgetAction(m_recentMenu); - wa->setDefaultWidget(label); - return; - } - foreach (QString r, recent) { - QAction *a = m_recentMenu->addAction(r); - connect(a, SIGNAL(activated()), this, SLOT(recentMenuActivated())); - } -} - -void MainWindow::createActions() -{ - //File actions - m_openAct = new QAction(QIcon(":/images/fileopen.png"), tr("&Open..."), this); - m_openAct->setStatusTip(tr("Open an existing repository or working folder")); - m_openAct->setShortcut(tr("Ctrl+O")); - - m_changeRemoteRepoAct = new QAction(tr("Set Remote &Location..."), this); - m_changeRemoteRepoAct->setStatusTip(tr("Set or change the default remote repository for pull and push actions")); - - m_settingsAct = new QAction(QIcon(":/images/settings.png"), tr("&Settings..."), this); - m_settingsAct->setStatusTip(tr("View and change application settings")); - -#ifdef Q_OS_WIN32 - m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("E&xit"), this); -#else - m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("&Quit"), this); -#endif - m_exitAct->setShortcuts(QKeySequence::Quit); - m_exitAct->setStatusTip(tr("Exit EasyMercurial")); - - //Repository actions - m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("&Refresh"), this); - m_hgRefreshAct->setShortcut(tr("Ctrl+R")); - m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder")); - - m_hgIncomingAct = new QAction(QIcon(":/images/incoming.png"), tr("Pre&view Incoming Changes"), this); - m_hgIncomingAct->setIconText(tr("Preview")); - m_hgIncomingAct->setStatusTip(tr("See what changes are available in the remote repository waiting to be pulled")); - - m_hgPullAct = new QAction(QIcon(":/images/pull.png"), tr("Pu&ll from Remote Repository"), this); - m_hgPullAct->setIconText(tr("Pull")); - m_hgPullAct->setShortcut(tr("Ctrl+L")); - m_hgPullAct->setStatusTip(tr("Pull changes from the remote repository to the local repository")); - - m_hgPushAct = new QAction(QIcon(":/images/push.png"), tr("Pus&h to Remote Repository"), this); - m_hgPushAct->setIconText(tr("Push")); - m_hgPushAct->setShortcut(tr("Ctrl+H")); - m_hgPushAct->setStatusTip(tr("Push changes from the local repository to the remote repository")); - - //Workfolder actions - m_hgFolderDiffAct = new QAction(QIcon(":/images/folderdiff.png"), tr("&Diff"), this); - m_hgFolderDiffAct->setIconText(tr("Diff")); - m_hgFolderDiffAct->setShortcut(tr("Ctrl+D")); - m_hgFolderDiffAct->setStatusTip(tr("See what has changed in the working folder compared with the last committed state")); - - m_hgRevertAct = new QAction(QIcon(":/images/undo.png"), tr("Re&vert"), this); - m_hgRevertAct->setStatusTip(tr("Throw away your changes and return to the last committed state")); - - m_hgAddAct = new QAction(QIcon(":/images/add.png"), tr("&Add Files"), this); - m_hgAddAct->setIconText(tr("Add")); - m_hgAddAct->setShortcut(tr("+")); - m_hgAddAct->setStatusTip(tr("Mark the selected files to be added on the next commit")); - - m_hgRemoveAct = new QAction(QIcon(":/images/remove.png"), tr("&Remove Files"), this); - m_hgRemoveAct->setIconText(tr("Remove")); - m_hgRemoveAct->setShortcut(tr("Del")); - m_hgRemoveAct->setStatusTip(tr("Mark the selected files to be removed from version control on the next commit")); - - m_hgUpdateAct = new QAction(QIcon(":/images/update.png"), tr("&Update to Branch Head"), this); - m_hgUpdateAct->setIconText(tr("Update")); - m_hgUpdateAct->setShortcut(tr("Ctrl+U")); - m_hgUpdateAct->setStatusTip(tr("Update the working folder to the head of the current repository branch")); - - m_hgCommitAct = new QAction(QIcon(":/images/commit.png"), tr("&Commit..."), this); - m_hgCommitAct->setShortcut(tr("Ctrl+Return")); - m_hgCommitAct->setStatusTip(tr("Commit your changes to the local repository")); - - m_hgMergeAct = new QAction(QIcon(":/images/merge.png"), tr("&Merge"), this); - m_hgMergeAct->setShortcut(tr("Ctrl+M")); - m_hgMergeAct->setStatusTip(tr("Merge the two independent sets of changes in the local repository into the working folder")); - - //Advanced actions - - m_hgIgnoreAct = new QAction(tr("Edit .hgignore File"), this); - m_hgIgnoreAct->setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial")); - - m_hgServeAct = new QAction(tr("Serve via HTTP"), this); - m_hgServeAct->setStatusTip(tr("Serve local repository via http for workgroup access")); - - //Help actions - m_aboutAct = new QAction(tr("About EasyMercurial"), this); - - // Miscellaneous - QShortcut *clearSelectionsShortcut = new QShortcut(Qt::Key_Escape, this); - connect(clearSelectionsShortcut, SIGNAL(activated()), - this, SLOT(clearSelections())); -} - -void MainWindow::createMenus() -{ - m_fileMenu = menuBar()->addMenu(tr("&File")); - - m_fileMenu->addAction(m_openAct); - m_recentMenu = m_fileMenu->addMenu(tr("Open Re¢")); - m_fileMenu->addAction(m_hgRefreshAct); - m_fileMenu->addSeparator(); - m_fileMenu->addAction(m_settingsAct); - m_fileMenu->addSeparator(); - m_fileMenu->addAction(m_exitAct); - - QMenu *workMenu; - workMenu = menuBar()->addMenu(tr("&Work")); - workMenu->addAction(m_hgFolderDiffAct); - workMenu->addSeparator(); - workMenu->addAction(m_hgUpdateAct); - workMenu->addAction(m_hgCommitAct); - workMenu->addAction(m_hgMergeAct); - workMenu->addSeparator(); - workMenu->addAction(m_hgAddAct); - workMenu->addAction(m_hgRemoveAct); - workMenu->addSeparator(); - workMenu->addAction(m_hgRevertAct); - - QMenu *remoteMenu; - remoteMenu = menuBar()->addMenu(tr("&Remote")); - remoteMenu->addAction(m_hgIncomingAct); - remoteMenu->addSeparator(); - remoteMenu->addAction(m_hgPullAct); - remoteMenu->addAction(m_hgPushAct); - remoteMenu->addSeparator(); - remoteMenu->addAction(m_changeRemoteRepoAct); - - m_advancedMenu = menuBar()->addMenu(tr("&Advanced")); - m_advancedMenu->addAction(m_hgIgnoreAct); - m_advancedMenu->addSeparator(); - m_advancedMenu->addAction(m_hgServeAct); - - m_helpMenu = menuBar()->addMenu(tr("Help")); - m_helpMenu->addAction(m_aboutAct); -} - -void MainWindow::createToolBars() -{ - m_fileToolBar = addToolBar(tr("File")); - m_fileToolBar->setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); - m_fileToolBar->addAction(m_openAct); - m_fileToolBar->addAction(m_hgRefreshAct); - m_fileToolBar->setMovable(false); - - m_repoToolBar = addToolBar(tr(REPOMENU_TITLE)); - m_repoToolBar->setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); - m_repoToolBar->addAction(m_hgIncomingAct); - m_repoToolBar->addAction(m_hgPullAct); - m_repoToolBar->addAction(m_hgPushAct); - m_repoToolBar->setMovable(false); - - m_workFolderToolBar = addToolBar(tr(WORKFOLDERMENU_TITLE)); - addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar); - m_workFolderToolBar->setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); - m_workFolderToolBar->addAction(m_hgFolderDiffAct); - m_workFolderToolBar->addSeparator(); - m_workFolderToolBar->addAction(m_hgRevertAct); - m_workFolderToolBar->addAction(m_hgUpdateAct); - m_workFolderToolBar->addAction(m_hgCommitAct); - m_workFolderToolBar->addAction(m_hgMergeAct); - m_workFolderToolBar->addSeparator(); - m_workFolderToolBar->addAction(m_hgAddAct); - m_workFolderToolBar->addAction(m_hgRemoveAct); - m_workFolderToolBar->setMovable(false); - - updateToolBarStyle(); -} - -void MainWindow::updateToolBarStyle() -{ - QSettings settings; - settings.beginGroup("Presentation"); - bool showText = settings.value("showiconlabels", true).toBool(); - settings.endGroup(); - - foreach (QToolButton *tb, findChildren()) { - tb->setToolButtonStyle(showText ? - Qt::ToolButtonTextUnderIcon : - Qt::ToolButtonIconOnly); - } -} - -void MainWindow::updateWorkFolderAndRepoNames() -{ - m_hgTabs->setLocalPath(m_workFolderPath); - - m_workStatus->setLocalPath(m_workFolderPath); - m_workStatus->setRemoteURL(m_remoteRepoPath); -} - -void MainWindow::createStatusBar() -{ - statusBar()->showMessage(tr("Ready")); -} - -void MainWindow::readSettings() -{ - QDir workFolder; - - QSettings settings; - - m_remoteRepoPath = settings.value("remoterepopath", "").toString(); - m_workFolderPath = settings.value("workfolderpath", "").toString(); - if (!workFolder.exists(m_workFolderPath)) - { - m_workFolderPath = ""; - } - - QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint(); - QSize size = settings.value("size", QSize(550, 550)).toSize(); - m_firstStart = settings.value("firststart", QVariant(true)).toBool(); - - resize(size); - move(pos); -} - -void MainWindow::writeSettings() -{ - QSettings settings; - settings.setValue("pos", pos()); - settings.setValue("size", size()); - settings.setValue("remoterepopath", m_remoteRepoPath); - settings.setValue("workfolderpath", m_workFolderPath); - settings.setValue("firststart", m_firstStart); -} - - - - diff -r 19cce6d2c470 -r b9c153e00e84 mainwindow.h --- a/mainwindow.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,256 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "hgtabwidget.h" -#include "hgrunner.h" -#include "common.h" -#include "changeset.h" -#include "hgaction.h" - -#include -#include -#include - -QT_BEGIN_NAMESPACE -class QAction; -class QMenu; -class QTimer; -QT_END_NAMESPACE - -class WorkStatusWidget; - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - MainWindow(QString myDirPath); - -protected: - void closeEvent(QCloseEvent *event); - -public slots: - void open(QString local); - void hgRefresh(); - void commandStarting(HgAction); - void commandCompleted(HgAction action, QString stdOut); - void commandFailed(HgAction action, QString stdErr); - void enableDisableActions(); - -private slots: - void about(); - void settings(); - void open(); - void recentMenuActivated(); - void changeRemoteRepo(); - void changeRemoteRepo(bool initial); - void startupDialog(); - void clearSelections(); - void showAllChanged(bool); - - void hgTest(); - void hgTestExtension(); - void hgQueryPaths(); - void hgStat(); - void hgRemove(); - void hgAdd(); - void hgCommit(); - void hgShowSummary(); - void hgShowSummaryFor(Changeset *); - void hgFolderDiff(); - void hgDiffToCurrent(QString); - void hgDiffToParent(QString, QString); - void hgUpdate(); - void hgRevert(); - void hgMerge(); - void hgRedoMerge(); - void hgCloneFromRemote(); - void hgInit(); - void hgIncoming(); - void hgPush(); - void hgPull(); - void hgUpdateToRev(QString); - void hgMergeFrom(QString); - void hgResolveList(); - void hgTag(QString); - void hgNewBranch(); - void hgNoBranch(); - void hgServe(); - void hgIgnore(); - - void hgAnnotateFiles(QStringList); - void hgDiffFiles(QStringList); - void hgCommitFiles(QStringList); - void hgRevertFiles(QStringList); - void hgRenameFiles(QStringList); - void hgCopyFiles(QStringList); - void hgAddFiles(QStringList); - void hgRemoveFiles(QStringList); - void hgRedoFileMerges(QStringList); - void hgMarkFilesResolved(QStringList); - void hgIgnoreFiles(QStringList); - void hgUnIgnoreFiles(QStringList); - - void fsDirectoryChanged(QString); - void fsFileChanged(QString); - void checkFilesystem(); - void actuallyRestoreFileSystemWatcher(); - -private: - void hgQueryBranch(); - void hgQueryHeads(); - void hgQueryParents(); - void hgLog(); - void hgLogIncremental(QStringList prune); - - void updateRecentMenu(); - void createActions(); - void connectActions(); - void connectTabsSignals(); - void createMenus(); - void createToolBars(); - void updateToolBarStyle(); - void createStatusBar(); - void readSettings(); - void splitChangeSets(QStringList *list, QString hgLogOutput); - void reportNewRemoteHeads(QString); - void reportAuthFailed(QString); - void writeSettings(); - - QStringList listAllUpIpV4Addresses(); - QString filterTag(QString tag); - - QString getUserInfo() const; - - bool openLocal(QString); - bool openRemote(QString, QString); - bool openInit(QString); - - bool complainAboutFilePath(QString); - bool complainAboutUnknownFolder(QString); - bool complainAboutInitInRepo(QString); - bool complainAboutInitFile(QString); - bool complainAboutCloneToExisting(QString); - bool complainAboutCloneToFile(QString); - QString complainAboutCloneToExistingFolder(QString local, QString remote); // returns new location, or empty string for cancel - - bool askAboutUnknownFolder(QString); - bool askToInitExisting(QString); - bool askToInitNew(QString); - bool askToOpenParentRepo(QString, QString); - bool askToOpenInsteadOfInit(QString); - - void showIncoming(QString); - void showPullResult(QString); - void showPushResult(QString); - int extractChangeCount(QString); - QString format1(QString); - QString format3(QString, QString, QString); - - void clearState(); - - void updateFileSystemWatcher(); - void suspendFileSystemWatcher(); - void restoreFileSystemWatcher(); - - void updateWorkFolderAndRepoNames(); - - WorkStatusWidget *m_workStatus; - HgTabWidget *m_hgTabs; - - QString m_remoteRepoPath; - QString m_workFolderPath; - QString m_currentBranch; - Changesets m_currentHeads; - Changesets m_currentParents; - int m_commitsSincePush; - bool m_stateUnknown; - bool m_hgIsOK; - bool m_needNewLog; - - bool m_firstStart; - - bool m_showAllFiles; - - //Actions enabled flags - bool m_remoteRepoActionsEnabled; - bool m_localRepoActionsEnabled; - - QString m_myDirPath; - - // File menu actions - QAction *m_openAct; - QAction *m_changeRemoteRepoAct; - QAction *m_settingsAct; - QAction *m_exitAct; - - // Repo actions - QAction *m_hgIncomingAct; - QAction *m_hgPushAct; - QAction *m_hgPullAct; - QAction *m_hgRefreshAct; - QAction *m_hgFolderDiffAct; - QAction *m_hgChgSetDiffAct; - QAction *m_hgRevertAct; - QAction *m_hgAddAct; - QAction *m_hgRemoveAct; - QAction *m_hgUpdateAct; - QAction *m_hgCommitAct; - QAction *m_hgMergeAct; - QAction *m_hgUpdateToRevAct; - QAction *m_hgAnnotateAct; - QAction *m_hgIgnoreAct; - QAction *m_hgServeAct; - - // Menus - QMenu *m_fileMenu; - QMenu *m_recentMenu; - QMenu *m_advancedMenu; - QMenu *m_helpMenu; - - // Help menu actions - QAction *m_aboutAct; - - QToolBar *m_fileToolBar; - QToolBar *m_repoToolBar; - QToolBar *m_workFolderToolBar; - - HgRunner *m_runner; - - bool m_shouldHgStat; - - QString getDiffBinaryName(); - QString getMergeBinaryName(); - QString getEditorBinaryName(); - - QFileSystemWatcher *m_fsWatcher; - QTimer *m_fsWatcherGeneralTimer; - QTimer *m_fsWatcherRestoreTimer; - bool m_fsWatcherSuspended; - - QString m_lastStatOutput; - QStringList m_lastRevertedFiles; - - bool m_justMerged; - QString m_mergeTargetRevision; - QString m_mergeCommitComment; -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 moreinformationdialog.cpp --- a/moreinformationdialog.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "moreinformationdialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -MoreInformationDialog::MoreInformationDialog(QString title, - QString head, - QString text, - QString more, - QWidget *parent) : - QDialog(parent) -{ - setWindowTitle(title); - - QGridLayout *layout = new QGridLayout; - layout->setSpacing(10); - setLayout(layout); - - m_iconLabel = new QLabel; - layout->addWidget(m_iconLabel, 0, 0, 2, 1, Qt::AlignTop); - - QLabel *headLabel = new QLabel(QString("

%1

").arg(head)); - layout->addWidget(headLabel, 0, 1); - - QLabel *textLabel = new QLabel(text); - textLabel->setTextFormat(Qt::RichText); - textLabel->setWordWrap(true); - layout->addWidget(textLabel, 1, 1, Qt::AlignTop); - - QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); - connect(bb, SIGNAL(accepted()), this, SLOT(accept())); - layout->addWidget(bb, 2, 0, 1, 2); - - m_moreButton = bb->addButton(tr("More Details..."), - QDialogButtonBox::ActionRole); - m_moreButton->setAutoDefault(false); - m_moreButton->setDefault(false); - - connect(m_moreButton, SIGNAL(clicked()), this, SLOT(moreClicked())); - - bb->button(QDialogButtonBox::Ok)->setDefault(true); - - m_moreText = new QTextEdit(); - m_moreText->setAcceptRichText(false); - m_moreText->document()->setPlainText(more); - m_moreText->setMinimumWidth(360); - m_moreText->setReadOnly(true); - m_moreText->setLineWrapMode(QTextEdit::NoWrap); - - QFont font("Monospace"); - font.setStyleHint(QFont::TypeWriter); - m_moreText->setFont(font); - - layout->addWidget(m_moreText, 3, 0, 1, 2); - - m_moreText->hide(); - if (more == "") m_moreButton->hide(); - - layout->setRowStretch(1, 20); - layout->setColumnStretch(1, 20); - setMinimumWidth(400); -} - -MoreInformationDialog::~MoreInformationDialog() -{ -} - -void -MoreInformationDialog::moreClicked() -{ - if (m_moreText->isVisible()) { - m_moreText->hide(); - m_moreButton->setText(tr("Show Details...")); - } else { - m_moreText->show(); - m_moreButton->setText(tr("Hide Details...")); - } - adjustSize(); -} - -void -MoreInformationDialog::setIcon(QIcon icon) -{ - QStyle *style = qApp->style(); - int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this); - m_iconLabel->setPixmap(icon.pixmap(iconSize, iconSize)); -} - -void -MoreInformationDialog::critical(QWidget *parent, QString title, QString head, - QString text, QString more) -{ - MoreInformationDialog d(title, head, text, more, parent); - QStyle *style = qApp->style(); - d.setIcon(style->standardIcon(QStyle::SP_MessageBoxCritical, 0, &d)); - d.exec(); -} - -void -MoreInformationDialog::information(QWidget *parent, QString title, QString head, - QString text, QString more) -{ - MoreInformationDialog d(title, head, text, more, parent); - QStyle *style = qApp->style(); - d.setIcon(style->standardIcon(QStyle::SP_MessageBoxInformation, 0, &d)); - d.exec(); -} - -void -MoreInformationDialog::warning(QWidget *parent, QString title, QString head, - QString text, QString more) -{ - MoreInformationDialog d(title, head, text, more, parent); - QStyle *style = qApp->style(); - d.setIcon(style->standardIcon(QStyle::SP_MessageBoxWarning, 0, &d)); - d.exec(); -} - diff -r 19cce6d2c470 -r b9c153e00e84 moreinformationdialog.h --- a/moreinformationdialog.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on hgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef MORE_INFORMATION_DIALOG_H -#define MORE_INFORMATION_DIALOG_H - -#include -#include - -class QLabel; -class QTextEdit; -class QPushButton; - -/** - * Provide methods like the QMessageBox static methods, to call up - * dialogs with "More information" buttons in them. QMessageBox does - * have an optional additional-details field, but it doesn't behave - * quite as we'd like with regard to layout - */ - -class MoreInformationDialog : public QDialog -{ - Q_OBJECT - -public: - MoreInformationDialog(QString title, - QString head, - QString text, - QString more, - QWidget *parent = 0); - - ~MoreInformationDialog(); - - void setIcon(QIcon); - - static void critical(QWidget *parent, QString title, QString head, QString text, QString more); - static void information(QWidget *parent, QString title, QString head, QString text, QString more); - static void warning(QWidget *parent, QString title, QString head, QString text, QString more); - -private slots: - void moreClicked(); - -private: - QLabel *m_iconLabel; - QPushButton *m_moreButton; - QTextEdit *m_moreText; -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 multichoicedialog.cpp --- a/multichoicedialog.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,345 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "multichoicedialog.h" - -#include "selectablelabel.h" - -#include "debug.h" - -#include -#include -#include -#include -#include -#include -#include - -MultiChoiceDialog::MultiChoiceDialog(QString title, QString heading, QWidget *parent) : - QDialog(parent) -{ - setModal(true); - setWindowTitle(title); - - QGridLayout *outer = new QGridLayout; - setLayout(outer); - - outer->addWidget(new QLabel(heading), 0, 0, 1, 3); - - QWidget *innerWidget = new QWidget; - outer->addWidget(innerWidget, 1, 0, 1, 3); - m_choiceLayout = new QHBoxLayout; - innerWidget->setLayout(m_choiceLayout); - - m_descriptionLabel = new QLabel; - outer->addWidget(m_descriptionLabel, 2, 0, 1, 3); - - QFont f = m_descriptionLabel->font(); - f.setPointSize(f.pointSize() * 0.95); - m_descriptionLabel->setFont(f); - - m_urlLabel = new QLabel(tr("&URL:")); - outer->addWidget(m_urlLabel, 3, 0); - - m_urlCombo = new QComboBox(); - m_urlCombo->setEditable(true); - m_urlLabel->setBuddy(m_urlCombo); - connect(m_urlCombo, SIGNAL(editTextChanged(const QString &)), - this, SLOT(urlChanged(const QString &))); - outer->addWidget(m_urlCombo, 3, 1, 1, 2); - - m_fileLabel = new QLabel(tr("&File:")); - outer->addWidget(m_fileLabel, 4, 0); - - m_fileCombo = new QComboBox(); - m_fileCombo->setEditable(true); - m_fileLabel->setBuddy(m_fileCombo); - connect(m_fileCombo, SIGNAL(editTextChanged(const QString &)), - this, SLOT(fileChanged(const QString &))); - outer->addWidget(m_fileCombo, 4, 1); - outer->setColumnStretch(1, 20); - - m_browseButton = new QPushButton(tr("Browse...")); - outer->addWidget(m_browseButton, 4, 2); - connect(m_browseButton, SIGNAL(clicked()), this, SLOT(browse())); - - QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok | - QDialogButtonBox::Cancel); - connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); - connect(bbox, SIGNAL(rejected()), this, SLOT(reject())); - outer->addWidget(bbox, 5, 0, 1, 3); - - m_okButton = bbox->button(QDialogButtonBox::Ok); - updateOkButton(); - - setMinimumWidth(480); -} - -QString -MultiChoiceDialog::getCurrentChoice() -{ - return m_currentChoice; -} - -void -MultiChoiceDialog::setCurrentChoice(QString c) -{ - m_currentChoice = c; - choiceChanged(); -} - -QString -MultiChoiceDialog::getArgument() -{ - if (m_argTypes[m_currentChoice] == UrlArg || - m_argTypes[m_currentChoice] == UrlToDirectoryArg) { - return m_urlCombo->currentText(); - } else { - return m_fileCombo->currentText(); - } -} - -QString -MultiChoiceDialog::getAdditionalArgument() -{ - if (m_argTypes[m_currentChoice] == UrlToDirectoryArg) { - return m_fileCombo->currentText(); - } else { - return ""; - } -} - -void -MultiChoiceDialog::addRecentArgument(QString id, QString arg, - bool additionalArgument) -{ - if (additionalArgument) { - RecentFiles(QString("Recent-%1-add").arg(id)).addFile(arg); - } else { - RecentFiles(QString("Recent-%1").arg(id)).addFile(arg); - } -} - -void -MultiChoiceDialog::addChoice(QString id, QString text, - QString description, ArgType arg) -{ - bool first = (m_texts.empty()); - - m_texts[id] = text; - m_descriptions[id] = description; - m_argTypes[id] = arg; - - if (arg != NoArg) { - m_recentFiles[id] = QSharedPointer - (new RecentFiles(QString("Recent-%1").arg(id))); - } - - SelectableLabel *cb = new SelectableLabel; - cb->setSelectedText(text); - cb->setUnselectedText(text); - cb->setMaximumWidth(270); - - m_choiceLayout->addWidget(cb); - m_choiceButtons[cb] = id; - - connect(cb, SIGNAL(selectionChanged()), this, SLOT(choiceChanged())); - - if (first) { - m_currentChoice = id; - choiceChanged(); - } -} - -QString -MultiChoiceDialog::getDefaultPath() const -{ - QDir home(QDir::home()); - QDir dflt; - - dflt = QDir(home.filePath(tr("My Documents"))); - DEBUG << "testing " << dflt << endl; - if (dflt.exists()) return dflt.canonicalPath(); - - dflt = QDir(home.filePath(tr("Documents"))); - DEBUG << "testing " << dflt << endl; - if (dflt.exists()) return dflt.canonicalPath(); - - DEBUG << "all failed, returning " << home << endl; - return home.canonicalPath(); -} - -void -MultiChoiceDialog::browse() -{ - QString origin = getArgument(); - - if (origin == "") { - origin = getDefaultPath(); - } - - QString path = origin; - - if (m_argTypes[m_currentChoice] == DirectoryArg || - m_argTypes[m_currentChoice] == UrlToDirectoryArg) { - - path = QFileDialog::getExistingDirectory - (this, tr("Open Directory"), origin, - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - if (path != QString()) { - m_fileCombo->lineEdit()->setText(path + QDir::separator()); - } - - } else { - - path = QFileDialog::getOpenFileName - (this, tr("Open File"), origin); - if (path != QString()) { - m_fileCombo->lineEdit()->setText(path); - } - } -} - -void -MultiChoiceDialog::urlChanged(const QString &s) -{ - updateOkButton(); -} - -void -MultiChoiceDialog::fileChanged(const QString &s) -{ - updateOkButton(); -} - -void -MultiChoiceDialog::updateOkButton() -{ -/* This doesn't work well - if (m_argTypes[m_currentChoice] != UrlToDirectoryArg) { - return; - } - QDir dirPath(m_fileCombo->currentText()); - if (!dirPath.exists()) { - if (!dirPath.cdUp()) return; - } - QString url = m_urlCombo->currentText(); - if (QRegExp("^\\w+://").indexIn(url) < 0) { - return; - } - QString urlDirName = url; - urlDirName.replace(QRegExp("^.*\\//.*\\/"), ""); - if (urlDirName == "" || urlDirName == url) { - return; - } - m_fileCombo->lineEdit()->setText(dirPath.filePath(urlDirName)); -*/ - if (m_argTypes[m_currentChoice] == UrlToDirectoryArg) { - m_okButton->setEnabled(getArgument() != "" && - getAdditionalArgument() != ""); - } else { - m_okButton->setEnabled(getArgument() != ""); - } -} - -void -MultiChoiceDialog::choiceChanged() -{ - DEBUG << "choiceChanged" << endl; - - if (m_choiceButtons.empty()) return; - - QString id = ""; - - QObject *s = sender(); - QWidget *w = qobject_cast(s); - if (w) id = m_choiceButtons[w]; - - if (id == m_currentChoice) return; - if (id == "") { - // Happens when this is called for the very first time, when - // m_currentChoice has been set to the intended ID but no - // button has actually been pressed -- then we need to - // initialise - id = m_currentChoice; - } - - m_currentChoice = id; - - foreach (QWidget *cw, m_choiceButtons.keys()) { - SelectableLabel *sl = qobject_cast(cw); - if (sl) { - sl->setSelected(m_choiceButtons[cw] == id); - } - } - - m_descriptionLabel->setText(m_descriptions[id]); - - m_fileLabel->hide(); - m_fileCombo->hide(); - m_browseButton->hide(); - m_urlLabel->hide(); - m_urlCombo->hide(); - - QSharedPointer rf = m_recentFiles[id]; - m_fileCombo->clear(); - m_urlCombo->clear(); - - switch (m_argTypes[id]) { - - case NoArg: - break; - - case FileArg: - m_fileLabel->setText(tr("&File:")); - m_fileLabel->show(); - m_fileCombo->show(); - m_fileCombo->addItems(rf->getRecent()); - m_browseButton->show(); - break; - - case DirectoryArg: - m_fileLabel->setText(tr("&Folder:")); - m_fileLabel->show(); - m_fileCombo->show(); - m_fileCombo->addItems(rf->getRecent()); - m_browseButton->show(); - break; - - case UrlArg: - m_urlLabel->show(); - m_urlCombo->show(); - m_urlCombo->addItems(rf->getRecent()); - break; - - case UrlToDirectoryArg: - m_urlLabel->show(); - m_urlCombo->show(); - m_urlCombo->addItems(rf->getRecent()); - m_fileLabel->setText(tr("&Folder:")); - m_fileLabel->show(); - m_fileCombo->show(); - m_fileCombo->lineEdit()->setText(getDefaultPath()); - m_browseButton->show(); - break; - } - - updateOkButton(); - adjustSize(); -} - - diff -r 19cce6d2c470 -r b9c153e00e84 multichoicedialog.h --- a/multichoicedialog.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef MULTICHOICEDIALOG_H -#define MULTICHOICEDIALOG_H - -#include "recentfiles.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class MultiChoiceDialog : public QDialog -{ - Q_OBJECT -public: - explicit MultiChoiceDialog(QString title, QString heading, - QWidget *parent = 0); - - enum ArgType { - NoArg, - FileArg, - DirectoryArg, - UrlArg, - UrlToDirectoryArg - }; - - void addChoice(QString identifier, QString text, - QString description, ArgType arg); - - void setCurrentChoice(QString); - QString getCurrentChoice(); - QString getArgument(); - QString getAdditionalArgument(); - - static void addRecentArgument(QString identifier, QString name, - bool additionalArgument = false); - -private slots: - void choiceChanged(); - void urlChanged(const QString &); - void fileChanged(const QString &); - void browse(); - -private: - void updateOkButton(); - - QMap m_texts; - QMap m_descriptions; - QMap m_argTypes; - QMap > m_recentFiles; - - QString m_currentChoice; - QMap m_choiceButtons; - - QHBoxLayout *m_choiceLayout; - QLabel *m_descriptionLabel; - QLabel *m_fileLabel; - QComboBox *m_fileCombo; - QAbstractButton *m_browseButton; - QLabel *m_urlLabel; - QComboBox *m_urlCombo; - QAbstractButton *m_okButton; - - QString getDefaultPath() const; -}; - -#endif // MULTICHOICEDIALOG_H diff -r 19cce6d2c470 -r b9c153e00e84 panned.cpp --- a/panned.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,249 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "panned.h" -#include "debug.h" - -#include -#include -#include - -#include - -#include - -Panned::Panned() : - m_dragging(false) -{ - m_dragTimer = new QTimer(this); - m_dragTimerMs = 50; - connect(m_dragTimer, SIGNAL(timeout()), this, SLOT(dragTimerTimeout())); - setRenderHints(QPainter::Antialiasing | - QPainter::TextAntialiasing); -} - -void -Panned::resizeEvent(QResizeEvent *ev) -{ - DEBUG << "Panned::resizeEvent()" << endl; - - QPointF nearpt = mapToScene(0, 0); - QPointF farpt = mapToScene(width(), height()); - QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y()); - QRectF pr(nearpt, sz); - - QGraphicsView::resizeEvent(ev); - - if (pr != m_pannedRect) { - DEBUG << "Panned: setting panned rect to " << pr << endl; - m_pannedRect = pr; - centerOn(pr.center()); - emit pannedRectChanged(pr); - } -} - -void -Panned::setScene(QGraphicsScene *s) -{ - if (!scene()) { - QGraphicsView::setScene(s); - return; - } - - QPointF nearpt = mapToScene(0, 0); - QPointF farpt = mapToScene(width(), height()); - QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y()); - QRectF pr(nearpt, sz); - - QGraphicsView::setScene(s); - - DEBUG << "Panned::setScene: pr = " << pr << ", sceneRect = " << sceneRect() << endl; - - if (scene() && sceneRect().intersects(pr)) { - DEBUG << "Panned::setScene: restoring old rect " << pr << endl; - m_pannedRect = pr; - centerOn(pr.center()); - emit pannedRectChanged(pr); - } -} - -void -Panned::paintEvent(QPaintEvent *e) -{ - QGraphicsView::paintEvent(e); -} - -void -Panned::drawForeground(QPainter *paint, const QRectF &) -{ - QPointF nearpt = mapToScene(0, 0); - QPointF farpt = mapToScene(width(), height()); - QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y()); - QRectF pr(nearpt, sz); - - if (pr != m_pannedRect) { - DEBUG << "Panned::drawForeground: visible rect " << pr << " differs from panned rect " << m_pannedRect << ", updating panned rect" <modifiers() & Qt::ControlModifier) { - int d = ev->delta(); - if (d > 0) { - while (d > 0) { - zoomOut(); - d -= 120; - } - } else { - while (d < 0) { - zoomIn(); - d += 120; - } - } - } else { - emit wheelEventReceived(ev); - QGraphicsView::wheelEvent(ev); - } -} - -void -Panned::slotEmulateWheelEvent(QWheelEvent *ev) -{ - QGraphicsView::wheelEvent(ev); -} - -void -Panned::mousePressEvent(QMouseEvent *ev) -{ - if (dragMode() != QGraphicsView::ScrollHandDrag || - ev->button() != Qt::LeftButton) { - QGraphicsView::mousePressEvent(ev); - return; - } - - DEBUG << "Panned::mousePressEvent: have left button in drag mode" << endl; - - setDragMode(QGraphicsView::NoDrag); - QGraphicsView::mousePressEvent(ev); - setDragMode(QGraphicsView::ScrollHandDrag); - - if (!ev->isAccepted()) { - ev->accept(); - m_dragging = true; - m_lastDragPos = ev->pos(); - m_lastOrigin = QPoint(horizontalScrollBar()->value(), - verticalScrollBar()->value()); - m_velocity = QPointF(0, 0); - m_dragTimer->start(m_dragTimerMs); - } - -} - -void -Panned::mouseMoveEvent(QMouseEvent *ev) -{ - if (!m_dragging) { - QGraphicsView::mouseMoveEvent(ev); - return; - } - DEBUG << "Panned::mouseMoveEvent: dragging" << endl; - ev->accept(); - QScrollBar *hBar = horizontalScrollBar(); - QScrollBar *vBar = verticalScrollBar(); - QPoint delta = ev->pos() - m_lastDragPos; - hBar->setValue(hBar->value() + (isRightToLeft() ? delta.x() : -delta.x())); - vBar->setValue(vBar->value() - delta.y()); - m_lastDragPos = ev->pos(); -} - -void -Panned::mouseReleaseEvent(QMouseEvent *ev) -{ - if (!m_dragging) { - QGraphicsView::mouseReleaseEvent(ev); - return; - } - DEBUG << "Panned::mouseReleaseEvent: dragging" << endl; - ev->accept(); - m_dragging = false; -} - -void -Panned::dragTimerTimeout() -{ - QPoint origin = QPoint(horizontalScrollBar()->value(), - verticalScrollBar()->value()); - if (m_dragging) { - m_velocity = QPointF - (float(origin.x() - m_lastOrigin.x()) / m_dragTimerMs, - float(origin.y() - m_lastOrigin.y()) / m_dragTimerMs); - m_lastOrigin = origin; - DEBUG << "Panned::dragTimerTimeout: velocity = " << m_velocity << endl; - } else { - if (origin == m_lastOrigin) { - m_dragTimer->stop(); - } - float x = m_velocity.x(), y = m_velocity.y(); - if (fabsf(x) > 1.0/m_dragTimerMs) x = x * 0.9f; - else x = 0.f; - if (fabsf(y) > 1.0/m_dragTimerMs) y = y * 0.9f; - else y = 0.f; - m_velocity = QPointF(x, y); - DEBUG << "Panned::dragTimerTimeout: velocity adjusted to " << m_velocity << endl; - m_lastOrigin = origin; - //!!! need to store origin in floats - horizontalScrollBar()->setValue(m_lastOrigin.x() + - m_velocity.x() * m_dragTimerMs); - verticalScrollBar()->setValue(m_lastOrigin.y() + - m_velocity.y() * m_dragTimerMs); - } -} - -void -Panned::leaveEvent(QEvent *) -{ - emit mouseLeaves(); -} diff -r 19cce6d2c470 -r b9c153e00e84 panned.h --- a/panned.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef _PANNED_H_ -#define _PANNED_H_ - -#include - -class QWheelEvent; -class QEvent; -class QTimer; - -class Panned : public QGraphicsView -{ - Q_OBJECT - -public: - Panned(); - virtual ~Panned() { } - - virtual void setScene(QGraphicsScene *s); - -signals: - void pannedRectChanged(QRectF); - void wheelEventReceived(QWheelEvent *); - void pannedContentsScrolled(); - void mouseLeaves(); - -public slots: - void slotSetPannedRect(QRectF); - void slotEmulateWheelEvent(QWheelEvent *ev); - - void zoomIn(); - void zoomOut(); - -private slots: - void dragTimerTimeout(); - -protected: - QRectF m_pannedRect; - - QPoint m_lastDragPos; - QPoint m_lastOrigin; - QPointF m_velocity; - bool m_dragging; - int m_dragTimerMs; - QTimer *m_dragTimer; - - virtual void mousePressEvent(QMouseEvent *); - virtual void mouseMoveEvent(QMouseEvent *); - virtual void mouseReleaseEvent(QMouseEvent *); - - virtual void paintEvent(QPaintEvent *); - virtual void resizeEvent(QResizeEvent *); - virtual void drawForeground(QPainter *, const QRectF &); - virtual void wheelEvent(QWheelEvent *); - virtual void leaveEvent(QEvent *); -}; - -#endif - diff -r 19cce6d2c470 -r b9c153e00e84 panner.cpp --- a/panner.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,285 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "panner.h" -#include "panned.h" -#include "debug.h" - -#include -#include -#include - -#include - -class PannerScene : public QGraphicsScene -{ -public: - friend class Panner; -}; - -Panner::Panner() : - m_clicked(false) -{ - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setOptimizationFlags(QGraphicsView::DontSavePainterState | - QGraphicsView::IndirectPainting); - setMouseTracking(true); - setInteractive(false); -} - -void -Panner::fit(QRectF r) -{ - Qt::AspectRatioMode m = Qt::IgnoreAspectRatio; - if (height() > width()) { - // Our panner is vertical; if the source is not tall, - // don't stretch it to fit in the height, it'd look weird - if (r.height() < height() * 2) { - m = Qt::KeepAspectRatio; - } - } else { - // Similarly, but horizontal - if (r.width() < width() * 2) { - m = Qt::KeepAspectRatio; - } - } - DEBUG << "Panner: fit mode " << m << endl; - fitInView(r, m); -} - -void -Panner::setScene(QGraphicsScene *s) -{ - if (scene()) { - disconnect(scene(), SIGNAL(changed(const QList &)), - this, SLOT(slotSceneChanged(const QList &))); - disconnect(scene(), SIGNAL(sceneRectChanged(const QRectF &)), - this, SLOT(slotSceneRectChanged(const QRectF &))); - } - QGraphicsView::setScene(s); - m_cache = QPixmap(); - if (scene()) { - QRectF r = sceneRect(); - DEBUG << "scene rect: " << r << ", my rect " << rect() << endl; - fit(r); - connect(scene(), SIGNAL(changed(const QList &)), - this, SLOT(slotSceneChanged(const QList &))); - connect(scene(), SIGNAL(sceneRectChanged(const QRectF &)), - this, SLOT(slotSceneRectChanged(const QRectF &))); - } -} - -void -Panner::connectToPanned(Panned *p) -{ - connect(p, SIGNAL(pannedRectChanged(QRectF)), - this, SLOT(slotSetPannedRect(QRectF))); - - connect(this, SIGNAL(pannedRectChanged(QRectF)), - p, SLOT(slotSetPannedRect(QRectF))); - - connect(this, SIGNAL(zoomIn()), - p, SLOT(zoomIn())); - - connect(this, SIGNAL(zoomOut()), - p, SLOT(zoomOut())); -} - -void -Panner::slotSetPannedRect(QRectF rect) -{ - m_pannedRect = rect; - viewport()->update(); -} - -void -Panner::resizeEvent(QResizeEvent *) -{ - DEBUG << "Panner::resizeEvent" << endl; - if (scene()) fit(sceneRect()); - m_cache = QPixmap(); -} - -void -Panner::slotSceneRectChanged(const QRectF &newRect) -{ - DEBUG << "Panner::slotSceneRectChanged" << endl; - if (!scene()) return; // spurious - fit(newRect); - m_cache = QPixmap(); - viewport()->update(); -} - -void -Panner::slotSceneChanged(const QList &) -{ - DEBUG << "Panner::slotSceneChanged" << endl; - if (!scene()) return; // spurious - m_cache = QPixmap(); - viewport()->update(); -} - -void -Panner::paintEvent(QPaintEvent *e) -{ - QPaintEvent *e2 = new QPaintEvent(e->region().boundingRect()); - QGraphicsView::paintEvent(e2); - - QPainter paint; - paint.begin(viewport()); - paint.setClipRegion(e->region()); - - QPainterPath path; - path.addRect(rect()); - path.addPolygon(mapFromScene(m_pannedRect)); - - QColor c(QColor::fromHsv(211, 194, 238)); - c.setAlpha(80); - paint.setPen(Qt::NoPen); - paint.setBrush(c); - paint.drawPath(path); - - paint.setBrush(Qt::NoBrush); - paint.setPen(QPen(QColor::fromHsv(211, 194, 238), 0)); - paint.drawConvexPolygon(mapFromScene(m_pannedRect)); - - paint.end(); - - emit pannerChanged(m_pannedRect); -} - -void -Panner::updateScene(const QList &rects) -{ - DEBUG << "Panner::updateScene" << endl; -// if (!m_cache.isNull()) m_cache = QPixmap(); - QGraphicsView::updateScene(rects); -} - -void -Panner::drawItems(QPainter *painter, int numItems, - QGraphicsItem *items[], - const QStyleOptionGraphicsItem options[]) -{ - if (m_cache.size() != viewport()->size()) { - - DEBUG << "Panner: cache size " << m_cache.size() << " != viewport size " << viewport()->size() << ": recreating cache" << endl; - - QGraphicsScene *s = scene(); - if (!s) return; - PannerScene *ps = static_cast(s); - - m_cache = QPixmap(viewport()->size()); - m_cache.fill(Qt::transparent); - QPainter cachePainter; - cachePainter.begin(&m_cache); - cachePainter.setTransform(viewportTransform()); - ps->drawItems(&cachePainter, numItems, items, options); - cachePainter.end(); - - DEBUG << "done" << endl; - } - - painter->save(); - painter->setTransform(QTransform()); - painter->drawPixmap(0, 0, m_cache); - painter->restore(); -} - -void -Panner::mousePressEvent(QMouseEvent *e) -{ - if (e->button() != Qt::LeftButton) { - QGraphicsView::mouseDoubleClickEvent(e); - return; - } - m_clicked = true; - m_clickedRect = m_pannedRect; - m_clickedPoint = e->pos(); -} - -void -Panner::mouseDoubleClickEvent(QMouseEvent *e) -{ - if (e->button() != Qt::LeftButton) { - QGraphicsView::mouseDoubleClickEvent(e); - return; - } - - moveTo(e->pos()); -} - -void -Panner::mouseMoveEvent(QMouseEvent *e) -{ - if (!m_clicked) return; - QPointF cp = mapToScene(m_clickedPoint); - QPointF mp = mapToScene(e->pos()); - QPointF delta = mp - cp; - QRectF nr = m_clickedRect; - nr.translate(delta); - m_pannedRect = nr; - emit pannedRectChanged(m_pannedRect); - viewport()->update(); -} - -void -Panner::mouseReleaseEvent(QMouseEvent *e) -{ - if (e->button() != Qt::LeftButton) { - QGraphicsView::mouseDoubleClickEvent(e); - return; - } - - if (m_clicked) { - mouseMoveEvent(e); - } - - m_clicked = false; - viewport()->update(); -} - -void -Panner::wheelEvent(QWheelEvent *e) -{ - int d = e->delta(); - if (d > 0) { - while (d > 0) { - emit zoomOut(); - d -= 120; - } - } else { - while (d < 0) { - emit zoomIn(); - d += 120; - } - } -} - -void -Panner::moveTo(QPoint p) -{ - QPointF sp = mapToScene(p); - QRectF nr = m_pannedRect; - double d = sp.x() - nr.center().x(); - nr.translate(d, 0); - slotSetPannedRect(nr); - emit pannedRectChanged(m_pannedRect); - viewport()->update(); -} - diff -r 19cce6d2c470 -r b9c153e00e84 panner.h --- a/panner.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef _PANNER_H_ -#define _PANNER_H_ - -#include - -class Panned; - -class Panner : public QGraphicsView -{ - Q_OBJECT - -public: - Panner(); - virtual ~Panner() { } - - virtual void setScene(QGraphicsScene *); - - void connectToPanned(Panned *p); - -signals: - void pannedRectChanged(QRectF); - void pannerChanged(QRectF); - void zoomIn(); - void zoomOut(); - -public slots: - void slotSetPannedRect(QRectF); - -protected slots: - void slotSceneRectChanged(const QRectF &); - void slotSceneChanged(const QList &); - -protected: - QRectF m_pannedRect; - - void moveTo(QPoint); - - void fit(QRectF); - - virtual void paintEvent(QPaintEvent *); - virtual void mousePressEvent(QMouseEvent *e); - virtual void mouseMoveEvent(QMouseEvent *e); - virtual void mouseReleaseEvent(QMouseEvent *e); - virtual void mouseDoubleClickEvent(QMouseEvent *e); - virtual void wheelEvent(QWheelEvent *e); - - virtual void resizeEvent(QResizeEvent *); - - virtual void updateScene(const QList &); - virtual void drawItems(QPainter *, int, QGraphicsItem *[], - const QStyleOptionGraphicsItem []); - - bool m_clicked; - QRectF m_clickedRect; - QPoint m_clickedPoint; - - QPixmap m_cache; -}; - -#endif - diff -r 19cce6d2c470 -r b9c153e00e84 recentfiles.cpp --- a/recentfiles.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "recentfiles.h" - -#include -#include -#include - -RecentFiles::RecentFiles(QString settingsGroup, size_t maxCount, - bool ignoreTemporaries) : - m_settingsGroup(settingsGroup), - m_maxCount(maxCount), - m_ignoreTemporaries(ignoreTemporaries) -{ - read(); -} - -RecentFiles::~RecentFiles() -{ - // nothing -} - -void -RecentFiles::read() -{ - m_names.clear(); - QSettings settings; - settings.beginGroup(m_settingsGroup); - - for (size_t i = 0; i < 100; ++i) { - QString key = QString("recent-%1").arg(i); - QString name = settings.value(key, "").toString(); - if (name == "") break; - if (i < m_maxCount) m_names.push_back(name); - else settings.setValue(key, ""); - } - - settings.endGroup(); -} - -void -RecentFiles::write() -{ - QSettings settings; - settings.beginGroup(m_settingsGroup); - - for (size_t i = 0; i < m_maxCount; ++i) { - QString key = QString("recent-%1").arg(i); - QString name = ""; - if (i < m_names.size()) name = m_names[i]; - settings.setValue(key, name); - } - - settings.endGroup(); -} - -void -RecentFiles::truncateAndWrite() -{ - while (m_names.size() > m_maxCount) { - m_names.pop_back(); - } - write(); -} - -QStringList -RecentFiles::getRecent() const -{ - QStringList names; - for (size_t i = 0; i < m_maxCount; ++i) { - if (i < m_names.size()) { - names.push_back(m_names[i]); - } - } - return names; -} - -void -RecentFiles::add(QString name) -{ - bool have = false; - for (size_t i = 0; i < m_names.size(); ++i) { - if (m_names[i] == name) { - have = true; - break; - } - } - - if (!have) { - m_names.push_front(name); - } else { - std::deque newnames; - newnames.push_back(name); - for (size_t i = 0; i < m_names.size(); ++i) { - if (m_names[i] == name) continue; - newnames.push_back(m_names[i]); - } - m_names = newnames; - } - - truncateAndWrite(); - emit recentChanged(); -} - -void -RecentFiles::addFile(QString name) -{ - static QRegExp schemeRE("^[a-zA-Z]{2,5}://"); - static QRegExp tempRE("[\\/][Tt]e?mp[\\/]"); - if (schemeRE.indexIn(name) == 0) { - add(name); - } else { - QString absPath = QFileInfo(name).absoluteFilePath(); - if (tempRE.indexIn(absPath) != -1) { - if (!m_ignoreTemporaries) { - add(absPath); - } - } else { - add(absPath); - } - } -} - - diff -r 19cce6d2c470 -r b9c153e00e84 recentfiles.h --- a/recentfiles.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef _RECENT_FILES_H_ -#define _RECENT_FILES_H_ - -#include -#include -#include -#include - -/** - * RecentFiles manages a list of the names of recently-used objects, - * saving and restoring that list via QSettings. The names do not - * actually have to refer to files. - */ - -class RecentFiles : public QObject -{ - Q_OBJECT - -public: - /** - * Construct a RecentFiles object that saves and restores in the - * given QSettings group and truncates when the given count of - * strings is reached. - */ - RecentFiles(QString settingsGroup = "RecentFiles", - size_t maxCount = 10, - bool ignoreTemporaries = true); - virtual ~RecentFiles(); - - QString getSettingsGroup() const { return m_settingsGroup; } - - int getMaxCount() const { return m_maxCount; } - - QStringList getRecent() const; - - /** - * Add a name that should be treated as a literal string. - */ - void add(QString name); - - /** - * Add a name that is known to be either a file path or a URL. If - * it looks like a URL, add it literally; otherwise treat it as a - * file path and canonicalise it appropriately. Also takes into - * account the preference for whether to include temporary files - * in the recent files menu: the file will not be added if the - * preference is set and the file appears to be a temporary one. - */ - void addFile(QString name); - -signals: - void recentChanged(); - -protected: - QString m_settingsGroup; - size_t m_maxCount; - bool m_ignoreTemporaries; - - std::deque m_names; - - void read(); - void write(); - void truncateAndWrite(); -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 repositorydialog.cpp --- a/repositorydialog.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "repositorydialog.h" - -RepositoryDialog::RepositoryDialog(QWidget *parent) : - QDialog(parent) -{ - setModal(true); - setWindowTitle(tr("Open Repository")); - - - - -} diff -r 19cce6d2c470 -r b9c153e00e84 repositorydialog.h --- a/repositorydialog.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef REPOSITORYDIALOG_H -#define REPOSITORYDIALOG_H - -#include - -class RepositoryDialog : public QDialog -{ - Q_OBJECT - -public: - RepositoryDialog(QWidget *parent = 0); - - - -}; - -#endif // REPOSITORYDIALOG_H diff -r 19cce6d2c470 -r b9c153e00e84 selectablelabel.cpp --- a/selectablelabel.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,141 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "selectablelabel.h" - -#include "debug.h" - -#include -#include - -SelectableLabel::SelectableLabel(QWidget *p) : - QLabel(p), - m_selected(false) -{ - setTextFormat(Qt::RichText); - setupStyle(); - setOpenExternalLinks(true); -} - -SelectableLabel::~SelectableLabel() -{ -} - -void -SelectableLabel::setUnselectedText(QString text) -{ - if (m_unselectedText == text) return; - m_unselectedText = text; - if (!m_selected) { - setText(m_unselectedText); - resize(sizeHint()); - } -} - -void -SelectableLabel::setSelectedText(QString text) -{ - if (m_selectedText == text) return; - m_selectedText = text; - if (m_selected) { - setText(m_selectedText); - resize(sizeHint()); - } -} - -void -SelectableLabel::setupStyle() -{ - QPalette palette = QApplication::palette(); - - setTextInteractionFlags(Qt::LinksAccessibleByKeyboard | - Qt::LinksAccessibleByMouse | - Qt::TextSelectableByMouse); - - if (m_selected) { - setStyleSheet - (QString("QLabel { background: %1; border: 1px solid %2; padding: 7px } ") - .arg(palette.light().color().name()) - .arg(palette.dark().color().name())); - } else { - setStyleSheet - (QString("QLabel { border: 0; padding: 7px } ")); - } -} - -void -SelectableLabel::setSelected(bool s) -{ - if (m_selected == s) return; - m_selected = s; - if (m_selected) { - setText(m_selectedText); - } else { - setText(m_unselectedText); - } - setupStyle(); - parentWidget()->resize(parentWidget()->sizeHint()); -} - -void -SelectableLabel::toggle() -{ - setSelected(!m_selected); -} - -void -SelectableLabel::mousePressEvent(QMouseEvent *e) -{ - m_swallowRelease = !m_selected; - setSelected(true); - QLabel::mousePressEvent(e); - emit selectionChanged(); -} - -void -SelectableLabel::mouseDoubleClickEvent(QMouseEvent *e) -{ - QLabel::mouseDoubleClickEvent(e); - emit doubleClicked(); -} - -void -SelectableLabel::mouseReleaseEvent(QMouseEvent *e) -{ - if (!m_swallowRelease) QLabel::mouseReleaseEvent(e); - m_swallowRelease = false; -} - -void -SelectableLabel::enterEvent(QEvent *) -{ -// std::cerr << "enterEvent" << std::endl; -// QPalette palette = QApplication::palette(); -// palette.setColor(QPalette::Window, Qt::gray); -// setStyleSheet("background: gray"); -// setPalette(palette); -} - -void -SelectableLabel::leaveEvent(QEvent *) -{ -// std::cerr << "leaveEvent" << std::endl; -// setStyleSheet("background: white"); -// QPalette palette = QApplication::palette(); -// palette.setColor(QPalette::Window, Qt::gray); -// setPalette(palette); -} diff -r 19cce6d2c470 -r b9c153e00e84 selectablelabel.h --- a/selectablelabel.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef _SELECTABLE_LABEL_H_ -#define _SELECTABLE_LABEL_H_ - -#include - -class SelectableLabel : public QLabel -{ - Q_OBJECT - -public: - SelectableLabel(QWidget *parent = 0); - virtual ~SelectableLabel(); - - void setSelectedText(QString); - void setUnselectedText(QString); - - bool isSelected() const { return m_selected; } - -signals: - void selectionChanged(); - void doubleClicked(); - -public slots: - void setSelected(bool); - void toggle(); - -protected: - virtual void mousePressEvent(QMouseEvent *e); - virtual void mouseReleaseEvent(QMouseEvent *e); - virtual void mouseDoubleClickEvent(QMouseEvent *e); - virtual void enterEvent(QEvent *); - virtual void leaveEvent(QEvent *); - void setupStyle(); - QString m_selectedText; - QString m_unselectedText; - bool m_selected; - bool m_swallowRelease; -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 settingsdialog.cpp --- a/settingsdialog.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,469 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "settingsdialog.h" -#include "common.h" -#include "debug.h" - -#include -#include -#include -#include -#include -#include -#include - -QString -SettingsDialog::m_installPath; - -SettingsDialog::SettingsDialog(QWidget *parent) : - QDialog(parent), - m_presentationChanged(false) -{ - setModal(true); - setWindowTitle(tr("Settings")); - - QGridLayout *mainLayout = new QGridLayout; - setLayout(mainLayout); - - - - QGroupBox *meBox = new QGroupBox(tr("User details")); - mainLayout->addWidget(meBox, 0, 0); - QGridLayout *meLayout = new QGridLayout; - meBox->setLayout(meLayout); - - int row = 0; - - meLayout->addWidget(new QLabel(tr("Name:")), row, 0); - - m_nameEdit = new QLineEdit(); - meLayout->addWidget(m_nameEdit, row++, 1); - - meLayout->addWidget(new QLabel(tr("Email address:")), row, 0); - - m_emailEdit = new QLineEdit(); - meLayout->addWidget(m_emailEdit, row++, 1); - - - - QGroupBox *lookBox = new QGroupBox(tr("Presentation")); - mainLayout->addWidget(lookBox, 1, 0); - QGridLayout *lookLayout = new QGridLayout; - lookBox->setLayout(lookLayout); - - row = 0; - - m_showIconLabels = new QCheckBox(tr("Show labels on toolbar icons")); - lookLayout->addWidget(m_showIconLabels, row++, 0, 1, 2); - - m_showExtraText = new QCheckBox(tr("Show long descriptions for file status headings")); - lookLayout->addWidget(m_showExtraText, row++, 0, 1, 2); - -#ifdef NOT_IMPLEMENTED_YET - lookLayout->addWidget(new QLabel(tr("Place the work and history views")), row, 0); - m_workHistoryArrangement = new QComboBox(); - m_workHistoryArrangement->addItem(tr("In separate tabs")); - m_workHistoryArrangement->addItem(tr("Side-by-side in a single pane")); - lookLayout->addWidget(m_workHistoryArrangement, row++, 1, Qt::AlignLeft); - lookLayout->setColumnStretch(1, 20); -#endif - - lookLayout->addWidget(new QLabel(tr("Label the history timeline with")), row, 0); - m_dateFormat = new QComboBox(); - m_dateFormat->addItem(tr("Ages, for example \"5 weeks ago\"")); - m_dateFormat->addItem(tr("Dates, for example \"2010-06-23\"")); - lookLayout->addWidget(m_dateFormat, row++, 1, Qt::AlignLeft); - lookLayout->setColumnStretch(1, 20); - - - QGroupBox *pathsBox = new QGroupBox(tr("System application locations")); - mainLayout->addWidget(pathsBox, 2, 0); - QGridLayout *pathsLayout = new QGridLayout; - pathsBox->setLayout(pathsLayout); - - row = 0; - - pathsLayout->addWidget(new QLabel(tr("Mercurial (hg) program:")), row, 0); - - m_hgPathLabel = new QLineEdit(); - pathsLayout->addWidget(m_hgPathLabel, row, 2); - - QPushButton *browse = new QPushButton(tr("Browse...")); - pathsLayout->addWidget(browse, row++, 1); - connect(browse, SIGNAL(clicked()), this, SLOT(hgPathBrowse())); - - pathsLayout->addWidget(new QLabel(tr("External diff program:")), row, 0); - - m_diffPathLabel = new QLineEdit(); - pathsLayout->addWidget(m_diffPathLabel, row, 2); - - browse = new QPushButton(tr("Browse...")); - pathsLayout->addWidget(browse, row++, 1); - connect(browse, SIGNAL(clicked()), this, SLOT(diffPathBrowse())); - - pathsLayout->addWidget(new QLabel(tr("External file-merge program:")), row, 0); - - m_mergePathLabel = new QLineEdit(); - pathsLayout->addWidget(m_mergePathLabel, row, 2); - - browse = new QPushButton(tr("Browse...")); - pathsLayout->addWidget(browse, row++, 1); - connect(browse, SIGNAL(clicked()), this, SLOT(mergePathBrowse())); - - pathsLayout->addWidget(new QLabel(tr("External text editor:")), row, 0); - - m_editPathLabel = new QLineEdit(); - pathsLayout->addWidget(m_editPathLabel, row, 2); - - browse = new QPushButton(tr("Browse...")); - pathsLayout->addWidget(browse, row++, 1); - connect(browse, SIGNAL(clicked()), this, SLOT(editPathBrowse())); - - pathsLayout->addWidget(new QLabel(tr("EasyHg Mercurial extension:")), row, 0); - - m_extensionPathLabel = new QLineEdit(); - pathsLayout->addWidget(m_extensionPathLabel, row, 2); - - browse = new QPushButton(tr("Browse...")); - pathsLayout->addWidget(browse, row++, 1); - connect(browse, SIGNAL(clicked()), this, SLOT(extensionPathBrowse())); - - //!!! more info plz - m_useExtension = new QCheckBox(tr("Use EasyHg Mercurial extension")); - pathsLayout->addWidget(m_useExtension, row++, 2); - - - reset(); // loads current defaults from settings - - - QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok); - connect(bbox->addButton(tr("Restore defaults"), QDialogButtonBox::ResetRole), - SIGNAL(clicked()), this, SLOT(restoreDefaults())); - connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); - mainLayout->addWidget(bbox, 3, 0); - m_ok = bbox->button(QDialogButtonBox::Ok); -} - -void -SettingsDialog::hgPathBrowse() -{ - browseFor(tr("Mercurial program"), m_hgPathLabel); -} - -void -SettingsDialog::diffPathBrowse() -{ - browseFor(tr("External diff program"), m_diffPathLabel); -} - -void -SettingsDialog::mergePathBrowse() -{ - browseFor(tr("External file-merge program"), m_mergePathLabel); -} - -void -SettingsDialog::editPathBrowse() -{ - browseFor(tr("External text editor"), m_editPathLabel); -} - -void -SettingsDialog::extensionPathBrowse() -{ - browseFor(tr("EasyHg Mercurial extension"), m_extensionPathLabel); -} - -void -SettingsDialog::browseFor(QString title, QLineEdit *edit) -{ - QString origin = edit->text(); - - if (origin == "") { -#ifdef Q_OS_WIN32 - origin = "c:"; -#else - origin = QDir::homePath(); -#endif - } - - QString path = QFileDialog::getOpenFileName(this, title, origin); - if (path != QString()) { - edit->setText(path); - } -} - -void -SettingsDialog::restoreDefaults() -{ - if (QMessageBox::question - (this, tr("Restore default settings?"), - tr("Restore default settings?

Are you sure you want to reset all settings to their default values?"), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Cancel) == QMessageBox::Ok) { - clear(); - findDefaultLocations(); - reset(); - } -} - -void -SettingsDialog::findDefaultLocations(QString installPath) -{ - m_installPath = installPath; - findHgBinaryName(); - findExtension(); - findDiffBinaryName(); - findMergeBinaryName(); - findEditorBinaryName(); -} - -void -SettingsDialog::findHgBinaryName() -{ - QSettings settings; - settings.beginGroup("Locations"); - QString hg = settings.value("hgbinary", "").toString(); - if (hg == "") { - hg = findInPath("hg", m_installPath, true); - } - if (hg != "") { - settings.setValue("hgbinary", hg); - } -} - -QString -SettingsDialog::getUnbundledExtensionFileName() -{ - QString home = QDir::homePath(); - QString target = QString("%1/.easyhg").arg(home); - QString extpath = QString("%1/easyhg.py").arg(target); - return extpath; -} - -void -SettingsDialog::findExtension() -{ - QSettings settings; - settings.beginGroup("Locations"); - - QString extpath = settings.value("extensionpath", "").toString(); - if (extpath != "" || !QFile(extpath).exists()) { - - extpath = getUnbundledExtensionFileName(); - - if (!QFile(extpath).exists()) { - extpath = findInPath("easyhg.py", m_installPath, false); - } - } - - settings.setValue("extensionpath", extpath); -} - -void -SettingsDialog::findDiffBinaryName() -{ - QSettings settings; - settings.beginGroup("Locations"); - QString diff = settings.value("extdiffbinary", "").toString(); - if (diff == "") { - QStringList bases; -#ifdef Q_OS_WIN32 - bases << "easyhg-extdiff.bat"; -#else - bases << "easyhg-extdiff.sh"; -#endif - bases << "kompare" << "kdiff3" << "meld"; - bool found = false; - foreach (QString base, bases) { - diff = findInPath(base, m_installPath, true); - if (diff != "") { - found = true; - break; - } - } - if (found) { - settings.setValue("extdiffbinary", diff); - } - } -} - -void -SettingsDialog::findMergeBinaryName() -{ - QSettings settings; - settings.beginGroup("Locations"); - if (settings.contains("mergebinary")) { - return; - } - QString merge; - QStringList bases; -#ifdef Q_OS_WIN32 - bases << "easyhg-merge.bat"; -#else - bases << "easyhg-merge.sh"; -#endif - // NB it's not a good idea to add other tools here, as command - // line argument ordering varies. Configure them through hgrc - // instead - bool found = false; - foreach (QString base, bases) { - merge = findInPath(base, m_installPath, true); - if (merge != "") { - found = true; - break; - } - } - if (found) { - settings.setValue("mergebinary", merge); - } -} - -void -SettingsDialog::findEditorBinaryName() -{ - QSettings settings; - settings.beginGroup("Locations"); - QString editor = settings.value("editorbinary", "").toString(); - if (editor == "") { - QStringList bases; - bases -#if defined Q_OS_WIN32 - << "wordpad.exe" - << "C:\\Program Files\\Windows NT\\Accessories\\wordpad.exe" - << "notepad.exe" -#elif defined Q_OS_MAC - << "/Applications/TextEdit.app/Contents/MacOS/TextEdit" -#else - << "gedit" << "kate" -#endif - ; - bool found = false; - foreach (QString base, bases) { - editor = findInPath(base, m_installPath, true); - if (editor != "") { - found = true; - break; - } - } - if (found) { - settings.setValue("editorbinary", editor); - } - } -} - -void -SettingsDialog::clear() -{ - // Clear everything that has a default setting - DEBUG << "SettingsDialog::clear" << endl; - QSettings settings; - settings.beginGroup("Presentation"); - settings.remove("showiconlabels"); - settings.remove("showhelpfultext"); - settings.endGroup(); - settings.beginGroup("Locations"); - settings.remove("hgbinary"); - settings.remove("extdiffbinary"); - settings.remove("mergebinary"); - settings.remove("editorbinary"); - settings.remove("extensionpath"); - settings.endGroup(); - settings.beginGroup("General"); - settings.remove("useextension"); - settings.endGroup(); -} - -void -SettingsDialog::reset() -{ - DEBUG << "SettingsDialog::reset" << endl; - QSettings settings; - settings.beginGroup("User Information"); - m_nameEdit->setText(settings.value("name", getUserRealName()).toString()); - m_emailEdit->setText(settings.value("email").toString()); - settings.endGroup(); - settings.beginGroup("Presentation"); - m_showIconLabels->setChecked(settings.value("showiconlabels", true).toBool()); - m_showExtraText->setChecked(settings.value("showhelpfultext", true).toBool()); -#ifdef NOT_IMPLEMENTED_YET - m_workHistoryArrangement->setCurrentIndex(settings.value("workhistoryarrangement", 0).toInt()); -#endif - m_dateFormat->setCurrentIndex(settings.value("dateformat", 0).toInt()); - settings.endGroup(); - settings.beginGroup("Locations"); - m_hgPathLabel->setText(settings.value("hgbinary").toString()); - m_diffPathLabel->setText(settings.value("extdiffbinary").toString()); - m_mergePathLabel->setText(settings.value("mergebinary").toString()); - m_editPathLabel->setText(settings.value("editorbinary").toString()); - m_extensionPathLabel->setText(settings.value("extensionpath").toString()); - settings.endGroup(); - settings.beginGroup("General"); - m_useExtension->setChecked(settings.value("useextension", true).toBool()); - settings.endGroup(); -} - -void -SettingsDialog::accept() -{ - DEBUG << "SettingsDialog::accept" << endl; - QSettings settings; - settings.beginGroup("User Information"); - settings.setValue("name", m_nameEdit->text()); - settings.setValue("email", m_emailEdit->text()); - settings.endGroup(); - settings.beginGroup("Presentation"); - bool b; - b = m_showIconLabels->isChecked(); - if (b != settings.value("showiconlabels", true)) { - settings.setValue("showiconlabels", b); - m_presentationChanged = true; - } - b = m_showExtraText->isChecked(); - if (b != settings.value("showhelpfultext", true)) { - settings.setValue("showhelpfultext", b); - m_presentationChanged = true; - } - int i; -#ifdef NOT_IMPLEMENTED_YET - i = m_workHistoryArrangement->currentIndex(); - if (i != settings.value("workhistoryarrangement", 0)) { - settings.setValue("workhistoryarrangement", i); - m_presentationChanged = true; - } -#endif - i = m_dateFormat->currentIndex(); - if (i != settings.value("dateformat", 0)) { - settings.setValue("dateformat", i); - m_presentationChanged = true; - } - settings.endGroup(); - settings.beginGroup("Locations"); - settings.setValue("hgbinary", m_hgPathLabel->text()); - settings.setValue("extdiffbinary", m_diffPathLabel->text()); - settings.setValue("mergebinary", m_mergePathLabel->text()); - settings.setValue("editorbinary", m_editPathLabel->text()); - settings.setValue("extensionpath", m_extensionPathLabel->text()); - settings.endGroup(); - settings.beginGroup("General"); - settings.setValue("useextension", m_useExtension->isChecked()); - settings.endGroup(); - QDialog::accept(); -} - - diff -r 19cce6d2c470 -r b9c153e00e84 settingsdialog.h --- a/settingsdialog.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef SETTINGS_DIALOG_H -#define SETTINGS_DIALOG_H - -#include -#include -#include -#include -#include -#include - -class SettingsDialog : public QDialog -{ - Q_OBJECT - -public: - SettingsDialog(QWidget *parent = 0); - - bool presentationChanged() { - return m_presentationChanged; - } - - static void findDefaultLocations(QString installPath = m_installPath); - static QString getUnbundledExtensionFileName(); - -private slots: - void hgPathBrowse(); - void diffPathBrowse(); - void mergePathBrowse(); - void editPathBrowse(); - void extensionPathBrowse(); - - void accept(); - void reset(); - void clear(); - void restoreDefaults(); - -private: - QLineEdit *m_nameEdit; - QLineEdit *m_emailEdit; - QLineEdit *m_hgPathLabel; - QLineEdit *m_diffPathLabel; - QLineEdit *m_mergePathLabel; - QLineEdit *m_editPathLabel; - - QCheckBox *m_useExtension; - QLineEdit *m_extensionPathLabel; - - QCheckBox *m_showIconLabels; - QCheckBox *m_showExtraText; - QComboBox *m_dateFormat; -#ifdef NOT_IMPLEMENTED_YET - QComboBox *m_workHistoryArrangement; -#endif - - QPushButton *m_ok; - - bool m_presentationChanged; - - void browseFor(QString, QLineEdit *); - - static void findHgBinaryName(); - static void findExtension(); - static void findDiffBinaryName(); - static void findMergeBinaryName(); - static void findEditorBinaryName(); - - static QString m_installPath; -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/annotatedialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/annotatedialog.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,94 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "annotatedialog.h" +#include "common.h" +#include "colourset.h" +#include "debug.h" + +#include +#include +#include +#include +#include + +AnnotateDialog::AnnotateDialog(QWidget *w, QString text) : + QDialog(w) +{ + setMinimumWidth(800); + setMinimumHeight(500); + + text.replace("\r\n", "\n"); + QStringList lines = text.split("\n"); + + QGridLayout *layout = new QGridLayout; + QTableWidget *table = new QTableWidget; + + QRegExp annotateLineRE = QRegExp("^([^:]+) ([a-z0-9]{12}) ([0-9-]+): (.*)$"); + + table->setRowCount(lines.size()); + table->setColumnCount(4); + table->horizontalHeader()->setStretchLastSection(true); + table->verticalHeader()->setDefaultSectionSize + (table->verticalHeader()->fontMetrics().height() + 2); + + QStringList labels; + labels << tr("User") << tr("Revision") << tr("Date") << tr("Content"); + table->setHorizontalHeaderLabels(labels); + + table->setShowGrid(false); + + QFont monofont("Monospace"); + monofont.setStyleHint(QFont::TypeWriter); + + int row = 0; + + foreach (QString line, lines) { + if (annotateLineRE.indexIn(line) == 0) { + QStringList items = annotateLineRE.capturedTexts(); + QString id = items[2]; + QColor colour = ColourSet::instance()->getColourFor(id); + QColor bg = QColor::fromHsv(colour.hue(), + 30, + 230); + // note items[0] is the whole match, so we want 1-4 + for (int col = 0; col+1 < items.size(); ++col) { + QString item = items[col+1]; + if (col == 0) item = item.trimmed(); + QTableWidgetItem *wi = new QTableWidgetItem(item); + wi->setFlags(Qt::ItemIsEnabled); + wi->setBackground(bg); + if (col == 3) { // id, content + wi->setFont(monofont); + } + table->setItem(row, col, wi); + } + } else { + DEBUG << "AnnotateDialog: Failed to match RE in line: " << line << " at row " << row << endl; + } + ++row; + } + + QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); + connect(bb, SIGNAL(accepted()), this, SLOT(accept())); + + layout->addWidget(table, 0, 0); + layout->addWidget(bb, 1, 0); + + setLayout(layout); +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/annotatedialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/annotatedialog.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,31 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef ANNOTATE_DIALOG_H +#define ANNOTATE_DIALOG_H + +#include + +class AnnotateDialog : public QDialog +{ + Q_OBJECT + +public: + AnnotateDialog(QWidget *parent, QString output); +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/changeset.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/changeset.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,101 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "changeset.h" +#include "common.h" + +#include + +Changeset::Changeset(const LogEntry &e) +{ + foreach (QString key, e.keys()) { + if (key == "parents") { + QStringList parents = e.value(key).split + (" ", QString::SkipEmptyParts); + setParents(parents); + } else if (key == "tag") { + QStringList tags = e.value(key).split + (" ", QString::SkipEmptyParts); + setTags(tags); + } else if (key == "timestamp") { + setTimestamp(e.value(key).split(" ")[0].toULongLong()); + } else if (key == "changeset") { + setId(e.value(key)); + } else { + setProperty(key.toLocal8Bit().data(), e.value(key)); + } + } +} + +QString Changeset::getLogTemplate() +{ + return "id: {rev}:{node|short}\\nauthor: {author}\\nbranch: {branches}\\ntag: {tags}\\ndatetime: {date|isodate}\\ntimestamp: {date|hgdate}\\nage: {date|age}\\nparents: {parents}\\ncomment: {desc|json}\\n\\n"; +} + +QString Changeset::formatHtml() +{ + QString description; + QString rowTemplate = "%1 %2"; + + description = ""; + + QString c = comment().trimmed(); + c = c.replace(QRegExp("^\""), ""); + c = c.replace(QRegExp("\"$"), ""); + c = c.replace("\\\"", "\""); + c = xmlEncode(c); + c = c.replace("\\n", "
"); + + QStringList propNames, propTexts; + + propNames << "id" + << "author" + << "datetime" + << "branch" + << "tags" + << "comment"; + + propTexts << QObject::tr("Identifier:") + << QObject::tr("Author:") + << QObject::tr("Date:") + << QObject::tr("Branch:") + << QObject::tr("Tag:") + << QObject::tr("Comment:"); + + for (int i = 0; i < propNames.size(); ++i) { + QString prop = propNames[i]; + QString value; + if (prop == "id") { + value = hashOf(id()); + } else if (prop == "comment") { + value = c; + } else if (prop == "tags") { + value = tags().join(" "); + } else { + value = xmlEncode(property(prop.toLocal8Bit().data()).toString()); + } + if (value != "") { + description += rowTemplate + .arg(xmlEncode(propTexts[i])) + .arg(value); + } + } + + description += "
"; + + return description; +} diff -r 19cce6d2c470 -r b9c153e00e84 src/changeset.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/changeset.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,152 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef CHANGESET_H +#define CHANGESET_H + +#include +#include +#include +#include +#include + +#include "logparser.h" + +class Changeset; + +typedef QList Changesets; + +class Changeset : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged STORED true); + Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged STORED true); + Q_PROPERTY(QString branch READ branch WRITE setBranch NOTIFY branchChanged STORED true); + Q_PROPERTY(QStringList tags READ tags WRITE setTags NOTIFY tagsChanged STORED true); + Q_PROPERTY(QString datetime READ datetime WRITE setDatetime NOTIFY datetimeChanged STORED true); + Q_PROPERTY(qulonglong timestamp READ timestamp WRITE setTimestamp NOTIFY timestampChanged STORED true); + Q_PROPERTY(QString age READ age WRITE setAge NOTIFY ageChanged STORED true); + Q_PROPERTY(QStringList parents READ parents WRITE setParents NOTIFY parentsChanged STORED true); + Q_PROPERTY(QStringList children READ children WRITE setChildren NOTIFY childrenChanged STORED true); + Q_PROPERTY(QString comment READ comment WRITE setComment NOTIFY commentChanged STORED true); + +public: + Changeset() : QObject() { } + explicit Changeset(const LogEntry &e); + + QString id() const { return m_id; } + QString author() const { return m_author; } + QString branch() const { return m_branch; } + QStringList tags() const { return m_tags; } + QString datetime() const { return m_datetime; } + qulonglong timestamp() const { return m_timestamp; } + QString age() const { return m_age; } + QStringList parents() const { return m_parents; } + QString comment() const { return m_comment; } + + /** + * The children property is not obtained from Hg, but set in + * Grapher::layout() based on reported parents + */ + QStringList children() const { return m_children; } + + int number() const { + return id().split(':')[0].toInt(); + } + + QString authorName() const { + QString a = author(); + return a.replace(QRegExp("\\s*<[^>]*>"), ""); + } + + QString date() const { + return datetime().split(' ')[0]; + } + + bool isOnBranch(QString branch) { + QString b = m_branch; + if (branch == "") branch = "default"; + if (b == "") b = "default"; + if (branch == b) return true; + return false; + } + + static QString hashOf(QString id) { + return id.split(':')[1]; + } + + static QStringList getIds(Changesets csets) { + QStringList ids; + foreach (Changeset *cs, csets) ids.push_back(cs->id()); + return ids; + } + + static Changesets parseChangesets(QString logText) { + Changesets csets; + LogList log = LogParser(logText).parse(); + foreach (LogEntry e, log) { + csets.push_back(new Changeset(e)); + } + return csets; + } + + static QString getLogTemplate(); + + QString formatHtml(); + +signals: + void idChanged(QString id); + void authorChanged(QString author); + void branchChanged(QString branch); + void tagsChanged(QStringList tags); + void datetimeChanged(QString datetime); + void timestampChanged(qulonglong timestamp); + void ageChanged(QString age); + void parentsChanged(QStringList parents); + void childrenChanged(QStringList children); + void commentChanged(QString comment); + +public slots: + void setId(QString id) { m_id = id; emit idChanged(id); } + void setAuthor(QString author) { m_author = author; emit authorChanged(author); } + void setBranch(QString branch) { m_branch = branch; emit branchChanged(branch); } + void setTags(QStringList tags) { m_tags = tags; emit tagsChanged(tags); } + void addTag(QString tag) { m_tags.push_back(tag); emit tagsChanged(m_tags); } + void setDatetime(QString datetime) { m_datetime = datetime; emit datetimeChanged(datetime); } + void setTimestamp(qulonglong timestamp) { m_timestamp = timestamp; emit timestampChanged(timestamp); } + void setAge(QString age) { m_age = age; emit ageChanged(age); } + void setParents(QStringList parents) { m_parents = parents; emit parentsChanged(parents); } + void setChildren(QStringList children) { m_children = children; emit childrenChanged(m_children); } + void addChild(QString child) { m_children.push_back(child); emit childrenChanged(m_children); } + void setComment(QString comment) { m_comment = comment; emit commentChanged(comment); } + +private: + QString m_id; + QString m_author; + QString m_branch; + QStringList m_tags; + QString m_datetime; + qulonglong m_timestamp; + QString m_age; + QStringList m_parents; + QStringList m_children; + QString m_comment; +}; + + +#endif // CHANGESET_H diff -r 19cce6d2c470 -r b9c153e00e84 src/changesetdetailitem.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/changesetdetailitem.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,126 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "changesetdetailitem.h" +#include "changeset.h" +#include "textabbrev.h" +#include "colourset.h" +#include "debug.h" +#include "common.h" + +#include +#include + +ChangesetDetailItem::ChangesetDetailItem(Changeset *cs) : + m_changeset(cs), m_doc(0) +{ + m_font = QFont(); + m_font.setPixelSize(11); + m_font.setBold(false); + m_font.setItalic(false); + + makeDocument(); +} + +ChangesetDetailItem::~ChangesetDetailItem() +{ + delete m_doc; +} + +QRectF +ChangesetDetailItem::boundingRect() const +{ + int w = 350; + m_doc->setTextWidth(w); + return QRectF(-10, -10, w + 10, m_doc->size().height() + 10); +} + +void +ChangesetDetailItem::paint(QPainter *paint, + const QStyleOptionGraphicsItem *option, + QWidget *w) +{ + paint->save(); + + ColourSet *colourSet = ColourSet::instance(); + QColor branchColour = colourSet->getColourFor(m_changeset->branch()); + QColor userColour = colourSet->getColourFor(m_changeset->author()); + + QFont f(m_font); + + QTransform t = paint->worldTransform(); + float scale = std::min(t.m11(), t.m22()); + if (scale > 1.0) { + int ps = int((f.pixelSize() / scale) + 0.5); + if (ps < 8) ps = 8; + f.setPixelSize(ps); + } + + if (scale < 0.1) { + paint->setPen(QPen(branchColour, 0)); + } else { + paint->setPen(QPen(branchColour, 2)); + } + + paint->setFont(f); + QFontMetrics fm(f); + int fh = fm.height(); + + int width = 350; + m_doc->setTextWidth(width); + int height = m_doc->size().height(); + + QRectF r(0.5, 0.5, width - 1, height - 1); + paint->setBrush(Qt::white); + paint->drawRect(r); + + if (scale < 0.1) { + paint->restore(); + return; + } + + // little triangle connecting to its "owning" changeset item + paint->setBrush(branchColour); + QVector pts; + pts.push_back(QPointF(0, height/3 - 5)); + pts.push_back(QPointF(0, height/3 + 5)); + pts.push_back(QPointF(-10, height/3)); + pts.push_back(QPointF(0, height/3 - 5)); + paint->drawPolygon(QPolygonF(pts)); + +/* + paint->setBrush(branchColour); + QVector pts; + pts.push_back(QPointF(width/2 - 5, 0)); + pts.push_back(QPointF(width/2 + 5, 0)); + pts.push_back(QPointF(width/2, -10)); + pts.push_back(QPointF(width/2 - 5, 0)); + paint->drawPolygon(QPolygonF(pts)); +*/ + m_doc->drawContents(paint, r); + + paint->restore(); +} + +void +ChangesetDetailItem::makeDocument() +{ + delete m_doc; + m_doc = new QTextDocument; + m_doc->setHtml(m_changeset->formatHtml()); +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/changesetdetailitem.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/changesetdetailitem.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,45 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef CHANGESETDETAILITEM_H +#define CHANGESETDETAILITEM_H + +#include +#include + +class Changeset; + +class ChangesetDetailItem : public QGraphicsItem +{ +public: + ChangesetDetailItem(Changeset *cs); + virtual ~ChangesetDetailItem(); + + virtual QRectF boundingRect() const; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + Changeset *getChangeset() { return m_changeset; } + +private: + QFont m_font; + Changeset *m_changeset; + QTextDocument *m_doc; + + void makeDocument(); +}; + +#endif // CHANGESETDETAILITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 src/changesetitem.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/changesetitem.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,383 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "changesetitem.h" +#include "changesetscene.h" +#include "changesetdetailitem.h" +#include "changeset.h" +#include "textabbrev.h" +#include "colourset.h" +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ChangesetItem::ChangesetItem(Changeset *cs) : + m_changeset(cs), m_detail(0), + m_showBranch(false), m_column(0), m_row(0), m_wide(false), + m_current(false), m_new(false) +{ + m_font = QFont(); + m_font.setPixelSize(11); + m_font.setBold(false); + m_font.setItalic(false); + setCursor(Qt::ArrowCursor); +} + +QString +ChangesetItem::getId() +{ + return m_changeset->id(); +} + +QRectF +ChangesetItem::boundingRect() const +{ + int w = 100; + if (m_wide) w = 180; + return QRectF(-((w-50)/2 - 1), -30, w - 3, 90); +} + +void +ChangesetItem::showDetail() +{ + if (m_detail) return; + m_detail = new ChangesetDetailItem(m_changeset); + m_detail->setZValue(zValue() + 1); + scene()->addItem(m_detail); + int w = 100; + if (m_wide) w = 180; + int h = 80; +// m_detail->moveBy(x() - (m_detail->boundingRect().width() - 50) / 2, +// y() + 60); + m_detail->moveBy(x() + (w + 50) / 2 + 10 + 0.5, + y() - (m_detail->boundingRect().height() - h) / 2 + 0.5); + emit detailShown(); +} + +void +ChangesetItem::hideDetail() +{ + if (!m_detail) return; + scene()->removeItem(m_detail); + delete m_detail; + m_detail = 0; + emit detailHidden(); +} + +void +ChangesetItem::mousePressEvent(QGraphicsSceneMouseEvent *e) +{ + DEBUG << "ChangesetItem::mousePressEvent" << endl; + if (e->button() == Qt::LeftButton) { + if (m_detail) { + hideDetail(); + } else { + showDetail(); + } + } else if (e->button() == Qt::RightButton) { + if (m_detail) { + hideDetail(); + } + activateMenu(); + } +} + +void +ChangesetItem::activateMenu() +{ + m_parentDiffActions.clear(); + m_summaryActions.clear(); + + QMenu *menu = new QMenu; + QLabel *label = new QLabel(tr(" Revision: %1") + .arg(Changeset::hashOf(m_changeset->id()))); + QWidgetAction *wa = new QWidgetAction(menu); + wa->setDefaultWidget(label); + menu->addAction(wa); + menu->addSeparator(); + + QAction *copyId = menu->addAction(tr("Copy identifier to clipboard")); + connect(copyId, SIGNAL(triggered()), this, SLOT(copyIdActivated())); + + QAction *stat = menu->addAction(tr("Summarise changes")); + connect(stat, SIGNAL(triggered()), this, SLOT(showSummaryActivated())); + + menu->addSeparator(); + + QStringList parents = m_changeset->parents(); + + QString leftId, rightId; + bool havePositions = false; + + if (parents.size() > 1) { + ChangesetScene *cs = dynamic_cast(scene()); + if (cs && parents.size() == 2) { + ChangesetItem *i0 = cs->getItemById(parents[0]); + ChangesetItem *i1 = cs->getItemById(parents[1]); + if (i0 && i1) { + if (i0->x() < i1->x()) { + leftId = parents[0]; + rightId = parents[1]; + } else { + leftId = parents[1]; + rightId = parents[0]; + } + havePositions = true; + } + } + } + + if (parents.size() > 1) { + if (havePositions) { + + QAction *diff = menu->addAction(tr("Diff to left parent")); + connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); + m_parentDiffActions[diff] = leftId; + + diff = menu->addAction(tr("Diff to right parent")); + connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); + m_parentDiffActions[diff] = rightId; + + } else { + + foreach (QString parentId, parents) { + QString text = tr("Diff to parent %1").arg(Changeset::hashOf(parentId)); + QAction *diff = menu->addAction(text); + connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); + m_parentDiffActions[diff] = parentId; + } + } + + } else { + + QAction *diff = menu->addAction(tr("Diff to parent")); + connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); + } + + QAction *diffCurrent = menu->addAction(tr("Diff to current working folder")); + connect(diffCurrent, SIGNAL(triggered()), this, SLOT(diffToCurrentActivated())); + + menu->addSeparator(); + + QAction *update = menu->addAction(tr("Update to this revision")); + connect(update, SIGNAL(triggered()), this, SLOT(updateActivated())); + + QAction *merge = menu->addAction(tr("Merge from here to current")); + connect(merge, SIGNAL(triggered()), this, SLOT(mergeActivated())); + + menu->addSeparator(); + + QAction *branch = menu->addAction(tr("Start new branch...")); + branch->setEnabled(m_current); + connect(branch, SIGNAL(triggered()), this, SLOT(newBranchActivated())); + + QAction *tag = menu->addAction(tr("Add tag...")); + connect(tag, SIGNAL(triggered()), this, SLOT(tagActivated())); + + menu->exec(QCursor::pos()); + + ungrabMouse(); +} + +void +ChangesetItem::copyIdActivated() +{ + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(Changeset::hashOf(m_changeset->id())); +} + +void ChangesetItem::diffToParentActivated() +{ + QAction *a = qobject_cast(sender()); + QString parentId; + if (m_parentDiffActions.contains(a)) { + parentId = m_parentDiffActions[a]; + DEBUG << "ChangesetItem::diffToParentActivated: specific parent " + << parentId << " selected" << endl; + } else { + parentId = m_changeset->parents()[0]; + DEBUG << "ChangesetItem::diffToParentActivated: " + << "no specific parent selected, using first parent " + << parentId << endl; + } + + emit diffToParent(getId(), parentId); +} + +void ChangesetItem::showSummaryActivated() +{ + emit showSummary(m_changeset); +} + +void ChangesetItem::updateActivated() { emit updateTo(getId()); } +void ChangesetItem::diffToCurrentActivated() { emit diffToCurrent(getId()); } +void ChangesetItem::mergeActivated() { emit mergeFrom(getId()); } +void ChangesetItem::tagActivated() { emit tag(getId()); } +void ChangesetItem::newBranchActivated() { emit newBranch(getId()); } + +void +ChangesetItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *) +{ + paint->save(); + + ColourSet *colourSet = ColourSet::instance(); + QColor branchColour = colourSet->getColourFor(m_changeset->branch()); + QColor userColour = colourSet->getColourFor(m_changeset->author()); + + QFont f(m_font); + + QTransform t = paint->worldTransform(); + float scale = std::min(t.m11(), t.m22()); + if (scale > 1.0) { + int ps = int((f.pixelSize() / scale) + 0.5); + if (ps < 8) ps = 8; + f.setPixelSize(ps); + } + + bool showText = (scale >= 0.2); + bool showProperLines = (scale >= 0.1); + + if (!showProperLines) { + paint->setPen(QPen(branchColour, 0)); + } else { + paint->setPen(QPen(branchColour, 2)); + } + + paint->setFont(f); + QFontMetrics fm(f); + int fh = fm.height(); + + int width = 100; + if (m_wide) width = 180; + int x0 = -((width - 50) / 2 - 1); + + int textwid = width - 7; + + QString comment; + QStringList lines; + int lineCount = 3; + + if (showText) { + + comment = m_changeset->comment().trimmed(); + comment = comment.replace("\\n", " "); + comment = comment.replace(QRegExp("^\"\\s*\\**\\s*"), ""); + comment = comment.replace(QRegExp("\"$"), ""); + comment = comment.replace("\\\"", "\""); + + comment = TextAbbrev::abbreviate(comment, fm, textwid, + TextAbbrev::ElideEnd, "...", 3); + // abbreviate() changes this (ouch!), restore it + textwid = width - 5; + + lines = comment.split('\n'); + lineCount = lines.size(); + + if (lineCount < 2) lineCount = 2; + } + + int height = (lineCount + 1) * fh + 2; + QRectF r(x0, 0, width - 3, height); + + if (showProperLines) { + + paint->setBrush(Qt::white); + + if (m_current) { + paint->drawRect(QRectF(x0 - 4, -4, width + 5, height + 8)); + } + + if (m_new) { + paint->save(); + paint->setPen(Qt::yellow); + paint->setBrush(Qt::NoBrush); + paint->drawRect(QRectF(x0 - 2, -2, width + 1, height + 4)); + paint->restore(); + } + } + + paint->drawRect(r); + + if (!showText) { + paint->restore(); + return; + } + + paint->fillRect(QRectF(x0 + 0.5, 0.5, width - 4, fh - 0.5), + QBrush(userColour)); + + paint->setPen(QPen(Qt::white)); + + QString person = TextAbbrev::abbreviate(m_changeset->authorName(), + fm, textwid); + paint->drawText(x0 + 3, fm.ascent(), person); + + paint->setPen(QPen(Qt::black)); + + QStringList tags = m_changeset->tags(); + if (!tags.empty()) { + QStringList nonTipTags; + foreach (QString t, tags) { + // I'm not convinced that showing the tip tag really + // works; I think perhaps it confuses as much as it + // illuminates. But also, our current implementation + // doesn't interact well with it because it moves -- it's + // the one thing that can actually (in normal use) change + // inside an existing changeset record even during an + // incremental update + if (t != "tip") nonTipTags.push_back(t); + } + if (!nonTipTags.empty()) { + QString tagText = nonTipTags.join(" ").trimmed(); + int tw = fm.width(tagText); + paint->fillRect(QRectF(x0 + width - 8 - tw, 1, tw + 4, fh - 1), + QBrush(Qt::yellow)); + paint->drawText(x0 + width - 6 - tw, fm.ascent(), tagText); + } + } + + if (m_showBranch) { + // write branch name + paint->save(); + f.setBold(true); + paint->setFont(f); + paint->setPen(QPen(branchColour)); + QString branch = m_changeset->branch(); + if (branch == "") branch = "default"; + int wid = width - 3; + branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid); + paint->drawText(x0, -fh + fm.ascent() - 4, branch); + f.setBold(false); + paint->restore(); + } + + paint->setFont(f); + + for (int i = 0; i < lines.size(); ++i) { + paint->drawText(x0 + 3, i * fh + fh + fm.ascent(), lines[i].trimmed()); + } + + paint->restore(); +} diff -r 19cce6d2c470 -r b9c153e00e84 src/changesetitem.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/changesetitem.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,105 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef CHANGESETITEM_H +#define CHANGESETITEM_H + +#include +#include + +class Changeset; +class ChangesetDetailItem; + +class QAction; + +class ChangesetItem : public QGraphicsObject +{ + Q_OBJECT + +public: + ChangesetItem(Changeset *cs); + + virtual QRectF boundingRect() const; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + Changeset *getChangeset() { return m_changeset; } + QString getId(); + + int column() const { return m_column; } + int row() const { return m_row; } + void setColumn(int c) { m_column = c; setX(c * 100); } + void setRow(int r) { m_row = r; setY(r * 90); } + + bool isWide() const { return m_wide; } + void setWide(bool w) { m_wide = w; } + + bool isCurrent() const { return m_current; } + void setCurrent(bool c) { m_current = c; } + + bool isNew() const { return m_new; } + void setNew(bool n) { m_new = n; } + + bool showBranch() const { return m_showBranch; } + void setShowBranch(bool s) { m_showBranch = s; } + +signals: + void detailShown(); + void detailHidden(); + + void updateTo(QString); + void diffToCurrent(QString); + void diffToParent(QString child, QString parent); + void showSummary(Changeset *); + void mergeFrom(QString); + void newBranch(QString); + void tag(QString); + +public slots: + void showDetail(); + void hideDetail(); + +private slots: + void copyIdActivated(); + void updateActivated(); + void diffToParentActivated(); + void showSummaryActivated(); + void diffToCurrentActivated(); + void mergeActivated(); + void tagActivated(); + void newBranchActivated(); + +protected: + virtual void mousePressEvent(QGraphicsSceneMouseEvent *); + +private: + void activateMenu(); + + QFont m_font; + Changeset *m_changeset; + ChangesetDetailItem *m_detail; + bool m_showBranch; + int m_column; + int m_row; + bool m_wide; + bool m_current; + bool m_new; + + QMap m_parentDiffActions; + QMap m_summaryActions; +}; + +#endif // CHANGESETITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 src/changesetscene.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/changesetscene.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,134 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "changesetscene.h" +#include "changesetitem.h" +#include "uncommitteditem.h" +#include "dateitem.h" + +ChangesetScene::ChangesetScene() + : QGraphicsScene(), m_detailShown(0) +{ +} + +void +ChangesetScene::addChangesetItem(ChangesetItem *item) +{ + addItem(item); + + connect(item, SIGNAL(detailShown()), + this, SLOT(changesetDetailShown())); + + connect(item, SIGNAL(detailHidden()), + this, SLOT(changesetDetailHidden())); + + connect(item, SIGNAL(updateTo(QString)), + this, SIGNAL(updateTo(QString))); + + connect(item, SIGNAL(diffToCurrent(QString)), + this, SIGNAL(diffToCurrent(QString))); + + connect(item, SIGNAL(diffToParent(QString, QString)), + this, SIGNAL(diffToParent(QString, QString))); + + connect(item, SIGNAL(showSummary(Changeset *)), + this, SIGNAL(showSummary(Changeset *))); + + connect(item, SIGNAL(mergeFrom(QString)), + this, SIGNAL(mergeFrom(QString))); + + connect(item, SIGNAL(newBranch(QString)), + this, SIGNAL(newBranch(QString))); + + connect(item, SIGNAL(tag(QString)), + this, SIGNAL(tag(QString))); +} + +void +ChangesetScene::addUncommittedItem(UncommittedItem *item) +{ + addItem(item); + + connect(item, SIGNAL(commit()), + this, SIGNAL(commit())); + + connect(item, SIGNAL(revert()), + this, SIGNAL(revert())); + + connect(item, SIGNAL(diff()), + this, SIGNAL(diffWorkingFolder())); + + connect(item, SIGNAL(showSummary()), + this, SIGNAL(showSummary())); + + connect(item, SIGNAL(showWork()), + this, SIGNAL(showWork())); + + connect(item, SIGNAL(newBranch()), + this, SIGNAL(newBranch())); + + connect(item, SIGNAL(noBranch()), + this, SIGNAL(noBranch())); + +} + +void +ChangesetScene::addDateItem(DateItem *item) +{ + addItem(item); + + connect(item, SIGNAL(clicked()), + this, SLOT(dateItemClicked())); +} + +void +ChangesetScene::changesetDetailShown() +{ + ChangesetItem *csi = qobject_cast(sender()); + if (!csi) return; + + if (m_detailShown && m_detailShown != csi) { + m_detailShown->hideDetail(); + } + m_detailShown = csi; +} + +void +ChangesetScene::changesetDetailHidden() +{ + m_detailShown = 0; +} + +void +ChangesetScene::dateItemClicked() +{ + if (m_detailShown) { + m_detailShown->hideDetail(); + } +} + +ChangesetItem * +ChangesetScene::getItemById(QString id) +{ + foreach (QGraphicsItem *it, items()) { + ChangesetItem *csit = dynamic_cast(it); + if (csit && csit->getId() == id) return csit; + } + return 0; +} + + diff -r 19cce6d2c470 -r b9c153e00e84 src/changesetscene.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/changesetscene.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,67 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef CHANGESETSCENE_H +#define CHANGESETSCENE_H + +#include + +class ChangesetItem; +class Changeset; +class UncommittedItem; +class DateItem; + +class ChangesetScene : public QGraphicsScene +{ + Q_OBJECT + +public: + ChangesetScene(); + + void addChangesetItem(ChangesetItem *item); + void addUncommittedItem(UncommittedItem *item); + void addDateItem(DateItem *item); + + ChangesetItem *getItemById(QString id); // Slow: traversal required + +signals: + void commit(); + void revert(); + void diffWorkingFolder(); + void showSummary(); + void showWork(); + void newBranch(); + void noBranch(); + + void updateTo(QString id); + void diffToParent(QString id, QString parent); + void showSummary(Changeset *); + void diffToCurrent(QString id); + void mergeFrom(QString id); + void newBranch(QString id); + void tag(QString id); + +private slots: + void changesetDetailShown(); + void changesetDetailHidden(); + void dateItemClicked(); + +private: + ChangesetItem *m_detailShown; +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/clickablelabel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/clickablelabel.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,84 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _CLICKABLE_LABEL_H_ +#define _CLICKABLE_LABEL_H_ + +#include + +class ClickableLabel : public QLabel +{ + Q_OBJECT + + Q_PROPERTY(bool mouseUnderline READ mouseUnderline WRITE setMouseUnderline) + +public: + ClickableLabel(const QString &text, QWidget *parent = 0) : + QLabel(text, parent), + m_naturalText(text) + { } + + ClickableLabel(QWidget *parent = 0) : + QLabel(parent) + { } + + ~ClickableLabel() + { } + + void setText(const QString &t) { + m_naturalText = t; + QLabel::setText(t); + } + + bool mouseUnderline() const { + return m_mouseUnderline; + } + + void setMouseUnderline(bool mu) { + m_mouseUnderline = mu; + if (mu) { + setTextFormat(Qt::RichText); + setCursor(Qt::PointingHandCursor); + } + } + +signals: + void clicked(); + +protected: + virtual void enterEvent(QEvent *) { + if (m_mouseUnderline) { + QLabel::setText(tr("%1").arg(m_naturalText)); + } + } + + virtual void leaveEvent(QEvent *) { + if (m_mouseUnderline) { + QLabel::setText(m_naturalText); + } + } + + virtual void mousePressEvent(QMouseEvent *) { + emit clicked(); + } + +private: + bool m_mouseUnderline; + QString m_naturalText; +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/colourset.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colourset.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,52 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +#include "colourset.h" + +ColourSet +ColourSet::m_instance; + +ColourSet::ColourSet() { } + +ColourSet * +ColourSet::instance() +{ + return &m_instance; +} + +QColor +ColourSet::getColourFor(QString n) +{ + if (m_defaultNames.contains(n)) return Qt::black; + if (m_colours.contains(n)) return m_colours[n]; + + QColor c; + + if (m_colours.empty()) { + c = QColor::fromHsv(0, 200, 100); + } else { + c = QColor::fromHsv((m_lastColour.hue() + 70) % 360, 200, 100); + } + + m_colours[n] = c; + m_lastColour = c; + return c; +} + + + diff -r 19cce6d2c470 -r b9c153e00e84 src/colourset.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colourset.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,45 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _COLOURSET_H_ +#define _COLOURSET_H_ + +#include +#include +#include +#include + +class ColourSet +{ +public: + void clearDefaultNames() { m_defaultNames.clear(); } + void addDefaultName(QString n) { m_defaultNames.insert(n); } + + QColor getColourFor(QString n); + + static ColourSet *instance(); + +private: + ColourSet(); + QSet m_defaultNames; + QMap m_colours; + QColor m_lastColour; + + static ColourSet m_instance; +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/common.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/common.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,259 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "common.h" +#include "debug.h" + +#include +#include +#include +#include +#include + +#include + +#ifdef Q_OS_WIN32 +#define _WIN32_WINNT 0x0500 +#define SECURITY_WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +QString findInPath(QString name, QString installPath, bool executableRequired) +{ + bool found = false; + if (name != "") { + if (name[0] != '/' +#ifdef Q_OS_WIN32 + && (QRegExp("^\\w:").indexIn(name) != 0) +#endif + ) { +#ifdef Q_OS_WIN32 + QChar pathSep = ';'; +#else + QChar pathSep = ':'; +#endif + name = QFileInfo(name).fileName(); + QString path = + QProcessEnvironment::systemEnvironment().value("PATH"); + DEBUG << "findInPath: seeking location for binary " << name + << ": system path is " << path << endl; + if (installPath != "") { + DEBUG << "findInPath: install path is " << installPath + << ", adding to system path" << endl; + //!!! path = path + pathSep + installPath; + path = installPath + pathSep + path; + } +#ifndef Q_OS_WIN32 + //!!! + path = path + ":/usr/local/bin"; + DEBUG << "... adding /usr/local/bin just in case (fix and add settings dlg please)" + << endl; +#endif + QStringList elements = path.split(pathSep, QString::SkipEmptyParts); + foreach (QString element, elements) { + QString full = QDir(element).filePath(name); + QFileInfo fi(full); + DEBUG << "findInPath: looking at " << full << endl; + if (fi.exists() && fi.isFile()) { + DEBUG << "findInPath: it's a file" << endl; + if (!executableRequired || fi.isExecutable()) { + name = full; + DEBUG << "findInPath: found at " << name << endl; + found = true; + break; + } + } + } + } else { + // absolute path given + QFileInfo fi(name); + DEBUG << "findInPath: looking at absolute path " << name << endl; + if (fi.exists() && fi.isFile()) { + DEBUG << "findInPath: it's a file" << endl; + if (!executableRequired || fi.isExecutable()) { + DEBUG << "findInPath: found at " << name << endl; + found = true; + } + } + } + } +#ifdef Q_OS_WIN32 + if (!found) { + if (!name.endsWith(".exe")) { + return findInPath(name + ".exe", installPath, executableRequired); + } + } +#endif + if (found) { + return name; + } else { + return ""; + } +} + +#ifdef Q_OS_WIN32 +QString getUserRealName() +{ + TCHAR buf[1024]; + long unsigned int maxlen = 1000; + LPTSTR info = buf; + + if (!GetUserNameEx(NameDisplay, info, &maxlen)) { + DEBUG << "GetUserNameEx failed: " << GetLastError() << endl; + return ""; + } + +#ifdef UNICODE + return QString::fromUtf16((const unsigned short *)info); +#else + return QString::fromLocal8Bit(info); +#endif +} +#else +#ifdef Q_OS_MAC +// Nothing here: definition is in common_osx.mm +#else +QString getUserRealName() +{ + const int maxlen = 1023; + char buf[maxlen + 2]; + + if (getlogin_r(buf, maxlen)) return ""; + + struct passwd *p = getpwnam(buf); + if (!p) return ""; + + QString s(p->pw_gecos); + if (s != "") s = s.split(',')[0]; + return s; +} +#endif +#endif + +void loseControllingTerminal() +{ +#ifndef Q_OS_WIN32 + + if (!isatty(0)) { + DEBUG << "stdin is not a terminal" << endl; + } else { + DEBUG << "stdin is a terminal, detaching from it" << endl; + if (ioctl(0, TIOCNOTTY, NULL) < 0) { + perror("ioctl failed"); + DEBUG << "ioctl for TIOCNOTTY on stdin failed (errno = " << errno << ")" << endl; + } else { + DEBUG << "ioctl for TIOCNOTTY on stdin succeeded" << endl; + return; + } + } + + int ttyfd = open("/dev/tty", O_RDWR); + if (ttyfd < 0) { + DEBUG << "failed to open controlling terminal" << endl; + } else { + if (ioctl(ttyfd, TIOCNOTTY, NULL) < 0) { + perror("ioctl failed"); + DEBUG << "ioctl for TIOCNOTTY on controlling terminal failed (errno = " << errno << ")" << endl; + } else { + DEBUG << "ioctl for TIOCNOTTY on controlling terminal succeeded" << endl; + return; + } + } + +#endif +} + +void installSignalHandlers() +{ +#ifndef Q_OS_WIN32 + sigset_t sgnals; + sigemptyset (&sgnals); + sigaddset(&sgnals, SIGHUP); + sigaddset(&sgnals, SIGCONT); + pthread_sigmask(SIG_BLOCK, &sgnals, 0); +#endif +} + +FolderStatus getFolderStatus(QString path) +{ + if (path != "/" && path.endsWith("/")) { + path = path.left(path.length()-1); + } + DEBUG << "getFolderStatus: " << path << endl; + QFileInfo fi(path); + if (fi.exists()) { + DEBUG << "exists" << endl; + QDir dir(path); + if (!dir.exists()) { // returns false for files + DEBUG << "not directory" << endl; + return FolderIsFile; + } + if (QDir(dir.filePath(".hg")).exists()) { + DEBUG << "has repo" << endl; + return FolderHasRepo; + } + return FolderExists; + } else { + QDir parent = fi.dir(); + if (parent.exists()) { + DEBUG << "parent exists" << endl; + return FolderParentExists; + } + return FolderUnknown; + } +} + +QString getContainingRepoFolder(QString path) +{ + if (getFolderStatus(path) == FolderHasRepo) return ""; + + QFileInfo me(path); + QFileInfo parent(me.dir().absolutePath()); + + while (me != parent) { + QString parentPath = parent.filePath(); + if (getFolderStatus(parentPath) == FolderHasRepo) { + return parentPath; + } + me = parent; + parent = me.dir().absolutePath(); + } + + return ""; +} + +QString xmlEncode(QString s) +{ + s + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + + return s; +} diff -r 19cce6d2c470 -r b9c153e00e84 src/common.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/common.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,69 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef COMMON_H +#define COMMON_H + +#include + +#define MY_ICON_SIZE 32 +//!!!: +#define REPOMENU_TITLE "Repository actions" +#define WORKFOLDERMENU_TITLE "Workfolder actions" + +extern QString findInPath(QString name, QString installPath, bool executable); + +extern QString getSystem(); +extern QString getHgDirName(); + +extern QString getUserRealName(); + +extern void loseControllingTerminal(); + +void installSignalHandlers(); + +/** + * Status used in testing whether a folder argument (received from the + * user) is valid for particular uses. + */ +enum FolderStatus { + FolderUnknown, /// Neither the folder nor its parent exists + FolderParentExists, /// The folder is absent, but its parent exists + FolderExists, /// The folder exists and has no .hg repo in it + FolderHasRepo, /// The folder exists and has an .hg repo in it + FolderIsFile /// The "folder" is actually a file +}; + +FolderStatus getFolderStatus(QString path); + +/** + * If the given path is somewhere within an existing repository, + * return the path of the root directory of the repository (i.e. the + * one with .hg in it). + * + * If the given path is _not_ in a repository, or the given path _is_ + * the root directory of a repository, return QString(). Use + * getFolderStatus to distinguish between these cases. + */ +QString getContainingRepoFolder(QString path); + +QString xmlEncode(QString); + + +#endif //COMMON_H + + diff -r 19cce6d2c470 -r b9c153e00e84 src/confirmcommentdialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/confirmcommentdialog.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,257 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "confirmcommentdialog.h" +#include "common.h" + +#include +#include +#include +#include +#include +#include + +ConfirmCommentDialog::ConfirmCommentDialog(QWidget *parent, + QString title, + QString introText, + QString initialComment, + QString okButtonText) : + QDialog(parent) +{ + setWindowTitle(title); + + QGridLayout *layout = new QGridLayout; + setLayout(layout); + QLabel *label = new QLabel(introText); + label->setWordWrap(true); + layout->addWidget(label, 0, 0); + + m_textEdit = new QTextEdit; + m_textEdit->setAcceptRichText(false); + m_textEdit->document()->setPlainText(initialComment); + m_textEdit->setMinimumWidth(360); + connect(m_textEdit, SIGNAL(textChanged()), this, SLOT(commentChanged())); + layout->addWidget(m_textEdit, 1, 0); + + QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel); + layout->addWidget(bbox, 2, 0); + m_ok = bbox->button(QDialogButtonBox::Ok); + m_ok->setDefault(true); + m_ok->setEnabled(initialComment != ""); + m_ok->setText(okButtonText); + bbox->button(QDialogButtonBox::Cancel)->setAutoDefault(false); + + connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); + connect(bbox, SIGNAL(rejected()), this, SLOT(reject())); +} + +void ConfirmCommentDialog::commentChanged() +{ + m_ok->setEnabled(getComment() != ""); +} + +QString ConfirmCommentDialog::getComment() const +{ + return m_textEdit->document()->toPlainText(); +} + +QString ConfirmCommentDialog::buildFilesText(QString intro, QStringList files) +{ + QString text; + + if (intro == "") text = ""; + else text = "" + intro + "

"; + + text += ""; + foreach (QString file, files) { + text += "   " + xmlEncode(file) + "
"; + } + text += "
"; + + return text; +} + +bool ConfirmCommentDialog::confirm(QWidget *parent, + QString title, + QString head, + QString text, + QString okButtonText) +{ + QMessageBox box(QMessageBox::Question, + title, + head, + QMessageBox::Cancel, + parent); + + box.setInformativeText(text); + + QPushButton *ok = box.addButton(QMessageBox::Ok); + ok->setText(okButtonText); + box.setDefaultButton(QMessageBox::Ok); + if (box.exec() == -1) return false; + return box.standardButton(box.clickedButton()) == QMessageBox::Ok; +} + +bool ConfirmCommentDialog::confirmDangerous(QWidget *parent, + QString title, + QString head, + QString text, + QString okButtonText) +{ + QMessageBox box(QMessageBox::Warning, + title, + head, + QMessageBox::Cancel, + parent); + + box.setInformativeText(text); + + QPushButton *ok = box.addButton(QMessageBox::Ok); + ok->setText(okButtonText); + box.setDefaultButton(QMessageBox::Cancel); + if (box.exec() == -1) return false; + return box.standardButton(box.clickedButton()) == QMessageBox::Ok; +} + +bool ConfirmCommentDialog::confirmFilesAction(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString okButtonText) +{ + QString text; + if (files.size() <= 10) { + text = buildFilesText(introText, files); + } else { + text = "" + introTextWithCount + ""; + } + return confirm(parent, title, text, "", okButtonText); +} + +bool ConfirmCommentDialog::confirmDangerousFilesAction(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString okButtonText) +{ + QString text; + if (files.size() <= 10) { + text = buildFilesText(introText, files); + } else { + text = "" + introTextWithCount + ""; + } + return confirmDangerous(parent, title, text, "", okButtonText); +} + +bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString &comment, + QString okButtonText) +{ + return confirmAndComment(parent, title, introText, + introTextWithCount, files, comment, false, + okButtonText); +} + +bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString &comment, + QString okButtonText) +{ + return confirmAndComment(parent, title, introText, + introTextWithCount, files, comment, true, + okButtonText); +} + +bool ConfirmCommentDialog::confirmAndComment(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString &comment, + bool longComment, + QString okButtonText) +{ + QString text; + if (files.size() <= 10) { + text = buildFilesText(introText, files); + } else { + text = "" + introTextWithCount; + } + text += tr("

Please enter your comment:"); + return confirmAndComment(parent, title, text, comment, longComment, + okButtonText); +} + +bool ConfirmCommentDialog::confirmAndGetShortComment(QWidget *parent, + QString title, + QString introText, + QString &comment, + QString okButtonText) +{ + return confirmAndComment(parent, title, introText, comment, false, + okButtonText); +} + +bool ConfirmCommentDialog::confirmAndGetLongComment(QWidget *parent, + QString title, + QString introText, + QString &comment, + QString okButtonText) +{ + return confirmAndComment(parent, title, introText, comment, true, + okButtonText); +} + +bool ConfirmCommentDialog::confirmAndComment(QWidget *parent, + QString title, + QString introText, + QString &comment, + bool longComment, + QString okButtonText) +{ + bool ok = false; + if (!longComment) { + QInputDialog d(parent); + d.setWindowTitle(title); + d.setLabelText(introText); + d.setTextValue(comment); + d.setOkButtonText(okButtonText); + d.setTextEchoMode(QLineEdit::Normal); + if (d.exec() == QDialog::Accepted) { + comment = d.textValue(); + ok = true; + } + } else { + ConfirmCommentDialog d(parent, title, introText, comment, okButtonText); + if (d.exec() == QDialog::Accepted) { + comment = d.getComment(); + ok = true; + } + } + + return ok; +} diff -r 19cce6d2c470 -r b9c153e00e84 src/confirmcommentdialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/confirmcommentdialog.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,121 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef CONFIRMCOMMENTDIALOG_H +#define CONFIRMCOMMENTDIALOG_H + +#include +#include +#include +#include +#include +#include + +class ConfirmCommentDialog : public QDialog +{ + Q_OBJECT + +public: + static bool confirm(QWidget *parent, + QString title, + QString head, + QString text, + QString okButtonText); + + static bool confirmDangerous(QWidget *parent, + QString title, + QString head, + QString text, + QString okButtonText); + + static bool confirmFilesAction(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString okButtonText); + + static bool confirmDangerousFilesAction(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString okButtonText); + + static bool confirmAndGetShortComment(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString &comment, + QString okButtonText); + + static bool confirmAndGetLongComment(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString &comment, + QString okButtonText); + + static bool confirmAndGetShortComment(QWidget *parent, + QString title, + QString introText, + QString &comment, + QString okButtonText); + + static bool confirmAndGetLongComment(QWidget *parent, + QString title, + QString introText, + QString &comment, + QString okButtonText); + +private slots: + void commentChanged(); + +private: + ConfirmCommentDialog(QWidget *parent, + QString title, + QString introText, + QString initialComment, + QString okButtonText); + + static bool confirmAndComment(QWidget *parent, + QString title, + QString introText, + QString introTextWithCount, + QStringList files, + QString &comment, + bool longComment, + QString okButtonText); + + static bool confirmAndComment(QWidget *parent, + QString title, + QString introText, + QString &comment, + bool longComment, + QString okButtonText); + + static QString buildFilesText(QString intro, QStringList files); + + QString getComment() const; + + QTextEdit *m_textEdit; + QPushButton *m_ok; +}; + +#endif // CONFIRMCOMMENTDIALOG_H diff -r 19cce6d2c470 -r b9c153e00e84 src/connectionitem.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/connectionitem.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,132 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "connectionitem.h" +#include "uncommitteditem.h" + +#include "changesetitem.h" +#include "changeset.h" +#include "colourset.h" + +#include + +QRectF +ConnectionItem::boundingRect() const +{ + if (!m_parent || !(m_child || m_uncommitted)) return QRectF(); + float xscale = 100; + float yscale = 90; + float size = 50; + + int p_col = m_parent->column(), p_row = m_parent->row(); + int c_col, c_row; + if (m_child) { + c_col = m_child->column(); c_row = m_child->row(); + } else { + c_col = m_uncommitted->column(); c_row = m_uncommitted->row(); + } + + return QRectF(xscale * c_col + size/2 - 2, + yscale * c_row + size - 2, + xscale * p_col - xscale * c_col + 4, + yscale * p_row - yscale * c_row - size + 4) + .normalized(); +} + +void +ConnectionItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *) +{ + if (!m_parent || !(m_child || m_uncommitted)) return; + QPainterPath p; + + paint->save(); + + ColourSet *colourSet = ColourSet::instance(); + QString branch; + if (m_child) branch = m_child->getChangeset()->branch(); + else branch = m_uncommitted->branch(); + QColor branchColour = colourSet->getColourFor(branch); + + Qt::PenStyle ls = Qt::SolidLine; + if (!m_child) ls = Qt::DashLine; + + QTransform t = paint->worldTransform(); + float scale = std::min(t.m11(), t.m22()); + if (scale < 0.2) { + paint->setPen(QPen(branchColour, 0, ls)); + } else { + paint->setPen(QPen(branchColour, 2, ls)); + } + + float xscale = 100; + + float yscale = 90; + float size = 50; + float ygap = yscale - size - 2; + + int p_col = m_parent->column(), p_row = m_parent->row(); + int c_col, c_row; + if (m_child) { + c_col = m_child->column(); c_row = m_child->row(); + } else { + c_col = m_uncommitted->column(); c_row = m_uncommitted->row(); + } + + float c_x = xscale * c_col + size/2; + float p_x = xscale * p_col + size/2; + + // ensure line reaches the box, even if it's in a small height -- + // doesn't matter if we overshoot as the box is opaque and has a + // greater Z value + p.moveTo(c_x, yscale * c_row + size - 20); + + p.lineTo(c_x, yscale * c_row + size); + + if (p_col == c_col) { + + p.lineTo(p_x, yscale * p_row); + + } else if (m_type == Split || m_type == Normal) { + + // place the bulk of the line on the child (i.e. branch) row + + if (abs(p_row - c_row) > 1) { + p.lineTo(c_x, yscale * p_row - ygap); + } + + p.cubicTo(c_x, yscale * p_row, + p_x, yscale * p_row - ygap, + p_x, yscale * p_row); + + } else if (m_type == Merge) { + + // place bulk of the line on the parent row + + p.cubicTo(c_x, yscale * c_row + size + ygap, + p_x, yscale * c_row + size, + p_x, yscale * c_row + size + ygap); + + if (abs(p_row - c_row) > 1) { + p.lineTo(p_x, yscale * p_row); + } + } + + paint->drawPath(p); + paint->restore(); +} + + diff -r 19cce6d2c470 -r b9c153e00e84 src/connectionitem.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/connectionitem.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,61 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef CONNECTIONITEM_H +#define CONNECTIONITEM_H + +#include + +class Connection; + +class ChangesetItem; +class UncommittedItem; + +class ConnectionItem : public QGraphicsItem +{ +public: + enum Type { + Normal, + Split, + Merge + }; + + ConnectionItem() : m_type(Normal), m_parent(0), m_child(0) { } + + virtual QRectF boundingRect() const; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + Type connectionType() const { return m_type; } + void setConnectionType(Type t) { m_type = t; } + + //!!! deletion signals from parent/child? + + ChangesetItem *parent() { return m_parent; } + ChangesetItem *child() { return m_child; } + + void setParent(ChangesetItem *p) { m_parent = p; } + void setChild(ChangesetItem *c) { m_child = c; } + void setChild(UncommittedItem *u) { m_uncommitted = u; } + +private: + Type m_type; + ChangesetItem *m_parent; + ChangesetItem *m_child; + UncommittedItem *m_uncommitted; +}; + +#endif // CONNECTIONITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 src/dateitem.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dateitem.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,91 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "dateitem.h" + +#include "debug.h" + +#include +#include +#include +#include + +DateItem::DateItem() : + m_minrow(0), m_maxrow(0), + m_mincol(0), m_maxcol(0), + m_even(false) +{ +} + +void +DateItem::setRows(int minrow, int n) +{ + m_minrow = minrow; + m_maxrow = minrow + n - 1; + setY(m_minrow * 90); +} + +void +DateItem::setCols(int mincol, int n) +{ + m_mincol = mincol; + m_maxcol = mincol + n - 1; + setX(m_mincol * 100); +} + +void +DateItem::mousePressEvent(QGraphicsSceneMouseEvent *e) +{ + DEBUG << "DateItem::mousePressEvent" << endl; + if (e->button() == Qt::LeftButton) { + emit clicked(); + } + e->ignore(); +} + +QRectF +DateItem::boundingRect() const +{ + return QRectF(-75, -25, + (m_maxcol - m_mincol + 1) * 100 + 100, + (m_maxrow - m_minrow + 1) * 90).normalized(); +} + +void +DateItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *opt, QWidget *w) +{ + QBrush brush; + + if (m_even) { + QColor c(QColor::fromRgb(240, 240, 240)); + brush = QBrush(c); + } else { + QColor c(QColor::fromRgb(250, 250, 250)); + brush = QBrush(c); + } + + paint->fillRect(boundingRect(), brush); + + paint->save(); + QFont f(paint->font()); + f.setBold(true); + paint->setFont(f); + paint->drawText(-70, -10, m_dateString); + paint->restore(); +} + + diff -r 19cce6d2c470 -r b9c153e00e84 src/dateitem.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dateitem.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,56 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef DATEITEM_H +#define DATEITEM_H + +#include + +class DateItem : public QGraphicsObject +{ + Q_OBJECT + +public: + DateItem(); + + virtual QRectF boundingRect() const; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + void setRows(int minrow, int n); + void setCols(int mincol, int n); + + void setEven(bool e) { m_even = e; } + + QString dateString() const { return m_dateString; } + void setDateString(QString s) { m_dateString = s; } + +signals: + void clicked(); + +protected: + virtual void mousePressEvent(QGraphicsSceneMouseEvent *); + +private: + QString m_dateString; + int m_minrow; + int m_maxrow; + int m_mincol; + int m_maxcol; + bool m_even; +}; + +#endif // DATEITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 src/debug.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/debug.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,84 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QDebug & +getEasyHgDebug() +{ + static QFile *logFile = 0; + static QDebug *debug = 0; + static QMutex mutex; + static char *prefix; + mutex.lock(); + if (!debug) { + prefix = new char[20]; + sprintf(prefix, "[%lu]", (unsigned long)QCoreApplication::applicationPid()); + logFile = new QFile(QDir::homePath() + "/.easyhg.log"); + if (logFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) { + QDebug(QtDebugMsg) << (const char *)prefix + << "Opened debug log file " + << logFile->fileName(); + debug = new QDebug(logFile); + } else { + QDebug(QtWarningMsg) << (const char *)prefix + << "Failed to open debug log file " + << logFile->fileName() + << " for writing, using console debug instead"; + debug = new QDebug(QtDebugMsg); + delete logFile; + logFile = 0; + } + *debug << endl << (const char *)prefix << "Log started at " + << QDateTime::currentDateTime().toString(); + } + mutex.unlock(); + + QDebug &dref = *debug; + return dref << endl << (const char *)prefix; +} + +QDebug & +operator<<(QDebug &dbg, const std::string &s) +{ + dbg << QString::fromUtf8(s.c_str()); + return dbg; +} + +std::ostream & +operator<<(std::ostream &target, const QString &str) +{ + return target << str.toLocal8Bit().data(); +} + +std::ostream & +operator<<(std::ostream &target, const QUrl &u) +{ + return target << "<" << u.toString() << ">"; +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/debug.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/debug.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,67 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _DEBUG_H_ +#define _DEBUG_H_ + +#include +#include +#include +#include + +class QString; +class QUrl; + +QDebug &operator<<(QDebug &, const std::string &); +std::ostream &operator<<(std::ostream &, const QString &); +std::ostream &operator<<(std::ostream &, const QUrl &); + +#ifndef NDEBUG + +extern QDebug &getEasyHgDebug(); + +#define DEBUG getEasyHgDebug() + +template +inline QDebug &operator<<(QDebug &d, const T &t) { + QString s; + QTextStream ts(&s); + ts << t; + d << s; + return d; +} + +#else + +class NoDebug +{ +public: + inline NoDebug() {} + inline ~NoDebug(){} + + template + inline NoDebug &operator<<(const T &) { return *this; } + + inline NoDebug &operator<<(QTextStreamFunction) { return *this; } +}; + +#define DEBUG NoDebug() + +#endif /* !NDEBUG */ + +#endif /* !_DEBUG_H_ */ + diff -r 19cce6d2c470 -r b9c153e00e84 src/filestates.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/filestates.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,221 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "filestates.h" + +#include "debug.h" + +#include + +FileStates::FileStates() +{ +} + +void FileStates::clearBuckets() +{ + m_clean.clear(); + m_modified.clear(); + m_added.clear(); + m_removed.clear(); + m_missing.clear(); + m_inConflict.clear(); + m_unknown.clear(); + m_ignored.clear(); +} + +FileStates::State FileStates::charToState(QChar c, bool *ok) +{ + // Note that InConflict does not correspond to a stat char -- it's + // reported separately, by resolve --list, which shows U for + // Unresolved -- stat reports files in conflict as M, which means + // they will appear in more than one bin if we handle them + // naively. 'u' is also used by stat as the command option for + // Unknown, but the stat output uses ? for these so there's no + // ambiguity in parsing. + + if (ok) *ok = true; + if (c == 'M') return Modified; + if (c == 'A') return Added; + if (c == 'R') return Removed; + if (c == '!') return Missing; + if (c == 'U') return InConflict; + if (c == '?') return Unknown; + if (c == 'C') return Clean; + if (c == 'I') return Ignored; + if (ok) *ok = false; + return Unknown; +} + +QStringList *FileStates::stateToBucket(State s) +{ + switch (s) { + case Clean: return &m_clean; + case Modified: return &m_modified; + case Added: return &m_added; + case Unknown: return &m_unknown; + case Removed: return &m_removed; + case Missing: return &m_missing; + case InConflict: return &m_inConflict; + case Ignored: return &m_ignored; + + default: return &m_clean; + } +} + +void FileStates::parseStates(QString text) +{ + text.replace("\r\n", "\n"); + + clearBuckets(); + m_stateMap.clear(); + + QStringList lines = text.split("\n", QString::SkipEmptyParts); + + foreach (QString line, lines) { + + if (line.length() < 3 || line[1] != ' ') { + continue; + } + + QChar c = line[0]; + bool ok = false; + State s = charToState(c, &ok); + if (!ok) continue; + + QString file = line.right(line.length() - 2); + m_stateMap[file] = s; + } + + foreach (QString file, m_stateMap.keys()) { + QStringList *bucket = stateToBucket(m_stateMap[file]); + if (bucket) bucket->push_back(file); + } + + DEBUG << "FileStates: " + << m_modified.size() << " modified, " << m_added.size() + << " added, " << m_removed.size() << " removed, " << m_missing.size() + << " missing, " << m_inConflict.size() << " in conflict, " + << m_unknown.size() << " unknown" << endl; +} + +QStringList FileStates::filesInState(State s) const +{ + QStringList *sl = const_cast(this)->stateToBucket(s); + if (sl) return *sl; + else return QStringList(); +} + +bool FileStates::isInState(QString file, State s) const +{ + return filesInState(s).contains(file); +} + +FileStates::State FileStates::stateOf(QString file) const +{ + if (m_stateMap.contains(file)) { + return m_stateMap[file]; + } + DEBUG << "FileStates: WARNING: getStateOfFile: file " + << file << " is unknown to us: returning Unknown state, " + << "but unknown to us is not supposed to be the same " + << "thing as unknown state..." + << endl; + return Unknown; +} + +FileStates::Activities FileStates::activitiesSupportedBy(State s) +{ + Activities a; + + switch (s) { + + case Modified: + a << Annotate << Diff << Commit << Revert << Rename << Copy << Remove; + break; + + case Added: + a << Commit << Revert << Rename << Copy << Remove; + break; + + case Removed: + a << Commit << Revert << Add; + break; + + case InConflict: + a << Annotate << Diff << RedoMerge << MarkResolved << Revert; + break; + + case Missing: + a << Revert << Remove; + break; + + case Unknown: + a << Add << Ignore; + break; + + case Clean: + a << Annotate << Rename << Copy << Remove; + break; + + case Ignored: + a << UnIgnore; + break; + } + + return a; +} + +bool FileStates::supportsActivity(State s, Activity a) +{ + return activitiesSupportedBy(s).contains(a); +} + +int FileStates::activityGroup(Activity a) +{ + switch (a) { + case Annotate: case Diff: return 0; + case Commit: case Revert: return 1; + case Rename: case Copy: return 2; + case Add: case Remove: return 3; + case RedoMerge: case MarkResolved: return 4; + case Ignore: case UnIgnore: return 5; + } + return 0; +} + +bool FileStates::supportsActivity(QString file, Activity a) const +{ + return supportsActivity(stateOf(file), a); +} + +QStringList FileStates::filesSupportingActivity(Activity a) const +{ + QStringList f; + for (int i = int(FirstState); i <= int(LastState); ++i) { + State s = (State)i; + if (supportsActivity(s, a)) { + f << filesInState(s); + } + } + return f; +} + +FileStates::Activities FileStates::activitiesSupportedBy(QString file) const +{ + return activitiesSupportedBy(stateOf(file)); +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/filestates.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/filestates.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,108 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef FILESTATES_H +#define FILESTATES_H + +#include +#include +#include + +class FileStates +{ +public: + FileStates(); + + enum State { + + // These are in the order in which they want to be listed in + // the interface + + Modified, + Added, + Removed, + InConflict, + Missing, + Unknown, + Clean, + Ignored, + + FirstState = Modified, + LastState = Ignored + }; + + void parseStates(QString text); + + bool isInState(QString file, State s) const; + QStringList filesInState(State s) const; + State stateOf(QString file) const; + + enum Activity { + + // These are in the order in which they want to be listed in + // the context menu + + Diff, + Annotate, + + Commit, + Revert, + + Rename, + Copy, + + Add, + Remove, + + RedoMerge, + MarkResolved, + + Ignore, + UnIgnore, + + FirstActivity = Diff, + LastActivity = UnIgnore + }; + + typedef QList Activities; + + static bool supportsActivity(State s, Activity a); + static Activities activitiesSupportedBy(State s); + static int activityGroup(Activity a); + + bool supportsActivity(QString file, Activity a) const; + QStringList filesSupportingActivity(Activity) const; + Activities activitiesSupportedBy(QString file) const; + +private: + QStringList m_modified; + QStringList m_added; + QStringList m_unknown; + QStringList m_removed; + QStringList m_missing; + QStringList m_inConflict; + QStringList m_clean; + QStringList m_ignored; + QMap m_stateMap; + + void clearBuckets(); + + State charToState(QChar, bool * = 0); + QStringList *stateToBucket(State); +}; + +#endif // FILESTATES_H diff -r 19cce6d2c470 -r b9c153e00e84 src/filestatuswidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/filestatuswidget.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,538 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "filestatuswidget.h" +#include "debug.h" +#include "multichoicedialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +FileStatusWidget::FileStatusWidget(QWidget *parent) : + QWidget(parent), + m_dateReference(0) +{ + QGridLayout *layout = new QGridLayout; + layout->setMargin(10); + setLayout(layout); + + int row = 0; + + m_noModificationsLabel = new QLabel; + setNoModificationsLabelText(); + layout->addWidget(m_noModificationsLabel, row, 0); + m_noModificationsLabel->hide(); + + m_simpleLabels[FileStates::Clean] = tr("Unmodified:"); + m_simpleLabels[FileStates::Modified] = tr("Modified:"); + m_simpleLabels[FileStates::Added] = tr("Added:"); + m_simpleLabels[FileStates::Removed] = tr("Removed:"); + m_simpleLabels[FileStates::Missing] = tr("Missing:"); + m_simpleLabels[FileStates::InConflict] = tr("In Conflict:"); + m_simpleLabels[FileStates::Unknown] = tr("Untracked:"); + m_simpleLabels[FileStates::Ignored] = tr("Ignored:"); + + m_actionLabels[FileStates::Annotate] = tr("Show annotated version"); + m_actionLabels[FileStates::Diff] = tr("Diff to parent"); + m_actionLabels[FileStates::Commit] = tr("Commit..."); + m_actionLabels[FileStates::Revert] = tr("Revert to last committed state"); + m_actionLabels[FileStates::Rename] = tr("Rename..."); + m_actionLabels[FileStates::Copy] = tr("Copy..."); + m_actionLabels[FileStates::Add] = tr("Add to version control"); + m_actionLabels[FileStates::Remove] = tr("Remove from version control"); + m_actionLabels[FileStates::RedoMerge] = tr("Redo merge"); + m_actionLabels[FileStates::MarkResolved] = tr("Mark conflict as resolved"); + m_actionLabels[FileStates::Ignore] = tr("Ignore"); + m_actionLabels[FileStates::UnIgnore] = tr("Stop ignoring"); + + m_descriptions[FileStates::Clean] = tr("You have not changed these files."); + m_descriptions[FileStates::Modified] = tr("You have changed these files since you last committed them."); + m_descriptions[FileStates::Added] = tr("These files will be added to version control next time you commit them."); + m_descriptions[FileStates::Removed] = tr("These files will be removed from version control next time you commit them.
" + "They will not be deleted from the local folder."); + m_descriptions[FileStates::Missing] = tr("These files are recorded in the version control, but absent from your working folder.
" + "If you intended to delete them, select them and use Remove to tell the version control system about it.
" + "If you deleted them by accident, select them and use Revert to restore their previous contents."); + m_descriptions[FileStates::InConflict] = tr("These files are unresolved following an incomplete merge.
Select a file and use Merge to try to resolve the merge again."); + m_descriptions[FileStates::Unknown] = tr("These files are in your working folder but are not under version control.
" +// "Select a file and use Add to place it under version control or Ignore to remove it from this list."); + "Select a file and use Add to place it under version control."); + m_descriptions[FileStates::Ignored] = tr("These files have names that match entries in the working folder's .hgignore file,
" + "and so will be ignored by the version control system."); + + m_highlightExplanation = tr("Files highlighted in red " + "have appeared since your most recent commit or update."); + + m_boxesParent = new QWidget(this); + layout->addWidget(m_boxesParent, ++row, 0); + + QGridLayout *boxesLayout = new QGridLayout; + boxesLayout->setMargin(0); + m_boxesParent->setLayout(boxesLayout); + int boxRow = 0; + + for (int i = int(FileStates::FirstState); + i <= int(FileStates::LastState); ++i) { + + FileStates::State s = FileStates::State(i); + + QWidget *box = new QWidget(m_boxesParent); + QGridLayout *boxlayout = new QGridLayout; + boxlayout->setMargin(0); + box->setLayout(boxlayout); + + boxlayout->addItem(new QSpacerItem(3, 3), 0, 0); + + QLabel *label = new QLabel(labelFor(s)); + label->setWordWrap(true); + boxlayout->addWidget(label, 1, 0); + + QListWidget *w = new QListWidget; + m_stateListMap[s] = w; + w->setSelectionMode(QListWidget::ExtendedSelection); + boxlayout->addWidget(w, 2, 0); + + connect(w, SIGNAL(itemSelectionChanged()), + this, SLOT(itemSelectionChanged())); + connect(w, SIGNAL(itemDoubleClicked(QListWidgetItem *)), + this, SLOT(itemDoubleClicked(QListWidgetItem *))); + + FileStates::Activities activities = m_fileStates.activitiesSupportedBy(s); + int prevGroup = -1; + foreach (FileStates::Activity a, activities) { + // Skip activities which are not yet implemented + if (a == FileStates::Ignore || + a == FileStates::UnIgnore) { + continue; + } + int group = FileStates::activityGroup(a); + if (group != prevGroup && prevGroup != -1) { + QAction *sep = new QAction("", w); + sep->setSeparator(true); + w->insertAction(0, sep); + } + prevGroup = group; + QAction *act = new QAction(m_actionLabels[a], w); + act->setProperty("state", s); + act->setProperty("activity", a); + connect(act, SIGNAL(triggered()), this, SLOT(menuActionActivated())); + w->insertAction(0, act); + } + w->setContextMenuPolicy(Qt::ActionsContextMenu); + + boxlayout->addItem(new QSpacerItem(2, 2), 3, 0); + + boxesLayout->addWidget(box, ++boxRow, 0); + m_boxes.push_back(box); + box->hide(); + } + + m_gridlyLayout = false; + + layout->setRowStretch(++row, 20); + + layout->addItem(new QSpacerItem(8, 8), ++row, 0); + + m_showAllFiles = new QCheckBox(tr("Show all files"), this); + m_showAllFiles->setEnabled(false); + layout->addWidget(m_showAllFiles, ++row, 0, Qt::AlignLeft); + connect(m_showAllFiles, SIGNAL(toggled(bool)), + this, SIGNAL(showAllChanged(bool))); +} + +FileStatusWidget::~FileStatusWidget() +{ + delete m_dateReference; +} + +QString FileStatusWidget::labelFor(FileStates::State s, bool addHighlightExplanation) +{ + QSettings settings; + settings.beginGroup("Presentation"); + if (settings.value("showhelpfultext", true).toBool()) { + if (addHighlightExplanation) { + return QString("%1
%2
%3
") + .arg(m_simpleLabels[s]) + .arg(m_descriptions[s]) + .arg(m_highlightExplanation); + } else { + return QString("%1
%2
") + .arg(m_simpleLabels[s]) + .arg(m_descriptions[s]); + } + } else { + return QString("%1") + .arg(m_simpleLabels[s]); + } + settings.endGroup(); +} + +void FileStatusWidget::setNoModificationsLabelText() +{ + QSettings settings; + settings.beginGroup("Presentation"); + if (settings.value("showhelpfultext", true).toBool()) { + m_noModificationsLabel->setText + (tr("This area will list files in your working folder that you have changed.

At the moment you have no uncommitted changes.

To see changes previously made to the repository,
switch to the History tab.

%1
") +#if defined Q_OS_MAC + .arg(tr("To open the working folder in Finder,
click on the “Local” folder path shown above.")) +#elif defined Q_OS_WIN32 + .arg(tr("To open the working folder in Windows Explorer,
click on the “Local” folder path shown above.")) +#else + .arg(tr("To open the working folder in your system file manager,
click the “Local” folder path shown above.")) +#endif + ); + } else { + m_noModificationsLabel->setText + (tr("You have no uncommitted changes.")); + } +} + + +void FileStatusWidget::menuActionActivated() +{ + QAction *act = qobject_cast(sender()); + if (!act) return; + + FileStates::State state = (FileStates::State) + act->property("state").toUInt(); + FileStates::Activity activity = (FileStates::Activity) + act->property("activity").toUInt(); + + DEBUG << "menuActionActivated: state = " << state << ", activity = " + << activity << endl; + + if (!FileStates::supportsActivity(state, activity)) { + std::cerr << "WARNING: FileStatusWidget::menuActionActivated: " + << "Action state " << state << " does not support activity " + << activity << std::endl; + return; + } + + QStringList files = getSelectedFilesInState(state); + + switch (activity) { + case FileStates::Annotate: emit annotateFiles(files); break; + case FileStates::Diff: emit diffFiles(files); break; + case FileStates::Commit: emit commitFiles(files); break; + case FileStates::Revert: emit revertFiles(files); break; + case FileStates::Rename: emit renameFiles(files); break; + case FileStates::Copy: emit copyFiles(files); break; + case FileStates::Add: emit addFiles(files); break; + case FileStates::Remove: emit removeFiles(files); break; + case FileStates::RedoMerge: emit redoFileMerges(files); break; + case FileStates::MarkResolved: emit markFilesResolved(files); break; + case FileStates::Ignore: emit ignoreFiles(files); break; + case FileStates::UnIgnore: emit unIgnoreFiles(files); break; + } +} + +void FileStatusWidget::itemDoubleClicked(QListWidgetItem *item) +{ + QStringList files; + QString file = item->text(); + files << file; + + switch (m_fileStates.stateOf(file)) { + + case FileStates::Modified: + case FileStates::InConflict: + emit diffFiles(files); + break; + + case FileStates::Clean: + case FileStates::Missing: + emit annotateFiles(files); + break; + } +} + +void FileStatusWidget::itemSelectionChanged() +{ + DEBUG << "FileStatusWidget::itemSelectionChanged" << endl; + + QListWidget *list = qobject_cast(sender()); + + if (list) { + foreach (QListWidget *w, m_stateListMap) { + if (w != list) { + w->blockSignals(true); + w->clearSelection(); + w->blockSignals(false); + } + } + } + + m_selectedFiles.clear(); + + foreach (QListWidget *w, m_stateListMap) { + QList sel = w->selectedItems(); + foreach (QListWidgetItem *i, sel) { + m_selectedFiles.push_back(i->text()); + DEBUG << "file " << i->text() << " is selected" << endl; + } + } + + emit selectionChanged(); +} + +void FileStatusWidget::clearSelections() +{ + m_selectedFiles.clear(); + foreach (QListWidget *w, m_stateListMap) { + w->clearSelection(); + } +} + +bool FileStatusWidget::haveChangesToCommit() const +{ + return !getAllCommittableFiles().empty(); +} + +bool FileStatusWidget::haveSelection() const +{ + return !m_selectedFiles.empty(); +} + +QStringList FileStatusWidget::getSelectedFilesInState(FileStates::State s) const +{ + QStringList files; + foreach (QString f, m_selectedFiles) { + if (m_fileStates.stateOf(f) == s) files.push_back(f); + } + return files; +} + +QStringList FileStatusWidget::getSelectedFilesSupportingActivity(FileStates::Activity a) const +{ + QStringList files; + foreach (QString f, m_selectedFiles) { + if (m_fileStates.supportsActivity(f, a)) files.push_back(f); + } + return files; +} + +QStringList FileStatusWidget::getAllCommittableFiles() const +{ + return m_fileStates.filesSupportingActivity(FileStates::Commit); +} + +QStringList FileStatusWidget::getAllRevertableFiles() const +{ + return m_fileStates.filesSupportingActivity(FileStates::Revert); +} + +QStringList FileStatusWidget::getAllUnresolvedFiles() const +{ + return m_fileStates.filesInState(FileStates::InConflict); +} + +QStringList FileStatusWidget::getSelectedAddableFiles() const +{ + return getSelectedFilesSupportingActivity(FileStates::Add); +} + +QStringList FileStatusWidget::getSelectedRemovableFiles() const +{ + return getSelectedFilesSupportingActivity(FileStates::Remove); +} + +QString +FileStatusWidget::localPath() const +{ + return m_localPath; +} + +void +FileStatusWidget::setLocalPath(QString p) +{ + m_localPath = p; + delete m_dateReference; + m_dateReference = new QFileInfo(p + "/.hg/dirstate"); + if (!m_dateReference->exists() || + !m_dateReference->isFile() || + !m_dateReference->isReadable()) { + DEBUG << "FileStatusWidget::setLocalPath: date reference file " + << m_dateReference->absoluteFilePath() + << " does not exist, is not a file, or cannot be read" + << endl; + delete m_dateReference; + m_dateReference = 0; + m_showAllFiles->setEnabled(false); + } else { + m_showAllFiles->setEnabled(true); + } +} + +void +FileStatusWidget::setFileStates(FileStates p) +{ + m_fileStates = p; + updateWidgets(); +} + +void +FileStatusWidget::updateWidgets() +{ + QDateTime lastInteractionTime; + if (m_dateReference) { + lastInteractionTime = m_dateReference->lastModified(); + DEBUG << "reference time: " << lastInteractionTime << endl; + } + + QSet selectedFiles; + foreach (QString f, m_selectedFiles) selectedFiles.insert(f); + + int visibleCount = 0; + + foreach (FileStates::State s, m_stateListMap.keys()) { + + QListWidget *w = m_stateListMap[s]; + w->clear(); + QStringList files = m_fileStates.filesInState(s); + + QStringList highPriority, lowPriority; + + foreach (QString file, files) { + + bool highlighted = false; + + if (s == FileStates::Unknown) { + // We want to highlight untracked files that have appeared + // since the last interaction with the repo + QString fn(m_localPath + "/" + file); + DEBUG << "comparing with " << fn << endl; + QFileInfo fi(fn); + if (fi.exists() && fi.created() > lastInteractionTime) { + DEBUG << "file " << fn << " is newer (" << fi.lastModified() + << ") than reference" << endl; + highlighted = true; + } + } + + if (highlighted) { + highPriority.push_back(file); + } else { + lowPriority.push_back(file); + } + } + + foreach (QString file, highPriority) { + QListWidgetItem *item = new QListWidgetItem(file); + w->addItem(item); + item->setForeground(QColor("#d40000")); + item->setSelected(selectedFiles.contains(file)); + } + + foreach (QString file, lowPriority) { + QListWidgetItem *item = new QListWidgetItem(file); + w->addItem(item); + item->setSelected(selectedFiles.contains(file)); + } + + setLabelFor(w, s, !highPriority.empty()); + + if (files.empty()) { + w->parentWidget()->hide(); + } else { + w->parentWidget()->show(); + ++visibleCount; + } + } + + m_noModificationsLabel->setVisible(visibleCount == 0); + + if (visibleCount > 3) { + layoutBoxesGridly(visibleCount); + } else { + layoutBoxesLinearly(); + } + + setNoModificationsLabelText(); +} + +void FileStatusWidget::layoutBoxesGridly(int visibleCount) +{ + if (m_gridlyLayout && m_lastGridlyCount == visibleCount) return; + + delete m_boxesParent->layout(); + + QGridLayout *layout = new QGridLayout; + layout->setMargin(0); + m_boxesParent->setLayout(layout); + + int row = 0; + int col = 0; + + DEBUG << "FileStatusWidget::layoutBoxesGridly: visibleCount = " + << visibleCount << endl; + + for (int i = 0; i < m_boxes.size(); ++i) { + + if (!m_boxes[i]->isVisible()) continue; + + if (col == 0 && row >= (visibleCount+1)/2) { + layout->addItem(new QSpacerItem(10, 5), 0, 1); + col = 2; + row = 0; + } + + layout->addWidget(m_boxes[i], row, col); + + ++row; + } + + m_gridlyLayout = true; + m_lastGridlyCount = visibleCount; +} + +void FileStatusWidget::layoutBoxesLinearly() +{ + if (!m_gridlyLayout) return; + + delete m_boxesParent->layout(); + + QGridLayout *layout = new QGridLayout; + layout->setMargin(0); + m_boxesParent->setLayout(layout); + + for (int i = 0; i < m_boxes.size(); ++i) { + layout->addWidget(m_boxes[i], i, 0); + } + + m_gridlyLayout = false; +} + +void FileStatusWidget::setLabelFor(QWidget *w, FileStates::State s, bool addHighlight) +{ + QString text = labelFor(s, addHighlight); + QWidget *p = w->parentWidget(); + QList ql = p->findChildren(); + if (!ql.empty()) ql[0]->setText(text); +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/filestatuswidget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/filestatuswidget.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,114 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef FILESTATUSWIDGET_H +#define FILESTATUSWIDGET_H + +#include "filestates.h" + +#include +#include + +class QLabel; +class QListWidget; +class QListWidgetItem; +class QPushButton; +class QFileInfo; +class QCheckBox; + +class FileStatusWidget : public QWidget +{ + Q_OBJECT + +public: + FileStatusWidget(QWidget *parent = 0); + ~FileStatusWidget(); + + QString localPath() const; + void setLocalPath(QString p); + + FileStates fileStates() const; + void setFileStates(FileStates sp); + + bool haveChangesToCommit() const; + bool haveSelection() const; + + QStringList getAllCommittableFiles() const; + QStringList getAllRevertableFiles() const; + QStringList getAllUnresolvedFiles() const; + + QStringList getSelectedAddableFiles() const; + QStringList getSelectedRemovableFiles() const; + +signals: + void selectionChanged(); + void showAllChanged(bool); + + void annotateFiles(QStringList); + void diffFiles(QStringList); + void commitFiles(QStringList); + void revertFiles(QStringList); + void renameFiles(QStringList); + void copyFiles(QStringList); + void addFiles(QStringList); + void removeFiles(QStringList); + void redoFileMerges(QStringList); + void markFilesResolved(QStringList); + void ignoreFiles(QStringList); + void unIgnoreFiles(QStringList); + +public slots: + void clearSelections(); + void updateWidgets(); + +private slots: + void menuActionActivated(); + void itemSelectionChanged(); + void itemDoubleClicked(QListWidgetItem *); + +private: + QString m_localPath; + QLabel *m_noModificationsLabel; + + QCheckBox *m_showAllFiles; + + FileStates m_fileStates; + QMap m_simpleLabels; + QMap m_descriptions; + QMap m_stateListMap; + QMap m_actionLabels; + QString m_highlightExplanation; + + QFileInfo *m_dateReference; + QStringList m_selectedFiles; + + bool m_gridlyLayout; + int m_lastGridlyCount; + QList m_boxes; + QWidget *m_boxesParent; + + void layoutBoxesGridly(int count); + void layoutBoxesLinearly(); + void setNoModificationsLabelText(); + QString labelFor(FileStates::State, bool addHighlightExplanation = false); + void setLabelFor(QWidget *w, FileStates::State, bool addHighlightExplanation); + + QStringList getSelectedFilesInState(FileStates::State s) const; + QStringList getSelectedFilesSupportingActivity(FileStates::Activity) const; +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/grapher.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/grapher.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,603 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "grapher.h" +#include "connectionitem.h" +#include "dateitem.h" +#include "debug.h" +#include "changesetscene.h" + +#include + +#include + +Grapher::Grapher(ChangesetScene *scene) : + m_scene(scene) +{ + QSettings settings; + settings.beginGroup("Presentation"); + m_showDates = (settings.value("dateformat", 0) == 1); +} + +int Grapher::findAvailableColumn(int row, int parent, bool preferParentCol) +{ + int col = parent; + if (preferParentCol) { + if (isAvailable(row, col)) return col; + } + while (col > 0) { + if (isAvailable(row, --col)) return col; + } + while (col < 0) { + if (isAvailable(row, ++col)) return col; + } + col = parent; + int sign = (col < 0 ? -1 : 1); + while (1) { + col += sign; + if (isAvailable(row, col)) return col; + } +} + +bool Grapher::isAvailable(int row, int col) +{ + if (m_alloc.contains(row) && m_alloc[row].contains(col)) return false; + if (!m_haveAllocatedUncommittedColumn) return true; + if (!m_uncommitted) return true; + return !(row <= m_uncommittedParentRow && col == m_uncommitted->column()); +} + +void Grapher::layoutRow(QString id) +{ + if (m_handled.contains(id)) { + return; + } + if (!m_changesets.contains(id)) { + throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); + } + if (!m_items.contains(id)) { + throw LayoutException(QString("Changeset %1 not in item map").arg(id)); + } + Changeset *cs = m_changesets[id]; + ChangesetItem *item = m_items[id]; + DEBUG << "layoutRow: Looking at " << id.toStdString() << endl; + + int row = 0; + int nparents = cs->parents().size(); + + if (nparents > 0) { + bool haveRow = false; + foreach (QString parentId, cs->parents()) { + + if (!m_changesets.contains(parentId)) continue; + if (!m_items.contains(parentId)) continue; + + if (!m_handled.contains(parentId)) { + layoutRow(parentId); + } + + ChangesetItem *parentItem = m_items[parentId]; + if (!haveRow || parentItem->row() < row) { + row = parentItem->row(); + haveRow = true; + } + } + row = row - 1; + } + + // row is now an upper bound on our eventual row (because we want + // to be above all parents). But we also want to ensure we are + // above all nodes that have earlier dates (to the nearest day). + // m_rowDates maps each row to a date: use that. + + QString date; + if (m_showDates) { + date = cs->date(); + } else { + date = cs->age(); + } + while (m_rowDates.contains(row) && m_rowDates[row] != date) { + --row; + } + + // We have already laid out all nodes that have earlier timestamps + // than this one, so we know (among other things) that we can + // safely fill in this row has having this date, if it isn't in + // the map yet (it cannot have an earlier date) + + if (!m_rowDates.contains(row)) { + m_rowDates[row] = date; + } + + // If we're the parent of the uncommitted item, make a note of our + // row (we need it later, to avoid overwriting the connecting line) + if (!m_uncommittedParents.empty() && m_uncommittedParents[0] == id) { + m_uncommittedParentRow = row; + } + + DEBUG << "putting " << cs->id().toStdString() << " at row " << row + << endl; + + item->setRow(row); + m_handled.insert(id); +} + +void Grapher::layoutCol(QString id) +{ + if (m_handled.contains(id)) { + DEBUG << "Already looked at " << id.toStdString() << endl; + return; + } + if (!m_changesets.contains(id)) { + throw LayoutException(QString("Changeset %1 not in ID map").arg(id)); + } + if (!m_items.contains(id)) { + throw LayoutException(QString("Changeset %1 not in item map").arg(id)); + } + + Changeset *cs = m_changesets[id]; + DEBUG << "layoutCol: Looking at " << id.toStdString() << endl; + + ChangesetItem *item = m_items[id]; + + int col = 0; + int row = item->row(); + QString branch = cs->branch(); + + int nparents = cs->parents().size(); + QString parentId; + int parentsOnSameBranch = 0; + + switch (nparents) { + + case 0: + col = m_branchHomes[cs->branch()]; + col = findAvailableColumn(row, col, true); + break; + + case 1: + parentId = cs->parents()[0]; + + if (!m_changesets.contains(parentId) || + !m_changesets[parentId]->isOnBranch(branch)) { + // new branch + col = m_branchHomes[branch]; + } else { + col = m_items[parentId]->column(); + } + + col = findAvailableColumn(row, col, true); + break; + + case 2: + // a merge: behave differently if parents are both on the same + // branch (we also want to behave differently for nodes that + // have multiple children on the same branch -- spreading them + // out rather than having affinity to a specific branch) + + foreach (QString parentId, cs->parents()) { + if (!m_changesets.contains(parentId)) continue; + if (m_changesets[parentId]->isOnBranch(branch)) { + ChangesetItem *parentItem = m_items[parentId]; + col += parentItem->column(); + parentsOnSameBranch++; + } + } + + if (parentsOnSameBranch > 0) { + col /= parentsOnSameBranch; + col = findAvailableColumn(item->row(), col, true); + } else { + col = findAvailableColumn(item->row(), col, false); + } + break; + } + + DEBUG << "putting " << cs->id().toStdString() << " at col " << col << endl; + + m_alloc[row].insert(col); + item->setColumn(col); + m_handled.insert(id); + + // If we're the first parent of the uncommitted item, it should be + // given the same column as us (we already noted that its + // connecting line would end at our row) + + if (m_uncommittedParents.contains(id)) { + if (m_uncommittedParents[0] == id) { + int ucol = findAvailableColumn(row-1, col, true); + m_uncommitted->setColumn(ucol); + m_haveAllocatedUncommittedColumn = true; + } + // also, if the uncommitted item has a different branch from + // any of its parents, tell it to show the branch + if (!cs->isOnBranch(m_uncommitted->branch())) { + DEBUG << "Uncommitted branch " << m_uncommitted->branch() + << " differs from my branch " << cs->branch() + << ", asking it to show branch" << endl; + m_uncommitted->setShowBranch(true); + } + } + + + // Normally the children will lay out themselves, but we can do + // a better job in some special cases: + + int nchildren = cs->children().size(); + + // look for merging children and children distant from us but in a + // straight line, and make sure nobody is going to overwrite their + // connection lines + + foreach (QString childId, cs->children()) { + DEBUG << "reserving connection line space" << endl; + if (!m_changesets.contains(childId)) continue; + Changeset *child = m_changesets[childId]; + int childRow = m_items[childId]->row(); + if (child->parents().size() > 1 || + child->isOnBranch(cs->branch())) { + for (int r = row-1; r > childRow; --r) { + m_alloc[r].insert(col); + } + } + } + + // look for the case where exactly two children have the same + // branch as us: split them to a little either side of our position + + if (nchildren > 1) { + QList special; + foreach (QString childId, cs->children()) { + if (!m_changesets.contains(childId)) continue; + Changeset *child = m_changesets[childId]; + if (child->isOnBranch(branch) && + child->parents().size() == 1) { + special.push_back(childId); + } + } + if (special.size() == 2) { + DEBUG << "handling split-in-two for children " << special[0] << " and " << special[1] << endl; + for (int i = 0; i < 2; ++i) { + int off = i * 2 - 1; // 0 -> -1, 1 -> 1 + ChangesetItem *it = m_items[special[i]]; + it->setColumn(findAvailableColumn(it->row(), col + off, true)); + for (int r = row-1; r >= it->row(); --r) { + m_alloc[r].insert(it->column()); + } + m_handled.insert(special[i]); + } + } + } +} + +bool Grapher::rangesConflict(const Range &r1, const Range &r2) +{ + // allow some additional space at edges. we really ought also to + // permit space at the end of a branch up to the point where the + // merge happens + int a1 = r1.first - 2, b1 = r1.second + 2; + int a2 = r2.first - 2, b2 = r2.second + 2; + if (a1 > b2 || b1 < a2) return false; + if (a2 > b1 || b2 < a1) return false; + return true; +} + +void Grapher::allocateBranchHomes(Changesets csets) +{ + foreach (Changeset *cs, csets) { + QString branch = cs->branch(); + ChangesetItem *item = m_items[cs->id()]; + if (!item) continue; + int row = item->row(); + if (!m_branchRanges.contains(branch)) { + m_branchRanges[branch] = Range(row, row); + } else { + Range p = m_branchRanges[branch]; + if (row < p.first) p.first = row; + if (row > p.second) p.second = row; + m_branchRanges[branch] = p; + } + } + + m_branchHomes[""] = 0; + m_branchHomes["default"] = 0; + + foreach (QString branch, m_branchRanges.keys()) { + if (branch == "") continue; + QSet taken; + taken.insert(0); + Range myRange = m_branchRanges[branch]; + foreach (QString other, m_branchRanges.keys()) { + if (other == branch || other == "") continue; + Range otherRange = m_branchRanges[other]; + if (rangesConflict(myRange, otherRange)) { + if (m_branchHomes.contains(other)) { + taken.insert(m_branchHomes[other]); + } + } + } + int home = 2; + while (taken.contains(home)) { + if (home > 0) { + if (home % 2 == 1) { + home = -home; + } else { + home = home + 1; + } + } else { + if ((-home) % 2 == 1) { + home = home + 1; + } else { + home = -(home-2); + } + } + } + m_branchHomes[branch] = home; + } + + foreach (QString branch, m_branchRanges.keys()) { + DEBUG << branch.toStdString() << ": " << m_branchRanges[branch].first << " - " << m_branchRanges[branch].second << ", home " << m_branchHomes[branch] << endl; + } +} + +static bool +compareChangesetsByDate(Changeset *const &a, Changeset *const &b) +{ + return a->timestamp() < b->timestamp(); +} + +ChangesetItem * +Grapher::getItemFor(Changeset *cs) +{ + if (!cs || !m_items.contains(cs->id())) return 0; + return m_items[cs->id()]; +} + +void Grapher::layout(Changesets csets, + QStringList uncommittedParents, + QString uncommittedBranch) +{ + m_changesets.clear(); + m_items.clear(); + m_alloc.clear(); + m_branchHomes.clear(); + + m_uncommittedParents = uncommittedParents; + m_haveAllocatedUncommittedColumn = false; + m_uncommittedParentRow = 0; + m_uncommitted = 0; + + DEBUG << "Grapher::layout: Have " << csets.size() << " changesets" << endl; + + if (csets.empty()) return; + + // Create (but don't yet position) the changeset items + + foreach (Changeset *cs, csets) { + + QString id = cs->id(); + + if (id == "") { + throw LayoutException("Changeset has no ID"); + } + if (m_changesets.contains(id)) { + DEBUG << "Duplicate changeset ID " << id + << " in Grapher::layout()" << endl; + throw LayoutException(QString("Duplicate changeset ID %1").arg(id)); + } + + m_changesets[id] = cs; + + ChangesetItem *item = new ChangesetItem(cs); + item->setX(0); + item->setY(0); + item->setZValue(0); + m_items[id] = item; + m_scene->addChangesetItem(item); + } + + // Add the connecting lines + + foreach (Changeset *cs, csets) { + QString id = cs->id(); + ChangesetItem *item = m_items[id]; + bool merge = (cs->parents().size() > 1); + foreach (QString parentId, cs->parents()) { + if (!m_changesets.contains(parentId)) continue; + Changeset *parent = m_changesets[parentId]; + parent->addChild(id); + ConnectionItem *conn = new ConnectionItem(); + if (merge) conn->setConnectionType(ConnectionItem::Merge); + conn->setChild(item); + conn->setParent(m_items[parentId]); + conn->setZValue(-1); + m_scene->addItem(conn); + } + } + + // Add uncommitted item and connecting line as necessary + + if (!m_uncommittedParents.empty()) { + + m_uncommitted = new UncommittedItem(); + m_uncommitted->setBranch(uncommittedBranch); + m_uncommitted->setZValue(10); + m_scene->addUncommittedItem(m_uncommitted); + + bool haveParentOnBranch = false; + foreach (QString p, m_uncommittedParents) { + ConnectionItem *conn = new ConnectionItem(); + conn->setConnectionType(ConnectionItem::Merge); + ChangesetItem *pitem = m_items[p]; + conn->setParent(pitem); + conn->setChild(m_uncommitted); + conn->setZValue(0); + m_scene->addItem(conn); + if (pitem) { + if (pitem->getChangeset()->branch() == uncommittedBranch) { + haveParentOnBranch = true; + } + } + } + + // If the uncommitted item has no parents on the same branch, + // tell it it has a new branch (the "show branch" flag is set + // elsewhere for this item) + m_uncommitted->setIsNewBranch(!haveParentOnBranch); + } + + // Add the branch labels + + foreach (Changeset *cs, csets) { + QString id = cs->id(); + ChangesetItem *item = m_items[id]; + bool haveChildOnSameBranch = false; + foreach (QString childId, cs->children()) { + Changeset *child = m_changesets[childId]; + if (child->branch() == cs->branch()) { + haveChildOnSameBranch = true; + break; + } + } + item->setShowBranch(!haveChildOnSameBranch); + } + + // We need to lay out the changesets in forward chronological + // order. We have no guarantees about the order in which + // changesets appear in the list -- in a simple repository they + // will generally be reverse chronological, but that's far from + // guaranteed. So, sort explicitly using the date comparator + // above + + qStableSort(csets.begin(), csets.end(), compareChangesetsByDate); + + foreach (Changeset *cs, csets) { + DEBUG << "id " << cs->id().toStdString() << ", ts " << cs->timestamp() + << ", date " << cs->datetime().toStdString() << endl; + } + + m_handled.clear(); + foreach (Changeset *cs, csets) { + layoutRow(cs->id()); + } + + allocateBranchHomes(csets); + + m_handled.clear(); + foreach (Changeset *cs, csets) { + foreach (QString parentId, cs->parents()) { + if (!m_handled.contains(parentId) && + m_changesets.contains(parentId)) { + layoutCol(parentId); + } + } + layoutCol(cs->id()); + } + + // Find row and column extents. We know that 0 is an upper bound + // on row, and that mincol must be <= 0 and maxcol >= 0, so these + // initial values are good + + int minrow = 0, maxrow = 0; + int mincol = 0, maxcol = 0; + + foreach (int r, m_alloc.keys()) { + if (r < minrow) minrow = r; + if (r > maxrow) maxrow = r; + ColumnSet &c = m_alloc[r]; + foreach (int i, c) { + if (i < mincol) mincol = i; + if (i > maxcol) maxcol = i; + } + } + + int datemincol = mincol, datemaxcol = maxcol; + + if (mincol == maxcol) { + --datemincol; + ++datemaxcol; + } else if (m_alloc[minrow].contains(mincol)) { + --datemincol; + } + + // We've given the uncommitted item a column, but not a row yet -- + // it always goes at the top + + if (m_uncommitted) { + --minrow; + DEBUG << "putting uncommitted item at row " << minrow << endl; + m_uncommitted->setRow(minrow); + } + + // Changeset items that have nothing to either side of them can be + // made double-width + + foreach (Changeset *cs, csets) { + ChangesetItem *item = m_items[cs->id()]; + if (isAvailable(item->row(), item->column()-1) && + isAvailable(item->row(), item->column()+1)) { + item->setWide(true); + } + } + + if (m_uncommitted) { + if (isAvailable(m_uncommitted->row(), m_uncommitted->column()-1) && + isAvailable(m_uncommitted->row(), m_uncommitted->column()+1)) { + m_uncommitted->setWide(true); + } + } + + QString prevDate; + int changeRow = 0; + + bool even = false; + int n = 0; + + for (int row = minrow; row <= maxrow; ++row) { + + QString date = m_rowDates[row]; + n++; + + if (date != prevDate) { + if (prevDate != "") { + DateItem *item = new DateItem(); + item->setDateString(prevDate); + item->setCols(datemincol, datemaxcol - datemincol + 1); + item->setRows(changeRow, n); + item->setEven(even); + item->setZValue(-2); + m_scene->addDateItem(item); + even = !even; + } + prevDate = date; + changeRow = row; + n = 0; + } + } + + if (n > 0) { + DateItem *item = new DateItem(); + item->setDateString(prevDate); + item->setCols(datemincol, datemaxcol - datemincol + 1); + item->setRows(changeRow, n+1); + item->setEven(even); + item->setZValue(-2); + m_scene->addDateItem(item); + even = !even; + } +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/grapher.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/grapher.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,97 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef GRAPHER_H +#define GRAPHER_H + +#include "changeset.h" +#include "changesetitem.h" +#include "uncommitteditem.h" +#include "changesetscene.h" + +#include +#include +#include + +#include + +class Grapher +{ +public: + Grapher(ChangesetScene *scene); + + void layout(Changesets csets, + QStringList uncommittedParents, + QString uncommittedBranch); + + ChangesetItem *getItemFor(Changeset *cs); + + UncommittedItem *getUncommittedItem() { return m_uncommitted; } + + class LayoutException : public std::exception { + public: + LayoutException(QString message) throw() : m_message(message) { } + virtual ~LayoutException() throw() { } + virtual const char *what() const throw() { + return m_message.toLocal8Bit().data(); + } + protected: + QString m_message; + }; + +private: + ChangesetScene *m_scene; + + typedef QMap IdChangesetMap; + IdChangesetMap m_changesets; + + typedef QMap IdItemMap; + IdItemMap m_items; + + typedef QSet ColumnSet; + typedef QMap GridAlloc; + GridAlloc m_alloc; + + typedef QPair Range; + typedef QMap BranchRangeMap; + BranchRangeMap m_branchRanges; + + typedef QMap BranchColumnMap; + BranchColumnMap m_branchHomes; + + typedef QSet IdSet; + IdSet m_handled; + + typedef QMap RowDateMap; + RowDateMap m_rowDates; + + bool m_showDates; + + QStringList m_uncommittedParents; + int m_uncommittedParentRow; + UncommittedItem *m_uncommitted; + bool m_haveAllocatedUncommittedColumn; + + void layoutRow(QString id); + void layoutCol(QString id); + void allocateBranchHomes(Changesets csets); + bool rangesConflict(const Range &r1, const Range &r2); + int findAvailableColumn(int row, int parent, bool preferParentCol); + bool isAvailable(int row, int col); +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/hgaction.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hgaction.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,120 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef HGACTION_H +#define HGACTION_H + +#include +#include + +enum HGACTIONS +{ + ACT_NONE, + ACT_TEST_HG, + ACT_TEST_HG_EXT, + ACT_QUERY_PATHS, + ACT_QUERY_BRANCH, + ACT_STAT, + ACT_RESOLVE_LIST, + ACT_QUERY_HEADS, + ACT_QUERY_PARENTS, + ACT_LOG, + ACT_LOG_INCREMENTAL, + ACT_REMOVE, + ACT_ADD, + ACT_INCOMING, + ACT_PUSH, + ACT_PULL, + ACT_CLONEFROMREMOTE, + ACT_INIT, + ACT_COMMIT, + ACT_ANNOTATE, + ACT_UNCOMMITTED_SUMMARY, + ACT_DIFF_SUMMARY, + ACT_FOLDERDIFF, + ACT_CHGSETDIFF, + ACT_UPDATE, + ACT_REVERT, + ACT_MERGE, + ACT_SERVE, + ACT_RESOLVE_MARK, + ACT_RETRY_MERGE, + ACT_TAG, + ACT_NEW_BRANCH, + ACT_HG_IGNORE, + ACT_COPY_FILE, + ACT_RENAME_FILE +}; + +struct HgAction +{ + HGACTIONS action; + QString workingDir; + QStringList params; + QString executable; // empty for normal Hg, but gets filled in by hgrunner + void *extraData; + + HgAction() : action(ACT_NONE) { } + + HgAction(HGACTIONS _action, QString _wd, QStringList _params) : + action(_action), workingDir(_wd), params(_params), extraData(0) { } + + HgAction(HGACTIONS _action, QString _wd, QStringList _params, void *_d) : + action(_action), workingDir(_wd), params(_params), extraData(_d) { } + + bool operator==(const HgAction &a) { + return (a.action == action && a.workingDir == workingDir && + a.params == params && a.executable == executable && + a.extraData == extraData); + } + + bool shouldBeFast() const { + switch (action) { + case ACT_NONE: + case ACT_TEST_HG: + case ACT_TEST_HG_EXT: + case ACT_QUERY_PATHS: + case ACT_QUERY_BRANCH: + case ACT_STAT: + case ACT_RESOLVE_LIST: + case ACT_QUERY_HEADS: + case ACT_QUERY_PARENTS: + case ACT_LOG_INCREMENTAL: + return true; + default: + return false; + } + } + + bool mayBeInteractive() const { + switch (action) { + case ACT_TEST_HG_EXT: // so we force the module load to be tested + case ACT_INCOMING: + case ACT_PUSH: + case ACT_PULL: + case ACT_CLONEFROMREMOTE: + case ACT_FOLDERDIFF: + case ACT_CHGSETDIFF: + case ACT_SERVE: + return true; + default: + return false; + } + } +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/hgrunner.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hgrunner.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,544 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "hgrunner.h" +#include "common.h" +#include "debug.h" +#include "settingsdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifndef Q_OS_WIN32 +#include +#include +#include +#endif + +HgRunner::HgRunner(QString myDirPath, QWidget * parent) : + QProgressBar(parent), + m_myDirPath(myDirPath) +{ + m_proc = 0; + + // Always unbundle the extension: even if it already exists (in + // case we're upgrading) and even if we're not going to use it (so + // that it's available in case someone wants to use it later, + // e.g. to fix a malfunctioning setup). But the path we actually + // prefer is the one in the settings first, if it exists; then the + // unbundled one; then anything in the path if for some reason + // unbundling failed + unbundleExtension(); + + setTextVisible(false); + setVisible(false); + m_isRunning = false; +} + +HgRunner::~HgRunner() +{ + closeTerminal(); + if (m_proc) { + m_proc->kill(); + m_proc->deleteLater(); + } +} + +QString HgRunner::getUnbundledFileName() +{ + return SettingsDialog::getUnbundledExtensionFileName(); +} + +QString HgRunner::unbundleExtension() +{ + // Pull out the bundled Python file into a temporary file, and + // copy it to our known extension location, replacing the magic + // text NO_EASYHG_IMPORT_PATH with our installation location + + QString bundled = ":easyhg.py"; + QString unbundled = getUnbundledFileName(); + + QString target = QFileInfo(unbundled).path(); + if (!QDir().mkpath(target)) { + DEBUG << "Failed to make unbundle path " << target << endl; + std::cerr << "Failed to make unbundle path " << target << std::endl; + return ""; + } + + QFile bf(bundled); + DEBUG << "unbundle: bundled file will be " << bundled << endl; + if (!bf.exists() || !bf.open(QIODevice::ReadOnly)) { + DEBUG << "Bundled extension is missing!" << endl; + return ""; + } + + QTemporaryFile tmpfile(QString("%1/easyhg.py.XXXXXX").arg(target)); + tmpfile.setAutoRemove(false); + DEBUG << "unbundle: temp file will be " << tmpfile.fileName() << endl; + if (!tmpfile.open()) { + DEBUG << "Failed to open temporary file " << tmpfile.fileName() << endl; + std::cerr << "Failed to open temporary file " << tmpfile.fileName() << std::endl; + return ""; + } + + QString all = QString::fromUtf8(bf.readAll()); + all.replace("NO_EASYHG_IMPORT_PATH", m_myDirPath); + tmpfile.write(all.toUtf8()); + DEBUG << "unbundle: wrote " << all.length() << " characters" << endl; + + tmpfile.close(); + + QFile ef(unbundled); + if (ef.exists()) { + DEBUG << "unbundle: removing old file " << unbundled << endl; + ef.remove(); + } + DEBUG << "unbundle: renaming " << tmpfile.fileName() << " to " << unbundled << endl; + if (!tmpfile.rename(unbundled)) { + DEBUG << "Failed to move temporary file to target file " << unbundled << endl; + std::cerr << "Failed to move temporary file to target file " << unbundled << std::endl; + return ""; + } + + DEBUG << "Unbundled extension to " << unbundled << endl; + return unbundled; +} + +void HgRunner::requestAction(HgAction action) +{ + DEBUG << "requestAction " << action.action << endl; + bool pushIt = true; + if (m_queue.empty()) { + if (action == m_currentAction) { + // this request is identical to the thing we're executing + DEBUG << "requestAction: we're already handling this one, ignoring identical request" << endl; + pushIt = false; + } + } else { + HgAction last = m_queue.back(); + if (action == last) { + // this request is identical to the previous thing we + // queued which we haven't executed yet + DEBUG << "requestAction: we're already queueing this one, ignoring identical request" << endl; + pushIt = false; + } + } + if (pushIt) m_queue.push_back(action); + checkQueue(); +} + +QString HgRunner::getHgBinaryName() +{ + QSettings settings; + settings.beginGroup("Locations"); + return settings.value("hgbinary", "").toString(); +} + +QString HgRunner::getExtensionLocation() +{ + QSettings settings; + settings.beginGroup("Locations"); + QString extpath = settings.value("extensionpath", "").toString(); + if (extpath != "" && QFile(extpath).exists()) return extpath; + return ""; +} + +void HgRunner::started() +{ + DEBUG << "started" << endl; + /* + m_proc->write("blah\n"); + m_proc->write("blah\n"); + m_proc -> closeWriteChannel(); + */ +} + +void HgRunner::noteUsername(QString name) +{ + m_userName = name; +} + +void HgRunner::noteRealm(QString realm) +{ + m_realm = realm; +} + +void HgRunner::getUsername() +{ + if (m_ptyFile) { + bool ok = false; + QString prompt = tr("User name:"); + if (m_realm != "") { + prompt = tr("User name for \"%1\":").arg(m_realm); + } + QString pwd = QInputDialog::getText + (qobject_cast(parent()), + tr("Enter user name"), prompt, + QLineEdit::Normal, QString(), &ok); + if (ok) { + m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8()); + m_ptyFile->flush(); + return; + } else { + DEBUG << "HgRunner::getUsername: user cancelled" << endl; + killCurrentCommand(); + return; + } + } + // user cancelled or something went wrong + DEBUG << "HgRunner::getUsername: something went wrong" << endl; + killCurrentCommand(); +} + +void HgRunner::getPassword() +{ + if (m_ptyFile) { + bool ok = false; + QString prompt = tr("Password:"); + if (m_userName != "") { + if (m_realm != "") { + prompt = tr("Password for \"%1\" at \"%2\":") + .arg(m_userName).arg(m_realm); + } else { + prompt = tr("Password for user \"%1\":") + .arg(m_userName); + } + } + QString pwd = QInputDialog::getText + (qobject_cast(parent()), + tr("Enter password"), prompt, + QLineEdit::Password, QString(), &ok); + if (ok) { + m_ptyFile->write(QString("%1\n").arg(pwd).toUtf8()); + m_ptyFile->flush(); + return; + } else { + DEBUG << "HgRunner::getPassword: user cancelled" << endl; + killCurrentCommand(); + return; + } + } + // user cancelled or something went wrong + DEBUG << "HgRunner::getPassword: something went wrong" << endl; + killCurrentCommand(); +} + +bool HgRunner::checkPrompts(QString chunk) +{ + //DEBUG << "checkPrompts: " << chunk << endl; + + if (!m_currentAction.mayBeInteractive()) return false; + + QString text = chunk.trimmed(); + QString lower = text.toLower(); + if (lower.endsWith("password:")) { + getPassword(); + return true; + } + if (lower.endsWith("user:") || lower.endsWith("username:")) { + getUsername(); + return true; + } + QRegExp userRe("\\buser(name)?:\\s*([^\\s]+)"); + if (userRe.indexIn(text) >= 0) { + noteUsername(userRe.cap(2)); + } + QRegExp realmRe("\\brealmr:\\s*([^\\s]+)"); + if (realmRe.indexIn(text) >= 0) { + noteRealm(realmRe.cap(1)); + } + return false; +} + +void HgRunner::dataReadyStdout() +{ + DEBUG << "dataReadyStdout" << endl; + QString chunk = QString::fromUtf8(m_proc->readAllStandardOutput()); + if (!checkPrompts(chunk)) { + m_stdout += chunk; + } +} + +void HgRunner::dataReadyStderr() +{ + DEBUG << "dataReadyStderr" << endl; + QString chunk = QString::fromUtf8(m_proc->readAllStandardError()); + DEBUG << chunk; + if (!checkPrompts(chunk)) { + m_stderr += chunk; + } +} + +void HgRunner::dataReadyPty() +{ + DEBUG << "dataReadyPty" << endl; + QString chunk = QString::fromUtf8(m_ptyFile->readAll()); + DEBUG << "chunk of " << chunk.length() << " chars" << endl; + if (!checkPrompts(chunk)) { + m_stdout += chunk; + } +} + +void HgRunner::error(QProcess::ProcessError) +{ + finished(-1, QProcess::CrashExit); +} + +void HgRunner::finished(int procExitCode, QProcess::ExitStatus procExitStatus) +{ + // Save the current action and reset m_currentAction before we + // emit a signal to mark the completion; otherwise we may be + // resetting the action after a slot has already tried to set it + // to something else to start a new action + + HgAction completedAction = m_currentAction; + + m_isRunning = false; + m_currentAction = HgAction(); + + //closeProcInput(); + m_proc->deleteLater(); + m_proc = 0; + + if (completedAction.action == ACT_NONE) { + DEBUG << "HgRunner::finished: WARNING: completed action is ACT_NONE" << endl; + } else { + if (procExitCode == 0 && procExitStatus == QProcess::NormalExit) { + DEBUG << "HgRunner::finished: Command completed successfully" + << endl; +// DEBUG << "stdout is " << m_stdout << endl; + emit commandCompleted(completedAction, m_stdout); + } else { + DEBUG << "HgRunner::finished: Command failed, exit code " + << procExitCode << ", exit status " << procExitStatus + << ", stderr follows" << endl; + DEBUG << m_stderr << endl; + emit commandFailed(completedAction, m_stderr); + } + } + + checkQueue(); +} + +void HgRunner::killCurrentActions() +{ + m_queue.clear(); + killCurrentCommand(); +} + +void HgRunner::killCurrentCommand() +{ + if (m_isRunning) { + m_currentAction.action = ACT_NONE; // so that we don't bother to notify + m_proc->kill(); + } +} + +void HgRunner::checkQueue() +{ + if (m_isRunning) { + return; + } + if (m_queue.empty()) { + hide(); + return; + } + HgAction toRun = m_queue.front(); + m_queue.pop_front(); + DEBUG << "checkQueue: have action: running " << toRun.action << endl; + startCommand(toRun); +} + +void HgRunner::startCommand(HgAction action) +{ + QString executable = action.executable; + bool interactive = false; + QStringList params = action.params; + + if (action.workingDir.isEmpty()) { + // We require a working directory, never just operate in pwd + emit commandFailed(action, "EasyMercurial: No working directory supplied, will not run Mercurial command without one"); + return; + } + + QSettings settings; + settings.beginGroup("General"); + + if (executable == "") { + // This is a Hg command + executable = getHgBinaryName(); + + if (action.mayBeInteractive()) { + params.push_front("ui.interactive=true"); + params.push_front("--config"); + + if (settings.value("useextension", true).toBool()) { + QString extpath = getExtensionLocation(); + params.push_front(QString("extensions.easyhg=%1").arg(extpath)); + params.push_front("--config"); + } + interactive = true; + } + + //!!! want an option to use the mercurial_keyring extension as well + } + + m_isRunning = true; + setRange(0, 0); + if (!action.shouldBeFast()) show(); + m_stdout.clear(); + m_stderr.clear(); + m_realm = ""; + m_userName = ""; + + m_proc = new QProcess; + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + +#ifdef Q_OS_WIN32 + // On Win32 we like to bundle Hg and other executables with EasyHg + if (m_myDirPath != "") { + env.insert("PATH", m_myDirPath + ";" + env.value("PATH")); + } +#endif + +#ifdef Q_OS_MAC + if (settings.value("python32", false).toBool()) { + env.insert("VERSIONER_PYTHON_PREFER_32_BIT", "1"); + } +#endif + + env.insert("LANG", "en_US.utf8"); + env.insert("LC_ALL", "en_US.utf8"); + env.insert("HGPLAIN", "1"); + m_proc->setProcessEnvironment(env); + + connect(m_proc, SIGNAL(started()), this, SLOT(started())); + connect(m_proc, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(error(QProcess::ProcessError))); + connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), + this, SLOT(finished(int, QProcess::ExitStatus))); + connect(m_proc, SIGNAL(readyReadStandardOutput()), + this, SLOT(dataReadyStdout())); + connect(m_proc, SIGNAL(readyReadStandardError()), + this, SLOT(dataReadyStderr())); + + m_proc->setWorkingDirectory(action.workingDir); + + if (interactive) { + openTerminal(); + if (m_ptySlaveFilename != "") { + DEBUG << "HgRunner: connecting to pseudoterminal" << endl; + m_proc->setStandardInputFile(m_ptySlaveFilename); +// m_proc->setStandardOutputFile(m_ptySlaveFilename); +// m_proc->setStandardErrorFile(m_ptySlaveFilename); + } + } + + QString cmdline = executable; + foreach (QString param, params) cmdline += " " + param; + DEBUG << "HgRunner: starting: " << cmdline << " with cwd " + << action.workingDir << endl; + + m_currentAction = action; + + // fill these out with what we actually ran + m_currentAction.executable = executable; + m_currentAction.params = params; + + DEBUG << "set current action to " << m_currentAction.action << endl; + + emit commandStarting(action); + + m_proc->start(executable, params); +} + +void HgRunner::closeProcInput() +{ + DEBUG << "closeProcInput" << endl; + + m_proc->closeWriteChannel(); +} + +void HgRunner::openTerminal() +{ +#ifndef Q_OS_WIN32 + if (m_ptySlaveFilename != "") return; // already open + DEBUG << "HgRunner::openTerminal: trying to open new pty" << endl; + int master = posix_openpt(O_RDWR | O_NOCTTY); + if (master < 0) { + DEBUG << "openpt failed" << endl; + perror("openpt failed"); + return; + } + struct termios t; + if (tcgetattr(master, &t)) { + DEBUG << "tcgetattr failed" << endl; + perror("tcgetattr failed"); + } + cfmakeraw(&t); + if (tcsetattr(master, TCSANOW, &t)) { + DEBUG << "tcsetattr failed" << endl; + perror("tcsetattr failed"); + } + if (grantpt(master)) { + perror("grantpt failed"); + } + if (unlockpt(master)) { + perror("unlockpt failed"); + } + char *slave = ptsname(master); + if (!slave) { + perror("ptsname failed"); + ::close(master); + return; + } + m_ptyMasterFd = master; + m_ptyFile = new QFile(); + connect(m_ptyFile, SIGNAL(readyRead()), this, SLOT(dataReadyPty())); + if (!m_ptyFile->open(m_ptyMasterFd, QFile::ReadWrite)) { + DEBUG << "HgRunner::openTerminal: Failed to open QFile on master fd" << endl; + } + m_ptySlaveFilename = slave; + DEBUG << "HgRunner::openTerminal: succeeded, slave is " + << m_ptySlaveFilename << endl; +#endif +} + +void HgRunner::closeTerminal() +{ +#ifndef Q_OS_WIN32 + if (m_ptySlaveFilename != "") { + delete m_ptyFile; + m_ptyFile = 0; + ::close(m_ptyMasterFd); + m_ptySlaveFilename = ""; + } +#endif +} diff -r 19cce6d2c470 -r b9c153e00e84 src/hgrunner.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hgrunner.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,97 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef HGRUNNER_H +#define HGRUNNER_H + +#include "hgaction.h" + +#include +#include +#include +#include +#include + +#include + +class HgRunner : public QProgressBar +{ + Q_OBJECT + +public: + HgRunner(QString myDirPath, QWidget * parent = 0); + ~HgRunner(); + + void requestAction(HgAction action); + void killCurrentActions(); // kill anything running; clear the queue + +signals: + void commandStarting(HgAction action); + void commandCompleted(HgAction action, QString stdOut); + void commandFailed(HgAction action, QString stdErr); + +private slots: + void started(); + void error(QProcess::ProcessError); + void finished(int procExitCode, QProcess::ExitStatus procExitStatus); + void dataReadyStdout(); + void dataReadyStderr(); + void dataReadyPty(); + +private: + void checkQueue(); + void startCommand(HgAction action); + void closeProcInput(); + void killCurrentCommand(); + + void noteUsername(QString); + void noteRealm(QString); + void getUsername(); + void getPassword(); + bool checkPrompts(QString); + + void openTerminal(); + void closeTerminal(); + + QString getHgBinaryName(); + QString getExtensionLocation(); + + QString getUnbundledFileName(); + QString unbundleExtension(); + + int m_ptyMasterFd; + int m_ptySlaveFd; + QString m_ptySlaveFilename; + QFile *m_ptyFile; + + bool m_isRunning; + QProcess *m_proc; + QString m_stdout; + QString m_stderr; + + QString m_userName; + QString m_realm; + + QString m_myDirPath; + QString m_extensionPath; + + typedef std::deque ActionQueue; + ActionQueue m_queue; + HgAction m_currentAction; +}; + +#endif // HGRUNNER_H diff -r 19cce6d2c470 -r b9c153e00e84 src/hgtabwidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hgtabwidget.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,265 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "hgtabwidget.h" +#include "common.h" +#include "filestatuswidget.h" +#include "historywidget.h" + +#include +#include +#include + +#include + +HgTabWidget::HgTabWidget(QWidget *parent, + QString workFolderPath) : + QTabWidget(parent) +{ + // Work tab + m_fileStatusWidget = new FileStatusWidget; + m_fileStatusWidget->setLocalPath(workFolderPath); + + connect(m_fileStatusWidget, SIGNAL(selectionChanged()), + this, SIGNAL(selectionChanged())); + + connect(m_fileStatusWidget, SIGNAL(showAllChanged(bool)), + this, SIGNAL(showAllChanged(bool))); + + connect(m_fileStatusWidget, SIGNAL(annotateFiles(QStringList)), + this, SIGNAL(annotateFiles(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(diffFiles(QStringList)), + this, SIGNAL(diffFiles(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(commitFiles(QStringList)), + this, SIGNAL(commitFiles(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(revertFiles(QStringList)), + this, SIGNAL(revertFiles(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(renameFiles(QStringList)), + this, SIGNAL(renameFiles(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(copyFiles(QStringList)), + this, SIGNAL(copyFiles(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(addFiles(QStringList)), + this, SIGNAL(addFiles(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(removeFiles(QStringList)), + this, SIGNAL(removeFiles(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(redoFileMerges(QStringList)), + this, SIGNAL(redoFileMerges(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(markFilesResolved(QStringList)), + this, SIGNAL(markFilesResolved(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(ignoreFiles(QStringList)), + this, SIGNAL(ignoreFiles(QStringList))); + + connect(m_fileStatusWidget, SIGNAL(unIgnoreFiles(QStringList)), + this, SIGNAL(unIgnoreFiles(QStringList))); + + addTab(m_fileStatusWidget, tr("My work")); + + // History graph tab + m_historyWidget = new HistoryWidget; + addTab(m_historyWidget, tr("History")); + + connect(m_historyWidget, SIGNAL(commit()), + this, SIGNAL(commit())); + + connect(m_historyWidget, SIGNAL(revert()), + this, SIGNAL(revert())); + + connect(m_historyWidget, SIGNAL(showSummary()), + this, SIGNAL(showSummary())); + + connect(m_historyWidget, SIGNAL(newBranch()), + this, SIGNAL(newBranch())); + + connect(m_historyWidget, SIGNAL(noBranch()), + this, SIGNAL(noBranch())); + + connect(m_historyWidget, SIGNAL(diffWorkingFolder()), + this, SIGNAL(diffWorkingFolder())); + + connect(m_historyWidget, SIGNAL(showWork()), + this, SLOT(showWorkTab())); + + connect(m_historyWidget, SIGNAL(updateTo(QString)), + this, SIGNAL(updateTo(QString))); + + connect(m_historyWidget, SIGNAL(diffToCurrent(QString)), + this, SIGNAL(diffToCurrent(QString))); + + connect(m_historyWidget, SIGNAL(diffToParent(QString, QString)), + this, SIGNAL(diffToParent(QString, QString))); + + connect(m_historyWidget, SIGNAL(showSummary(Changeset *)), + this, SIGNAL(showSummary(Changeset *))); + + connect(m_historyWidget, SIGNAL(mergeFrom(QString)), + this, SIGNAL(mergeFrom(QString))); + + connect(m_historyWidget, SIGNAL(newBranch(QString)), + this, SIGNAL(newBranch(QString))); + + connect(m_historyWidget, SIGNAL(tag(QString)), + this, SIGNAL(tag(QString))); +} + +void HgTabWidget::clearSelections() +{ + m_fileStatusWidget->clearSelections(); +} + +void HgTabWidget::setCurrent(QStringList ids, QString branch) +{ + bool showUncommitted = haveChangesToCommit(); + m_historyWidget->setCurrent(ids, branch, showUncommitted); +} + +void HgTabWidget::updateFileStates() +{ + m_fileStatusWidget->updateWidgets(); +} + +void HgTabWidget::updateHistory() +{ + m_historyWidget->update(); +} + +bool HgTabWidget::canDiff() const +{ + return canRevert(); +} + +bool HgTabWidget::canCommit() const +{ + if (!m_fileStatusWidget->haveChangesToCommit()) return false; + if (!m_fileStatusWidget->getAllUnresolvedFiles().empty()) return false; + return true; +} + +bool HgTabWidget::canRevert() const +{ + // Not the same as canCommit() -- we can revert (and diff) + // unresolved files, but we can't commit them + if (!m_fileStatusWidget->haveChangesToCommit() && + m_fileStatusWidget->getAllUnresolvedFiles().empty()) return false; + return true; +} + +bool HgTabWidget::canAdd() const +{ + // Permit this only when work tab is visible + if (currentIndex() != 0) return false; + + QStringList addable = m_fileStatusWidget->getSelectedAddableFiles(); + if (addable.empty()) return false; + + QStringList removable = m_fileStatusWidget->getSelectedRemovableFiles(); + if (!removable.empty()) return false; + + return true; +} + +bool HgTabWidget::canRemove() const +{ + // Permit this only when work tab is visible + if (currentIndex() != 0) return false; + + if (m_fileStatusWidget->getSelectedRemovableFiles().empty()) return false; + if (!m_fileStatusWidget->getSelectedAddableFiles().empty()) return false; + return true; +} + +bool HgTabWidget::canResolve() const +{ + return !m_fileStatusWidget->getAllUnresolvedFiles().empty(); +} + +bool HgTabWidget::haveChangesToCommit() const +{ + return m_fileStatusWidget->haveChangesToCommit(); +} + +QStringList HgTabWidget::getAllCommittableFiles() const +{ + return m_fileStatusWidget->getAllCommittableFiles(); +} + +QStringList HgTabWidget::getAllRevertableFiles() const +{ + return m_fileStatusWidget->getAllRevertableFiles(); +} + +QStringList HgTabWidget::getSelectedAddableFiles() const +{ + return m_fileStatusWidget->getSelectedAddableFiles(); +} + +QStringList HgTabWidget::getSelectedRemovableFiles() const +{ + return m_fileStatusWidget->getSelectedRemovableFiles(); +} + +QStringList HgTabWidget::getAllUnresolvedFiles() const +{ + return m_fileStatusWidget->getAllUnresolvedFiles(); +} + +void HgTabWidget::updateWorkFolderFileList(QString fileList) +{ + m_fileStates.parseStates(fileList); + m_fileStatusWidget->setFileStates(m_fileStates); +} + +void HgTabWidget::setNewLog(QString hgLogList) +{ + m_historyWidget->parseNewLog(hgLogList); + if (m_historyWidget->haveNewItems()) { + showHistoryTab(); + } +} + +void HgTabWidget::addIncrementalLog(QString hgLogList) +{ + m_historyWidget->parseIncrementalLog(hgLogList); + if (m_historyWidget->haveNewItems()) { + showHistoryTab(); + } +} + +void HgTabWidget::setLocalPath(QString workFolderPath) +{ + m_fileStatusWidget->setLocalPath(workFolderPath); +} + +void HgTabWidget::showWorkTab() +{ + setCurrentWidget(m_fileStatusWidget); +} + +void HgTabWidget::showHistoryTab() +{ + setCurrentWidget(m_historyWidget); +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/hgtabwidget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hgtabwidget.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,117 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef HGTABWIDGET_H +#define HGTABWIDGET_H + +#include "changeset.h" +#include "common.h" +#include "filestates.h" + +#include +#include +#include +#include +#include +#include +#include + +class FileStatusWidget; +class HistoryWidget; + +class HgTabWidget: public QTabWidget +{ + Q_OBJECT + +public: + HgTabWidget(QWidget *parent, QString workFolderPath); + + void updateWorkFolderFileList(QString fileList); + + void setNewLog(QString hgLogList); + void addIncrementalLog(QString hgLogList); + + void setLocalPath(QString workFolderPath); + + void setCurrent(QStringList ids, QString branch); + + void updateFileStates(); + void updateHistory(); + + FileStates getFileStates() { return m_fileStates; } + + bool canDiff() const; + bool canCommit() const; + bool canRevert() const; + bool canAdd() const; + bool canRemove() const; + bool canResolve() const; + bool haveChangesToCommit() const; + + QStringList getAllCommittableFiles() const; + QStringList getAllRevertableFiles() const; + QStringList getAllUnresolvedFiles() const; + + QStringList getSelectedAddableFiles() const; + QStringList getSelectedRemovableFiles() const; + +signals: + void selectionChanged(); + void showAllChanged(bool); + + void commit(); + void revert(); + void diffWorkingFolder(); + void showSummary(); + void newBranch(); + void noBranch(); + + void updateTo(QString id); + void diffToParent(QString id, QString parent); + void showSummary(Changeset *); + void diffToCurrent(QString id); + void mergeFrom(QString id); + void newBranch(QString id); + void tag(QString id); + + void annotateFiles(QStringList); + void diffFiles(QStringList); + void commitFiles(QStringList); + void revertFiles(QStringList); + void renameFiles(QStringList); + void copyFiles(QStringList); + void addFiles(QStringList); + void removeFiles(QStringList); + void redoFileMerges(QStringList); + void markFilesResolved(QStringList); + void ignoreFiles(QStringList); + void unIgnoreFiles(QStringList); + +public slots: + void clearSelections(); + void showWorkTab(); + void showHistoryTab(); + +private: + FileStatusWidget *m_fileStatusWidget; + HistoryWidget *m_historyWidget; + FileStates m_fileStates; + + Changesets parseChangeSets(QString changeSetsStr); +}; + +#endif // HGTABWIDGET_H diff -r 19cce6d2c470 -r b9c153e00e84 src/historywidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/historywidget.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,298 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "historywidget.h" + +#include "changesetscene.h" +#include "panned.h" +#include "panner.h" +#include "grapher.h" +#include "debug.h" +#include "uncommitteditem.h" + +#include + +#include + +HistoryWidget::HistoryWidget() : + m_showUncommitted(false), + m_refreshNeeded(false) +{ + m_panned = new Panned; + m_panner = new Panner; + + m_panned->setDragMode(QGraphicsView::ScrollHandDrag); + m_panned->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); + + QGridLayout *layout = new QGridLayout; + layout->addWidget(m_panned, 0, 0); + layout->addWidget(m_panner, 0, 1); + m_panner->setMaximumWidth(80); + m_panner->connectToPanned(m_panned); + + setLayout(layout); +} + +HistoryWidget::~HistoryWidget() +{ + clearChangesets(); +} + +QGraphicsScene *HistoryWidget::scene() +{ + return m_panned->scene(); +} + +void HistoryWidget::clearChangesets() +{ + foreach (Changeset *cs, m_changesets) delete cs; + m_changesets.clear(); +} + +void HistoryWidget::setCurrent(QStringList ids, QString branch, + bool showUncommitted) +{ + if (m_currentIds == ids && + m_currentBranch == branch && + m_showUncommitted == showUncommitted) return; + + DEBUG << "HistoryWidget::setCurrent: " << ids.size() << " ids, " + << "showUncommitted: " << showUncommitted << endl; + + m_currentIds.clear(); + m_currentBranch = branch; + m_showUncommitted = showUncommitted; + + if (ids.empty()) return; + + foreach (QString id, ids) { + m_currentIds.push_back(id); + } + + m_refreshNeeded = true; +} + +void HistoryWidget::parseNewLog(QString log) +{ + DEBUG << "HistoryWidget::parseNewLog: log has " << log.length() << " chars" << endl; + Changesets csets = Changeset::parseChangesets(log); + DEBUG << "HistoryWidget::parseNewLog: log has " << csets.size() << " changesets" << endl; + replaceChangesets(csets); + m_refreshNeeded = true; +} + +void HistoryWidget::parseIncrementalLog(QString log) +{ + DEBUG << "HistoryWidget::parseIncrementalLog: log has " << log.length() << " chars" << endl; + Changesets csets = Changeset::parseChangesets(log); + DEBUG << "HistoryWidget::parseIncrementalLog: log has " << csets.size() << " changesets" << endl; + if (!csets.empty()) { + addChangesets(csets); + } + m_refreshNeeded = true; +} + +void HistoryWidget::replaceChangesets(Changesets csets) +{ + QSet oldIds; + foreach (Changeset *cs, m_changesets) { + oldIds.insert(cs->id()); + } + + QSet newIds; + foreach (Changeset *cs, csets) { + if (!oldIds.contains(cs->id())) { + newIds.insert(cs->id()); + } + } + + if (newIds.size() == csets.size()) { + // completely new set, unrelated to the old: don't mark new + m_newIds.clear(); + } else { + m_newIds = newIds; + } + + clearChangesets(); + m_changesets = csets; +} + +void HistoryWidget::addChangesets(Changesets csets) +{ + m_newIds.clear(); + + if (csets.empty()) return; + + foreach (Changeset *cs, csets) { + m_newIds.insert(cs->id()); + } + + DEBUG << "addChangesets: " << csets.size() << " new changesets have (" + << m_changesets.size() << " already)" << endl; + + csets << m_changesets; + m_changesets = csets; +} + +void HistoryWidget::update() +{ + if (m_refreshNeeded) { + layoutAll(); + } +} + +void HistoryWidget::layoutAll() +{ + m_refreshNeeded = false; + + setChangesetParents(); + + ChangesetScene *scene = new ChangesetScene(); + ChangesetItem *tipItem = 0; + + QGraphicsScene *oldScene = m_panned->scene(); + + m_panned->setScene(0); + m_panner->setScene(0); + + delete oldScene; + + QGraphicsItem *toFocus = 0; + + if (!m_changesets.empty()) { + Grapher g(scene); + try { + g.layout(m_changesets, + m_showUncommitted ? m_currentIds : QStringList(), + m_currentBranch); + } catch (std::string s) { + std::cerr << "Internal error: Layout failed: " << s << std::endl; + } + toFocus = g.getUncommittedItem(); + if (!toFocus) { + toFocus = g.getItemFor(m_changesets[0]); + } + } + + m_panned->setScene(scene); + m_panner->setScene(scene); + + updateNewAndCurrentItems(); + + if (toFocus) { + toFocus->ensureVisible(); + } + + connectSceneSignals(); +} + +void HistoryWidget::setChangesetParents() +{ + for (int i = 0; i < m_changesets.size(); ++i) { + Changeset *cs = m_changesets[i]; + // Need to reset this, as Grapher::layout will recalculate it + // and we don't want to end up with twice the children for + // each parent... + cs->setChildren(QStringList()); + } + for (int i = 0; i+1 < m_changesets.size(); ++i) { + Changeset *cs = m_changesets[i]; + if (cs->parents().empty()) { + QStringList list; + list.push_back(m_changesets[i+1]->id()); + cs->setParents(list); + } + } +} + +void HistoryWidget::updateNewAndCurrentItems() +{ + QGraphicsScene *scene = m_panned->scene(); + if (!scene) return; + + QList items = scene->items(); + foreach (QGraphicsItem *it, items) { + + ChangesetItem *csit = dynamic_cast(it); + if (!csit) continue; + + QString id = csit->getChangeset()->id(); + + bool current = m_currentIds.contains(id); + if (current) { + DEBUG << "id " << id << " is current" << endl; + } + bool newid = m_newIds.contains(id); + if (newid) { + DEBUG << "id " << id << " is new" << endl; + } + + if (csit->isCurrent() != current || csit->isNew() != newid) { + csit->setCurrent(current); + csit->setNew(newid); + csit->update(); + } + } +} + +void HistoryWidget::connectSceneSignals() +{ + ChangesetScene *scene = qobject_cast(m_panned->scene()); + if (!scene) return; + + connect(scene, SIGNAL(commit()), + this, SIGNAL(commit())); + + connect(scene, SIGNAL(revert()), + this, SIGNAL(revert())); + + connect(scene, SIGNAL(diffWorkingFolder()), + this, SIGNAL(diffWorkingFolder())); + + connect(scene, SIGNAL(showSummary()), + this, SIGNAL(showSummary())); + + connect(scene, SIGNAL(showWork()), + this, SIGNAL(showWork())); + + connect(scene, SIGNAL(newBranch()), + this, SIGNAL(newBranch())); + + connect(scene, SIGNAL(noBranch()), + this, SIGNAL(noBranch())); + + connect(scene, SIGNAL(updateTo(QString)), + this, SIGNAL(updateTo(QString))); + + connect(scene, SIGNAL(diffToCurrent(QString)), + this, SIGNAL(diffToCurrent(QString))); + + connect(scene, SIGNAL(diffToParent(QString, QString)), + this, SIGNAL(diffToParent(QString, QString))); + + connect(scene, SIGNAL(showSummary(Changeset *)), + this, SIGNAL(showSummary(Changeset *))); + + connect(scene, SIGNAL(mergeFrom(QString)), + this, SIGNAL(mergeFrom(QString))); + + connect(scene, SIGNAL(newBranch(QString)), + this, SIGNAL(newBranch(QString))); + + connect(scene, SIGNAL(tag(QString)), + this, SIGNAL(tag(QString))); +} diff -r 19cce6d2c470 -r b9c153e00e84 src/historywidget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/historywidget.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,86 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef HISTORYWIDGET_H +#define HISTORYWIDGET_H + +#include "changeset.h" + +#include +#include + +class Panned; +class Panner; +class UncommittedItem; +class QGraphicsScene; + +class HistoryWidget : public QWidget +{ + Q_OBJECT + +public: + HistoryWidget(); + virtual ~HistoryWidget(); + + void setCurrent(QStringList ids, QString branch, bool showUncommitted); + + void parseNewLog(QString log); + void parseIncrementalLog(QString log); + + bool haveNewItems() const { return !m_newIds.empty(); } + + void update(); + +signals: + void commit(); + void revert(); + void diffWorkingFolder(); + void showSummary(); + void showWork(); + void newBranch(); + void noBranch(); + + void updateTo(QString id); + void diffToParent(QString id, QString parent); + void showSummary(Changeset *); + void diffToCurrent(QString id); + void mergeFrom(QString id); + void newBranch(QString id); + void tag(QString id); + +private: + Changesets m_changesets; + QStringList m_currentIds; + QString m_currentBranch; + QSet m_newIds; + bool m_showUncommitted; + bool m_refreshNeeded; + + Panned *m_panned; + Panner *m_panner; + + QGraphicsScene *scene(); + void clearChangesets(); + void replaceChangesets(Changesets); + void addChangesets(Changesets); + void layoutAll(); + void setChangesetParents(); + void updateNewAndCurrentItems(); + void connectSceneSignals(); +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/incomingdialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/incomingdialog.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "incomingdialog.h" +#include "changeset.h" +#include "common.h" + +#include +#include +#include +#include +#include +#include + +IncomingDialog::IncomingDialog(QWidget *w, QString text) : + QDialog(w) +{ + QString head; + QString body; + bool scroll; + + Changesets csets = Changeset::parseChangesets(text); + if (csets.empty()) { + head = tr("No changes waiting to pull"); + if (text.trimmed() != "") { + body = QString("

%1

%2") + .arg(tr("The command output was:")) + .arg(xmlEncode(text).replace("\n", "
")); + } else { + body = tr("Your local repository already contains all changes found in the remote repository."); + } + scroll = false; + } else { + head = tr("There are %n change(s) ready to pull", "", csets.size()); + foreach (Changeset *cs, csets) { + body += cs->formatHtml() + "

"; + delete cs; + } + scroll = true; + } + + QGridLayout *layout = new QGridLayout; + setLayout(layout); + + QLabel *info = new QLabel; + QStyle *style = qApp->style(); + int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this); + info->setPixmap(style->standardIcon(QStyle::SP_MessageBoxInformation, 0, this) + .pixmap(iconSize, iconSize)); + layout->addWidget(info, 0, 0, 2, 1); + + QLabel *headLabel = new QLabel(QString("

%1

").arg(head)); + layout->addWidget(headLabel, 0, 1); + + QLabel *textLabel = new QLabel(body); + if (csets.empty()) textLabel->setWordWrap(true); + + if (scroll) { + QScrollArea *sa = new QScrollArea; + layout->addWidget(sa, 1, 1); + layout->setRowStretch(1, 20); + sa->setWidget(textLabel); + } else { + layout->addWidget(textLabel, 1, 1); + } + + QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); + connect(bb, SIGNAL(accepted()), this, SLOT(accept())); + layout->addWidget(bb, 2, 0, 1, 2); + + layout->setColumnStretch(1, 20); + setMinimumWidth(400); +} + + diff -r 19cce6d2c470 -r b9c153e00e84 src/incomingdialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/incomingdialog.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,31 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef INCOMING_DIALOG_H +#define INCOMING_DIALOG_H + +#include + +class IncomingDialog : public QDialog +{ + Q_OBJECT + +public: + IncomingDialog(QWidget *parent, QString incoming); +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/logparser.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/logparser.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,54 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "logparser.h" + +#include "debug.h" + +#include +#include + +LogParser::LogParser(QString text, QString separator) : + m_text(text), m_sep(separator) +{ + m_text.replace("\r\n", "\n"); +} + +QStringList LogParser::split() +{ + return m_text.split("\n\n", QString::SkipEmptyParts); +} + +LogList LogParser::parse() +{ + LogList results; + QRegExp re(QString("^(\\w+)\\s*%1\\s+([^\\s].*)$").arg(m_sep)); + QStringList entries = split(); + foreach (QString entry, entries) { + LogEntry dictionary; + QStringList lines = entry.split('\n'); + foreach (QString line, lines) { + if (re.indexIn(line) == 0) { + QString key = re.cap(1); + QString value = re.cap(2); + dictionary[key.trimmed()] = value; + } + } + results.push_back(dictionary); + } + return results; +} diff -r 19cce6d2c470 -r b9c153e00e84 src/logparser.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/logparser.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,42 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef LOGPARSER_H +#define LOGPARSER_H + +#include +#include +#include +#include + +typedef QMap LogEntry; +typedef QList LogList; + +class LogParser : public QObject +{ +public: + LogParser(QString text, QString separator = ":"); + + QStringList split(); + LogList parse(); + +private: + QString m_text; + QString m_sep; +}; + +#endif // LOGPARSER_H diff -r 19cce6d2c470 -r b9c153e00e84 src/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,64 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "mainwindow.h" +#include "common.h" +#include "debug.h" + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QApplication::setOrganizationName("easymercurial"); + QApplication::setOrganizationDomain("easymercurial.org"); + QApplication::setApplicationName(QApplication::tr("EasyMercurial")); + + // Lose our controlling terminal (so we can provide a new pty to + // capture password requests) + loseControllingTerminal(); + + installSignalHandlers(); + + QTranslator translator; + QString language = QLocale::system().name(); + QString trname = QString("easyhg_%1").arg(language); + translator.load(trname, ":"); + app.installTranslator(&translator); + + QStringList args = app.arguments(); + + QString myDirPath = QFileInfo(QDir::current().absoluteFilePath(args[0])) + .canonicalPath(); + + MainWindow mainWin(myDirPath); + mainWin.show(); + + if (args.size() == 2) { + QString path = args[1]; + DEBUG << "Opening " << args[1] << endl; + if (QDir(path).exists()) { + path = QDir(path).canonicalPath(); + mainWin.open(path); + } + } + + return app.exec(); +} diff -r 19cce6d2c470 -r b9c153e00e84 src/mainwindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainwindow.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,2839 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mainwindow.h" +#include "multichoicedialog.h" +#include "startupdialog.h" +#include "colourset.h" +#include "debug.h" +#include "logparser.h" +#include "confirmcommentdialog.h" +#include "incomingdialog.h" +#include "settingsdialog.h" +#include "moreinformationdialog.h" +#include "annotatedialog.h" +#include "version.h" +#include "workstatuswidget.h" + + +MainWindow::MainWindow(QString myDirPath) : + m_myDirPath(myDirPath), + m_fsWatcherGeneralTimer(0), + m_fsWatcherRestoreTimer(0), + m_fsWatcherSuspended(false) +{ + setWindowIcon(QIcon(":images/easyhg-icon.png")); + + QString wndTitle; + + m_showAllFiles = false; + + m_fsWatcher = 0; + m_commitsSincePush = 0; + m_shouldHgStat = true; + + createActions(); + createMenus(); + createToolBars(); + createStatusBar(); + + m_runner = new HgRunner(m_myDirPath, this); + connect(m_runner, SIGNAL(commandStarting(HgAction)), + this, SLOT(commandStarting(HgAction))); + connect(m_runner, SIGNAL(commandCompleted(HgAction, QString)), + this, SLOT(commandCompleted(HgAction, QString))); + connect(m_runner, SIGNAL(commandFailed(HgAction, QString)), + this, SLOT(commandFailed(HgAction, QString))); + statusBar()->addPermanentWidget(m_runner); + + setWindowTitle(tr("EasyMercurial")); + + m_remoteRepoPath = ""; + m_workFolderPath = ""; + + readSettings(); + + m_justMerged = false; + + QWidget *central = new QWidget(this); + setCentralWidget(central); + + QGridLayout *cl = new QGridLayout(central); + int row = 0; + +#ifndef Q_OS_MAC + cl->setMargin(0); +#endif + + m_workStatus = new WorkStatusWidget(this); + cl->addWidget(m_workStatus, row++, 0); + + m_hgTabs = new HgTabWidget(central, m_workFolderPath); + connectTabsSignals(); + + cl->addWidget(m_hgTabs, row++, 0); + + connect(m_hgTabs, SIGNAL(selectionChanged()), + this, SLOT(enableDisableActions())); + connect(m_hgTabs, SIGNAL(showAllChanged(bool)), + this, SLOT(showAllChanged(bool))); + + setUnifiedTitleAndToolBarOnMac(true); + connectActions(); + clearState(); + enableDisableActions(); + + if (m_firstStart) { + startupDialog(); + } + + SettingsDialog::findDefaultLocations(m_myDirPath); + + ColourSet *cs = ColourSet::instance(); + cs->clearDefaultNames(); + cs->addDefaultName(""); + cs->addDefaultName("default"); + cs->addDefaultName(getUserInfo()); + + hgTest(); + updateRecentMenu(); +} + + +void MainWindow::closeEvent(QCloseEvent *) +{ + writeSettings(); + delete m_fsWatcher; +} + + +QString MainWindow::getUserInfo() const +{ + QSettings settings; + settings.beginGroup("User Information"); + QString name = settings.value("name", getUserRealName()).toString(); + QString email = settings.value("email", "").toString(); + + QString identifier; + + if (email != "") { + identifier = QString("%1 <%2>").arg(name).arg(email); + } else { + identifier = name; + } + + return identifier; +} + +void MainWindow::about() +{ + QMessageBox::about(this, tr("About EasyMercurial"), + tr("

EasyMercurial v%1

" +#ifdef Q_OS_MAC + "" +#endif + "

EasyMercurial is a simple user interface for the " + "Mercurial version control system.

" + "

Credits and Copyright

" + "

Development carried out by Chris Cannam for " + "SoundSoftware.ac.uk at the Centre for Digital Music, " + "Queen Mary, University of London.

" + "

EasyMercurial is based on HgExplorer by " + "Jari Korhonen, with thanks.

" + "

" + "Copyright © 2011 Queen Mary, University of London.
" + "Copyright © 2010 Jari Korhonen.
" + "Copyright © 2011 Chris Cannam." + "

" + "

" + "This program requires Mercurial, by Matt Mackall and others.
" + "This program uses Qt by Nokia.
" + "This program uses Nuvola icons by David Vignoni.
" + "This program may use KDiff3 by Joachim Eibl.
" + "This program may use PyQt by River Bank Computing.
" + "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others." + "

" + "

License

" + "

This program is free software; you can redistribute it and/or " + "modify it under the terms of the GNU General Public License as " + "published by the Free Software Foundation; either version 2 of the " + "License, or (at your option) any later version. See the file " + "COPYING included with this distribution for more information.

" +#ifdef Q_OS_MAC + "
" +#endif + ).arg(EASYHG_VERSION)); +} + +void MainWindow::clearSelections() +{ + m_hgTabs->clearSelections(); +} + +void MainWindow::showAllChanged(bool s) +{ + m_showAllFiles = s; + hgQueryPaths(); +} + +void MainWindow::hgRefresh() +{ + clearState(); + hgQueryPaths(); +} + +void MainWindow::hgTest() +{ + QStringList params; + //!!! should we test version output? Really we want at least 1.7.x + //!!! for options such as merge --tool + params << "--version"; + m_runner->requestAction(HgAction(ACT_TEST_HG, m_myDirPath, params)); +} + +void MainWindow::hgTestExtension() +{ + QStringList params; + params << "--version"; + m_runner->requestAction(HgAction(ACT_TEST_HG_EXT, m_myDirPath, params)); +} + +void MainWindow::hgStat() +{ + QStringList params; + + if (m_showAllFiles) { + params << "stat" << "-A"; + } else { + params << "stat" << "-ardum"; + } + + m_lastStatOutput = ""; + + m_runner->requestAction(HgAction(ACT_STAT, m_workFolderPath, params)); +} + +void MainWindow::hgQueryPaths() +{ + // Quickest is to just read the file + + QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc"); + + QString path; + + if (hgrc.exists()) { + QSettings s(hgrc.canonicalFilePath(), QSettings::IniFormat); + s.beginGroup("paths"); + path = s.value("default").toString(); + } + + m_remoteRepoPath = path; + + // We have to do this here, because commandCompleted won't be called + MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); + MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); + updateWorkFolderAndRepoNames(); + + hgQueryBranch(); + return; + +/* The classic method! + + QStringList params; + params << "paths"; + m_runner->requestAction(HgAction(ACT_QUERY_PATHS, m_workFolderPath, params)); +*/ +} + +void MainWindow::hgQueryBranch() +{ + // Quickest is to just read the file + + QFile hgbr(m_workFolderPath + "/.hg/branch"); + + QString br = "default"; + + if (hgbr.exists() && hgbr.open(QFile::ReadOnly)) { + QByteArray ba = hgbr.readLine(); + br = QString::fromUtf8(ba).trimmed(); + } + + m_currentBranch = br; + + // We have to do this here, because commandCompleted won't be called + hgStat(); + return; + +/* The classic method! + + QStringList params; + params << "branch"; + m_runner->requestAction(HgAction(ACT_QUERY_BRANCH, m_workFolderPath, params)); +*/ +} + +void MainWindow::hgQueryHeads() +{ + QStringList params; + // On empty repos, "hg heads" will fail -- we don't care about + // that. Use --closed option so as to include closed branches; + // otherwise we'll be stuck if the user updates into one, and our + // incremental log will end up with spurious stuff in it because + // we won't be pruning at the ends of closed branches + params << "heads" << "--closed"; + m_runner->requestAction(HgAction(ACT_QUERY_HEADS, m_workFolderPath, params)); +} + +void MainWindow::hgLog() +{ + QStringList params; + params << "log"; + params << "--template"; + params << Changeset::getLogTemplate(); + + m_runner->requestAction(HgAction(ACT_LOG, m_workFolderPath, params)); +} + +void MainWindow::hgLogIncremental(QStringList prune) +{ + // Sometimes we can be called with prune empty -- it represents + // the current heads, but if we have none already and for some + // reason are being prompted for an incremental update, we may run + // into trouble. In that case, make this a full log instead + + if (prune.empty()) { + hgLog(); + return; + } + + QStringList params; + params << "log"; + + foreach (QString p, prune) { + params << "--prune" << Changeset::hashOf(p); + } + + params << "--template"; + params << Changeset::getLogTemplate(); + + m_runner->requestAction(HgAction(ACT_LOG_INCREMENTAL, m_workFolderPath, params)); +} + +void MainWindow::hgQueryParents() +{ + QStringList params; + params << "parents"; + m_runner->requestAction(HgAction(ACT_QUERY_PARENTS, m_workFolderPath, params)); +} + +void MainWindow::hgAnnotateFiles(QStringList files) +{ + QStringList params; + + if (!files.isEmpty()) { + params << "annotate" << "-udqc" << "--" << files; + m_runner->requestAction(HgAction(ACT_ANNOTATE, m_workFolderPath, params)); + } +} + +void MainWindow::hgResolveList() +{ + QStringList params; + + params << "resolve" << "--list"; + m_runner->requestAction(HgAction(ACT_RESOLVE_LIST, m_workFolderPath, params)); +} + +void MainWindow::hgAdd() +{ + // hgExplorer permitted adding "all" files -- I'm not sure + // that one is a good idea, let's require the user to select + + hgAddFiles(m_hgTabs->getSelectedAddableFiles()); +} + +void MainWindow::hgAddFiles(QStringList files) +{ + QStringList params; + + if (!files.empty()) { + params << "add" << "--" << files; + m_runner->requestAction(HgAction(ACT_ADD, m_workFolderPath, params)); + } +} + +void MainWindow::hgRemove() +{ + hgRemoveFiles(m_hgTabs->getSelectedRemovableFiles()); +} + +void MainWindow::hgRemoveFiles(QStringList files) +{ + QStringList params; + + if (!files.empty()) { + params << "remove" << "--after" << "--force" << "--" << files; + m_runner->requestAction(HgAction(ACT_REMOVE, m_workFolderPath, params)); + } +} + +void MainWindow::hgCommit() +{ + hgCommitFiles(QStringList()); +} + +void MainWindow::hgCommitFiles(QStringList files) +{ + QStringList params; + QString comment; + + if (m_justMerged) { + comment = m_mergeCommitComment; + } + + QStringList allFiles = m_hgTabs->getAllCommittableFiles(); + QStringList reportFiles = files; + if (reportFiles.empty()) { + reportFiles = allFiles; + } + + QString subsetNote; + if (reportFiles != allFiles) { + subsetNote = tr("

Note: you are committing only the files you have selected, not all of the files that have been changed!"); + } + + QString cf(tr("Commit files")); + + QString branchText; + if (m_currentBranch == "" || m_currentBranch == "default") { + branchText = tr("the default branch"); + } else { + branchText = tr("branch \"%1\"").arg(m_currentBranch); + } + + if (ConfirmCommentDialog::confirmAndGetLongComment + (this, + cf, + tr("

%1

%2%3").arg(cf) + .arg(tr("You are about to commit the following files to %1:").arg(branchText)) + .arg(subsetNote), + tr("

%1

%2%3").arg(cf) + .arg(tr("You are about to commit %n file(s) to %1.", "", reportFiles.size()).arg(branchText)) + .arg(subsetNote), + reportFiles, + comment, + tr("Co&mmit"))) { + + if (!m_justMerged && !files.empty()) { + // User wants to commit selected file(s) (and this is not + // merge commit, which would fail if we selected files) + params << "commit" << "--message" << comment + << "--user" << getUserInfo() << "--" << files; + } else { + // Commit all changes + params << "commit" << "--message" << comment + << "--user" << getUserInfo(); + } + + m_runner->requestAction(HgAction(ACT_COMMIT, m_workFolderPath, params)); + m_mergeCommitComment = ""; + } +} + +QString MainWindow::filterTag(QString tag) +{ + for(int i = 0; i < tag.size(); i++) { + if (tag[i].isLower() || tag[i].isUpper() || + tag[i].isDigit() || (tag[i] == QChar('.'))) { + //ok + } else { + tag[i] = QChar('_'); + } + } + return tag; +} + + +void MainWindow::hgNewBranch() +{ + QStringList params; + QString branch; + + if (ConfirmCommentDialog::confirmAndGetShortComment + (this, + tr("New Branch"), + tr("Enter new branch name:"), + branch, + tr("Start &Branch"))) { + if (!branch.isEmpty()) {//!!! do something better if it is empty + + params << "branch" << filterTag(branch); + m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params)); + } + } +} + +void MainWindow::hgNoBranch() +{ + if (m_currentParents.empty()) return; + + QString parentBranch = m_currentParents[0]->branch(); + if (parentBranch == "") parentBranch = "default"; + + QStringList params; + params << "branch" << parentBranch; + m_runner->requestAction(HgAction(ACT_NEW_BRANCH, m_workFolderPath, params)); +} + +void MainWindow::hgTag(QString id) +{ + QStringList params; + QString tag; + + if (ConfirmCommentDialog::confirmAndGetShortComment + (this, + tr("Tag"), + tr("Enter tag:"), + tag, + tr("Add &Tag"))) { + if (!tag.isEmpty()) {//!!! do something better if it is empty + + params << "tag" << "--user" << getUserInfo(); + params << "--rev" << Changeset::hashOf(id) << filterTag(tag); + + m_runner->requestAction(HgAction(ACT_TAG, m_workFolderPath, params)); + } + } +} + +void MainWindow::hgIgnore() +{ + QString hgIgnorePath; + QStringList params; + + hgIgnorePath = m_workFolderPath; + hgIgnorePath += "/.hgignore"; + + if (!QDir(m_workFolderPath).exists()) return; + QFile f(hgIgnorePath); + if (!f.exists()) { + f.open(QFile::WriteOnly); + QTextStream *ts = new QTextStream(&f); + *ts << "syntax: glob\n"; + delete ts; + f.close(); + } + + params << hgIgnorePath; + + QString editor = getEditorBinaryName(); + + if (editor == "") { + DEBUG << "Failed to find a text editor" << endl; + //!!! visible error! + return; + } + + HgAction action(ACT_HG_IGNORE, m_workFolderPath, params); + action.executable = editor; + + m_runner->requestAction(action); +} + +void MainWindow::hgIgnoreFiles(QStringList files) +{ + //!!! not implemented yet + DEBUG << "MainWindow::hgIgnoreFiles: Not implemented" << endl; +} + +void MainWindow::hgUnIgnoreFiles(QStringList files) +{ + //!!! not implemented yet + DEBUG << "MainWindow::hgUnIgnoreFiles: Not implemented" << endl; +} + +QString MainWindow::getDiffBinaryName() +{ + QSettings settings; + settings.beginGroup("Locations"); + return settings.value("extdiffbinary", "").toString(); +} + +QString MainWindow::getMergeBinaryName() +{ + QSettings settings; + settings.beginGroup("Locations"); + return settings.value("mergebinary", "").toString(); +} + +QString MainWindow::getEditorBinaryName() +{ + QSettings settings; + settings.beginGroup("Locations"); + return settings.value("editorbinary", "").toString(); +} + +void MainWindow::hgShowSummary() +{ + QStringList params; + + params << "diff" << "--stat"; + + m_runner->requestAction(HgAction(ACT_UNCOMMITTED_SUMMARY, m_workFolderPath, params)); +} + +void MainWindow::hgFolderDiff() +{ + hgDiffFiles(QStringList()); +} + +void MainWindow::hgDiffFiles(QStringList files) +{ + QString diff = getDiffBinaryName(); + if (diff == "") return; + + QStringList params; + + // Diff parent against working folder (folder diff) + + params << "--config" << "extensions.extdiff=" << "extdiff"; + params << "--program" << diff; + + params << "--" << files; // may be none: whole dir + + m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params)); +} + +void MainWindow::hgDiffToCurrent(QString id) +{ + QString diff = getDiffBinaryName(); + if (diff == "") return; + + QStringList params; + + // Diff given revision against working folder + + params << "--config" << "extensions.extdiff=" << "extdiff"; + params << "--program" << diff; + params << "--rev" << Changeset::hashOf(id); + + m_runner->requestAction(HgAction(ACT_FOLDERDIFF, m_workFolderPath, params)); +} + +void MainWindow::hgDiffToParent(QString child, QString parent) +{ + QString diff = getDiffBinaryName(); + if (diff == "") return; + + QStringList params; + + // Diff given revision against parent revision + + params << "--config" << "extensions.extdiff=" << "extdiff"; + params << "--program" << diff; + params << "--rev" << Changeset::hashOf(parent) + << "--rev" << Changeset::hashOf(child); + + m_runner->requestAction(HgAction(ACT_CHGSETDIFF, m_workFolderPath, params)); +} + + +void MainWindow::hgShowSummaryFor(Changeset *cs) +{ + QStringList params; + + // This will pick a default parent if there is more than one + // (whereas with diff we need to supply one). But it does need a + // bit more parsing + params << "log" << "--stat" << "--rev" << Changeset::hashOf(cs->id()); + + m_runner->requestAction(HgAction(ACT_DIFF_SUMMARY, m_workFolderPath, + params, cs)); +} + + +void MainWindow::hgUpdate() +{ + QStringList params; + + params << "update"; + + m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params)); +} + + +void MainWindow::hgUpdateToRev(QString id) +{ + QStringList params; + + params << "update" << "--rev" << Changeset::hashOf(id) << "--check"; + + m_runner->requestAction(HgAction(ACT_UPDATE, m_workFolderPath, params)); +} + + +void MainWindow::hgRevert() +{ + hgRevertFiles(QStringList()); +} + +void MainWindow::hgRevertFiles(QStringList files) +{ + QStringList params; + QString comment; + bool all = false; + + QStringList allFiles = m_hgTabs->getAllRevertableFiles(); + if (files.empty() || files == allFiles) { + files = allFiles; + all = true; + } + + QString subsetNote; + if (!all) { + subsetNote = tr("

Note: you are reverting only the files you have selected, not all of the files that have been changed!"); + } + + QString rf(tr("Revert files")); + + // Set up params before asking for confirmation, because there is + // a failure case here that we would need to report on early + + DEBUG << "hgRevert: m_justMerged = " << m_justMerged << ", m_mergeTargetRevision = " << m_mergeTargetRevision << endl; + + if (m_justMerged) { + + // This is a little fiddly. The proper way to "revert" the + // whole of an uncommitted merge is with "hg update --clean ." + // But if the user has selected only some files, we're sort of + // promising to revert only those, which means we need to + // specify which parent to revert to. We can only do that if + // we have a record of it, which we do if you just did the + // merge from within easyhg but don't if you've exited and + // restarted, or changed repository, since then. Hmmm. + + if (all) { + params << "update" << "--clean" << "."; + } else { + if (m_mergeTargetRevision != "") { + params << "revert" << "--rev" + << Changeset::hashOf(m_mergeTargetRevision) + << "--" << files; + } else { + QMessageBox::information + (this, tr("Unable to revert"), + tr("Sorry, unable to revert these files

EasyMercurial can only revert a subset of files during a merge if it still has a record of which parent was the original merge target; that information is no longer available.

This is a limitation of EasyMercurial. Consider reverting all files, or using hg revert with a specific revision at the command-line instead.
")); + return; + } + } + } else { + params << "revert" << "--" << files; + } + + if (ConfirmCommentDialog::confirmDangerousFilesAction + (this, + rf, + tr("

%1

%2%3").arg(rf) + .arg(tr("You are about to revert the following files to their previous committed state.

This will throw away any changes that you have made to these files but have not committed.")) + .arg(subsetNote), + tr("

%1

%2%3").arg(rf) + .arg(tr("You are about to revert %n file(s).

This will throw away any changes that you have made to these files but have not committed.", "", files.size())) + .arg(subsetNote), + files, + tr("Re&vert"))) { + + m_lastRevertedFiles = files; + + m_runner->requestAction(HgAction(ACT_REVERT, m_workFolderPath, params)); + } +} + + +void MainWindow::hgRenameFiles(QStringList files) +{ + QString renameTo; + + QString file; + if (files.empty()) return; + file = files[0]; + + if (ConfirmCommentDialog::confirmAndGetShortComment + (this, + tr("Rename"), + tr("Rename %1 to:").arg(xmlEncode(file)), + renameTo, + tr("Re&name"))) { + + if (renameTo != "" && renameTo != file) { + + QStringList params; + + params << "rename" << "--" << file << renameTo; + + m_runner->requestAction(HgAction(ACT_RENAME_FILE, m_workFolderPath, params)); + } + } +} + + +void MainWindow::hgCopyFiles(QStringList files) +{ + QString copyTo; + + QString file; + if (files.empty()) return; + file = files[0]; + + if (ConfirmCommentDialog::confirmAndGetShortComment + (this, + tr("Copy"), + tr("Copy %1 to:").arg(xmlEncode(file)), + copyTo, + tr("Co&py"))) { + + if (copyTo != "" && copyTo != file) { + + QStringList params; + + params << "copy" << "--" << file << copyTo; + + m_runner->requestAction(HgAction(ACT_COPY_FILE, m_workFolderPath, params)); + } + } +} + + +void MainWindow::hgMarkFilesResolved(QStringList files) +{ + QStringList params; + + params << "resolve" << "--mark"; + + if (files.empty()) { + params << "--all"; + } else { + params << "--" << files; + } + + m_runner->requestAction(HgAction(ACT_RESOLVE_MARK, m_workFolderPath, params)); +} + + +void MainWindow::hgRedoMerge() +{ + hgRedoFileMerges(QStringList()); +} + + +void MainWindow::hgRedoFileMerges(QStringList files) +{ + QStringList params; + + params << "resolve"; + + QString merge = getMergeBinaryName(); + if (merge != "") { + params << "--tool" << merge; + } + + if (files.empty()) { + params << "--all"; + } else { + params << "--" << files; + } + + if (m_currentParents.size() == 1) { + m_mergeTargetRevision = m_currentParents[0]->id(); + } + + m_runner->requestAction(HgAction(ACT_RETRY_MERGE, m_workFolderPath, params)); + + m_mergeCommitComment = tr("Merge"); +} + + +void MainWindow::hgMerge() +{ + if (m_hgTabs->canResolve()) { + hgRedoMerge(); + return; + } + + QStringList params; + + params << "merge"; + + QString merge = getMergeBinaryName(); + if (merge != "") { + params << "--tool" << merge; + } + + if (m_currentParents.size() == 1) { + m_mergeTargetRevision = m_currentParents[0]->id(); + } + + m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params)); + + m_mergeCommitComment = tr("Merge"); +} + + +void MainWindow::hgMergeFrom(QString id) +{ + QStringList params; + + params << "merge"; + params << "--rev" << Changeset::hashOf(id); + + QString merge = getMergeBinaryName(); + if (merge != "") { + params << "--tool" << merge; + } + + if (m_currentParents.size() == 1) { + m_mergeTargetRevision = m_currentParents[0]->id(); + } + + m_runner->requestAction(HgAction(ACT_MERGE, m_workFolderPath, params)); + + m_mergeCommitComment = ""; + + foreach (Changeset *cs, m_currentHeads) { + if (cs->id() == id && !cs->isOnBranch(m_currentBranch)) { + if (cs->branch() == "" || cs->branch() == "default") { + m_mergeCommitComment = tr("Merge from the default branch"); + } else { + m_mergeCommitComment = tr("Merge from branch \"%1\"").arg(cs->branch()); + } + } + } + + if (m_mergeCommitComment == "") { + m_mergeCommitComment = tr("Merge from %1").arg(id); + } +} + + +void MainWindow::hgCloneFromRemote() +{ + QStringList params; + + if (!QDir(m_workFolderPath).exists()) { + if (!QDir().mkpath(m_workFolderPath)) { + DEBUG << "hgCloneFromRemote: Failed to create target path " + << m_workFolderPath << endl; + //!!! report error + return; + } + } + + params << "clone" << m_remoteRepoPath << m_workFolderPath; + + updateWorkFolderAndRepoNames(); + m_hgTabs->updateWorkFolderFileList(""); + + m_runner->requestAction(HgAction(ACT_CLONEFROMREMOTE, m_workFolderPath, params)); +} + +void MainWindow::hgInit() +{ + QStringList params; + + params << "init"; + params << m_workFolderPath; + + m_runner->requestAction(HgAction(ACT_INIT, m_workFolderPath, params)); +} + +void MainWindow::hgIncoming() +{ + QStringList params; + + params << "incoming" << "--newest-first" << m_remoteRepoPath; + params << "--template" << Changeset::getLogTemplate(); + + m_runner->requestAction(HgAction(ACT_INCOMING, m_workFolderPath, params)); +} + +void MainWindow::hgPull() +{ + if (ConfirmCommentDialog::confirm + (this, tr("Confirm pull"), + tr("

Pull from remote repository?

"), + tr("

You are about to pull changes from the remote repository at %1.

").arg(xmlEncode(m_remoteRepoPath)), + tr("&Pull"))) { + + QStringList params; + params << "pull" << m_remoteRepoPath; + m_runner->requestAction(HgAction(ACT_PULL, m_workFolderPath, params)); + } +} + +void MainWindow::hgPush() +{ + if (m_remoteRepoPath.isEmpty()) { + changeRemoteRepo(true); + if (m_remoteRepoPath.isEmpty()) return; + } + + QString uncommittedNote; + if (m_hgTabs->canCommit()) { + uncommittedNote = tr("

Note: You have uncommitted changes. If you want to push these changes to the remote repository, you need to commit them first."); + } + + if (ConfirmCommentDialog::confirm + (this, tr("Confirm push"), + tr("

Push to remote repository?

"), + tr("

You are about to push your commits to the remote repository at %1.

%2
").arg(xmlEncode(m_remoteRepoPath)).arg(uncommittedNote), + tr("&Push"))) { + + QStringList params; + params << "push" << "--new-branch" << m_remoteRepoPath; + m_runner->requestAction(HgAction(ACT_PUSH, m_workFolderPath, params)); + } +} + +QStringList MainWindow::listAllUpIpV4Addresses() +{ + QStringList ret; + QList ifaces = QNetworkInterface::allInterfaces(); + + for (int i = 0; i < ifaces.count(); i++) { + QNetworkInterface iface = ifaces.at(i); + if (iface.flags().testFlag(QNetworkInterface::IsUp) + && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) { + for (int j=0; j

%1

") + .arg(tr("Running temporary server at %n address(es):", "", addrs.size())); + foreach (QString addr, addrs) { + ts << QString("
  http://%1:8000
").arg(xmlEncode(addr)); + } + ts << tr("

Press Close to stop the server and return.

"); + ts.flush(); + + params << "serve"; + + m_runner->requestAction(HgAction(ACT_SERVE, m_workFolderPath, params)); + + QMessageBox::information(this, tr("Serve"), msg, QMessageBox::Close); + + m_runner->killCurrentActions(); +} + +void MainWindow::startupDialog() +{ + StartupDialog *dlg = new StartupDialog(this); + if (dlg->exec()) m_firstStart = false; + else exit(0); +} + +void MainWindow::open() +{ + bool done = false; + + while (!done) { + + MultiChoiceDialog *d = new MultiChoiceDialog + (tr("Open Repository"), + tr("What would you like to open?"), + this); + + d->addChoice("remote", + tr("

Remote repository
"), + tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."), + MultiChoiceDialog::UrlToDirectoryArg); + + d->addChoice("local", + tr("

Local repository
"), + tr("Open an existing local Mercurial repository."), + MultiChoiceDialog::DirectoryArg); + + d->addChoice("init", + tr("

File folder
"), + tr("Open a local folder, by creating a Mercurial repository in it."), + MultiChoiceDialog::DirectoryArg); + + QSettings settings; + settings.beginGroup("General"); + QString lastChoice = settings.value("lastopentype", "remote").toString(); + if (lastChoice != "local" && + lastChoice != "remote" && + lastChoice != "init") { + lastChoice = "remote"; + } + + d->setCurrentChoice(lastChoice); + + if (d->exec() == QDialog::Accepted) { + + QString choice = d->getCurrentChoice(); + settings.setValue("lastopentype", choice); + + QString arg = d->getArgument().trimmed(); + + bool result = false; + + if (choice == "local") { + result = openLocal(arg); + } else if (choice == "remote") { + result = openRemote(arg, d->getAdditionalArgument().trimmed()); + } else if (choice == "init") { + result = openInit(arg); + } + + if (result) { + enableDisableActions(); + clearState(); + hgQueryPaths(); + done = true; + } + + } else { + + // cancelled + done = true; + } + + delete d; + } +} + +void MainWindow::recentMenuActivated() +{ + QAction *a = qobject_cast(sender()); + if (!a) return; + QString local = a->text(); + open(local); +} + +void MainWindow::changeRemoteRepo() +{ + changeRemoteRepo(false); +} + +void MainWindow::changeRemoteRepo(bool initial) +{ + // This will involve rewriting the local .hgrc + + QDir hgDir(m_workFolderPath + "/.hg"); + if (!hgDir.exists()) { + //!!! visible error! + return; + } + + QFileInfo hgrc(m_workFolderPath + "/.hg/hgrc"); + if (hgrc.exists() && !hgrc.isWritable()) { + //!!! visible error! + return; + } + + MultiChoiceDialog *d = new MultiChoiceDialog + (tr("Set Remote Location"), + tr("Set the remote location"), + this); + + QString explanation; + if (initial) { + explanation = tr("Provide a URL to use for push and pull actions from the current local repository.
This will be the default for subsequent pushes and pulls.
You can change it using “Set Remote Location” on the File menu."); + } else { + explanation = tr("Provide a new URL to use for push and pull actions from the current local repository."); + } + + d->addChoice("remote", + tr("

Remote repository
"), + explanation, + MultiChoiceDialog::UrlArg); + + if (d->exec() == QDialog::Accepted) { + + // New block to ensure QSettings is deleted before + // hgQueryPaths called. NB use of absoluteFilePath instead of + // canonicalFilePath, which would fail if the file did not yet + // exist + + { + QSettings s(hgrc.absoluteFilePath(), QSettings::IniFormat); + s.beginGroup("paths"); + s.setValue("default", d->getArgument()); + } + + m_stateUnknown = true; + hgQueryPaths(); + } + + delete d; +} + +void MainWindow::open(QString local) +{ + if (openLocal(local)) { + enableDisableActions(); + clearState(); + hgQueryPaths(); + } +} + +bool MainWindow::complainAboutFilePath(QString arg) +{ + QMessageBox::critical + (this, tr("File chosen"), + tr("Folder required

You asked to open \"%1\".
This is a file; to open a repository, you need to choose a folder.
").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::askAboutUnknownFolder(QString arg) +{ + bool result = (QMessageBox::question + (this, tr("Path does not exist"), + tr("Path does not exist: create it?

You asked to open a remote repository by cloning it to \"%1\". This folder does not exist, and neither does its parent.

Would you like to create the parent folder as well?
").arg(xmlEncode(arg)), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Cancel) + == QMessageBox::Ok); + if (result) { + QDir dir(arg); + dir.cdUp(); + if (!dir.mkpath(dir.absolutePath())) { + QMessageBox::critical + (this, tr("Failed to create folder"), + tr("Failed to create folder

Sorry, the path for the parent folder \"%1\" could not be created.
").arg(dir.absolutePath())); + return false; + } + return true; + } + return false; +} + +bool MainWindow::complainAboutUnknownFolder(QString arg) +{ + QMessageBox::critical + (this, tr("Folder does not exist"), + tr("Folder does not exist

You asked to open \"%1\".
This folder does not exist, and it cannot be created because its parent does not exist either.
").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::complainAboutInitInRepo(QString arg) +{ + QMessageBox::critical + (this, tr("Path is in existing repository"), + tr("Path is in an existing repository

You asked to initialise a repository at \"%1\".
This path is already inside an existing repository.
").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::complainAboutInitFile(QString arg) +{ + QMessageBox::critical + (this, tr("Path is a file"), + tr("Path is a file

You asked to initialise a repository at \"%1\".
This is an existing file; it is only possible to initialise in folders.
").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::complainAboutCloneToExisting(QString arg) +{ + QMessageBox::critical + (this, tr("Path is in existing repository"), + tr("Local path is in an existing repository

You asked to open a remote repository by cloning it to the local path \"%1\".
This path is already inside an existing repository.
Please provide a different folder name for the local repository.
").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::complainAboutCloneToFile(QString arg) +{ + QMessageBox::critical + (this, tr("Path is a file"), + tr("Local path is a file

You asked to open a remote repository by cloning it to the local path \"%1\".
This path is an existing file.
Please provide a new folder name for the local repository.
").arg(xmlEncode(arg))); + return false; +} + +QString MainWindow::complainAboutCloneToExistingFolder(QString arg, QString remote) +{ + // If the directory "arg" exists but is empty, then we accept it. + + // If the directory "arg" exists and is non-empty, but "arg" plus + // the last path component of "remote" does not exist, then offer + // the latter as an alternative path. + + QString offer; + + QDir d(arg); + + if (d.exists()) { + + if (d.entryList(QDir::Dirs | QDir::Files | + QDir::NoDotAndDotDot | + QDir::Hidden | QDir::System).empty()) { + // directory is empty; accept it + return arg; + } + + if (QRegExp("^\\w+://").indexIn(remote) >= 0) { + QString rpath = QUrl(remote).path(); + if (rpath != "") { + rpath = QDir(rpath).dirName(); + if (rpath != "" && !d.exists(rpath)) { + offer = d.filePath(rpath); + } + } + } + } + + if (offer != "") { + bool result = (QMessageBox::question + (this, tr("Folder exists"), + tr("Local folder already exists

You asked to open a remote repository by cloning it to \"%1\", but this folder already exists and so cannot be cloned to.

Would you like to create the new folder \"%2\" instead?
") + .arg(xmlEncode(arg)).arg(xmlEncode(offer)), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Cancel) + == QMessageBox::Ok); + if (result) return offer; + else return ""; + } + + QMessageBox::critical + (this, tr("Folder exists"), + tr("Local folder already exists

You asked to open a remote repository by cloning it to \"%1\", but this file or folder already exists and so cannot be cloned to.
Please provide a different folder name for the local repository.
").arg(xmlEncode(arg))); + return ""; +} + +bool MainWindow::askToOpenParentRepo(QString arg, QString parent) +{ + return (QMessageBox::question + (this, tr("Path is inside a repository"), + tr("Open the repository that contains this path?

You asked to open \"%1\".
This is not the root folder of a repository.
But it is inside a repository, whose root is at \"%2\".

Would you like to open that repository instead?
") + .arg(xmlEncode(arg)).arg(xmlEncode(parent)), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Ok) + == QMessageBox::Ok); +} + +bool MainWindow::askToInitExisting(QString arg) +{ + return (QMessageBox::question + (this, tr("Folder has no repository"), + tr("Initialise a repository here?

You asked to open \"%1\".
This folder is not a Mercurial working copy.

Would you like to initialise a repository here?
") + .arg(xmlEncode(arg)), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Ok) + == QMessageBox::Ok); +} + +bool MainWindow::askToInitNew(QString arg) +{ + return (QMessageBox::question + (this, tr("Folder does not exist"), + tr("Initialise a new repository?

You asked to open \"%1\".
This folder does not yet exist.

Would you like to create the folder and initialise a new empty repository in it?
") + .arg(xmlEncode(arg)), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Ok) + == QMessageBox::Ok); +} + +bool MainWindow::askToOpenInsteadOfInit(QString arg) +{ + return (QMessageBox::question + (this, tr("Repository exists"), + tr("Open existing repository?

You asked to initialise a new repository at \"%1\".
This folder already contains a repository. Would you like to open it?
") + .arg(xmlEncode(arg)), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Ok) + == QMessageBox::Ok); +} + +bool MainWindow::openLocal(QString local) +{ + DEBUG << "open " << local << endl; + + FolderStatus status = getFolderStatus(local); + QString containing = getContainingRepoFolder(local); + + switch (status) { + + case FolderHasRepo: + // fine + break; + + case FolderExists: + if (containing != "") { + if (!askToOpenParentRepo(local, containing)) return false; + local = containing; + } else { + //!!! No -- this is likely to happen far more by accident + // than because the user actually wanted to init something. + // Don't ask, just politely reject. + if (!askToInitExisting(local)) return false; + return openInit(local); + } + break; + + case FolderParentExists: + if (containing != "") { + if (!askToOpenParentRepo(local, containing)) return false; + local = containing; + } else { + if (!askToInitNew(local)) return false; + return openInit(local); + } + break; + + case FolderUnknown: + if (containing != "") { + if (!askToOpenParentRepo(local, containing)) return false; + local = containing; + } else { + return complainAboutUnknownFolder(local); + } + break; + + case FolderIsFile: + return complainAboutFilePath(local); + } + + m_workFolderPath = local; + m_remoteRepoPath = ""; + return true; +} + +bool MainWindow::openRemote(QString remote, QString local) +{ + DEBUG << "clone " << remote << " to " << local << endl; + + FolderStatus status = getFolderStatus(local); + QString containing = getContainingRepoFolder(local); + + DEBUG << "status = " << status << ", containing = " << containing << endl; + + if (status == FolderHasRepo || containing != "") { + return complainAboutCloneToExisting(local); + } + + if (status == FolderIsFile) { + return complainAboutCloneToFile(local); + } + + if (status == FolderUnknown) { + if (!askAboutUnknownFolder(local)) { + return false; + } + } + + if (status == FolderExists) { + local = complainAboutCloneToExistingFolder(local, remote); + if (local == "") return false; + } + + m_workFolderPath = local; + m_remoteRepoPath = remote; + hgCloneFromRemote(); + + return true; +} + +bool MainWindow::openInit(QString local) +{ + DEBUG << "openInit " << local << endl; + + FolderStatus status = getFolderStatus(local); + QString containing = getContainingRepoFolder(local); + + DEBUG << "status = " << status << ", containing = " << containing << endl; + + if (status == FolderHasRepo) { + if (!askToOpenInsteadOfInit(local)) return false; + } + + if (containing != "") { + return complainAboutInitInRepo(local); + } + + if (status == FolderIsFile) { + return complainAboutInitFile(local); + } + + if (status == FolderUnknown) { + return complainAboutUnknownFolder(local); + } + + m_workFolderPath = local; + m_remoteRepoPath = ""; + hgInit(); + return true; +} + +void MainWindow::settings() +{ + SettingsDialog *settingsDlg = new SettingsDialog(this); + settingsDlg->exec(); + + if (settingsDlg->presentationChanged()) { + m_hgTabs->updateFileStates(); + updateToolBarStyle(); + hgRefresh(); + } +} + +void MainWindow::updateFileSystemWatcher() +{ + bool justCreated = false; + if (!m_fsWatcher) { + m_fsWatcher = new QFileSystemWatcher(); + justCreated = true; + } + + // QFileSystemWatcher will refuse to add a file or directory to + // its watch list that it is already watching -- fine, that's what + // we want -- but it prints a warning when this happens, which is + // annoying because it would be the normal case for us. So we'll + // check for duplicates ourselves. + QSet alreadyWatched; + QStringList dl(m_fsWatcher->directories()); + foreach (QString d, dl) alreadyWatched.insert(d); + + std::deque pending; + pending.push_back(m_workFolderPath); + + while (!pending.empty()) { + + QString path = pending.front(); + pending.pop_front(); + if (!alreadyWatched.contains(path)) { + m_fsWatcher->addPath(path); + DEBUG << "Added to file system watcher: " << path << endl; + } + + QDir d(path); + if (d.exists()) { + d.setFilter(QDir::Dirs | QDir::NoDotAndDotDot | + QDir::Readable | QDir::NoSymLinks); + foreach (QString entry, d.entryList()) { + if (entry.startsWith('.')) continue; + QString entryPath = d.absoluteFilePath(entry); + pending.push_back(entryPath); + } + } + } + + // The general timer isn't really related to the fs watcher + // object, it just does something similar -- every now and then we + // do a refresh just to update the history dates etc + + m_fsWatcherGeneralTimer = new QTimer(this); + connect(m_fsWatcherGeneralTimer, SIGNAL(timeout()), + this, SLOT(checkFilesystem())); + m_fsWatcherGeneralTimer->setInterval(30 * 60 * 1000); // half an hour + m_fsWatcherGeneralTimer->start(); + + if (justCreated) { + connect(m_fsWatcher, SIGNAL(directoryChanged(QString)), + this, SLOT(fsDirectoryChanged(QString))); + connect(m_fsWatcher, SIGNAL(fileChanged(QString)), + this, SLOT(fsFileChanged(QString))); + } +} + +void MainWindow::suspendFileSystemWatcher() +{ + DEBUG << "MainWindow::suspendFileSystemWatcher" << endl; + if (m_fsWatcher) { + m_fsWatcherSuspended = true; + if (m_fsWatcherRestoreTimer) { + delete m_fsWatcherRestoreTimer; + m_fsWatcherRestoreTimer = 0; + } + m_fsWatcherGeneralTimer->stop(); + } +} + +void MainWindow::restoreFileSystemWatcher() +{ + DEBUG << "MainWindow::restoreFileSystemWatcher" << endl; + if (m_fsWatcherRestoreTimer) delete m_fsWatcherRestoreTimer; + + // The restore timer is used to leave a polite interval between + // being asked to restore the watcher and actually doing so. It's + // a single shot timer each time it's used, but we don't use + // QTimer::singleShot because we want to stop the previous one if + // it's running (via deleting it) + + m_fsWatcherRestoreTimer = new QTimer(this); + connect(m_fsWatcherRestoreTimer, SIGNAL(timeout()), + this, SLOT(actuallyRestoreFileSystemWatcher())); + m_fsWatcherRestoreTimer->setInterval(1000); + m_fsWatcherRestoreTimer->setSingleShot(true); + m_fsWatcherRestoreTimer->start(); +} + +void MainWindow::actuallyRestoreFileSystemWatcher() +{ + DEBUG << "MainWindow::actuallyRestoreFileSystemWatcher" << endl; + if (m_fsWatcher) { + m_fsWatcherSuspended = false; + m_fsWatcherGeneralTimer->start(); + } +} + +void MainWindow::checkFilesystem() +{ + DEBUG << "MainWindow::checkFilesystem" << endl; + hgRefresh(); +} + +void MainWindow::fsDirectoryChanged(QString d) +{ + DEBUG << "MainWindow::fsDirectoryChanged " << d << endl; + if (!m_fsWatcherSuspended) { + hgStat(); + } +} + +void MainWindow::fsFileChanged(QString f) +{ + DEBUG << "MainWindow::fsFileChanged " << f << endl; + if (!m_fsWatcherSuspended) { + hgStat(); + } +} + +QString MainWindow::format1(QString head) +{ + return QString("

%1

").arg(head); +} + +QString MainWindow::format3(QString head, QString intro, QString code) +{ + code = xmlEncode(code).replace("\n", "
") +#ifndef Q_OS_WIN32 + // The hard hyphen comes out funny on Windows + .replace("-", "‑") +#endif + .replace(" ", " "); + if (intro == "") { + return QString("

%1

%2

") + .arg(head).arg(code); + } else if (code == "") { + return QString("

%1

%2

") + .arg(head).arg(intro); + } else { + return QString("

%1

%2

%3

") + .arg(head).arg(intro).arg(code); + } +} + +void MainWindow::showIncoming(QString output) +{ + m_runner->hide(); + IncomingDialog *d = new IncomingDialog(this, output); + d->exec(); + delete d; +} + +int MainWindow::extractChangeCount(QString text) +{ + QRegExp re("added (\\d+) ch\\w+ with (\\d+) ch\\w+ to (\\d+) f\\w+"); + if (re.indexIn(text) >= 0) { + return re.cap(1).toInt(); + } else if (text.contains("no changes")) { + return 0; + } else { + return -1; // unknown + } +} + +void MainWindow::showPushResult(QString output) +{ + QString head; + QString report; + int n = extractChangeCount(output); + if (n > 0) { + head = tr("Pushed %n changeset(s)", "", n); + report = tr("Successfully pushed to the remote repository at %1.").arg(xmlEncode(m_remoteRepoPath)); + } else if (n == 0) { + head = tr("No changes to push"); + report = tr("The remote repository already contains all changes that have been committed locally."); + if (m_hgTabs->canCommit()) { + report = tr("%1

You do have some uncommitted changes. If you wish to push those to the remote repository, commit them locally first.").arg(report); + } + } else { + head = tr("Push complete"); + } + m_runner->hide(); + + MoreInformationDialog::information(this, tr("Push complete"), + head, report, output); +} + +void MainWindow::showPullResult(QString output) +{ + QString head; + QString report; + int n = extractChangeCount(output); + if (n > 0) { + head = tr("Pulled %n changeset(s)", "", n); + report = tr("New changes will be highlighted in yellow in the history."); + } else if (n == 0) { + head = tr("No changes to pull"); + report = tr("Your local repository already contains all changes found in the remote repository."); + } else { + head = tr("Pull complete"); + } + m_runner->hide(); + + MoreInformationDialog::information(this, tr("Pull complete"), + head, report, output); +} + +void MainWindow::reportNewRemoteHeads(QString output) +{ + bool headsAreLocal = false; + + if (m_currentParents.size() == 1) { + int m_currentBranchHeads = 0; + bool parentIsHead = false; + Changeset *parent = m_currentParents[0]; + foreach (Changeset *head, m_currentHeads) { + if (head->isOnBranch(m_currentBranch)) { + ++m_currentBranchHeads; + } + if (parent->id() == head->id()) { + parentIsHead = true; + } + } + if (m_currentBranchHeads == 2 && parentIsHead) { + headsAreLocal = true; + } + } + + if (headsAreLocal) { + MoreInformationDialog::warning + (this, + tr("Push failed"), + tr("Push failed"), + tr("Your local repository could not be pushed to the remote repository.

You may need to merge the changes locally first."), + output); + } else if (m_hgTabs->canCommit() && m_currentParents.size() > 1) { + MoreInformationDialog::warning + (this, + tr("Push failed"), + tr("Push failed"), + tr("Your local repository could not be pushed to the remote repository.

You have an uncommitted merge in your local folder. You probably need to commit it before you push."), + output); + } else { + MoreInformationDialog::warning + (this, + tr("Push failed"), + tr("Push failed"), + tr("Your local repository could not be pushed to the remote repository.

The remote repository may have been changed by someone else since you last pushed. Try pulling and merging their changes into your local repository first."), + output); + } +} + +void MainWindow::reportAuthFailed(QString output) +{ + MoreInformationDialog::warning + (this, + tr("Authorization failed"), + tr("Authorization failed"), + tr("You may have entered an incorrect user name or password, or the remote URL may be wrong.

Or you may lack the necessary permissions on the remote repository.

Check with the administrator of your remote repository if necessary."), + output); +} + +void MainWindow::commandStarting(HgAction action) +{ + // Annoyingly, hg stat actually modifies the working directory -- + // it creates files called hg-checklink and hg-checkexec to test + // properties of the filesystem. For safety's sake, suspend the + // fs watcher while running commands, and restore it shortly after + // a command has finished. + + if (action.action == ACT_STAT) { + suspendFileSystemWatcher(); + } +} + +void MainWindow::commandFailed(HgAction action, QString output) +{ + DEBUG << "MainWindow::commandFailed" << endl; + restoreFileSystemWatcher(); + + QString setstr; +#ifdef Q_OS_MAC + setstr = tr("Preferences"); +#else + setstr = tr("Settings"); +#endif + + // Some commands we just have to ignore bad return values from, + // and some output gets special treatment. + + // Note our fallback case should always be to report a + // non-specific error and show the text -- in case output scraping + // fails (as it surely will). Note also that we must force the + // locale in order to ensure the output is scrapable; this happens + // in HgRunner and may break some system encodings. + + switch(action.action) { + case ACT_NONE: + // uh huh + return; + case ACT_TEST_HG: + MoreInformationDialog::warning + (this, + tr("Failed to run Mercurial"), + tr("Failed to run Mercurial"), + tr("The Mercurial program either could not be found or failed to run.
Check that the Mercurial program path is correct in %1.").arg(setstr), + output); + settings(); + return; + case ACT_TEST_HG_EXT: + MoreInformationDialog::warning + (this, + tr("Failed to run Mercurial"), + tr("Failed to run Mercurial with extension enabled"), + tr("The Mercurial program failed to run with the EasyMercurial interaction extension enabled.
This may indicate an installation problem.

You may be able to continue working if you switch off “Use EasyHg Mercurial Extension” in %1. Note that remote repositories that require authentication might not work if you do this.").arg(setstr), + output); + settings(); + return; + case ACT_CLONEFROMREMOTE: + // if clone fails, we have no repo + m_workFolderPath = ""; + enableDisableActions(); + break; // go on to default report + case ACT_INCOMING: + if (output.contains("authorization failed")) { + reportAuthFailed(output); + return; + } else if (output.contains("entry cancelled")) { + // ignore this, user cancelled username or password dialog + return; + } else { + // Incoming returns non-zero code and no output if the + // check was successful but there are no changes + // pending. This is the only case where we need to remove + // warning messages, because it's the only case where a + // non-zero code can be returned even though the command + // has for our purposes succeeded + QString replaced = output; + while (1) { + QString r1 = replaced; + r1.replace(QRegExp("warning: [^\\n]*"), ""); + if (r1 == replaced) break; + replaced = r1.trimmed(); + } + if (replaced == "") { + showIncoming(""); + return; + } + } + break; // go on to default report + case ACT_PULL: + if (output.contains("authorization failed")) { + reportAuthFailed(output); + return; + } else if (output.contains("entry cancelled")) { + // ignore this, user cancelled username or password dialog + return; + } + break; // go on to default report + case ACT_PUSH: + if (output.contains("creates new remote heads")) { + reportNewRemoteHeads(output); + return; + } else if (output.contains("authorization failed")) { + reportAuthFailed(output); + return; + } else if (output.contains("entry cancelled")) { + // ignore this, user cancelled username or password dialog + return; + } + break; // go on to default report + case ACT_QUERY_HEADS: + // fails if repo is empty; we don't care (if there's a genuine + // problem, something else will fail too). Pretend it + // succeeded, so that any further actions that are contingent + // on the success of the heads query get carried out properly. + commandCompleted(action, ""); + return; + case ACT_FOLDERDIFF: + case ACT_CHGSETDIFF: + // external program, unlikely to be anything useful in stderr + // and some return with failure codes when something as basic + // as the user closing the window via the wm happens + return; + case ACT_MERGE: + case ACT_RETRY_MERGE: + MoreInformationDialog::information + (this, tr("Merge"), tr("Merge failed"), + tr("Some files were not merged successfully.

You can Merge again to repeat the interactive merge; use Revert to abandon the merge entirely; or edit the files that are in conflict in an editor and, when you are happy with them, choose Mark Resolved in each file's right-button menu."), + output); + return; + case ACT_STAT: + break; // go on to default report + default: + break; + } + + QString command = action.executable; + if (command == "") command = "hg"; + foreach (QString arg, action.params) { + command += " " + arg; + } + + MoreInformationDialog::warning + (this, + tr("Command failed"), + tr("Command failed"), + tr("A Mercurial command failed to run correctly. This may indicate an installation problem or some other problem with EasyMercurial.

See “More Details” for the command output."), + output); +} + +void MainWindow::commandCompleted(HgAction completedAction, QString output) +{ + restoreFileSystemWatcher(); + HGACTIONS action = completedAction.action; + + if (action == ACT_NONE) return; + + bool headsChanged = false; + QStringList oldHeadIds; + + switch (action) { + + case ACT_TEST_HG: + break; + + case ACT_TEST_HG_EXT: + break; + + case ACT_QUERY_PATHS: + { + DEBUG << "stdout is " << output << endl; + LogParser lp(output, "="); + LogList ll = lp.parse(); + DEBUG << ll.size() << " results" << endl; + if (!ll.empty()) { + m_remoteRepoPath = lp.parse()[0]["default"].trimmed(); + DEBUG << "Set remote path to " << m_remoteRepoPath << endl; + } else { + m_remoteRepoPath = ""; + } + MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); + MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); + updateWorkFolderAndRepoNames(); + break; + } + + case ACT_QUERY_BRANCH: + m_currentBranch = output.trimmed(); + break; + + case ACT_STAT: + m_lastStatOutput = output; + updateFileSystemWatcher(); + break; + + case ACT_RESOLVE_LIST: + if (output != "") { + // Remove lines beginning with R (they are resolved, + // and the file stat parser treats R as removed) + QStringList outList = output.split('\n'); + QStringList winnowed; + foreach (QString line, outList) { + if (!line.startsWith("R ")) winnowed.push_back(line); + } + output = winnowed.join("\n"); + } + DEBUG << "m_lastStatOutput = " << m_lastStatOutput << endl; + DEBUG << "resolve output = " << output << endl; + m_hgTabs->updateWorkFolderFileList(m_lastStatOutput + output); + break; + + case ACT_RESOLVE_MARK: + m_shouldHgStat = true; + break; + + case ACT_INCOMING: + showIncoming(output); + break; + + case ACT_ANNOTATE: + { + AnnotateDialog dialog(this, output); + dialog.exec(); + m_shouldHgStat = true; + break; + } + + case ACT_PULL: + showPullResult(output); + m_shouldHgStat = true; + break; + + case ACT_PUSH: + showPushResult(output); + break; + + case ACT_INIT: + MultiChoiceDialog::addRecentArgument("init", m_workFolderPath); + MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); + enableDisableActions(); + m_shouldHgStat = true; + break; + + case ACT_CLONEFROMREMOTE: + MultiChoiceDialog::addRecentArgument("local", m_workFolderPath); + MultiChoiceDialog::addRecentArgument("remote", m_remoteRepoPath); + MultiChoiceDialog::addRecentArgument("remote", m_workFolderPath, true); + MoreInformationDialog::information + (this, + tr("Clone"), + tr("Clone successful"), + tr("The remote repository was successfully cloned to the local folder %1.").arg(xmlEncode(m_workFolderPath)), + output); + enableDisableActions(); + m_shouldHgStat = true; + break; + + case ACT_LOG: + m_hgTabs->setNewLog(output); + m_needNewLog = false; + break; + + case ACT_LOG_INCREMENTAL: + m_hgTabs->addIncrementalLog(output); + break; + + case ACT_QUERY_PARENTS: + { + foreach (Changeset *cs, m_currentParents) delete cs; + m_currentParents = Changeset::parseChangesets(output); + QStringList parentIds = Changeset::getIds(m_currentParents); + m_hgTabs->setCurrent(parentIds, m_currentBranch); + } + break; + + case ACT_QUERY_HEADS: + { + oldHeadIds = Changeset::getIds(m_currentHeads); + Changesets newHeads = Changeset::parseChangesets(output); + QStringList newHeadIds = Changeset::getIds(newHeads); + if (oldHeadIds != newHeadIds) { + DEBUG << "Heads changed, will prompt an incremental log if appropriate" << endl; + DEBUG << "Old heads: " << oldHeadIds.join(",") << endl; + DEBUG << "New heads: " << newHeadIds.join(",") << endl; + headsChanged = true; + foreach (Changeset *cs, m_currentHeads) delete cs; + m_currentHeads = newHeads; + } + } + break; + + case ACT_COMMIT: + if (m_currentParents.empty()) { + // first commit to empty repo + m_needNewLog = true; + } + m_hgTabs->clearSelections(); + m_justMerged = false; + m_shouldHgStat = true; + break; + + case ACT_REVERT: + hgMarkFilesResolved(m_lastRevertedFiles); + m_justMerged = false; + break; + + case ACT_REMOVE: + case ACT_ADD: + m_hgTabs->clearSelections(); + m_shouldHgStat = true; + break; + + case ACT_TAG: + m_needNewLog = true; + m_shouldHgStat = true; + break; + + case ACT_NEW_BRANCH: + m_shouldHgStat = true; + break; + + case ACT_UNCOMMITTED_SUMMARY: + QMessageBox::information(this, tr("Change summary"), + format3(tr("Summary of uncommitted changes"), + "", + output)); + break; + + case ACT_DIFF_SUMMARY: + { + // Output has log info first, diff following after a blank line + output.replace("\r\n", "\n"); + QStringList olist = output.split("\n\n", QString::SkipEmptyParts); + if (olist.size() > 1) output = olist[1]; + + Changeset *cs = (Changeset *)completedAction.extraData; + if (cs) { + QMessageBox::information + (this, tr("Change summary"), + format3(tr("Summary of changes"), + cs->formatHtml(), + output)); + } else if (output == "") { + // Can happen, for a merge commit (depending on parent) + QMessageBox::information(this, tr("Change summary"), + format3(tr("Summary of changes"), + tr("No changes"), + output)); + } else { + QMessageBox::information(this, tr("Change summary"), + format3(tr("Summary of changes"), + "", + output)); + } + break; + } + + case ACT_FOLDERDIFF: + case ACT_CHGSETDIFF: + case ACT_SERVE: + case ACT_HG_IGNORE: + m_shouldHgStat = true; + break; + + case ACT_UPDATE: + QMessageBox::information(this, tr("Update"), tr("

Update successful

%1

").arg(xmlEncode(output))); + m_shouldHgStat = true; + break; + + case ACT_MERGE: + MoreInformationDialog::information + (this, tr("Merge"), tr("Merge successful"), + tr("Remember to test and commit the result before making any further changes."), + output); + m_shouldHgStat = true; + m_justMerged = true; + break; + + case ACT_RETRY_MERGE: + QMessageBox::information(this, tr("Resolved"), + tr("

Merge resolved

Merge resolved successfully.
Remember to test and commit the result before making any further changes.

")); + m_shouldHgStat = true; + m_justMerged = true; + break; + + default: + break; + } + + // Sequence when no full log required: + // paths -> branch -> stat -> resolve-list -> heads -> + // incremental-log (only if heads changed) -> parents + // + // Sequence when full log required: + // paths -> branch -> stat -> resolve-list -> heads -> parents -> log + // + // Note we want to call enableDisableActions only once, at the end + // of whichever sequence is in use. + + bool noMore = false; + + switch (action) { + + case ACT_TEST_HG: + { + QSettings settings; + settings.beginGroup("General"); + if (settings.value("useextension", true).toBool()) { + hgTestExtension(); + } else if (m_workFolderPath == "") { + open(); + } else { + hgQueryPaths(); + } + break; + } + + case ACT_TEST_HG_EXT: + if (m_workFolderPath == "") { + open(); + } else{ + hgQueryPaths(); + } + break; + + case ACT_QUERY_PATHS: + hgQueryBranch(); + break; + + case ACT_QUERY_BRANCH: + hgStat(); + break; + + case ACT_STAT: + hgResolveList(); + break; + + case ACT_RESOLVE_LIST: + hgQueryHeads(); + break; + + case ACT_QUERY_HEADS: + if (headsChanged && !m_needNewLog) { + hgLogIncremental(oldHeadIds); + } else { + hgQueryParents(); + } + break; + + case ACT_LOG_INCREMENTAL: + hgQueryParents(); + break; + + case ACT_QUERY_PARENTS: + if (m_needNewLog) { + hgLog(); + } else { + // we're done + noMore = true; + } + break; + + case ACT_LOG: + // we're done + noMore = true; + break; + + default: + if (m_shouldHgStat) { + m_shouldHgStat = false; + hgQueryPaths(); + } else { + noMore = true; + } + break; + } + + if (noMore) { + m_stateUnknown = false; + enableDisableActions(); + m_hgTabs->updateHistory(); + updateRecentMenu(); + } +} + +void MainWindow::connectActions() +{ + connect(m_exitAct, SIGNAL(triggered()), this, SLOT(close())); + connect(m_aboutAct, SIGNAL(triggered()), this, SLOT(about())); + + connect(m_hgRefreshAct, SIGNAL(triggered()), this, SLOT(hgRefresh())); + connect(m_hgRemoveAct, SIGNAL(triggered()), this, SLOT(hgRemove())); + connect(m_hgAddAct, SIGNAL(triggered()), this, SLOT(hgAdd())); + connect(m_hgCommitAct, SIGNAL(triggered()), this, SLOT(hgCommit())); + connect(m_hgFolderDiffAct, SIGNAL(triggered()), this, SLOT(hgFolderDiff())); + connect(m_hgUpdateAct, SIGNAL(triggered()), this, SLOT(hgUpdate())); + connect(m_hgRevertAct, SIGNAL(triggered()), this, SLOT(hgRevert())); + connect(m_hgMergeAct, SIGNAL(triggered()), this, SLOT(hgMerge())); + connect(m_hgIgnoreAct, SIGNAL(triggered()), this, SLOT(hgIgnore())); + + connect(m_settingsAct, SIGNAL(triggered()), this, SLOT(settings())); + connect(m_openAct, SIGNAL(triggered()), this, SLOT(open())); + connect(m_changeRemoteRepoAct, SIGNAL(triggered()), this, SLOT(changeRemoteRepo())); + + connect(m_hgIncomingAct, SIGNAL(triggered()), this, SLOT(hgIncoming())); + connect(m_hgPullAct, SIGNAL(triggered()), this, SLOT(hgPull())); + connect(m_hgPushAct, SIGNAL(triggered()), this, SLOT(hgPush())); + + connect(m_hgServeAct, SIGNAL(triggered()), this, SLOT(hgServe())); +} + +void MainWindow::connectTabsSignals() +{ + connect(m_hgTabs, SIGNAL(currentChanged(int)), + this, SLOT(enableDisableActions())); + + connect(m_hgTabs, SIGNAL(commit()), + this, SLOT(hgCommit())); + + connect(m_hgTabs, SIGNAL(revert()), + this, SLOT(hgRevert())); + + connect(m_hgTabs, SIGNAL(diffWorkingFolder()), + this, SLOT(hgFolderDiff())); + + connect(m_hgTabs, SIGNAL(showSummary()), + this, SLOT(hgShowSummary())); + + connect(m_hgTabs, SIGNAL(newBranch()), + this, SLOT(hgNewBranch())); + + connect(m_hgTabs, SIGNAL(noBranch()), + this, SLOT(hgNoBranch())); + + connect(m_hgTabs, SIGNAL(updateTo(QString)), + this, SLOT(hgUpdateToRev(QString))); + + connect(m_hgTabs, SIGNAL(diffToCurrent(QString)), + this, SLOT(hgDiffToCurrent(QString))); + + connect(m_hgTabs, SIGNAL(diffToParent(QString, QString)), + this, SLOT(hgDiffToParent(QString, QString))); + + connect(m_hgTabs, SIGNAL(showSummary(Changeset *)), + this, SLOT(hgShowSummaryFor(Changeset *))); + + connect(m_hgTabs, SIGNAL(mergeFrom(QString)), + this, SLOT(hgMergeFrom(QString))); + + connect(m_hgTabs, SIGNAL(newBranch(QString)), + this, SLOT(hgNewBranch())); + + connect(m_hgTabs, SIGNAL(tag(QString)), + this, SLOT(hgTag(QString))); + + connect(m_hgTabs, SIGNAL(annotateFiles(QStringList)), + this, SLOT(hgAnnotateFiles(QStringList))); + + connect(m_hgTabs, SIGNAL(diffFiles(QStringList)), + this, SLOT(hgDiffFiles(QStringList))); + + connect(m_hgTabs, SIGNAL(commitFiles(QStringList)), + this, SLOT(hgCommitFiles(QStringList))); + + connect(m_hgTabs, SIGNAL(revertFiles(QStringList)), + this, SLOT(hgRevertFiles(QStringList))); + + connect(m_hgTabs, SIGNAL(renameFiles(QStringList)), + this, SLOT(hgRenameFiles(QStringList))); + + connect(m_hgTabs, SIGNAL(copyFiles(QStringList)), + this, SLOT(hgCopyFiles(QStringList))); + + connect(m_hgTabs, SIGNAL(addFiles(QStringList)), + this, SLOT(hgAddFiles(QStringList))); + + connect(m_hgTabs, SIGNAL(removeFiles(QStringList)), + this, SLOT(hgRemoveFiles(QStringList))); + + connect(m_hgTabs, SIGNAL(redoFileMerges(QStringList)), + this, SLOT(hgRedoFileMerges(QStringList))); + + connect(m_hgTabs, SIGNAL(markFilesResolved(QStringList)), + this, SLOT(hgMarkFilesResolved(QStringList))); + + connect(m_hgTabs, SIGNAL(ignoreFiles(QStringList)), + this, SLOT(hgIgnoreFiles(QStringList))); + + connect(m_hgTabs, SIGNAL(unIgnoreFiles(QStringList)), + this, SLOT(hgUnIgnoreFiles(QStringList))); +} + +void MainWindow::enableDisableActions() +{ + DEBUG << "MainWindow::enableDisableActions" << endl; + + QString dirname = QDir(m_workFolderPath).dirName(); + + if (m_workFolderPath != "") { // dirname of "" is ".", so test path instead + setWindowTitle(tr("EasyMercurial: %1").arg(dirname)); + } else { + setWindowTitle(tr("EasyMercurial")); + } + + //!!! should also do things like set the status texts for the + //!!! actions appropriately by context + + QDir localRepoDir; + QDir workFolderDir; + bool workFolderExist = true; + bool localRepoExist = true; + + m_remoteRepoActionsEnabled = true; + if (m_remoteRepoPath.isEmpty()) { + m_remoteRepoActionsEnabled = false; + } + + m_localRepoActionsEnabled = true; + if (m_workFolderPath.isEmpty()) { + m_localRepoActionsEnabled = false; + workFolderExist = false; + } + + if (m_workFolderPath == "" || !workFolderDir.exists(m_workFolderPath)) { + m_localRepoActionsEnabled = false; + workFolderExist = false; + } else { + workFolderExist = true; + } + + if (!localRepoDir.exists(m_workFolderPath + "/.hg")) { + m_localRepoActionsEnabled = false; + localRepoExist = false; + } + + bool haveDiff = false; + QSettings settings; + settings.beginGroup("Locations"); + if (settings.value("extdiffbinary", "").toString() != "") { + haveDiff = true; + } + settings.endGroup(); + + m_hgRefreshAct->setEnabled(m_localRepoActionsEnabled); + m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && haveDiff); + m_hgRevertAct->setEnabled(m_localRepoActionsEnabled); + m_hgAddAct->setEnabled(m_localRepoActionsEnabled); + m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled); + m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled); + m_hgCommitAct->setEnabled(m_localRepoActionsEnabled); + m_hgMergeAct->setEnabled(m_localRepoActionsEnabled); + m_hgServeAct->setEnabled(m_localRepoActionsEnabled); + m_hgIgnoreAct->setEnabled(m_localRepoActionsEnabled); + + DEBUG << "m_localRepoActionsEnabled = " << m_localRepoActionsEnabled << endl; + DEBUG << "canCommit = " << m_hgTabs->canCommit() << endl; + + m_hgAddAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canAdd()); + m_hgRemoveAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRemove()); + m_hgCommitAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canCommit()); + m_hgRevertAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canRevert()); + m_hgFolderDiffAct->setEnabled(m_localRepoActionsEnabled && m_hgTabs->canDiff()); + + // A default merge makes sense if: + // * there is only one parent (if there are two, we have an uncommitted merge) and + // * there are exactly two heads that have the same branch as the current branch and + // * our parent is one of those heads + // + // A default update makes sense if: + // * there is only one parent and + // * the parent is not one of the current heads + + bool canMerge = false; + bool canUpdate = false; + bool haveMerge = false; + bool emptyRepo = false; + bool noWorkingCopy = false; + bool newBranch = false; + int m_currentBranchHeads = 0; + + if (m_currentParents.size() == 1) { + bool parentIsHead = false; + Changeset *parent = m_currentParents[0]; + foreach (Changeset *head, m_currentHeads) { + DEBUG << "head branch " << head->branch() << ", current branch " << m_currentBranch << endl; + if (head->isOnBranch(m_currentBranch)) { + ++m_currentBranchHeads; + } + if (parent->id() == head->id()) { + parentIsHead = true; + } + } + if (m_currentBranchHeads == 2 && parentIsHead) { + canMerge = true; + } + if (m_currentBranchHeads == 0 && parentIsHead) { + // Just created a new branch + newBranch = true; + } + if (!parentIsHead) { + canUpdate = true; + DEBUG << "parent id = " << parent->id() << endl; + DEBUG << " head ids "<setState(tr("No repository open")); + } else { + m_workStatus->setState(tr("(Examining repository)")); + } + } else if (emptyRepo) { + m_workStatus->setState(tr("Nothing committed to this repository yet")); + } else if (noWorkingCopy) { + m_workStatus->setState(tr("No working copy yet: consider updating")); + } else if (canMerge) { + m_workStatus->setState(tr("Awaiting merge on %1").arg(branchText)); + } else if (!m_hgTabs->getAllUnresolvedFiles().empty()) { + m_workStatus->setState(tr("Have unresolved files following merge on %1").arg(branchText)); + } else if (haveMerge) { + m_workStatus->setState(tr("Have merged but not yet committed on %1").arg(branchText)); + } else if (newBranch) { + m_workStatus->setState(tr("On %1. New branch: has not yet been committed").arg(branchText)); + } else if (canUpdate) { + if (m_hgTabs->haveChangesToCommit()) { + // have uncommitted changes + m_workStatus->setState(tr("On %1. Not at the head of the branch").arg(branchText)); + } else { + // no uncommitted changes + m_workStatus->setState(tr("On %1. Not at the head of the branch: consider updating").arg(branchText)); + } + } else if (m_currentBranchHeads > 1) { + m_workStatus->setState(tr("At one of %n heads of %1", "", m_currentBranchHeads).arg(branchText)); + } else { + m_workStatus->setState(tr("At the head of %1").arg(branchText)); + } +} + + +void MainWindow::updateRecentMenu() +{ + m_recentMenu->clear(); + RecentFiles rf("Recent-local"); + QStringList recent = rf.getRecent(); + if (recent.empty()) { + QLabel *label = new QLabel(tr("No recent local repositories")); + QWidgetAction *wa = new QWidgetAction(m_recentMenu); + wa->setDefaultWidget(label); + return; + } + foreach (QString r, recent) { + QAction *a = m_recentMenu->addAction(r); + connect(a, SIGNAL(activated()), this, SLOT(recentMenuActivated())); + } +} + +void MainWindow::createActions() +{ + //File actions + m_openAct = new QAction(QIcon(":/images/fileopen.png"), tr("&Open..."), this); + m_openAct->setStatusTip(tr("Open an existing repository or working folder")); + m_openAct->setShortcut(tr("Ctrl+O")); + + m_changeRemoteRepoAct = new QAction(tr("Set Remote &Location..."), this); + m_changeRemoteRepoAct->setStatusTip(tr("Set or change the default remote repository for pull and push actions")); + + m_settingsAct = new QAction(QIcon(":/images/settings.png"), tr("&Settings..."), this); + m_settingsAct->setStatusTip(tr("View and change application settings")); + +#ifdef Q_OS_WIN32 + m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("E&xit"), this); +#else + m_exitAct = new QAction(QIcon(":/images/exit.png"), tr("&Quit"), this); +#endif + m_exitAct->setShortcuts(QKeySequence::Quit); + m_exitAct->setStatusTip(tr("Exit EasyMercurial")); + + //Repository actions + m_hgRefreshAct = new QAction(QIcon(":/images/status.png"), tr("&Refresh"), this); + m_hgRefreshAct->setShortcut(tr("Ctrl+R")); + m_hgRefreshAct->setStatusTip(tr("Refresh the window to show the current state of the working folder")); + + m_hgIncomingAct = new QAction(QIcon(":/images/incoming.png"), tr("Pre&view Incoming Changes"), this); + m_hgIncomingAct->setIconText(tr("Preview")); + m_hgIncomingAct->setStatusTip(tr("See what changes are available in the remote repository waiting to be pulled")); + + m_hgPullAct = new QAction(QIcon(":/images/pull.png"), tr("Pu&ll from Remote Repository"), this); + m_hgPullAct->setIconText(tr("Pull")); + m_hgPullAct->setShortcut(tr("Ctrl+L")); + m_hgPullAct->setStatusTip(tr("Pull changes from the remote repository to the local repository")); + + m_hgPushAct = new QAction(QIcon(":/images/push.png"), tr("Pus&h to Remote Repository"), this); + m_hgPushAct->setIconText(tr("Push")); + m_hgPushAct->setShortcut(tr("Ctrl+H")); + m_hgPushAct->setStatusTip(tr("Push changes from the local repository to the remote repository")); + + //Workfolder actions + m_hgFolderDiffAct = new QAction(QIcon(":/images/folderdiff.png"), tr("&Diff"), this); + m_hgFolderDiffAct->setIconText(tr("Diff")); + m_hgFolderDiffAct->setShortcut(tr("Ctrl+D")); + m_hgFolderDiffAct->setStatusTip(tr("See what has changed in the working folder compared with the last committed state")); + + m_hgRevertAct = new QAction(QIcon(":/images/undo.png"), tr("Re&vert"), this); + m_hgRevertAct->setStatusTip(tr("Throw away your changes and return to the last committed state")); + + m_hgAddAct = new QAction(QIcon(":/images/add.png"), tr("&Add Files"), this); + m_hgAddAct->setIconText(tr("Add")); + m_hgAddAct->setShortcut(tr("+")); + m_hgAddAct->setStatusTip(tr("Mark the selected files to be added on the next commit")); + + m_hgRemoveAct = new QAction(QIcon(":/images/remove.png"), tr("&Remove Files"), this); + m_hgRemoveAct->setIconText(tr("Remove")); + m_hgRemoveAct->setShortcut(tr("Del")); + m_hgRemoveAct->setStatusTip(tr("Mark the selected files to be removed from version control on the next commit")); + + m_hgUpdateAct = new QAction(QIcon(":/images/update.png"), tr("&Update to Branch Head"), this); + m_hgUpdateAct->setIconText(tr("Update")); + m_hgUpdateAct->setShortcut(tr("Ctrl+U")); + m_hgUpdateAct->setStatusTip(tr("Update the working folder to the head of the current repository branch")); + + m_hgCommitAct = new QAction(QIcon(":/images/commit.png"), tr("&Commit..."), this); + m_hgCommitAct->setShortcut(tr("Ctrl+Return")); + m_hgCommitAct->setStatusTip(tr("Commit your changes to the local repository")); + + m_hgMergeAct = new QAction(QIcon(":/images/merge.png"), tr("&Merge"), this); + m_hgMergeAct->setShortcut(tr("Ctrl+M")); + m_hgMergeAct->setStatusTip(tr("Merge the two independent sets of changes in the local repository into the working folder")); + + //Advanced actions + + m_hgIgnoreAct = new QAction(tr("Edit .hgignore File"), this); + m_hgIgnoreAct->setStatusTip(tr("Edit the .hgignore file, containing the names of files that should be ignored by Mercurial")); + + m_hgServeAct = new QAction(tr("Serve via HTTP"), this); + m_hgServeAct->setStatusTip(tr("Serve local repository via http for workgroup access")); + + //Help actions + m_aboutAct = new QAction(tr("About EasyMercurial"), this); + + // Miscellaneous + QShortcut *clearSelectionsShortcut = new QShortcut(Qt::Key_Escape, this); + connect(clearSelectionsShortcut, SIGNAL(activated()), + this, SLOT(clearSelections())); +} + +void MainWindow::createMenus() +{ + m_fileMenu = menuBar()->addMenu(tr("&File")); + + m_fileMenu->addAction(m_openAct); + m_recentMenu = m_fileMenu->addMenu(tr("Open Re¢")); + m_fileMenu->addAction(m_hgRefreshAct); + m_fileMenu->addSeparator(); + m_fileMenu->addAction(m_settingsAct); + m_fileMenu->addSeparator(); + m_fileMenu->addAction(m_exitAct); + + QMenu *workMenu; + workMenu = menuBar()->addMenu(tr("&Work")); + workMenu->addAction(m_hgFolderDiffAct); + workMenu->addSeparator(); + workMenu->addAction(m_hgUpdateAct); + workMenu->addAction(m_hgCommitAct); + workMenu->addAction(m_hgMergeAct); + workMenu->addSeparator(); + workMenu->addAction(m_hgAddAct); + workMenu->addAction(m_hgRemoveAct); + workMenu->addSeparator(); + workMenu->addAction(m_hgRevertAct); + + QMenu *remoteMenu; + remoteMenu = menuBar()->addMenu(tr("&Remote")); + remoteMenu->addAction(m_hgIncomingAct); + remoteMenu->addSeparator(); + remoteMenu->addAction(m_hgPullAct); + remoteMenu->addAction(m_hgPushAct); + remoteMenu->addSeparator(); + remoteMenu->addAction(m_changeRemoteRepoAct); + + m_advancedMenu = menuBar()->addMenu(tr("&Advanced")); + m_advancedMenu->addAction(m_hgIgnoreAct); + m_advancedMenu->addSeparator(); + m_advancedMenu->addAction(m_hgServeAct); + + m_helpMenu = menuBar()->addMenu(tr("Help")); + m_helpMenu->addAction(m_aboutAct); +} + +void MainWindow::createToolBars() +{ + m_fileToolBar = addToolBar(tr("File")); + m_fileToolBar->setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); + m_fileToolBar->addAction(m_openAct); + m_fileToolBar->addAction(m_hgRefreshAct); + m_fileToolBar->setMovable(false); + + m_repoToolBar = addToolBar(tr(REPOMENU_TITLE)); + m_repoToolBar->setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); + m_repoToolBar->addAction(m_hgIncomingAct); + m_repoToolBar->addAction(m_hgPullAct); + m_repoToolBar->addAction(m_hgPushAct); + m_repoToolBar->setMovable(false); + + m_workFolderToolBar = addToolBar(tr(WORKFOLDERMENU_TITLE)); + addToolBar(Qt::LeftToolBarArea, m_workFolderToolBar); + m_workFolderToolBar->setIconSize(QSize(MY_ICON_SIZE, MY_ICON_SIZE)); + m_workFolderToolBar->addAction(m_hgFolderDiffAct); + m_workFolderToolBar->addSeparator(); + m_workFolderToolBar->addAction(m_hgRevertAct); + m_workFolderToolBar->addAction(m_hgUpdateAct); + m_workFolderToolBar->addAction(m_hgCommitAct); + m_workFolderToolBar->addAction(m_hgMergeAct); + m_workFolderToolBar->addSeparator(); + m_workFolderToolBar->addAction(m_hgAddAct); + m_workFolderToolBar->addAction(m_hgRemoveAct); + m_workFolderToolBar->setMovable(false); + + updateToolBarStyle(); +} + +void MainWindow::updateToolBarStyle() +{ + QSettings settings; + settings.beginGroup("Presentation"); + bool showText = settings.value("showiconlabels", true).toBool(); + settings.endGroup(); + + foreach (QToolButton *tb, findChildren()) { + tb->setToolButtonStyle(showText ? + Qt::ToolButtonTextUnderIcon : + Qt::ToolButtonIconOnly); + } +} + +void MainWindow::updateWorkFolderAndRepoNames() +{ + m_hgTabs->setLocalPath(m_workFolderPath); + + m_workStatus->setLocalPath(m_workFolderPath); + m_workStatus->setRemoteURL(m_remoteRepoPath); +} + +void MainWindow::createStatusBar() +{ + statusBar()->showMessage(tr("Ready")); +} + +void MainWindow::readSettings() +{ + QDir workFolder; + + QSettings settings; + + m_remoteRepoPath = settings.value("remoterepopath", "").toString(); + m_workFolderPath = settings.value("workfolderpath", "").toString(); + if (!workFolder.exists(m_workFolderPath)) + { + m_workFolderPath = ""; + } + + QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint(); + QSize size = settings.value("size", QSize(550, 550)).toSize(); + m_firstStart = settings.value("firststart", QVariant(true)).toBool(); + + resize(size); + move(pos); +} + +void MainWindow::writeSettings() +{ + QSettings settings; + settings.setValue("pos", pos()); + settings.setValue("size", size()); + settings.setValue("remoterepopath", m_remoteRepoPath); + settings.setValue("workfolderpath", m_workFolderPath); + settings.setValue("firststart", m_firstStart); +} + + + + diff -r 19cce6d2c470 -r b9c153e00e84 src/mainwindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainwindow.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,256 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "hgtabwidget.h" +#include "hgrunner.h" +#include "common.h" +#include "changeset.h" +#include "hgaction.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QAction; +class QMenu; +class QTimer; +QT_END_NAMESPACE + +class WorkStatusWidget; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QString myDirPath); + +protected: + void closeEvent(QCloseEvent *event); + +public slots: + void open(QString local); + void hgRefresh(); + void commandStarting(HgAction); + void commandCompleted(HgAction action, QString stdOut); + void commandFailed(HgAction action, QString stdErr); + void enableDisableActions(); + +private slots: + void about(); + void settings(); + void open(); + void recentMenuActivated(); + void changeRemoteRepo(); + void changeRemoteRepo(bool initial); + void startupDialog(); + void clearSelections(); + void showAllChanged(bool); + + void hgTest(); + void hgTestExtension(); + void hgQueryPaths(); + void hgStat(); + void hgRemove(); + void hgAdd(); + void hgCommit(); + void hgShowSummary(); + void hgShowSummaryFor(Changeset *); + void hgFolderDiff(); + void hgDiffToCurrent(QString); + void hgDiffToParent(QString, QString); + void hgUpdate(); + void hgRevert(); + void hgMerge(); + void hgRedoMerge(); + void hgCloneFromRemote(); + void hgInit(); + void hgIncoming(); + void hgPush(); + void hgPull(); + void hgUpdateToRev(QString); + void hgMergeFrom(QString); + void hgResolveList(); + void hgTag(QString); + void hgNewBranch(); + void hgNoBranch(); + void hgServe(); + void hgIgnore(); + + void hgAnnotateFiles(QStringList); + void hgDiffFiles(QStringList); + void hgCommitFiles(QStringList); + void hgRevertFiles(QStringList); + void hgRenameFiles(QStringList); + void hgCopyFiles(QStringList); + void hgAddFiles(QStringList); + void hgRemoveFiles(QStringList); + void hgRedoFileMerges(QStringList); + void hgMarkFilesResolved(QStringList); + void hgIgnoreFiles(QStringList); + void hgUnIgnoreFiles(QStringList); + + void fsDirectoryChanged(QString); + void fsFileChanged(QString); + void checkFilesystem(); + void actuallyRestoreFileSystemWatcher(); + +private: + void hgQueryBranch(); + void hgQueryHeads(); + void hgQueryParents(); + void hgLog(); + void hgLogIncremental(QStringList prune); + + void updateRecentMenu(); + void createActions(); + void connectActions(); + void connectTabsSignals(); + void createMenus(); + void createToolBars(); + void updateToolBarStyle(); + void createStatusBar(); + void readSettings(); + void splitChangeSets(QStringList *list, QString hgLogOutput); + void reportNewRemoteHeads(QString); + void reportAuthFailed(QString); + void writeSettings(); + + QStringList listAllUpIpV4Addresses(); + QString filterTag(QString tag); + + QString getUserInfo() const; + + bool openLocal(QString); + bool openRemote(QString, QString); + bool openInit(QString); + + bool complainAboutFilePath(QString); + bool complainAboutUnknownFolder(QString); + bool complainAboutInitInRepo(QString); + bool complainAboutInitFile(QString); + bool complainAboutCloneToExisting(QString); + bool complainAboutCloneToFile(QString); + QString complainAboutCloneToExistingFolder(QString local, QString remote); // returns new location, or empty string for cancel + + bool askAboutUnknownFolder(QString); + bool askToInitExisting(QString); + bool askToInitNew(QString); + bool askToOpenParentRepo(QString, QString); + bool askToOpenInsteadOfInit(QString); + + void showIncoming(QString); + void showPullResult(QString); + void showPushResult(QString); + int extractChangeCount(QString); + QString format1(QString); + QString format3(QString, QString, QString); + + void clearState(); + + void updateFileSystemWatcher(); + void suspendFileSystemWatcher(); + void restoreFileSystemWatcher(); + + void updateWorkFolderAndRepoNames(); + + WorkStatusWidget *m_workStatus; + HgTabWidget *m_hgTabs; + + QString m_remoteRepoPath; + QString m_workFolderPath; + QString m_currentBranch; + Changesets m_currentHeads; + Changesets m_currentParents; + int m_commitsSincePush; + bool m_stateUnknown; + bool m_hgIsOK; + bool m_needNewLog; + + bool m_firstStart; + + bool m_showAllFiles; + + //Actions enabled flags + bool m_remoteRepoActionsEnabled; + bool m_localRepoActionsEnabled; + + QString m_myDirPath; + + // File menu actions + QAction *m_openAct; + QAction *m_changeRemoteRepoAct; + QAction *m_settingsAct; + QAction *m_exitAct; + + // Repo actions + QAction *m_hgIncomingAct; + QAction *m_hgPushAct; + QAction *m_hgPullAct; + QAction *m_hgRefreshAct; + QAction *m_hgFolderDiffAct; + QAction *m_hgChgSetDiffAct; + QAction *m_hgRevertAct; + QAction *m_hgAddAct; + QAction *m_hgRemoveAct; + QAction *m_hgUpdateAct; + QAction *m_hgCommitAct; + QAction *m_hgMergeAct; + QAction *m_hgUpdateToRevAct; + QAction *m_hgAnnotateAct; + QAction *m_hgIgnoreAct; + QAction *m_hgServeAct; + + // Menus + QMenu *m_fileMenu; + QMenu *m_recentMenu; + QMenu *m_advancedMenu; + QMenu *m_helpMenu; + + // Help menu actions + QAction *m_aboutAct; + + QToolBar *m_fileToolBar; + QToolBar *m_repoToolBar; + QToolBar *m_workFolderToolBar; + + HgRunner *m_runner; + + bool m_shouldHgStat; + + QString getDiffBinaryName(); + QString getMergeBinaryName(); + QString getEditorBinaryName(); + + QFileSystemWatcher *m_fsWatcher; + QTimer *m_fsWatcherGeneralTimer; + QTimer *m_fsWatcherRestoreTimer; + bool m_fsWatcherSuspended; + + QString m_lastStatOutput; + QStringList m_lastRevertedFiles; + + bool m_justMerged; + QString m_mergeTargetRevision; + QString m_mergeCommitComment; +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/moreinformationdialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/moreinformationdialog.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,141 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "moreinformationdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +MoreInformationDialog::MoreInformationDialog(QString title, + QString head, + QString text, + QString more, + QWidget *parent) : + QDialog(parent) +{ + setWindowTitle(title); + + QGridLayout *layout = new QGridLayout; + layout->setSpacing(10); + setLayout(layout); + + m_iconLabel = new QLabel; + layout->addWidget(m_iconLabel, 0, 0, 2, 1, Qt::AlignTop); + + QLabel *headLabel = new QLabel(QString("

%1

").arg(head)); + layout->addWidget(headLabel, 0, 1); + + QLabel *textLabel = new QLabel(text); + textLabel->setTextFormat(Qt::RichText); + textLabel->setWordWrap(true); + layout->addWidget(textLabel, 1, 1, Qt::AlignTop); + + QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok); + connect(bb, SIGNAL(accepted()), this, SLOT(accept())); + layout->addWidget(bb, 2, 0, 1, 2); + + m_moreButton = bb->addButton(tr("More Details..."), + QDialogButtonBox::ActionRole); + m_moreButton->setAutoDefault(false); + m_moreButton->setDefault(false); + + connect(m_moreButton, SIGNAL(clicked()), this, SLOT(moreClicked())); + + bb->button(QDialogButtonBox::Ok)->setDefault(true); + + m_moreText = new QTextEdit(); + m_moreText->setAcceptRichText(false); + m_moreText->document()->setPlainText(more); + m_moreText->setMinimumWidth(360); + m_moreText->setReadOnly(true); + m_moreText->setLineWrapMode(QTextEdit::NoWrap); + + QFont font("Monospace"); + font.setStyleHint(QFont::TypeWriter); + m_moreText->setFont(font); + + layout->addWidget(m_moreText, 3, 0, 1, 2); + + m_moreText->hide(); + if (more == "") m_moreButton->hide(); + + layout->setRowStretch(1, 20); + layout->setColumnStretch(1, 20); + setMinimumWidth(400); +} + +MoreInformationDialog::~MoreInformationDialog() +{ +} + +void +MoreInformationDialog::moreClicked() +{ + if (m_moreText->isVisible()) { + m_moreText->hide(); + m_moreButton->setText(tr("Show Details...")); + } else { + m_moreText->show(); + m_moreButton->setText(tr("Hide Details...")); + } + adjustSize(); +} + +void +MoreInformationDialog::setIcon(QIcon icon) +{ + QStyle *style = qApp->style(); + int iconSize = style->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, this); + m_iconLabel->setPixmap(icon.pixmap(iconSize, iconSize)); +} + +void +MoreInformationDialog::critical(QWidget *parent, QString title, QString head, + QString text, QString more) +{ + MoreInformationDialog d(title, head, text, more, parent); + QStyle *style = qApp->style(); + d.setIcon(style->standardIcon(QStyle::SP_MessageBoxCritical, 0, &d)); + d.exec(); +} + +void +MoreInformationDialog::information(QWidget *parent, QString title, QString head, + QString text, QString more) +{ + MoreInformationDialog d(title, head, text, more, parent); + QStyle *style = qApp->style(); + d.setIcon(style->standardIcon(QStyle::SP_MessageBoxInformation, 0, &d)); + d.exec(); +} + +void +MoreInformationDialog::warning(QWidget *parent, QString title, QString head, + QString text, QString more) +{ + MoreInformationDialog d(title, head, text, more, parent); + QStyle *style = qApp->style(); + d.setIcon(style->standardIcon(QStyle::SP_MessageBoxWarning, 0, &d)); + d.exec(); +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/moreinformationdialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/moreinformationdialog.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,63 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on hgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef MORE_INFORMATION_DIALOG_H +#define MORE_INFORMATION_DIALOG_H + +#include +#include + +class QLabel; +class QTextEdit; +class QPushButton; + +/** + * Provide methods like the QMessageBox static methods, to call up + * dialogs with "More information" buttons in them. QMessageBox does + * have an optional additional-details field, but it doesn't behave + * quite as we'd like with regard to layout + */ + +class MoreInformationDialog : public QDialog +{ + Q_OBJECT + +public: + MoreInformationDialog(QString title, + QString head, + QString text, + QString more, + QWidget *parent = 0); + + ~MoreInformationDialog(); + + void setIcon(QIcon); + + static void critical(QWidget *parent, QString title, QString head, QString text, QString more); + static void information(QWidget *parent, QString title, QString head, QString text, QString more); + static void warning(QWidget *parent, QString title, QString head, QString text, QString more); + +private slots: + void moreClicked(); + +private: + QLabel *m_iconLabel; + QPushButton *m_moreButton; + QTextEdit *m_moreText; +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/multichoicedialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/multichoicedialog.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,345 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "multichoicedialog.h" + +#include "selectablelabel.h" + +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include + +MultiChoiceDialog::MultiChoiceDialog(QString title, QString heading, QWidget *parent) : + QDialog(parent) +{ + setModal(true); + setWindowTitle(title); + + QGridLayout *outer = new QGridLayout; + setLayout(outer); + + outer->addWidget(new QLabel(heading), 0, 0, 1, 3); + + QWidget *innerWidget = new QWidget; + outer->addWidget(innerWidget, 1, 0, 1, 3); + m_choiceLayout = new QHBoxLayout; + innerWidget->setLayout(m_choiceLayout); + + m_descriptionLabel = new QLabel; + outer->addWidget(m_descriptionLabel, 2, 0, 1, 3); + + QFont f = m_descriptionLabel->font(); + f.setPointSize(f.pointSize() * 0.95); + m_descriptionLabel->setFont(f); + + m_urlLabel = new QLabel(tr("&URL:")); + outer->addWidget(m_urlLabel, 3, 0); + + m_urlCombo = new QComboBox(); + m_urlCombo->setEditable(true); + m_urlLabel->setBuddy(m_urlCombo); + connect(m_urlCombo, SIGNAL(editTextChanged(const QString &)), + this, SLOT(urlChanged(const QString &))); + outer->addWidget(m_urlCombo, 3, 1, 1, 2); + + m_fileLabel = new QLabel(tr("&File:")); + outer->addWidget(m_fileLabel, 4, 0); + + m_fileCombo = new QComboBox(); + m_fileCombo->setEditable(true); + m_fileLabel->setBuddy(m_fileCombo); + connect(m_fileCombo, SIGNAL(editTextChanged(const QString &)), + this, SLOT(fileChanged(const QString &))); + outer->addWidget(m_fileCombo, 4, 1); + outer->setColumnStretch(1, 20); + + m_browseButton = new QPushButton(tr("Browse...")); + outer->addWidget(m_browseButton, 4, 2); + connect(m_browseButton, SIGNAL(clicked()), this, SLOT(browse())); + + QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok | + QDialogButtonBox::Cancel); + connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); + connect(bbox, SIGNAL(rejected()), this, SLOT(reject())); + outer->addWidget(bbox, 5, 0, 1, 3); + + m_okButton = bbox->button(QDialogButtonBox::Ok); + updateOkButton(); + + setMinimumWidth(480); +} + +QString +MultiChoiceDialog::getCurrentChoice() +{ + return m_currentChoice; +} + +void +MultiChoiceDialog::setCurrentChoice(QString c) +{ + m_currentChoice = c; + choiceChanged(); +} + +QString +MultiChoiceDialog::getArgument() +{ + if (m_argTypes[m_currentChoice] == UrlArg || + m_argTypes[m_currentChoice] == UrlToDirectoryArg) { + return m_urlCombo->currentText(); + } else { + return m_fileCombo->currentText(); + } +} + +QString +MultiChoiceDialog::getAdditionalArgument() +{ + if (m_argTypes[m_currentChoice] == UrlToDirectoryArg) { + return m_fileCombo->currentText(); + } else { + return ""; + } +} + +void +MultiChoiceDialog::addRecentArgument(QString id, QString arg, + bool additionalArgument) +{ + if (additionalArgument) { + RecentFiles(QString("Recent-%1-add").arg(id)).addFile(arg); + } else { + RecentFiles(QString("Recent-%1").arg(id)).addFile(arg); + } +} + +void +MultiChoiceDialog::addChoice(QString id, QString text, + QString description, ArgType arg) +{ + bool first = (m_texts.empty()); + + m_texts[id] = text; + m_descriptions[id] = description; + m_argTypes[id] = arg; + + if (arg != NoArg) { + m_recentFiles[id] = QSharedPointer + (new RecentFiles(QString("Recent-%1").arg(id))); + } + + SelectableLabel *cb = new SelectableLabel; + cb->setSelectedText(text); + cb->setUnselectedText(text); + cb->setMaximumWidth(270); + + m_choiceLayout->addWidget(cb); + m_choiceButtons[cb] = id; + + connect(cb, SIGNAL(selectionChanged()), this, SLOT(choiceChanged())); + + if (first) { + m_currentChoice = id; + choiceChanged(); + } +} + +QString +MultiChoiceDialog::getDefaultPath() const +{ + QDir home(QDir::home()); + QDir dflt; + + dflt = QDir(home.filePath(tr("My Documents"))); + DEBUG << "testing " << dflt << endl; + if (dflt.exists()) return dflt.canonicalPath(); + + dflt = QDir(home.filePath(tr("Documents"))); + DEBUG << "testing " << dflt << endl; + if (dflt.exists()) return dflt.canonicalPath(); + + DEBUG << "all failed, returning " << home << endl; + return home.canonicalPath(); +} + +void +MultiChoiceDialog::browse() +{ + QString origin = getArgument(); + + if (origin == "") { + origin = getDefaultPath(); + } + + QString path = origin; + + if (m_argTypes[m_currentChoice] == DirectoryArg || + m_argTypes[m_currentChoice] == UrlToDirectoryArg) { + + path = QFileDialog::getExistingDirectory + (this, tr("Open Directory"), origin, + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (path != QString()) { + m_fileCombo->lineEdit()->setText(path + QDir::separator()); + } + + } else { + + path = QFileDialog::getOpenFileName + (this, tr("Open File"), origin); + if (path != QString()) { + m_fileCombo->lineEdit()->setText(path); + } + } +} + +void +MultiChoiceDialog::urlChanged(const QString &s) +{ + updateOkButton(); +} + +void +MultiChoiceDialog::fileChanged(const QString &s) +{ + updateOkButton(); +} + +void +MultiChoiceDialog::updateOkButton() +{ +/* This doesn't work well + if (m_argTypes[m_currentChoice] != UrlToDirectoryArg) { + return; + } + QDir dirPath(m_fileCombo->currentText()); + if (!dirPath.exists()) { + if (!dirPath.cdUp()) return; + } + QString url = m_urlCombo->currentText(); + if (QRegExp("^\\w+://").indexIn(url) < 0) { + return; + } + QString urlDirName = url; + urlDirName.replace(QRegExp("^.*\\//.*\\/"), ""); + if (urlDirName == "" || urlDirName == url) { + return; + } + m_fileCombo->lineEdit()->setText(dirPath.filePath(urlDirName)); +*/ + if (m_argTypes[m_currentChoice] == UrlToDirectoryArg) { + m_okButton->setEnabled(getArgument() != "" && + getAdditionalArgument() != ""); + } else { + m_okButton->setEnabled(getArgument() != ""); + } +} + +void +MultiChoiceDialog::choiceChanged() +{ + DEBUG << "choiceChanged" << endl; + + if (m_choiceButtons.empty()) return; + + QString id = ""; + + QObject *s = sender(); + QWidget *w = qobject_cast(s); + if (w) id = m_choiceButtons[w]; + + if (id == m_currentChoice) return; + if (id == "") { + // Happens when this is called for the very first time, when + // m_currentChoice has been set to the intended ID but no + // button has actually been pressed -- then we need to + // initialise + id = m_currentChoice; + } + + m_currentChoice = id; + + foreach (QWidget *cw, m_choiceButtons.keys()) { + SelectableLabel *sl = qobject_cast(cw); + if (sl) { + sl->setSelected(m_choiceButtons[cw] == id); + } + } + + m_descriptionLabel->setText(m_descriptions[id]); + + m_fileLabel->hide(); + m_fileCombo->hide(); + m_browseButton->hide(); + m_urlLabel->hide(); + m_urlCombo->hide(); + + QSharedPointer rf = m_recentFiles[id]; + m_fileCombo->clear(); + m_urlCombo->clear(); + + switch (m_argTypes[id]) { + + case NoArg: + break; + + case FileArg: + m_fileLabel->setText(tr("&File:")); + m_fileLabel->show(); + m_fileCombo->show(); + m_fileCombo->addItems(rf->getRecent()); + m_browseButton->show(); + break; + + case DirectoryArg: + m_fileLabel->setText(tr("&Folder:")); + m_fileLabel->show(); + m_fileCombo->show(); + m_fileCombo->addItems(rf->getRecent()); + m_browseButton->show(); + break; + + case UrlArg: + m_urlLabel->show(); + m_urlCombo->show(); + m_urlCombo->addItems(rf->getRecent()); + break; + + case UrlToDirectoryArg: + m_urlLabel->show(); + m_urlCombo->show(); + m_urlCombo->addItems(rf->getRecent()); + m_fileLabel->setText(tr("&Folder:")); + m_fileLabel->show(); + m_fileCombo->show(); + m_fileCombo->lineEdit()->setText(getDefaultPath()); + m_browseButton->show(); + break; + } + + updateOkButton(); + adjustSize(); +} + + diff -r 19cce6d2c470 -r b9c153e00e84 src/multichoicedialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/multichoicedialog.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,90 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef MULTICHOICEDIALOG_H +#define MULTICHOICEDIALOG_H + +#include "recentfiles.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class MultiChoiceDialog : public QDialog +{ + Q_OBJECT +public: + explicit MultiChoiceDialog(QString title, QString heading, + QWidget *parent = 0); + + enum ArgType { + NoArg, + FileArg, + DirectoryArg, + UrlArg, + UrlToDirectoryArg + }; + + void addChoice(QString identifier, QString text, + QString description, ArgType arg); + + void setCurrentChoice(QString); + QString getCurrentChoice(); + QString getArgument(); + QString getAdditionalArgument(); + + static void addRecentArgument(QString identifier, QString name, + bool additionalArgument = false); + +private slots: + void choiceChanged(); + void urlChanged(const QString &); + void fileChanged(const QString &); + void browse(); + +private: + void updateOkButton(); + + QMap m_texts; + QMap m_descriptions; + QMap m_argTypes; + QMap > m_recentFiles; + + QString m_currentChoice; + QMap m_choiceButtons; + + QHBoxLayout *m_choiceLayout; + QLabel *m_descriptionLabel; + QLabel *m_fileLabel; + QComboBox *m_fileCombo; + QAbstractButton *m_browseButton; + QLabel *m_urlLabel; + QComboBox *m_urlCombo; + QAbstractButton *m_okButton; + + QString getDefaultPath() const; +}; + +#endif // MULTICHOICEDIALOG_H diff -r 19cce6d2c470 -r b9c153e00e84 src/panned.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/panned.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,249 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "panned.h" +#include "debug.h" + +#include +#include +#include + +#include + +#include + +Panned::Panned() : + m_dragging(false) +{ + m_dragTimer = new QTimer(this); + m_dragTimerMs = 50; + connect(m_dragTimer, SIGNAL(timeout()), this, SLOT(dragTimerTimeout())); + setRenderHints(QPainter::Antialiasing | + QPainter::TextAntialiasing); +} + +void +Panned::resizeEvent(QResizeEvent *ev) +{ + DEBUG << "Panned::resizeEvent()" << endl; + + QPointF nearpt = mapToScene(0, 0); + QPointF farpt = mapToScene(width(), height()); + QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y()); + QRectF pr(nearpt, sz); + + QGraphicsView::resizeEvent(ev); + + if (pr != m_pannedRect) { + DEBUG << "Panned: setting panned rect to " << pr << endl; + m_pannedRect = pr; + centerOn(pr.center()); + emit pannedRectChanged(pr); + } +} + +void +Panned::setScene(QGraphicsScene *s) +{ + if (!scene()) { + QGraphicsView::setScene(s); + return; + } + + QPointF nearpt = mapToScene(0, 0); + QPointF farpt = mapToScene(width(), height()); + QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y()); + QRectF pr(nearpt, sz); + + QGraphicsView::setScene(s); + + DEBUG << "Panned::setScene: pr = " << pr << ", sceneRect = " << sceneRect() << endl; + + if (scene() && sceneRect().intersects(pr)) { + DEBUG << "Panned::setScene: restoring old rect " << pr << endl; + m_pannedRect = pr; + centerOn(pr.center()); + emit pannedRectChanged(pr); + } +} + +void +Panned::paintEvent(QPaintEvent *e) +{ + QGraphicsView::paintEvent(e); +} + +void +Panned::drawForeground(QPainter *paint, const QRectF &) +{ + QPointF nearpt = mapToScene(0, 0); + QPointF farpt = mapToScene(width(), height()); + QSizeF sz(farpt.x()-nearpt.x(), farpt.y()-nearpt.y()); + QRectF pr(nearpt, sz); + + if (pr != m_pannedRect) { + DEBUG << "Panned::drawForeground: visible rect " << pr << " differs from panned rect " << m_pannedRect << ", updating panned rect" <modifiers() & Qt::ControlModifier) { + int d = ev->delta(); + if (d > 0) { + while (d > 0) { + zoomOut(); + d -= 120; + } + } else { + while (d < 0) { + zoomIn(); + d += 120; + } + } + } else { + emit wheelEventReceived(ev); + QGraphicsView::wheelEvent(ev); + } +} + +void +Panned::slotEmulateWheelEvent(QWheelEvent *ev) +{ + QGraphicsView::wheelEvent(ev); +} + +void +Panned::mousePressEvent(QMouseEvent *ev) +{ + if (dragMode() != QGraphicsView::ScrollHandDrag || + ev->button() != Qt::LeftButton) { + QGraphicsView::mousePressEvent(ev); + return; + } + + DEBUG << "Panned::mousePressEvent: have left button in drag mode" << endl; + + setDragMode(QGraphicsView::NoDrag); + QGraphicsView::mousePressEvent(ev); + setDragMode(QGraphicsView::ScrollHandDrag); + + if (!ev->isAccepted()) { + ev->accept(); + m_dragging = true; + m_lastDragPos = ev->pos(); + m_lastOrigin = QPoint(horizontalScrollBar()->value(), + verticalScrollBar()->value()); + m_velocity = QPointF(0, 0); + m_dragTimer->start(m_dragTimerMs); + } + +} + +void +Panned::mouseMoveEvent(QMouseEvent *ev) +{ + if (!m_dragging) { + QGraphicsView::mouseMoveEvent(ev); + return; + } + DEBUG << "Panned::mouseMoveEvent: dragging" << endl; + ev->accept(); + QScrollBar *hBar = horizontalScrollBar(); + QScrollBar *vBar = verticalScrollBar(); + QPoint delta = ev->pos() - m_lastDragPos; + hBar->setValue(hBar->value() + (isRightToLeft() ? delta.x() : -delta.x())); + vBar->setValue(vBar->value() - delta.y()); + m_lastDragPos = ev->pos(); +} + +void +Panned::mouseReleaseEvent(QMouseEvent *ev) +{ + if (!m_dragging) { + QGraphicsView::mouseReleaseEvent(ev); + return; + } + DEBUG << "Panned::mouseReleaseEvent: dragging" << endl; + ev->accept(); + m_dragging = false; +} + +void +Panned::dragTimerTimeout() +{ + QPoint origin = QPoint(horizontalScrollBar()->value(), + verticalScrollBar()->value()); + if (m_dragging) { + m_velocity = QPointF + (float(origin.x() - m_lastOrigin.x()) / m_dragTimerMs, + float(origin.y() - m_lastOrigin.y()) / m_dragTimerMs); + m_lastOrigin = origin; + DEBUG << "Panned::dragTimerTimeout: velocity = " << m_velocity << endl; + } else { + if (origin == m_lastOrigin) { + m_dragTimer->stop(); + } + float x = m_velocity.x(), y = m_velocity.y(); + if (fabsf(x) > 1.0/m_dragTimerMs) x = x * 0.9f; + else x = 0.f; + if (fabsf(y) > 1.0/m_dragTimerMs) y = y * 0.9f; + else y = 0.f; + m_velocity = QPointF(x, y); + DEBUG << "Panned::dragTimerTimeout: velocity adjusted to " << m_velocity << endl; + m_lastOrigin = origin; + //!!! need to store origin in floats + horizontalScrollBar()->setValue(m_lastOrigin.x() + + m_velocity.x() * m_dragTimerMs); + verticalScrollBar()->setValue(m_lastOrigin.y() + + m_velocity.y() * m_dragTimerMs); + } +} + +void +Panned::leaveEvent(QEvent *) +{ + emit mouseLeaves(); +} diff -r 19cce6d2c470 -r b9c153e00e84 src/panned.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/panned.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PANNED_H_ +#define _PANNED_H_ + +#include + +class QWheelEvent; +class QEvent; +class QTimer; + +class Panned : public QGraphicsView +{ + Q_OBJECT + +public: + Panned(); + virtual ~Panned() { } + + virtual void setScene(QGraphicsScene *s); + +signals: + void pannedRectChanged(QRectF); + void wheelEventReceived(QWheelEvent *); + void pannedContentsScrolled(); + void mouseLeaves(); + +public slots: + void slotSetPannedRect(QRectF); + void slotEmulateWheelEvent(QWheelEvent *ev); + + void zoomIn(); + void zoomOut(); + +private slots: + void dragTimerTimeout(); + +protected: + QRectF m_pannedRect; + + QPoint m_lastDragPos; + QPoint m_lastOrigin; + QPointF m_velocity; + bool m_dragging; + int m_dragTimerMs; + QTimer *m_dragTimer; + + virtual void mousePressEvent(QMouseEvent *); + virtual void mouseMoveEvent(QMouseEvent *); + virtual void mouseReleaseEvent(QMouseEvent *); + + virtual void paintEvent(QPaintEvent *); + virtual void resizeEvent(QResizeEvent *); + virtual void drawForeground(QPainter *, const QRectF &); + virtual void wheelEvent(QWheelEvent *); + virtual void leaveEvent(QEvent *); +}; + +#endif + diff -r 19cce6d2c470 -r b9c153e00e84 src/panner.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/panner.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,285 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "panner.h" +#include "panned.h" +#include "debug.h" + +#include +#include +#include + +#include + +class PannerScene : public QGraphicsScene +{ +public: + friend class Panner; +}; + +Panner::Panner() : + m_clicked(false) +{ + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setOptimizationFlags(QGraphicsView::DontSavePainterState | + QGraphicsView::IndirectPainting); + setMouseTracking(true); + setInteractive(false); +} + +void +Panner::fit(QRectF r) +{ + Qt::AspectRatioMode m = Qt::IgnoreAspectRatio; + if (height() > width()) { + // Our panner is vertical; if the source is not tall, + // don't stretch it to fit in the height, it'd look weird + if (r.height() < height() * 2) { + m = Qt::KeepAspectRatio; + } + } else { + // Similarly, but horizontal + if (r.width() < width() * 2) { + m = Qt::KeepAspectRatio; + } + } + DEBUG << "Panner: fit mode " << m << endl; + fitInView(r, m); +} + +void +Panner::setScene(QGraphicsScene *s) +{ + if (scene()) { + disconnect(scene(), SIGNAL(changed(const QList &)), + this, SLOT(slotSceneChanged(const QList &))); + disconnect(scene(), SIGNAL(sceneRectChanged(const QRectF &)), + this, SLOT(slotSceneRectChanged(const QRectF &))); + } + QGraphicsView::setScene(s); + m_cache = QPixmap(); + if (scene()) { + QRectF r = sceneRect(); + DEBUG << "scene rect: " << r << ", my rect " << rect() << endl; + fit(r); + connect(scene(), SIGNAL(changed(const QList &)), + this, SLOT(slotSceneChanged(const QList &))); + connect(scene(), SIGNAL(sceneRectChanged(const QRectF &)), + this, SLOT(slotSceneRectChanged(const QRectF &))); + } +} + +void +Panner::connectToPanned(Panned *p) +{ + connect(p, SIGNAL(pannedRectChanged(QRectF)), + this, SLOT(slotSetPannedRect(QRectF))); + + connect(this, SIGNAL(pannedRectChanged(QRectF)), + p, SLOT(slotSetPannedRect(QRectF))); + + connect(this, SIGNAL(zoomIn()), + p, SLOT(zoomIn())); + + connect(this, SIGNAL(zoomOut()), + p, SLOT(zoomOut())); +} + +void +Panner::slotSetPannedRect(QRectF rect) +{ + m_pannedRect = rect; + viewport()->update(); +} + +void +Panner::resizeEvent(QResizeEvent *) +{ + DEBUG << "Panner::resizeEvent" << endl; + if (scene()) fit(sceneRect()); + m_cache = QPixmap(); +} + +void +Panner::slotSceneRectChanged(const QRectF &newRect) +{ + DEBUG << "Panner::slotSceneRectChanged" << endl; + if (!scene()) return; // spurious + fit(newRect); + m_cache = QPixmap(); + viewport()->update(); +} + +void +Panner::slotSceneChanged(const QList &) +{ + DEBUG << "Panner::slotSceneChanged" << endl; + if (!scene()) return; // spurious + m_cache = QPixmap(); + viewport()->update(); +} + +void +Panner::paintEvent(QPaintEvent *e) +{ + QPaintEvent *e2 = new QPaintEvent(e->region().boundingRect()); + QGraphicsView::paintEvent(e2); + + QPainter paint; + paint.begin(viewport()); + paint.setClipRegion(e->region()); + + QPainterPath path; + path.addRect(rect()); + path.addPolygon(mapFromScene(m_pannedRect)); + + QColor c(QColor::fromHsv(211, 194, 238)); + c.setAlpha(80); + paint.setPen(Qt::NoPen); + paint.setBrush(c); + paint.drawPath(path); + + paint.setBrush(Qt::NoBrush); + paint.setPen(QPen(QColor::fromHsv(211, 194, 238), 0)); + paint.drawConvexPolygon(mapFromScene(m_pannedRect)); + + paint.end(); + + emit pannerChanged(m_pannedRect); +} + +void +Panner::updateScene(const QList &rects) +{ + DEBUG << "Panner::updateScene" << endl; +// if (!m_cache.isNull()) m_cache = QPixmap(); + QGraphicsView::updateScene(rects); +} + +void +Panner::drawItems(QPainter *painter, int numItems, + QGraphicsItem *items[], + const QStyleOptionGraphicsItem options[]) +{ + if (m_cache.size() != viewport()->size()) { + + DEBUG << "Panner: cache size " << m_cache.size() << " != viewport size " << viewport()->size() << ": recreating cache" << endl; + + QGraphicsScene *s = scene(); + if (!s) return; + PannerScene *ps = static_cast(s); + + m_cache = QPixmap(viewport()->size()); + m_cache.fill(Qt::transparent); + QPainter cachePainter; + cachePainter.begin(&m_cache); + cachePainter.setTransform(viewportTransform()); + ps->drawItems(&cachePainter, numItems, items, options); + cachePainter.end(); + + DEBUG << "done" << endl; + } + + painter->save(); + painter->setTransform(QTransform()); + painter->drawPixmap(0, 0, m_cache); + painter->restore(); +} + +void +Panner::mousePressEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) { + QGraphicsView::mouseDoubleClickEvent(e); + return; + } + m_clicked = true; + m_clickedRect = m_pannedRect; + m_clickedPoint = e->pos(); +} + +void +Panner::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) { + QGraphicsView::mouseDoubleClickEvent(e); + return; + } + + moveTo(e->pos()); +} + +void +Panner::mouseMoveEvent(QMouseEvent *e) +{ + if (!m_clicked) return; + QPointF cp = mapToScene(m_clickedPoint); + QPointF mp = mapToScene(e->pos()); + QPointF delta = mp - cp; + QRectF nr = m_clickedRect; + nr.translate(delta); + m_pannedRect = nr; + emit pannedRectChanged(m_pannedRect); + viewport()->update(); +} + +void +Panner::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) { + QGraphicsView::mouseDoubleClickEvent(e); + return; + } + + if (m_clicked) { + mouseMoveEvent(e); + } + + m_clicked = false; + viewport()->update(); +} + +void +Panner::wheelEvent(QWheelEvent *e) +{ + int d = e->delta(); + if (d > 0) { + while (d > 0) { + emit zoomOut(); + d -= 120; + } + } else { + while (d < 0) { + emit zoomIn(); + d += 120; + } + } +} + +void +Panner::moveTo(QPoint p) +{ + QPointF sp = mapToScene(p); + QRectF nr = m_pannedRect; + double d = sp.x() - nr.center().x(); + nr.translate(d, 0); + slotSetPannedRect(nr); + emit pannedRectChanged(m_pannedRect); + viewport()->update(); +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/panner.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/panner.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,78 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _PANNER_H_ +#define _PANNER_H_ + +#include + +class Panned; + +class Panner : public QGraphicsView +{ + Q_OBJECT + +public: + Panner(); + virtual ~Panner() { } + + virtual void setScene(QGraphicsScene *); + + void connectToPanned(Panned *p); + +signals: + void pannedRectChanged(QRectF); + void pannerChanged(QRectF); + void zoomIn(); + void zoomOut(); + +public slots: + void slotSetPannedRect(QRectF); + +protected slots: + void slotSceneRectChanged(const QRectF &); + void slotSceneChanged(const QList &); + +protected: + QRectF m_pannedRect; + + void moveTo(QPoint); + + void fit(QRectF); + + virtual void paintEvent(QPaintEvent *); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void wheelEvent(QWheelEvent *e); + + virtual void resizeEvent(QResizeEvent *); + + virtual void updateScene(const QList &); + virtual void drawItems(QPainter *, int, QGraphicsItem *[], + const QStyleOptionGraphicsItem []); + + bool m_clicked; + QRectF m_clickedRect; + QPoint m_clickedPoint; + + QPixmap m_cache; +}; + +#endif + diff -r 19cce6d2c470 -r b9c153e00e84 src/recentfiles.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/recentfiles.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,139 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "recentfiles.h" + +#include +#include +#include + +RecentFiles::RecentFiles(QString settingsGroup, size_t maxCount, + bool ignoreTemporaries) : + m_settingsGroup(settingsGroup), + m_maxCount(maxCount), + m_ignoreTemporaries(ignoreTemporaries) +{ + read(); +} + +RecentFiles::~RecentFiles() +{ + // nothing +} + +void +RecentFiles::read() +{ + m_names.clear(); + QSettings settings; + settings.beginGroup(m_settingsGroup); + + for (size_t i = 0; i < 100; ++i) { + QString key = QString("recent-%1").arg(i); + QString name = settings.value(key, "").toString(); + if (name == "") break; + if (i < m_maxCount) m_names.push_back(name); + else settings.setValue(key, ""); + } + + settings.endGroup(); +} + +void +RecentFiles::write() +{ + QSettings settings; + settings.beginGroup(m_settingsGroup); + + for (size_t i = 0; i < m_maxCount; ++i) { + QString key = QString("recent-%1").arg(i); + QString name = ""; + if (i < m_names.size()) name = m_names[i]; + settings.setValue(key, name); + } + + settings.endGroup(); +} + +void +RecentFiles::truncateAndWrite() +{ + while (m_names.size() > m_maxCount) { + m_names.pop_back(); + } + write(); +} + +QStringList +RecentFiles::getRecent() const +{ + QStringList names; + for (size_t i = 0; i < m_maxCount; ++i) { + if (i < m_names.size()) { + names.push_back(m_names[i]); + } + } + return names; +} + +void +RecentFiles::add(QString name) +{ + bool have = false; + for (size_t i = 0; i < m_names.size(); ++i) { + if (m_names[i] == name) { + have = true; + break; + } + } + + if (!have) { + m_names.push_front(name); + } else { + std::deque newnames; + newnames.push_back(name); + for (size_t i = 0; i < m_names.size(); ++i) { + if (m_names[i] == name) continue; + newnames.push_back(m_names[i]); + } + m_names = newnames; + } + + truncateAndWrite(); + emit recentChanged(); +} + +void +RecentFiles::addFile(QString name) +{ + static QRegExp schemeRE("^[a-zA-Z]{2,5}://"); + static QRegExp tempRE("[\\/][Tt]e?mp[\\/]"); + if (schemeRE.indexIn(name) == 0) { + add(name); + } else { + QString absPath = QFileInfo(name).absoluteFilePath(); + if (tempRE.indexIn(absPath) != -1) { + if (!m_ignoreTemporaries) { + add(absPath); + } + } else { + add(absPath); + } + } +} + + diff -r 19cce6d2c470 -r b9c153e00e84 src/recentfiles.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/recentfiles.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,83 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _RECENT_FILES_H_ +#define _RECENT_FILES_H_ + +#include +#include +#include +#include + +/** + * RecentFiles manages a list of the names of recently-used objects, + * saving and restoring that list via QSettings. The names do not + * actually have to refer to files. + */ + +class RecentFiles : public QObject +{ + Q_OBJECT + +public: + /** + * Construct a RecentFiles object that saves and restores in the + * given QSettings group and truncates when the given count of + * strings is reached. + */ + RecentFiles(QString settingsGroup = "RecentFiles", + size_t maxCount = 10, + bool ignoreTemporaries = true); + virtual ~RecentFiles(); + + QString getSettingsGroup() const { return m_settingsGroup; } + + int getMaxCount() const { return m_maxCount; } + + QStringList getRecent() const; + + /** + * Add a name that should be treated as a literal string. + */ + void add(QString name); + + /** + * Add a name that is known to be either a file path or a URL. If + * it looks like a URL, add it literally; otherwise treat it as a + * file path and canonicalise it appropriately. Also takes into + * account the preference for whether to include temporary files + * in the recent files menu: the file will not be added if the + * preference is set and the file appears to be a temporary one. + */ + void addFile(QString name); + +signals: + void recentChanged(); + +protected: + QString m_settingsGroup; + size_t m_maxCount; + bool m_ignoreTemporaries; + + std::deque m_names; + + void read(); + void write(); + void truncateAndWrite(); +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/repositorydialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/repositorydialog.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,29 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "repositorydialog.h" + +RepositoryDialog::RepositoryDialog(QWidget *parent) : + QDialog(parent) +{ + setModal(true); + setWindowTitle(tr("Open Repository")); + + + + +} diff -r 19cce6d2c470 -r b9c153e00e84 src/repositorydialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/repositorydialog.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,34 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef REPOSITORYDIALOG_H +#define REPOSITORYDIALOG_H + +#include + +class RepositoryDialog : public QDialog +{ + Q_OBJECT + +public: + RepositoryDialog(QWidget *parent = 0); + + + +}; + +#endif // REPOSITORYDIALOG_H diff -r 19cce6d2c470 -r b9c153e00e84 src/selectablelabel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/selectablelabel.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,141 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "selectablelabel.h" + +#include "debug.h" + +#include +#include + +SelectableLabel::SelectableLabel(QWidget *p) : + QLabel(p), + m_selected(false) +{ + setTextFormat(Qt::RichText); + setupStyle(); + setOpenExternalLinks(true); +} + +SelectableLabel::~SelectableLabel() +{ +} + +void +SelectableLabel::setUnselectedText(QString text) +{ + if (m_unselectedText == text) return; + m_unselectedText = text; + if (!m_selected) { + setText(m_unselectedText); + resize(sizeHint()); + } +} + +void +SelectableLabel::setSelectedText(QString text) +{ + if (m_selectedText == text) return; + m_selectedText = text; + if (m_selected) { + setText(m_selectedText); + resize(sizeHint()); + } +} + +void +SelectableLabel::setupStyle() +{ + QPalette palette = QApplication::palette(); + + setTextInteractionFlags(Qt::LinksAccessibleByKeyboard | + Qt::LinksAccessibleByMouse | + Qt::TextSelectableByMouse); + + if (m_selected) { + setStyleSheet + (QString("QLabel { background: %1; border: 1px solid %2; padding: 7px } ") + .arg(palette.light().color().name()) + .arg(palette.dark().color().name())); + } else { + setStyleSheet + (QString("QLabel { border: 0; padding: 7px } ")); + } +} + +void +SelectableLabel::setSelected(bool s) +{ + if (m_selected == s) return; + m_selected = s; + if (m_selected) { + setText(m_selectedText); + } else { + setText(m_unselectedText); + } + setupStyle(); + parentWidget()->resize(parentWidget()->sizeHint()); +} + +void +SelectableLabel::toggle() +{ + setSelected(!m_selected); +} + +void +SelectableLabel::mousePressEvent(QMouseEvent *e) +{ + m_swallowRelease = !m_selected; + setSelected(true); + QLabel::mousePressEvent(e); + emit selectionChanged(); +} + +void +SelectableLabel::mouseDoubleClickEvent(QMouseEvent *e) +{ + QLabel::mouseDoubleClickEvent(e); + emit doubleClicked(); +} + +void +SelectableLabel::mouseReleaseEvent(QMouseEvent *e) +{ + if (!m_swallowRelease) QLabel::mouseReleaseEvent(e); + m_swallowRelease = false; +} + +void +SelectableLabel::enterEvent(QEvent *) +{ +// std::cerr << "enterEvent" << std::endl; +// QPalette palette = QApplication::palette(); +// palette.setColor(QPalette::Window, Qt::gray); +// setStyleSheet("background: gray"); +// setPalette(palette); +} + +void +SelectableLabel::leaveEvent(QEvent *) +{ +// std::cerr << "leaveEvent" << std::endl; +// setStyleSheet("background: white"); +// QPalette palette = QApplication::palette(); +// palette.setColor(QPalette::Window, Qt::gray); +// setPalette(palette); +} diff -r 19cce6d2c470 -r b9c153e00e84 src/selectablelabel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/selectablelabel.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,57 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SELECTABLE_LABEL_H_ +#define _SELECTABLE_LABEL_H_ + +#include + +class SelectableLabel : public QLabel +{ + Q_OBJECT + +public: + SelectableLabel(QWidget *parent = 0); + virtual ~SelectableLabel(); + + void setSelectedText(QString); + void setUnselectedText(QString); + + bool isSelected() const { return m_selected; } + +signals: + void selectionChanged(); + void doubleClicked(); + +public slots: + void setSelected(bool); + void toggle(); + +protected: + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + void setupStyle(); + QString m_selectedText; + QString m_unselectedText; + bool m_selected; + bool m_swallowRelease; +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/settingsdialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/settingsdialog.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,469 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "settingsdialog.h" +#include "common.h" +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include + +QString +SettingsDialog::m_installPath; + +SettingsDialog::SettingsDialog(QWidget *parent) : + QDialog(parent), + m_presentationChanged(false) +{ + setModal(true); + setWindowTitle(tr("Settings")); + + QGridLayout *mainLayout = new QGridLayout; + setLayout(mainLayout); + + + + QGroupBox *meBox = new QGroupBox(tr("User details")); + mainLayout->addWidget(meBox, 0, 0); + QGridLayout *meLayout = new QGridLayout; + meBox->setLayout(meLayout); + + int row = 0; + + meLayout->addWidget(new QLabel(tr("Name:")), row, 0); + + m_nameEdit = new QLineEdit(); + meLayout->addWidget(m_nameEdit, row++, 1); + + meLayout->addWidget(new QLabel(tr("Email address:")), row, 0); + + m_emailEdit = new QLineEdit(); + meLayout->addWidget(m_emailEdit, row++, 1); + + + + QGroupBox *lookBox = new QGroupBox(tr("Presentation")); + mainLayout->addWidget(lookBox, 1, 0); + QGridLayout *lookLayout = new QGridLayout; + lookBox->setLayout(lookLayout); + + row = 0; + + m_showIconLabels = new QCheckBox(tr("Show labels on toolbar icons")); + lookLayout->addWidget(m_showIconLabels, row++, 0, 1, 2); + + m_showExtraText = new QCheckBox(tr("Show long descriptions for file status headings")); + lookLayout->addWidget(m_showExtraText, row++, 0, 1, 2); + +#ifdef NOT_IMPLEMENTED_YET + lookLayout->addWidget(new QLabel(tr("Place the work and history views")), row, 0); + m_workHistoryArrangement = new QComboBox(); + m_workHistoryArrangement->addItem(tr("In separate tabs")); + m_workHistoryArrangement->addItem(tr("Side-by-side in a single pane")); + lookLayout->addWidget(m_workHistoryArrangement, row++, 1, Qt::AlignLeft); + lookLayout->setColumnStretch(1, 20); +#endif + + lookLayout->addWidget(new QLabel(tr("Label the history timeline with")), row, 0); + m_dateFormat = new QComboBox(); + m_dateFormat->addItem(tr("Ages, for example \"5 weeks ago\"")); + m_dateFormat->addItem(tr("Dates, for example \"2010-06-23\"")); + lookLayout->addWidget(m_dateFormat, row++, 1, Qt::AlignLeft); + lookLayout->setColumnStretch(1, 20); + + + QGroupBox *pathsBox = new QGroupBox(tr("System application locations")); + mainLayout->addWidget(pathsBox, 2, 0); + QGridLayout *pathsLayout = new QGridLayout; + pathsBox->setLayout(pathsLayout); + + row = 0; + + pathsLayout->addWidget(new QLabel(tr("Mercurial (hg) program:")), row, 0); + + m_hgPathLabel = new QLineEdit(); + pathsLayout->addWidget(m_hgPathLabel, row, 2); + + QPushButton *browse = new QPushButton(tr("Browse...")); + pathsLayout->addWidget(browse, row++, 1); + connect(browse, SIGNAL(clicked()), this, SLOT(hgPathBrowse())); + + pathsLayout->addWidget(new QLabel(tr("External diff program:")), row, 0); + + m_diffPathLabel = new QLineEdit(); + pathsLayout->addWidget(m_diffPathLabel, row, 2); + + browse = new QPushButton(tr("Browse...")); + pathsLayout->addWidget(browse, row++, 1); + connect(browse, SIGNAL(clicked()), this, SLOT(diffPathBrowse())); + + pathsLayout->addWidget(new QLabel(tr("External file-merge program:")), row, 0); + + m_mergePathLabel = new QLineEdit(); + pathsLayout->addWidget(m_mergePathLabel, row, 2); + + browse = new QPushButton(tr("Browse...")); + pathsLayout->addWidget(browse, row++, 1); + connect(browse, SIGNAL(clicked()), this, SLOT(mergePathBrowse())); + + pathsLayout->addWidget(new QLabel(tr("External text editor:")), row, 0); + + m_editPathLabel = new QLineEdit(); + pathsLayout->addWidget(m_editPathLabel, row, 2); + + browse = new QPushButton(tr("Browse...")); + pathsLayout->addWidget(browse, row++, 1); + connect(browse, SIGNAL(clicked()), this, SLOT(editPathBrowse())); + + pathsLayout->addWidget(new QLabel(tr("EasyHg Mercurial extension:")), row, 0); + + m_extensionPathLabel = new QLineEdit(); + pathsLayout->addWidget(m_extensionPathLabel, row, 2); + + browse = new QPushButton(tr("Browse...")); + pathsLayout->addWidget(browse, row++, 1); + connect(browse, SIGNAL(clicked()), this, SLOT(extensionPathBrowse())); + + //!!! more info plz + m_useExtension = new QCheckBox(tr("Use EasyHg Mercurial extension")); + pathsLayout->addWidget(m_useExtension, row++, 2); + + + reset(); // loads current defaults from settings + + + QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok); + connect(bbox->addButton(tr("Restore defaults"), QDialogButtonBox::ResetRole), + SIGNAL(clicked()), this, SLOT(restoreDefaults())); + connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); + mainLayout->addWidget(bbox, 3, 0); + m_ok = bbox->button(QDialogButtonBox::Ok); +} + +void +SettingsDialog::hgPathBrowse() +{ + browseFor(tr("Mercurial program"), m_hgPathLabel); +} + +void +SettingsDialog::diffPathBrowse() +{ + browseFor(tr("External diff program"), m_diffPathLabel); +} + +void +SettingsDialog::mergePathBrowse() +{ + browseFor(tr("External file-merge program"), m_mergePathLabel); +} + +void +SettingsDialog::editPathBrowse() +{ + browseFor(tr("External text editor"), m_editPathLabel); +} + +void +SettingsDialog::extensionPathBrowse() +{ + browseFor(tr("EasyHg Mercurial extension"), m_extensionPathLabel); +} + +void +SettingsDialog::browseFor(QString title, QLineEdit *edit) +{ + QString origin = edit->text(); + + if (origin == "") { +#ifdef Q_OS_WIN32 + origin = "c:"; +#else + origin = QDir::homePath(); +#endif + } + + QString path = QFileDialog::getOpenFileName(this, title, origin); + if (path != QString()) { + edit->setText(path); + } +} + +void +SettingsDialog::restoreDefaults() +{ + if (QMessageBox::question + (this, tr("Restore default settings?"), + tr("Restore default settings?

Are you sure you want to reset all settings to their default values?"), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Cancel) == QMessageBox::Ok) { + clear(); + findDefaultLocations(); + reset(); + } +} + +void +SettingsDialog::findDefaultLocations(QString installPath) +{ + m_installPath = installPath; + findHgBinaryName(); + findExtension(); + findDiffBinaryName(); + findMergeBinaryName(); + findEditorBinaryName(); +} + +void +SettingsDialog::findHgBinaryName() +{ + QSettings settings; + settings.beginGroup("Locations"); + QString hg = settings.value("hgbinary", "").toString(); + if (hg == "") { + hg = findInPath("hg", m_installPath, true); + } + if (hg != "") { + settings.setValue("hgbinary", hg); + } +} + +QString +SettingsDialog::getUnbundledExtensionFileName() +{ + QString home = QDir::homePath(); + QString target = QString("%1/.easyhg").arg(home); + QString extpath = QString("%1/easyhg.py").arg(target); + return extpath; +} + +void +SettingsDialog::findExtension() +{ + QSettings settings; + settings.beginGroup("Locations"); + + QString extpath = settings.value("extensionpath", "").toString(); + if (extpath != "" || !QFile(extpath).exists()) { + + extpath = getUnbundledExtensionFileName(); + + if (!QFile(extpath).exists()) { + extpath = findInPath("easyhg.py", m_installPath, false); + } + } + + settings.setValue("extensionpath", extpath); +} + +void +SettingsDialog::findDiffBinaryName() +{ + QSettings settings; + settings.beginGroup("Locations"); + QString diff = settings.value("extdiffbinary", "").toString(); + if (diff == "") { + QStringList bases; +#ifdef Q_OS_WIN32 + bases << "easyhg-extdiff.bat"; +#else + bases << "easyhg-extdiff.sh"; +#endif + bases << "kompare" << "kdiff3" << "meld"; + bool found = false; + foreach (QString base, bases) { + diff = findInPath(base, m_installPath, true); + if (diff != "") { + found = true; + break; + } + } + if (found) { + settings.setValue("extdiffbinary", diff); + } + } +} + +void +SettingsDialog::findMergeBinaryName() +{ + QSettings settings; + settings.beginGroup("Locations"); + if (settings.contains("mergebinary")) { + return; + } + QString merge; + QStringList bases; +#ifdef Q_OS_WIN32 + bases << "easyhg-merge.bat"; +#else + bases << "easyhg-merge.sh"; +#endif + // NB it's not a good idea to add other tools here, as command + // line argument ordering varies. Configure them through hgrc + // instead + bool found = false; + foreach (QString base, bases) { + merge = findInPath(base, m_installPath, true); + if (merge != "") { + found = true; + break; + } + } + if (found) { + settings.setValue("mergebinary", merge); + } +} + +void +SettingsDialog::findEditorBinaryName() +{ + QSettings settings; + settings.beginGroup("Locations"); + QString editor = settings.value("editorbinary", "").toString(); + if (editor == "") { + QStringList bases; + bases +#if defined Q_OS_WIN32 + << "wordpad.exe" + << "C:\\Program Files\\Windows NT\\Accessories\\wordpad.exe" + << "notepad.exe" +#elif defined Q_OS_MAC + << "/Applications/TextEdit.app/Contents/MacOS/TextEdit" +#else + << "gedit" << "kate" +#endif + ; + bool found = false; + foreach (QString base, bases) { + editor = findInPath(base, m_installPath, true); + if (editor != "") { + found = true; + break; + } + } + if (found) { + settings.setValue("editorbinary", editor); + } + } +} + +void +SettingsDialog::clear() +{ + // Clear everything that has a default setting + DEBUG << "SettingsDialog::clear" << endl; + QSettings settings; + settings.beginGroup("Presentation"); + settings.remove("showiconlabels"); + settings.remove("showhelpfultext"); + settings.endGroup(); + settings.beginGroup("Locations"); + settings.remove("hgbinary"); + settings.remove("extdiffbinary"); + settings.remove("mergebinary"); + settings.remove("editorbinary"); + settings.remove("extensionpath"); + settings.endGroup(); + settings.beginGroup("General"); + settings.remove("useextension"); + settings.endGroup(); +} + +void +SettingsDialog::reset() +{ + DEBUG << "SettingsDialog::reset" << endl; + QSettings settings; + settings.beginGroup("User Information"); + m_nameEdit->setText(settings.value("name", getUserRealName()).toString()); + m_emailEdit->setText(settings.value("email").toString()); + settings.endGroup(); + settings.beginGroup("Presentation"); + m_showIconLabels->setChecked(settings.value("showiconlabels", true).toBool()); + m_showExtraText->setChecked(settings.value("showhelpfultext", true).toBool()); +#ifdef NOT_IMPLEMENTED_YET + m_workHistoryArrangement->setCurrentIndex(settings.value("workhistoryarrangement", 0).toInt()); +#endif + m_dateFormat->setCurrentIndex(settings.value("dateformat", 0).toInt()); + settings.endGroup(); + settings.beginGroup("Locations"); + m_hgPathLabel->setText(settings.value("hgbinary").toString()); + m_diffPathLabel->setText(settings.value("extdiffbinary").toString()); + m_mergePathLabel->setText(settings.value("mergebinary").toString()); + m_editPathLabel->setText(settings.value("editorbinary").toString()); + m_extensionPathLabel->setText(settings.value("extensionpath").toString()); + settings.endGroup(); + settings.beginGroup("General"); + m_useExtension->setChecked(settings.value("useextension", true).toBool()); + settings.endGroup(); +} + +void +SettingsDialog::accept() +{ + DEBUG << "SettingsDialog::accept" << endl; + QSettings settings; + settings.beginGroup("User Information"); + settings.setValue("name", m_nameEdit->text()); + settings.setValue("email", m_emailEdit->text()); + settings.endGroup(); + settings.beginGroup("Presentation"); + bool b; + b = m_showIconLabels->isChecked(); + if (b != settings.value("showiconlabels", true)) { + settings.setValue("showiconlabels", b); + m_presentationChanged = true; + } + b = m_showExtraText->isChecked(); + if (b != settings.value("showhelpfultext", true)) { + settings.setValue("showhelpfultext", b); + m_presentationChanged = true; + } + int i; +#ifdef NOT_IMPLEMENTED_YET + i = m_workHistoryArrangement->currentIndex(); + if (i != settings.value("workhistoryarrangement", 0)) { + settings.setValue("workhistoryarrangement", i); + m_presentationChanged = true; + } +#endif + i = m_dateFormat->currentIndex(); + if (i != settings.value("dateformat", 0)) { + settings.setValue("dateformat", i); + m_presentationChanged = true; + } + settings.endGroup(); + settings.beginGroup("Locations"); + settings.setValue("hgbinary", m_hgPathLabel->text()); + settings.setValue("extdiffbinary", m_diffPathLabel->text()); + settings.setValue("mergebinary", m_mergePathLabel->text()); + settings.setValue("editorbinary", m_editPathLabel->text()); + settings.setValue("extensionpath", m_extensionPathLabel->text()); + settings.endGroup(); + settings.beginGroup("General"); + settings.setValue("useextension", m_useExtension->isChecked()); + settings.endGroup(); + QDialog::accept(); +} + + diff -r 19cce6d2c470 -r b9c153e00e84 src/settingsdialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/settingsdialog.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,87 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SETTINGS_DIALOG_H +#define SETTINGS_DIALOG_H + +#include +#include +#include +#include +#include +#include + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + SettingsDialog(QWidget *parent = 0); + + bool presentationChanged() { + return m_presentationChanged; + } + + static void findDefaultLocations(QString installPath = m_installPath); + static QString getUnbundledExtensionFileName(); + +private slots: + void hgPathBrowse(); + void diffPathBrowse(); + void mergePathBrowse(); + void editPathBrowse(); + void extensionPathBrowse(); + + void accept(); + void reset(); + void clear(); + void restoreDefaults(); + +private: + QLineEdit *m_nameEdit; + QLineEdit *m_emailEdit; + QLineEdit *m_hgPathLabel; + QLineEdit *m_diffPathLabel; + QLineEdit *m_mergePathLabel; + QLineEdit *m_editPathLabel; + + QCheckBox *m_useExtension; + QLineEdit *m_extensionPathLabel; + + QCheckBox *m_showIconLabels; + QCheckBox *m_showExtraText; + QComboBox *m_dateFormat; +#ifdef NOT_IMPLEMENTED_YET + QComboBox *m_workHistoryArrangement; +#endif + + QPushButton *m_ok; + + bool m_presentationChanged; + + void browseFor(QString, QLineEdit *); + + static void findHgBinaryName(); + static void findExtension(); + static void findDiffBinaryName(); + static void findMergeBinaryName(); + static void findEditorBinaryName(); + + static QString m_installPath; +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/startupdialog.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/startupdialog.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,113 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "startupdialog.h" +#include "common.h" + +#include +#include +#include + +StartupDialog::StartupDialog(QWidget *parent) : + QDialog(parent) +{ + setModal(true); + setWindowTitle(tr("About me")); + + QSettings settings; + settings.beginGroup("User Information"); + m_name = settings.value("name", getUserRealName()).toString(); + m_email = settings.value("email").toString(); + + QGridLayout *layout = new QGridLayout; + int row = 0; + + layout->addWidget(new QLabel(tr("Welcome to EasyMercurial!
How would you like to be identified in commit messages?")), + row++, 0, 1, 2); + + layout->addWidget(new QLabel(tr("Name:")), row, 0); + + m_nameEdit = new QLineEdit(); + m_nameEdit->setText(m_name); + connect(m_nameEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(realNameChanged(const QString &))); + layout->addWidget(m_nameEdit, row++, 1); + + layout->addWidget(new QLabel(tr("Email address:")), row, 0); + + m_emailEdit = new QLineEdit(); + m_emailEdit->setText(m_email); + connect(m_emailEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(emailChanged(const QString &))); + layout->addWidget(m_emailEdit, row++, 1); + + layout->addWidget(new QLabel(tr("
You will appear as:")), row, 0, Qt::AlignBottom); + m_example = new QLabel(); + layout->addWidget(m_example, row++, 1, Qt::AlignBottom); + + QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok); + connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); + layout->addWidget(bbox, row++, 0, 1, 2); + m_ok = bbox->button(QDialogButtonBox::Ok); + m_ok->setEnabled(false); + + setLayout(layout); + + m_ok->setEnabled(m_name != ""); + updateExample(); +} + +void +StartupDialog::realNameChanged(const QString &s) +{ + m_name = s.trimmed(); + m_ok->setEnabled(m_name != ""); + updateExample(); +} + +void +StartupDialog::emailChanged(const QString &s) +{ + m_email = s.trimmed(); + updateExample(); +} + +void +StartupDialog::accept() +{ + QSettings settings; + settings.beginGroup("User Information"); + settings.setValue("name", m_name); + settings.setValue("email", m_email); + QDialog::accept(); +} + +void +StartupDialog::updateExample() +{ + QString identifier; + + if (m_email != "") { + identifier = QString("%1 <%2>").arg(m_name).arg(m_email); + } else { + identifier = m_name; + } + + m_example->setText(identifier); +} + + diff -r 19cce6d2c470 -r b9c153e00e84 src/startupdialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/startupdialog.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,50 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef STARTUP_DIALOG_H +#define STARTUP_DIALOG_H + +#include +#include +#include +#include + +class StartupDialog : public QDialog +{ + Q_OBJECT + +public: + StartupDialog(QWidget *parent = 0); + +private slots: + void realNameChanged(const QString &); + void emailChanged(const QString &); + void accept(); + +private: + QLineEdit *m_nameEdit; + QLineEdit *m_emailEdit; + QLabel *m_example; + QPushButton *m_ok; + + QString m_name; + QString m_email; + + void updateExample(); +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 src/textabbrev.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/textabbrev.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,314 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "textabbrev.h" + +#include +#include + +#include + +QString +TextAbbrev::getDefaultEllipsis() +{ + return "..."; +} + +int +TextAbbrev::getFuzzLength(QString ellipsis) +{ + int len = ellipsis.length(); + if (len < 3) return len + 3; + else if (len > 5) return len + 5; + else return len * 2; +} + +int +TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis) +{ + int width = metrics.width(ellipsis); + return width * 2; +} + +QString +TextAbbrev::abbreviateTo(QString text, int characters, Policy policy, + QString ellipsis) +{ + switch (policy) { + + case ElideEnd: + case ElideEndAndCommonPrefixes: + text = text.left(characters) + ellipsis; + break; + + case ElideStart: + text = ellipsis + text.right(characters); + break; + + case ElideMiddle: + if (characters > 2) { + text = text.left(characters/2 + 1) + ellipsis + + text.right(characters - (characters/2 + 1)); + } else { + text = text.left(characters) + ellipsis; + } + break; + } + + return text; +} + +QString +TextAbbrev::abbreviate(QString text, int maxLength, Policy policy, bool fuzzy, + QString ellipsis) +{ + if (ellipsis == "") ellipsis = getDefaultEllipsis(); + int fl = (fuzzy ? getFuzzLength(ellipsis) : 0); + if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1; + if (text.length() <= maxLength + fl) return text; + + int truncated = maxLength - ellipsis.length(); + return abbreviateTo(text, truncated, policy, ellipsis); +} + +QString +TextAbbrev::abbreviate(QString text, + const QFontMetrics &metrics, int &maxWidth, + Policy policy, QString ellipsis, int wrapLines) +{ + if (ellipsis == "") ellipsis = getDefaultEllipsis(); + + int tw = metrics.width(text); + + if (tw <= maxWidth) { + maxWidth = tw; + return text; + } + + int truncated = text.length(); + QString original = text; + + if (wrapLines < 2) { + + while (tw > maxWidth && truncated > 1) { + + truncated--; + + if (truncated > ellipsis.length()) { + text = abbreviateTo(original, truncated, policy, ellipsis); + } else { + break; + } + + tw = metrics.width(text); + } + + maxWidth = tw; + return text; + + } else { + + QStringList words = text.split(' ', QString::SkipEmptyParts); + text = ""; + + tw = 0; + int i = 0; + QString good = ""; + int lastUsed = 0; + while (tw < maxWidth && i < words.size()) { + if (text != "") text += " "; + text += words[i++]; + tw = metrics.width(text); + if (tw < maxWidth) { + good = text; + lastUsed = i; + } + } + + if (tw < maxWidth) { + maxWidth = tw; + return text; + } + + text = good; + + QString remainder; + while (lastUsed < words.size()) { + if (remainder != "") remainder += " "; + remainder += words[lastUsed++]; + } + remainder = abbreviate(remainder, metrics, maxWidth, + policy, ellipsis, wrapLines - 1); + + maxWidth = std::max(maxWidth, tw); + text = text + '\n' + remainder; + return text; + } +} + +QStringList +TextAbbrev::abbreviate(const QStringList &texts, int maxLength, + Policy policy, bool fuzzy, QString ellipsis) +{ + if (policy == ElideEndAndCommonPrefixes && + texts.size() > 1) { + + if (ellipsis == "") ellipsis = getDefaultEllipsis(); + int fl = (fuzzy ? getFuzzLength(ellipsis) : 0); + if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1; + + int maxOrigLength = 0; + for (int i = 0; i < texts.size(); ++i) { + int len = texts[i].length(); + if (len > maxOrigLength) maxOrigLength = len; + } + if (maxOrigLength <= maxLength + fl) return texts; + + return abbreviate(elidePrefixes + (texts, maxOrigLength - maxLength, ellipsis), + maxLength, ElideEnd, fuzzy, ellipsis); + } + + QStringList results; + for (int i = 0; i < texts.size(); ++i) { + results.push_back + (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis)); + } + return results; +} + +QStringList +TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics, + int &maxWidth, Policy policy, QString ellipsis) +{ + if (policy == ElideEndAndCommonPrefixes && + texts.size() > 1) { + + if (ellipsis == "") ellipsis = getDefaultEllipsis(); + + int maxOrigWidth = 0; + for (int i = 0; i < texts.size(); ++i) { + int w = metrics.width(texts[i]); + if (w > maxOrigWidth) maxOrigWidth = w; + } + + return abbreviate(elidePrefixes(texts, metrics, + maxOrigWidth - maxWidth, ellipsis), + metrics, maxWidth, ElideEnd, ellipsis); + } + + QStringList results; + int maxAbbrWidth = 0; + for (int i = 0; i < texts.size(); ++i) { + int width = maxWidth; + QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis); + if (width > maxAbbrWidth) maxAbbrWidth = width; + results.push_back(abbr); + } + maxWidth = maxAbbrWidth; + return results; +} + +QStringList +TextAbbrev::elidePrefixes(const QStringList &texts, + int targetReduction, + QString ellipsis) +{ + if (texts.empty()) return texts; + int plen = getPrefixLength(texts); + int fl = getFuzzLength(ellipsis); + if (plen < fl) return texts; + + QString prefix = texts[0].left(plen); + int truncated = plen; + if (plen >= targetReduction + fl) { + truncated = plen - targetReduction; + } else { + truncated = fl; + } + prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis); + + QStringList results; + for (int i = 0; i < texts.size(); ++i) { + results.push_back + (prefix + texts[i].right(texts[i].length() - plen)); + } + return results; +} + +QStringList +TextAbbrev::elidePrefixes(const QStringList &texts, + const QFontMetrics &metrics, + int targetWidthReduction, + QString ellipsis) +{ + if (texts.empty()) return texts; + int plen = getPrefixLength(texts); + int fl = getFuzzLength(ellipsis); + if (plen < fl) return texts; + + QString prefix = texts[0].left(plen); + int pwid = metrics.width(prefix); + int twid = pwid - targetWidthReduction; + if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2; + prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis); + + QStringList results; + for (int i = 0; i < texts.size(); ++i) { + results.push_back + (prefix + texts[i].right(texts[i].length() - plen)); + } + return results; +} + +static bool +havePrefix(QString prefix, const QStringList &texts) +{ + for (int i = 1; i < texts.size(); ++i) { + if (!texts[i].startsWith(prefix)) return false; + } + return true; +} + +int +TextAbbrev::getPrefixLength(const QStringList &texts) +{ + QString reference = texts[0]; + + if (reference == "" || havePrefix(reference, texts)) { + return reference.length(); + } + + int candidate = reference.length(); + QString splitChars(";:,./#-!()$_+=[]{}\\"); + + while (--candidate > 1) { + if (splitChars.contains(reference[candidate])) { + if (havePrefix(reference.left(candidate), texts)) { + break; + } + } + } + +// std::cerr << "TextAbbrev::getPrefixLength: prefix length is " << candidate << std::endl; +// for (int i = 0; i < texts.size(); ++i) { +// std::cerr << texts[i].left(candidate).toStdString() << "|" << texts[i].right(texts[i].length() - candidate).toStdString() << std::endl; +// } + + return candidate; +} + diff -r 19cce6d2c470 -r b9c153e00e84 src/textabbrev.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/textabbrev.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,111 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TEXT_ABBREV_H_ +#define _TEXT_ABBREV_H_ + +#include +#include + +class QFontMetrics; + +class TextAbbrev +{ +public: + enum Policy { + ElideEnd, + ElideEndAndCommonPrefixes, + ElideStart, + ElideMiddle + }; + + /** + * Abbreviate the given text to the given maximum length + * (including ellipsis), using the given abbreviation policy. If + * fuzzy is true, the text will be left alone if it is "not much + * more than" the maximum length. + * + * If ellipsis is non-empty, it will be used to show elisions in + * preference to the default (which is "..."). + */ + static QString abbreviate(QString text, int maxLength, + Policy policy = ElideEnd, + bool fuzzy = true, + QString ellipsis = ""); + + /** + * Abbreviate the given text to the given maximum painted width, + * using the given abbreviation policy. maxWidth is also modified + * so as to return the painted width of the abbreviated text. + * + * If ellipsis is non-empty, it will be used to show elisions in + * preference to the default (which is tr("...")). + */ + static QString abbreviate(QString text, + const QFontMetrics &metrics, + int &maxWidth, + Policy policy = ElideEnd, + QString ellipsis = "", + int wrapLines = 1); + + /** + * Abbreviate all of the given texts to the given maximum length, + * using the given abbreviation policy. If fuzzy is true, texts + * that are "not much more than" the maximum length will be left + * alone. + * + * If ellipsis is non-empty, it will be used to show elisions in + * preference to the default (which is tr("...")). + */ + static QStringList abbreviate(const QStringList &texts, int maxLength, + Policy policy = ElideEndAndCommonPrefixes, + bool fuzzy = true, + QString ellipsis = ""); + + /** + * Abbreviate all of the given texts to the given maximum painted + * width, using the given abbreviation policy. maxWidth is also + * modified so as to return the maximum painted width of the + * abbreviated texts. + * + * If ellipsis is non-empty, it will be used to show elisions in + * preference to the default (which is tr("...")). + */ + static QStringList abbreviate(const QStringList &texts, + const QFontMetrics &metrics, + int &maxWidth, + Policy policy = ElideEndAndCommonPrefixes, + QString ellipsis = ""); + +protected: + static QString getDefaultEllipsis(); + static int getFuzzLength(QString ellipsis); + static int getFuzzWidth(const QFontMetrics &metrics, QString ellipsis); + static QString abbreviateTo(QString text, int characters, + Policy policy, QString ellipsis); + static QStringList elidePrefixes(const QStringList &texts, + int targetReduction, + QString ellipsis); + static QStringList elidePrefixes(const QStringList &texts, + const QFontMetrics &metrics, + int targetWidthReduction, + QString ellipsis); + static int getPrefixLength(const QStringList &texts); +}; + +#endif + diff -r 19cce6d2c470 -r b9c153e00e84 src/uncommitteditem.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uncommitteditem.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,170 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "uncommitteditem.h" +#include "colourset.h" +#include "debug.h" +#include "textabbrev.h" + +#include +#include +#include +#include +#include +#include +#include + +UncommittedItem::UncommittedItem() : + m_showBranch(false), m_isNewBranch(false), + m_column(0), m_row(0), m_wide(false) +{ + m_font = QFont(); + m_font.setPixelSize(11); + m_font.setBold(false); + m_font.setItalic(false); + setCursor(Qt::ArrowCursor); +} + +QRectF +UncommittedItem::boundingRect() const +{ + //!!! this stuff is gross, refactor with changesetitem and connectionitem + int w = 100; + if (m_wide) w = 180; + return QRectF(-((w-50)/2 - 1), -30, w - 3, 79 + 40); +} + +void +UncommittedItem::mousePressEvent(QGraphicsSceneMouseEvent *e) +{ + DEBUG << "UncommittedItem::mousePressEvent" << endl; + if (e->button() == Qt::RightButton) { + activateMenu(); + } +} + +void +UncommittedItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *e) +{ + DEBUG << "UncommittedItem::mouseDoubleClickEvent" << endl; + if (e->button() == Qt::LeftButton) { + emit showWork(); + } +} + +void +UncommittedItem::activateMenu() +{ + QMenu *menu = new QMenu; + QLabel *label = new QLabel(tr(" Uncommitted changes")); + QWidgetAction *wa = new QWidgetAction(menu); + wa->setDefaultWidget(label); + menu->addAction(wa); + menu->addSeparator(); + + QAction *dif = menu->addAction(tr("Diff")); + connect(dif, SIGNAL(triggered()), this, SIGNAL(diff())); + QAction *stat = menu->addAction(tr("Summarise changes")); + connect(stat, SIGNAL(triggered()), this, SIGNAL(showSummary())); + + menu->addSeparator(); + + QAction *commit = menu->addAction(tr("Commit...")); + connect(commit, SIGNAL(triggered()), this, SIGNAL(commit())); + QAction *revert = menu->addAction(tr("Revert...")); + connect(revert, SIGNAL(triggered()), this, SIGNAL(revert())); + + menu->addSeparator(); + + QAction *branch = menu->addAction(tr("Start new branch...")); + connect(branch, SIGNAL(triggered()), this, SIGNAL(newBranch())); + QAction *nobranch = menu->addAction(tr("Cancel new branch")); + nobranch->setEnabled(m_isNewBranch); + connect(nobranch, SIGNAL(triggered()), this, SIGNAL(noBranch())); + + menu->exec(QCursor::pos()); + + ungrabMouse(); +} + +void +UncommittedItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *option, + QWidget *w) +{ + paint->save(); + + ColourSet *colourSet = ColourSet::instance(); + QColor branchColour = colourSet->getColourFor(m_branch); + + QFont f(m_font); + + QTransform t = paint->worldTransform(); + float scale = std::min(t.m11(), t.m22()); + if (scale > 1.0) { + int ps = int((f.pixelSize() / scale) + 0.5); + if (ps < 8) ps = 8; + f.setPixelSize(ps); + } + + if (scale < 0.1) { + paint->setPen(QPen(branchColour, 0, Qt::DashLine)); + } else { + paint->setPen(QPen(branchColour, 2, Qt::DashLine)); + } + + paint->setFont(f); + QFontMetrics fm(f); + int fh = fm.height(); + + int width = 100; + if (m_wide) width = 180; + int x0 = -((width - 50) / 2 - 1); + + int height = 49; + QRectF r(x0, 0, width - 3, height); + paint->setBrush(Qt::white); + paint->drawRect(r); + + if (m_wide) { + QString label = tr("Uncommitted changes"); + paint->drawText(-(fm.width(label) - 50)/2, + 25 - fm.height()/2 + fm.ascent(), + label); + } else { + QString label = tr("Uncommitted"); + paint->drawText(-(fm.width(label) - 50)/2, + 25 - fm.height() + fm.ascent(), + label); + label = tr("changes"); + paint->drawText(-(fm.width(label) - 50)/2, + 25 + fm.ascent(), + label); + } + + if (m_showBranch && m_branch != "") { + // write branch name + f.setBold(true); + paint->setFont(f); + int wid = width - 3; + QString b = TextAbbrev::abbreviate(m_branch, QFontMetrics(f), wid); + paint->drawText(x0, -fh + fm.ascent() - 4, b); + f.setBold(false); + } + + paint->restore(); + return; +} diff -r 19cce6d2c470 -r b9c153e00e84 src/uncommitteditem.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/uncommitteditem.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,76 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef UNCOMMITTEDITEM_H +#define UNCOMMITTEDITEM_H + +#include +#include + +class UncommittedItem : public QGraphicsObject +{ + Q_OBJECT + +public: + UncommittedItem(); + + virtual QRectF boundingRect() const; + virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + QString branch() const { return m_branch; } + void setBranch(QString b) { m_branch = b; } + + bool showBranch() const { return m_showBranch; } + void setShowBranch(bool s) { m_showBranch = s; } + + bool isNewBranch() const { return m_isNewBranch; } + void setIsNewBranch(bool s) { m_isNewBranch = s; } + + int column() const { return m_column; } + int row() const { return m_row; } + void setColumn(int c) { m_column = c; setX(c * 100); } + void setRow(int r) { m_row = r; setY(r * 90); } + + bool isWide() const { return m_wide; } + void setWide(bool w) { m_wide = w; } + +signals: + void commit(); + void revert(); + void diff(); + void showSummary(); + void showWork(); + void newBranch(); + void noBranch(); + +protected: + virtual void mousePressEvent(QGraphicsSceneMouseEvent *); + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *); + +private: + void activateMenu(); + + QString m_branch; + bool m_showBranch; + bool m_isNewBranch; + QFont m_font; + int m_column; + int m_row; + bool m_wide; +}; + +#endif // UNCOMMITTEDITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 src/version.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/version.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,1 @@ +#define EASYHG_VERSION "0.9.5" diff -r 19cce6d2c470 -r b9c153e00e84 src/workstatuswidget.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/workstatuswidget.cpp Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,125 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "workstatuswidget.h" +#include "debug.h" +#include "clickablelabel.h" + +#include +#include +#include +#include +#include + +WorkStatusWidget::WorkStatusWidget(QWidget *parent) : + QWidget(parent) +{ + QGridLayout *layout = new QGridLayout; + layout->setMargin(6); + layout->setSpacing(6); + setLayout(layout); + + int row = 0; + +#ifndef Q_OS_MAC + layout->addItem(new QSpacerItem(1, 1), row, 0); + ++row; +#endif + + layout->addWidget(new QLabel(tr("Local:")), row, 1); + + m_openButton = new ClickableLabel; + QFont f(m_openButton->font()); + f.setBold(true); + m_openButton->setFont(f); + m_openButton->setMouseUnderline(true); + connect(m_openButton, SIGNAL(clicked()), this, SLOT(openButtonClicked())); + layout->addWidget(m_openButton, row, 2, 1, 2, Qt::AlignLeft); + + ++row; + layout->addWidget(new QLabel(tr("Remote:")), row, 1); + m_remoteURLLabel = new QLabel; + layout->addWidget(m_remoteURLLabel, row, 2, 1, 2); + + ++row; + layout->addWidget(new QLabel(tr("State:")), row, 1); + m_stateLabel = new QLabel; + layout->addWidget(m_stateLabel, row, 2, 1, 2); + + layout->setColumnStretch(2, 20); + + +} + +WorkStatusWidget::~WorkStatusWidget() +{ +} + +void +WorkStatusWidget::setLocalPath(QString p) +{ + m_localPath = p; + m_openButton->setText(p); + m_openButton->setEnabled(QDir(m_localPath).exists()); +} + +void +WorkStatusWidget::setRemoteURL(QString r) +{ + m_remoteURL = r; + m_remoteURLLabel->setText(r); +} + +void +WorkStatusWidget::setState(QString b) +{ + m_state = b; + updateStateLabel(); +} + +void +WorkStatusWidget::updateStateLabel() +{ + m_stateLabel->setText(m_state); +} + +void +WorkStatusWidget::openButtonClicked() +{ + QDir d(m_localPath); + if (d.exists()) { + QStringList args; + QString path = d.canonicalPath(); +#if defined Q_OS_WIN32 + // Although the Win32 API is quite happy to have + // forward slashes as directory separators, Windows + // Explorer is not + path = path.replace('/', '\\'); + args << path; + QProcess::execute("c:/windows/explorer.exe", args); +#else + args << path; + QProcess::execute( +#if defined Q_OS_MAC + "/usr/bin/open", +#else + "/usr/bin/xdg-open", +#endif + args); +#endif + } +} diff -r 19cce6d2c470 -r b9c153e00e84 src/workstatuswidget.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/workstatuswidget.h Thu Mar 24 10:27:51 2011 +0000 @@ -0,0 +1,62 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + EasyMercurial + + Based on HgExplorer by Jari Korhonen + Copyright (c) 2010 Jari Korhonen + Copyright (c) 2011 Chris Cannam + Copyright (c) 2011 Queen Mary, University of London + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef WORKSTATUSWIDGET_H +#define WORKSTATUSWIDGET_H + +#include + +class QLabel; +class QPushButton; +class QFileInfo; +class ClickableLabel; +class QCheckBox; + +class WorkStatusWidget : public QWidget +{ + Q_OBJECT + +public: + WorkStatusWidget(QWidget *parent = 0); + ~WorkStatusWidget(); + + QString localPath() const { return m_localPath; } + void setLocalPath(QString p); + + QString remoteURL() const { return m_remoteURL; } + void setRemoteURL(QString u); + + QString state() const { return m_state; } + void setState(QString b); + +private slots: + void openButtonClicked(); + +private: + QString m_localPath; + ClickableLabel *m_openButton; + + QString m_remoteURL; + QLabel *m_remoteURLLabel; + + QString m_state; + QLabel *m_stateLabel; + + void updateStateLabel(); +}; + +#endif diff -r 19cce6d2c470 -r b9c153e00e84 startupdialog.cpp --- a/startupdialog.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "startupdialog.h" -#include "common.h" - -#include -#include -#include - -StartupDialog::StartupDialog(QWidget *parent) : - QDialog(parent) -{ - setModal(true); - setWindowTitle(tr("About me")); - - QSettings settings; - settings.beginGroup("User Information"); - m_name = settings.value("name", getUserRealName()).toString(); - m_email = settings.value("email").toString(); - - QGridLayout *layout = new QGridLayout; - int row = 0; - - layout->addWidget(new QLabel(tr("Welcome to EasyMercurial!
How would you like to be identified in commit messages?")), - row++, 0, 1, 2); - - layout->addWidget(new QLabel(tr("Name:")), row, 0); - - m_nameEdit = new QLineEdit(); - m_nameEdit->setText(m_name); - connect(m_nameEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(realNameChanged(const QString &))); - layout->addWidget(m_nameEdit, row++, 1); - - layout->addWidget(new QLabel(tr("Email address:")), row, 0); - - m_emailEdit = new QLineEdit(); - m_emailEdit->setText(m_email); - connect(m_emailEdit, SIGNAL(textChanged(const QString &)), - this, SLOT(emailChanged(const QString &))); - layout->addWidget(m_emailEdit, row++, 1); - - layout->addWidget(new QLabel(tr("
You will appear as:")), row, 0, Qt::AlignBottom); - m_example = new QLabel(); - layout->addWidget(m_example, row++, 1, Qt::AlignBottom); - - QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok); - connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); - layout->addWidget(bbox, row++, 0, 1, 2); - m_ok = bbox->button(QDialogButtonBox::Ok); - m_ok->setEnabled(false); - - setLayout(layout); - - m_ok->setEnabled(m_name != ""); - updateExample(); -} - -void -StartupDialog::realNameChanged(const QString &s) -{ - m_name = s.trimmed(); - m_ok->setEnabled(m_name != ""); - updateExample(); -} - -void -StartupDialog::emailChanged(const QString &s) -{ - m_email = s.trimmed(); - updateExample(); -} - -void -StartupDialog::accept() -{ - QSettings settings; - settings.beginGroup("User Information"); - settings.setValue("name", m_name); - settings.setValue("email", m_email); - QDialog::accept(); -} - -void -StartupDialog::updateExample() -{ - QString identifier; - - if (m_email != "") { - identifier = QString("%1 <%2>").arg(m_name).arg(m_email); - } else { - identifier = m_name; - } - - m_example->setText(identifier); -} - - diff -r 19cce6d2c470 -r b9c153e00e84 startupdialog.h --- a/startupdialog.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef STARTUP_DIALOG_H -#define STARTUP_DIALOG_H - -#include -#include -#include -#include - -class StartupDialog : public QDialog -{ - Q_OBJECT - -public: - StartupDialog(QWidget *parent = 0); - -private slots: - void realNameChanged(const QString &); - void emailChanged(const QString &); - void accept(); - -private: - QLineEdit *m_nameEdit; - QLineEdit *m_emailEdit; - QLabel *m_example; - QPushButton *m_ok; - - QString m_name; - QString m_email; - - void updateExample(); -}; - -#endif diff -r 19cce6d2c470 -r b9c153e00e84 textabbrev.cpp --- a/textabbrev.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,314 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "textabbrev.h" - -#include -#include - -#include - -QString -TextAbbrev::getDefaultEllipsis() -{ - return "..."; -} - -int -TextAbbrev::getFuzzLength(QString ellipsis) -{ - int len = ellipsis.length(); - if (len < 3) return len + 3; - else if (len > 5) return len + 5; - else return len * 2; -} - -int -TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis) -{ - int width = metrics.width(ellipsis); - return width * 2; -} - -QString -TextAbbrev::abbreviateTo(QString text, int characters, Policy policy, - QString ellipsis) -{ - switch (policy) { - - case ElideEnd: - case ElideEndAndCommonPrefixes: - text = text.left(characters) + ellipsis; - break; - - case ElideStart: - text = ellipsis + text.right(characters); - break; - - case ElideMiddle: - if (characters > 2) { - text = text.left(characters/2 + 1) + ellipsis - + text.right(characters - (characters/2 + 1)); - } else { - text = text.left(characters) + ellipsis; - } - break; - } - - return text; -} - -QString -TextAbbrev::abbreviate(QString text, int maxLength, Policy policy, bool fuzzy, - QString ellipsis) -{ - if (ellipsis == "") ellipsis = getDefaultEllipsis(); - int fl = (fuzzy ? getFuzzLength(ellipsis) : 0); - if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1; - if (text.length() <= maxLength + fl) return text; - - int truncated = maxLength - ellipsis.length(); - return abbreviateTo(text, truncated, policy, ellipsis); -} - -QString -TextAbbrev::abbreviate(QString text, - const QFontMetrics &metrics, int &maxWidth, - Policy policy, QString ellipsis, int wrapLines) -{ - if (ellipsis == "") ellipsis = getDefaultEllipsis(); - - int tw = metrics.width(text); - - if (tw <= maxWidth) { - maxWidth = tw; - return text; - } - - int truncated = text.length(); - QString original = text; - - if (wrapLines < 2) { - - while (tw > maxWidth && truncated > 1) { - - truncated--; - - if (truncated > ellipsis.length()) { - text = abbreviateTo(original, truncated, policy, ellipsis); - } else { - break; - } - - tw = metrics.width(text); - } - - maxWidth = tw; - return text; - - } else { - - QStringList words = text.split(' ', QString::SkipEmptyParts); - text = ""; - - tw = 0; - int i = 0; - QString good = ""; - int lastUsed = 0; - while (tw < maxWidth && i < words.size()) { - if (text != "") text += " "; - text += words[i++]; - tw = metrics.width(text); - if (tw < maxWidth) { - good = text; - lastUsed = i; - } - } - - if (tw < maxWidth) { - maxWidth = tw; - return text; - } - - text = good; - - QString remainder; - while (lastUsed < words.size()) { - if (remainder != "") remainder += " "; - remainder += words[lastUsed++]; - } - remainder = abbreviate(remainder, metrics, maxWidth, - policy, ellipsis, wrapLines - 1); - - maxWidth = std::max(maxWidth, tw); - text = text + '\n' + remainder; - return text; - } -} - -QStringList -TextAbbrev::abbreviate(const QStringList &texts, int maxLength, - Policy policy, bool fuzzy, QString ellipsis) -{ - if (policy == ElideEndAndCommonPrefixes && - texts.size() > 1) { - - if (ellipsis == "") ellipsis = getDefaultEllipsis(); - int fl = (fuzzy ? getFuzzLength(ellipsis) : 0); - if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1; - - int maxOrigLength = 0; - for (int i = 0; i < texts.size(); ++i) { - int len = texts[i].length(); - if (len > maxOrigLength) maxOrigLength = len; - } - if (maxOrigLength <= maxLength + fl) return texts; - - return abbreviate(elidePrefixes - (texts, maxOrigLength - maxLength, ellipsis), - maxLength, ElideEnd, fuzzy, ellipsis); - } - - QStringList results; - for (int i = 0; i < texts.size(); ++i) { - results.push_back - (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis)); - } - return results; -} - -QStringList -TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics, - int &maxWidth, Policy policy, QString ellipsis) -{ - if (policy == ElideEndAndCommonPrefixes && - texts.size() > 1) { - - if (ellipsis == "") ellipsis = getDefaultEllipsis(); - - int maxOrigWidth = 0; - for (int i = 0; i < texts.size(); ++i) { - int w = metrics.width(texts[i]); - if (w > maxOrigWidth) maxOrigWidth = w; - } - - return abbreviate(elidePrefixes(texts, metrics, - maxOrigWidth - maxWidth, ellipsis), - metrics, maxWidth, ElideEnd, ellipsis); - } - - QStringList results; - int maxAbbrWidth = 0; - for (int i = 0; i < texts.size(); ++i) { - int width = maxWidth; - QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis); - if (width > maxAbbrWidth) maxAbbrWidth = width; - results.push_back(abbr); - } - maxWidth = maxAbbrWidth; - return results; -} - -QStringList -TextAbbrev::elidePrefixes(const QStringList &texts, - int targetReduction, - QString ellipsis) -{ - if (texts.empty()) return texts; - int plen = getPrefixLength(texts); - int fl = getFuzzLength(ellipsis); - if (plen < fl) return texts; - - QString prefix = texts[0].left(plen); - int truncated = plen; - if (plen >= targetReduction + fl) { - truncated = plen - targetReduction; - } else { - truncated = fl; - } - prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis); - - QStringList results; - for (int i = 0; i < texts.size(); ++i) { - results.push_back - (prefix + texts[i].right(texts[i].length() - plen)); - } - return results; -} - -QStringList -TextAbbrev::elidePrefixes(const QStringList &texts, - const QFontMetrics &metrics, - int targetWidthReduction, - QString ellipsis) -{ - if (texts.empty()) return texts; - int plen = getPrefixLength(texts); - int fl = getFuzzLength(ellipsis); - if (plen < fl) return texts; - - QString prefix = texts[0].left(plen); - int pwid = metrics.width(prefix); - int twid = pwid - targetWidthReduction; - if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2; - prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis); - - QStringList results; - for (int i = 0; i < texts.size(); ++i) { - results.push_back - (prefix + texts[i].right(texts[i].length() - plen)); - } - return results; -} - -static bool -havePrefix(QString prefix, const QStringList &texts) -{ - for (int i = 1; i < texts.size(); ++i) { - if (!texts[i].startsWith(prefix)) return false; - } - return true; -} - -int -TextAbbrev::getPrefixLength(const QStringList &texts) -{ - QString reference = texts[0]; - - if (reference == "" || havePrefix(reference, texts)) { - return reference.length(); - } - - int candidate = reference.length(); - QString splitChars(";:,./#-!()$_+=[]{}\\"); - - while (--candidate > 1) { - if (splitChars.contains(reference[candidate])) { - if (havePrefix(reference.left(candidate), texts)) { - break; - } - } - } - -// std::cerr << "TextAbbrev::getPrefixLength: prefix length is " << candidate << std::endl; -// for (int i = 0; i < texts.size(); ++i) { -// std::cerr << texts[i].left(candidate).toStdString() << "|" << texts[i].right(texts[i].length() - candidate).toStdString() << std::endl; -// } - - return candidate; -} - diff -r 19cce6d2c470 -r b9c153e00e84 textabbrev.h --- a/textabbrev.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef _TEXT_ABBREV_H_ -#define _TEXT_ABBREV_H_ - -#include -#include - -class QFontMetrics; - -class TextAbbrev -{ -public: - enum Policy { - ElideEnd, - ElideEndAndCommonPrefixes, - ElideStart, - ElideMiddle - }; - - /** - * Abbreviate the given text to the given maximum length - * (including ellipsis), using the given abbreviation policy. If - * fuzzy is true, the text will be left alone if it is "not much - * more than" the maximum length. - * - * If ellipsis is non-empty, it will be used to show elisions in - * preference to the default (which is "..."). - */ - static QString abbreviate(QString text, int maxLength, - Policy policy = ElideEnd, - bool fuzzy = true, - QString ellipsis = ""); - - /** - * Abbreviate the given text to the given maximum painted width, - * using the given abbreviation policy. maxWidth is also modified - * so as to return the painted width of the abbreviated text. - * - * If ellipsis is non-empty, it will be used to show elisions in - * preference to the default (which is tr("...")). - */ - static QString abbreviate(QString text, - const QFontMetrics &metrics, - int &maxWidth, - Policy policy = ElideEnd, - QString ellipsis = "", - int wrapLines = 1); - - /** - * Abbreviate all of the given texts to the given maximum length, - * using the given abbreviation policy. If fuzzy is true, texts - * that are "not much more than" the maximum length will be left - * alone. - * - * If ellipsis is non-empty, it will be used to show elisions in - * preference to the default (which is tr("...")). - */ - static QStringList abbreviate(const QStringList &texts, int maxLength, - Policy policy = ElideEndAndCommonPrefixes, - bool fuzzy = true, - QString ellipsis = ""); - - /** - * Abbreviate all of the given texts to the given maximum painted - * width, using the given abbreviation policy. maxWidth is also - * modified so as to return the maximum painted width of the - * abbreviated texts. - * - * If ellipsis is non-empty, it will be used to show elisions in - * preference to the default (which is tr("...")). - */ - static QStringList abbreviate(const QStringList &texts, - const QFontMetrics &metrics, - int &maxWidth, - Policy policy = ElideEndAndCommonPrefixes, - QString ellipsis = ""); - -protected: - static QString getDefaultEllipsis(); - static int getFuzzLength(QString ellipsis); - static int getFuzzWidth(const QFontMetrics &metrics, QString ellipsis); - static QString abbreviateTo(QString text, int characters, - Policy policy, QString ellipsis); - static QStringList elidePrefixes(const QStringList &texts, - int targetReduction, - QString ellipsis); - static QStringList elidePrefixes(const QStringList &texts, - const QFontMetrics &metrics, - int targetWidthReduction, - QString ellipsis); - static int getPrefixLength(const QStringList &texts); -}; - -#endif - diff -r 19cce6d2c470 -r b9c153e00e84 uncommitteditem.cpp --- a/uncommitteditem.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,170 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "uncommitteditem.h" -#include "colourset.h" -#include "debug.h" -#include "textabbrev.h" - -#include -#include -#include -#include -#include -#include -#include - -UncommittedItem::UncommittedItem() : - m_showBranch(false), m_isNewBranch(false), - m_column(0), m_row(0), m_wide(false) -{ - m_font = QFont(); - m_font.setPixelSize(11); - m_font.setBold(false); - m_font.setItalic(false); - setCursor(Qt::ArrowCursor); -} - -QRectF -UncommittedItem::boundingRect() const -{ - //!!! this stuff is gross, refactor with changesetitem and connectionitem - int w = 100; - if (m_wide) w = 180; - return QRectF(-((w-50)/2 - 1), -30, w - 3, 79 + 40); -} - -void -UncommittedItem::mousePressEvent(QGraphicsSceneMouseEvent *e) -{ - DEBUG << "UncommittedItem::mousePressEvent" << endl; - if (e->button() == Qt::RightButton) { - activateMenu(); - } -} - -void -UncommittedItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *e) -{ - DEBUG << "UncommittedItem::mouseDoubleClickEvent" << endl; - if (e->button() == Qt::LeftButton) { - emit showWork(); - } -} - -void -UncommittedItem::activateMenu() -{ - QMenu *menu = new QMenu; - QLabel *label = new QLabel(tr(" Uncommitted changes")); - QWidgetAction *wa = new QWidgetAction(menu); - wa->setDefaultWidget(label); - menu->addAction(wa); - menu->addSeparator(); - - QAction *dif = menu->addAction(tr("Diff")); - connect(dif, SIGNAL(triggered()), this, SIGNAL(diff())); - QAction *stat = menu->addAction(tr("Summarise changes")); - connect(stat, SIGNAL(triggered()), this, SIGNAL(showSummary())); - - menu->addSeparator(); - - QAction *commit = menu->addAction(tr("Commit...")); - connect(commit, SIGNAL(triggered()), this, SIGNAL(commit())); - QAction *revert = menu->addAction(tr("Revert...")); - connect(revert, SIGNAL(triggered()), this, SIGNAL(revert())); - - menu->addSeparator(); - - QAction *branch = menu->addAction(tr("Start new branch...")); - connect(branch, SIGNAL(triggered()), this, SIGNAL(newBranch())); - QAction *nobranch = menu->addAction(tr("Cancel new branch")); - nobranch->setEnabled(m_isNewBranch); - connect(nobranch, SIGNAL(triggered()), this, SIGNAL(noBranch())); - - menu->exec(QCursor::pos()); - - ungrabMouse(); -} - -void -UncommittedItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *option, - QWidget *w) -{ - paint->save(); - - ColourSet *colourSet = ColourSet::instance(); - QColor branchColour = colourSet->getColourFor(m_branch); - - QFont f(m_font); - - QTransform t = paint->worldTransform(); - float scale = std::min(t.m11(), t.m22()); - if (scale > 1.0) { - int ps = int((f.pixelSize() / scale) + 0.5); - if (ps < 8) ps = 8; - f.setPixelSize(ps); - } - - if (scale < 0.1) { - paint->setPen(QPen(branchColour, 0, Qt::DashLine)); - } else { - paint->setPen(QPen(branchColour, 2, Qt::DashLine)); - } - - paint->setFont(f); - QFontMetrics fm(f); - int fh = fm.height(); - - int width = 100; - if (m_wide) width = 180; - int x0 = -((width - 50) / 2 - 1); - - int height = 49; - QRectF r(x0, 0, width - 3, height); - paint->setBrush(Qt::white); - paint->drawRect(r); - - if (m_wide) { - QString label = tr("Uncommitted changes"); - paint->drawText(-(fm.width(label) - 50)/2, - 25 - fm.height()/2 + fm.ascent(), - label); - } else { - QString label = tr("Uncommitted"); - paint->drawText(-(fm.width(label) - 50)/2, - 25 - fm.height() + fm.ascent(), - label); - label = tr("changes"); - paint->drawText(-(fm.width(label) - 50)/2, - 25 + fm.ascent(), - label); - } - - if (m_showBranch && m_branch != "") { - // write branch name - f.setBold(true); - paint->setFont(f); - int wid = width - 3; - QString b = TextAbbrev::abbreviate(m_branch, QFontMetrics(f), wid); - paint->drawText(x0, -fh + fm.ascent() - 4, b); - f.setBold(false); - } - - paint->restore(); - return; -} diff -r 19cce6d2c470 -r b9c153e00e84 uncommitteditem.h --- a/uncommitteditem.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef UNCOMMITTEDITEM_H -#define UNCOMMITTEDITEM_H - -#include -#include - -class UncommittedItem : public QGraphicsObject -{ - Q_OBJECT - -public: - UncommittedItem(); - - virtual QRectF boundingRect() const; - virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); - - QString branch() const { return m_branch; } - void setBranch(QString b) { m_branch = b; } - - bool showBranch() const { return m_showBranch; } - void setShowBranch(bool s) { m_showBranch = s; } - - bool isNewBranch() const { return m_isNewBranch; } - void setIsNewBranch(bool s) { m_isNewBranch = s; } - - int column() const { return m_column; } - int row() const { return m_row; } - void setColumn(int c) { m_column = c; setX(c * 100); } - void setRow(int r) { m_row = r; setY(r * 90); } - - bool isWide() const { return m_wide; } - void setWide(bool w) { m_wide = w; } - -signals: - void commit(); - void revert(); - void diff(); - void showSummary(); - void showWork(); - void newBranch(); - void noBranch(); - -protected: - virtual void mousePressEvent(QGraphicsSceneMouseEvent *); - virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *); - -private: - void activateMenu(); - - QString m_branch; - bool m_showBranch; - bool m_isNewBranch; - QFont m_font; - int m_column; - int m_row; - bool m_wide; -}; - -#endif // UNCOMMITTEDITEM_H diff -r 19cce6d2c470 -r b9c153e00e84 version.h --- a/version.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -#define EASYHG_VERSION "0.9.5" diff -r 19cce6d2c470 -r b9c153e00e84 workstatuswidget.cpp --- a/workstatuswidget.cpp Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#include "workstatuswidget.h" -#include "debug.h" -#include "clickablelabel.h" - -#include -#include -#include -#include -#include - -WorkStatusWidget::WorkStatusWidget(QWidget *parent) : - QWidget(parent) -{ - QGridLayout *layout = new QGridLayout; - layout->setMargin(6); - layout->setSpacing(6); - setLayout(layout); - - int row = 0; - -#ifndef Q_OS_MAC - layout->addItem(new QSpacerItem(1, 1), row, 0); - ++row; -#endif - - layout->addWidget(new QLabel(tr("Local:")), row, 1); - - m_openButton = new ClickableLabel; - QFont f(m_openButton->font()); - f.setBold(true); - m_openButton->setFont(f); - m_openButton->setMouseUnderline(true); - connect(m_openButton, SIGNAL(clicked()), this, SLOT(openButtonClicked())); - layout->addWidget(m_openButton, row, 2, 1, 2, Qt::AlignLeft); - - ++row; - layout->addWidget(new QLabel(tr("Remote:")), row, 1); - m_remoteURLLabel = new QLabel; - layout->addWidget(m_remoteURLLabel, row, 2, 1, 2); - - ++row; - layout->addWidget(new QLabel(tr("State:")), row, 1); - m_stateLabel = new QLabel; - layout->addWidget(m_stateLabel, row, 2, 1, 2); - - layout->setColumnStretch(2, 20); - - -} - -WorkStatusWidget::~WorkStatusWidget() -{ -} - -void -WorkStatusWidget::setLocalPath(QString p) -{ - m_localPath = p; - m_openButton->setText(p); - m_openButton->setEnabled(QDir(m_localPath).exists()); -} - -void -WorkStatusWidget::setRemoteURL(QString r) -{ - m_remoteURL = r; - m_remoteURLLabel->setText(r); -} - -void -WorkStatusWidget::setState(QString b) -{ - m_state = b; - updateStateLabel(); -} - -void -WorkStatusWidget::updateStateLabel() -{ - m_stateLabel->setText(m_state); -} - -void -WorkStatusWidget::openButtonClicked() -{ - QDir d(m_localPath); - if (d.exists()) { - QStringList args; - QString path = d.canonicalPath(); -#if defined Q_OS_WIN32 - // Although the Win32 API is quite happy to have - // forward slashes as directory separators, Windows - // Explorer is not - path = path.replace('/', '\\'); - args << path; - QProcess::execute("c:/windows/explorer.exe", args); -#else - args << path; - QProcess::execute( -#if defined Q_OS_MAC - "/usr/bin/open", -#else - "/usr/bin/xdg-open", -#endif - args); -#endif - } -} diff -r 19cce6d2c470 -r b9c153e00e84 workstatuswidget.h --- a/workstatuswidget.h Thu Mar 24 10:20:47 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - EasyMercurial - - Based on HgExplorer by Jari Korhonen - Copyright (c) 2010 Jari Korhonen - Copyright (c) 2011 Chris Cannam - Copyright (c) 2011 Queen Mary, University of London - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef WORKSTATUSWIDGET_H -#define WORKSTATUSWIDGET_H - -#include - -class QLabel; -class QPushButton; -class QFileInfo; -class ClickableLabel; -class QCheckBox; - -class WorkStatusWidget : public QWidget -{ - Q_OBJECT - -public: - WorkStatusWidget(QWidget *parent = 0); - ~WorkStatusWidget(); - - QString localPath() const { return m_localPath; } - void setLocalPath(QString p); - - QString remoteURL() const { return m_remoteURL; } - void setRemoteURL(QString u); - - QString state() const { return m_state; } - void setState(QString b); - -private slots: - void openButtonClicked(); - -private: - QString m_localPath; - ClickableLabel *m_openButton; - - QString m_remoteURL; - QLabel *m_remoteURLLabel; - - QString m_state; - QLabel *m_stateLabel; - - void updateStateLabel(); -}; - -#endif