Mercurial > hg > easyhg
changeset 370:b9c153e00e84
Move source files to src/
line wrap: on
line diff
--- 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 <QDialogButtonBox> -#include <QLabel> -#include <QTableWidget> -#include <QHeaderView> -#include <QGridLayout> - -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); -} -
--- 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 <QDialog> - -class AnnotateDialog : public QDialog -{ - Q_OBJECT - -public: - AnnotateDialog(QWidget *parent, QString output); -}; - -#endif
--- 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 <QVariant> - -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 = "<tr><td><b>%1</b> </td><td>%2</td></tr>"; - - description = "<qt><table border=0>"; - - QString c = comment().trimmed(); - c = c.replace(QRegExp("^\""), ""); - c = c.replace(QRegExp("\"$"), ""); - c = c.replace("\\\"", "\""); - c = xmlEncode(c); - c = c.replace("\\n", "<br>"); - - 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 += "</table></qt>"; - - return description; -}
--- 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 <QObject> -#include <QString> -#include <QStringList> -#include <QList> -#include <QSharedPointer> - -#include "logparser.h" - -class Changeset; - -typedef QList<Changeset *> 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
--- 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 <QTextDocument> -#include <QPainter> - -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<QPointF> 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<QPointF> 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()); -} -
--- 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 <QGraphicsItem> -#include <QFont> - -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
--- 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 <QPainter> -#include <QGraphicsScene> -#include <QGraphicsSceneMouseEvent> -#include <QMenu> -#include <QAction> -#include <QLabel> -#include <QWidgetAction> -#include <QApplication> -#include <QClipboard> - -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("<qt><b> Revision: </b>%1</qt>") - .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<ChangesetScene *>(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<QAction *>(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(); -}
--- 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 <QGraphicsObject> -#include <QFont> - -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<QAction *, QString> m_parentDiffActions; - QMap<QAction *, QString> m_summaryActions; -}; - -#endif // CHANGESETITEM_H
--- 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<ChangesetItem *>(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<ChangesetItem *>(it); - if (csit && csit->getId() == id) return csit; - } - return 0; -} - -
--- 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 <QGraphicsScene> - -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
--- 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 <QLabel> - -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("<u>%1</u>").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
--- 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; -} - - -
--- 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 <QSet> -#include <QMap> -#include <QColor> -#include <QString> - -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<QString> m_defaultNames; - QMap<QString, QColor> m_colours; - QColor m_lastColour; - - static ColourSet m_instance; -}; - -#endif
--- 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 <QFileInfo> -#include <QProcessEnvironment> -#include <QStringList> -#include <QDir> -#include <QRegExp> - -#include <sys/types.h> - -#ifdef Q_OS_WIN32 -#define _WIN32_WINNT 0x0500 -#define SECURITY_WIN32 -#include <windows.h> -#include <security.h> -#else -#include <errno.h> -#include <pwd.h> -#include <unistd.h> -#include <sys/ioctl.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <signal.h> -#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; -}
--- 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 <QString> - -#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 - -
--- 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 <QMessageBox> -#include <QInputDialog> -#include <QGridLayout> -#include <QLabel> -#include <QTextEdit> -#include <QDialogButtonBox> - -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 = "<qt>"; - else text = "<qt>" + intro + "<p>"; - - text += "<code>"; - foreach (QString file, files) { - text += " " + xmlEncode(file) + "<br>"; - } - text += "</code></qt>"; - - 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 = "<qt>" + introTextWithCount + "</qt>"; - } - 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 = "<qt>" + introTextWithCount + "</qt>"; - } - 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 = "<qt>" + introTextWithCount; - } - text += tr("<p>Please enter your comment:</qt>"); - 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; -}
--- 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 <QDialog> -#include <QWidget> -#include <QString> -#include <QStringList> -#include <QTextEdit> -#include <QPushButton> - -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
--- 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 <QPainter> - -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(); -} - -
--- 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 <QGraphicsItem> - -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
--- 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 <QPainter> -#include <QBrush> -#include <QFont> -#include <QGraphicsSceneMouseEvent> - -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(); -} - -
--- 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 <QGraphicsObject> - -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
--- 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 <QString> -#include <QUrl> -#include <QMutex> -#include <QMutexLocker> -#include <QFile> -#include <QDir> -#include <QCoreApplication> -#include <QDateTime> - -#include <cstdio> - -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() << ">"; -} -
--- 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 <QDebug> -#include <QTextStream> -#include <string> -#include <iostream> - -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 <typename T> -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 <typename T> - inline NoDebug &operator<<(const T &) { return *this; } - - inline NoDebug &operator<<(QTextStreamFunction) { return *this; } -}; - -#define DEBUG NoDebug() - -#endif /* !NDEBUG */ - -#endif /* !_DEBUG_H_ */ -
--- 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 }
--- 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 <QMap> - -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<FileStates *>(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)); -} -
--- 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 <QStringList> -#include <QMap> -#include <QString> - -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<Activity> 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<QString, State> m_stateMap; - - void clearBuckets(); - - State charToState(QChar, bool * = 0); - QStringList *stateToBucket(State); -}; - -#endif // FILESTATES_H
--- 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 <QLabel> -#include <QListWidget> -#include <QGridLayout> -#include <QFileInfo> -#include <QApplication> -#include <QDateTime> -#include <QPushButton> -#include <QToolButton> -#include <QDir> -#include <QProcess> -#include <QCheckBox> -#include <QSettings> -#include <QAction> - -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.<br>" - "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.<br>" - "If you intended to delete them, select them and use Remove to tell the version control system about it.<br>" - "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.<br>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.<br>" -// "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,<br>" - "and so will be ignored by the version control system."); - - m_highlightExplanation = tr("Files highlighted <font color=#d40000>in red</font> " - "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("<qt><b>%1</b><br>%2<br>%3</qt>") - .arg(m_simpleLabels[s]) - .arg(m_descriptions[s]) - .arg(m_highlightExplanation); - } else { - return QString("<qt><b>%1</b><br>%2</qt>") - .arg(m_simpleLabels[s]) - .arg(m_descriptions[s]); - } - } else { - return QString("<qt><b>%1</b></qt>") - .arg(m_simpleLabels[s]); - } - settings.endGroup(); -} - -void FileStatusWidget::setNoModificationsLabelText() -{ - QSettings settings; - settings.beginGroup("Presentation"); - if (settings.value("showhelpfultext", true).toBool()) { - m_noModificationsLabel->setText - (tr("<qt>This area will list files in your working folder that you have changed.<br><br>At the moment you have no uncommitted changes.<br><br>To see changes previously made to the repository,<br>switch to the History tab.<br><br>%1</qt>") -#if defined Q_OS_MAC - .arg(tr("To open the working folder in Finder,<br>click on the “Local” folder path shown above.")) -#elif defined Q_OS_WIN32 - .arg(tr("To open the working folder in Windows Explorer,<br>click on the “Local” folder path shown above.")) -#else - .arg(tr("To open the working folder in your system file manager,<br>click the “Local” folder path shown above.")) -#endif - ); - } else { - m_noModificationsLabel->setText - (tr("<qt>You have no uncommitted changes.</qt>")); - } -} - - -void FileStatusWidget::menuActionActivated() -{ - QAction *act = qobject_cast<QAction *>(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<QListWidget *>(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<QListWidgetItem *> 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<QString> 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<QLabel *> ql = p->findChildren<QLabel *>(); - if (!ql.empty()) ql[0]->setText(text); -} -
--- 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 <QWidget> -#include <QList> - -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<FileStates::State, QString> m_simpleLabels; - QMap<FileStates::State, QString> m_descriptions; - QMap<FileStates::State, QListWidget *> m_stateListMap; - QMap<FileStates::Activity, QString> m_actionLabels; - QString m_highlightExplanation; - - QFileInfo *m_dateReference; - QStringList m_selectedFiles; - - bool m_gridlyLayout; - int m_lastGridlyCount; - QList<QWidget *> 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
--- 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 <QSettings> - -#include <iostream> - -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<QString> 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<int> 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; - } -} -
--- 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 <QSet> -#include <QMap> -#include <QPair> - -#include <exception> - -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<QString, Changeset *> IdChangesetMap; - IdChangesetMap m_changesets; - - typedef QMap<QString, ChangesetItem *> IdItemMap; - IdItemMap m_items; - - typedef QSet<int> ColumnSet; - typedef QMap<int, ColumnSet> GridAlloc; - GridAlloc m_alloc; - - typedef QPair<int, int> Range; - typedef QMap<QString, Range> BranchRangeMap; - BranchRangeMap m_branchRanges; - - typedef QMap<QString, int> BranchColumnMap; - BranchColumnMap m_branchHomes; - - typedef QSet<QString> IdSet; - IdSet m_handled; - - typedef QMap<int, QString> 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
--- 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 <QString> -#include <QStringList> - -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
--- 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 <QPushButton> -#include <QListWidget> -#include <QDialog> -#include <QLabel> -#include <QVBoxLayout> -#include <QSettings> -#include <QInputDialog> -#include <QTemporaryFile> -#include <QDir> - -#include <iostream> -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> - -#ifndef Q_OS_WIN32 -#include <unistd.h> -#include <termios.h> -#include <fcntl.h> -#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<QWidget *>(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<QWidget *>(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 -}
--- 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 <QProgressBar> -#include <QProcess> -#include <QByteArray> -#include <QRect> -#include <QFile> - -#include <deque> - -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<HgAction> ActionQueue; - ActionQueue m_queue; - HgAction m_currentAction; -}; - -#endif // HGRUNNER_H
--- 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 <QClipboard> -#include <QContextMenuEvent> -#include <QApplication> - -#include <iostream> - -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); -} -
--- 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 <QMenu> -#include <QListWidget> -#include <QGroupBox> -#include <QVBoxLayout> -#include <QCheckBox> -#include <QLabel> -#include <QTabWidget> - -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
--- 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 <iostream> - -#include <QGridLayout> - -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<QString> oldIds; - foreach (Changeset *cs, m_changesets) { - oldIds.insert(cs->id()); - } - - QSet<QString> 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<QGraphicsItem *> items = scene->items(); - foreach (QGraphicsItem *it, items) { - - ChangesetItem *csit = dynamic_cast<ChangesetItem *>(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<ChangesetScene *>(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))); -}
--- 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 <QWidget> -#include <QSet> - -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<QString> 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
--- 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 <QScrollArea> -#include <QApplication> -#include <QDialogButtonBox> -#include <QLabel> -#include <QGridLayout> -#include <QStyle> - -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("<p>%1</p><code>%2</code>") - .arg(tr("The command output was:")) - .arg(xmlEncode(text).replace("\n", "<br>")); - } else { - body = tr("<qt>Your local repository already contains all changes found in the remote repository.</qt>"); - } - scroll = false; - } else { - head = tr("There are %n change(s) ready to pull", "", csets.size()); - foreach (Changeset *cs, csets) { - body += cs->formatHtml() + "<p>"; - 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("<qt><h3>%1</h3></qt>").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); -} - -
--- 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 <QDialog> - -class IncomingDialog : public QDialog -{ - Q_OBJECT - -public: - IncomingDialog(QWidget *parent, QString incoming); -}; - -#endif
--- 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 <QStringList> -#include <QRegExp> - -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; -}
--- 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 <QObject> -#include <QString> -#include <QList> -#include <QMap> - -typedef QMap<QString, QString> LogEntry; -typedef QList<LogEntry> 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
--- 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 <QApplication> -#include <QTranslator> -#include <QDir> - -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(); -}
--- 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 <QStringList> -#include <QDir> -#include <QNetworkInterface> -#include <QHostAddress> -#include <QHostInfo> -#include <QDesktopServices> -#include <QStatusBar> -#include <QMessageBox> -#include <QMenuBar> -#include <QApplication> -#include <QToolBar> -#include <QToolButton> -#include <QSettings> -#include <QInputDialog> -#include <QWidgetAction> -#include <QRegExp> -#include <QShortcut> -#include <QUrl> -#include <QTimer> - -#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("<qt><h2>EasyMercurial v%1</h2>" -#ifdef Q_OS_MAC - "<font size=-1>" -#endif - "<p>EasyMercurial is a simple user interface for the " - "Mercurial</a> version control system.</p>" - "<h4>Credits and Copyright</h4>" - "<p>Development carried out by Chris Cannam for " - "SoundSoftware.ac.uk at the Centre for Digital Music, " - "Queen Mary, University of London.</p>" - "<p>EasyMercurial is based on HgExplorer by " - "Jari Korhonen, with thanks.</p>" - "<p style=\"margin-left: 2em;\">" - "Copyright © 2011 Queen Mary, University of London.<br>" - "Copyright © 2010 Jari Korhonen.<br>" - "Copyright © 2011 Chris Cannam." - "</p>" - "<p style=\"margin-left: 2em;\">" - "This program requires Mercurial, by Matt Mackall and others.<br>" - "This program uses Qt by Nokia.<br>" - "This program uses Nuvola icons by David Vignoni.<br>" - "This program may use KDiff3 by Joachim Eibl.<br>" - "This program may use PyQt by River Bank Computing.<br>" - "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others." - "</p>" - "<h4>License</h4>" - "<p>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.</p>" -#ifdef Q_OS_MAC - "</font>" -#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("<p><b>Note:</b> 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("<h3>%1</h3><p>%2%3").arg(cf) - .arg(tr("You are about to commit the following files to %1:").arg(branchText)) - .arg(subsetNote), - tr("<h3>%1</h3><p>%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("<p><b>Note:</b> 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("<qt><b>Sorry, unable to revert these files</b><br><br>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.<br><br>This is a limitation of EasyMercurial. Consider reverting all files, or using hg revert with a specific revision at the command-line instead.</qt>")); - return; - } - } - } else { - params << "revert" << "--" << files; - } - - if (ConfirmCommentDialog::confirmDangerousFilesAction - (this, - rf, - tr("<h3>%1</h3><p>%2%3").arg(rf) - .arg(tr("You are about to <b>revert</b> the following files to their previous committed state.<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed.")) - .arg(subsetNote), - tr("<h3>%1</h3><p>%2%3").arg(rf) - .arg(tr("You are about to <b>revert</b> %n file(s).<br><br>This will <b>throw away any changes</b> 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 <code>%1</code> 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 <code>%1</code> 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("<qt><h3>Pull from remote repository?</h3></qt>"), - tr("<qt><p>You are about to pull changes from the remote repository at <code>%1</code>.</p></qt>").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("<p><b>Note:</b> 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("<qt><h3>Push to remote repository?</h3></qt>"), - tr("<qt><p>You are about to push your commits to the remote repository at <code>%1</code>.</p>%2</qt>").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<QNetworkInterface> 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<iface.addressEntries().count(); j++) { - QHostAddress tmp = iface.addressEntries().at(j).ip(); - if (QAbstractSocket::IPv4Protocol == tmp.protocol()) { - ret.push_back(tmp.toString()); - } - } - } - } - return ret; -} - -void MainWindow::clearState() -{ - DEBUG << "MainWindow::clearState" << endl; - foreach (Changeset *cs, m_currentParents) delete cs; - m_currentParents.clear(); - foreach (Changeset *cs, m_currentHeads) delete cs; - m_currentHeads.clear(); - m_currentBranch = ""; - m_lastStatOutput = ""; - m_lastRevertedFiles.clear(); - m_mergeTargetRevision = ""; - m_mergeCommitComment = ""; - m_stateUnknown = true; - m_needNewLog = true; - if (m_fsWatcher) { - delete m_fsWatcherGeneralTimer; - m_fsWatcherGeneralTimer = 0; - delete m_fsWatcherRestoreTimer; - m_fsWatcherRestoreTimer = 0; - delete m_fsWatcher; - m_fsWatcher = 0; - } -} - -void MainWindow::hgServe() -{ - QStringList params; - QString msg; - - QStringList addrs = listAllUpIpV4Addresses(); - - if (addrs.empty()) { - QMessageBox::critical - (this, tr("Serve"), tr("Failed to identify an active IPv4 address")); - return; - } - - //!!! should find available port as well - - QTextStream ts(&msg); - ts << QString("<qt><p>%1</p>") - .arg(tr("Running temporary server at %n address(es):", "", addrs.size())); - foreach (QString addr, addrs) { - ts << QString("<pre> http://%1:8000</pre>").arg(xmlEncode(addr)); - } - ts << tr("<p>Press Close to stop the server and return.</p>"); - 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("<qt><big>What would you like to open?</big></qt>"), - this); - - d->addChoice("remote", - tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"), - tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."), - MultiChoiceDialog::UrlToDirectoryArg); - - d->addChoice("local", - tr("<qt><center><img src=\":images/hglogo-64.png\"><br>Local repository</center></qt>"), - tr("Open an existing local Mercurial repository."), - MultiChoiceDialog::DirectoryArg); - - d->addChoice("init", - tr("<qt><center><img src=\":images/hdd_unmount-64.png\"><br>File folder</center></qt>"), - 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<QAction *>(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("<qt><big>Set the remote location</big></qt>"), - this); - - QString explanation; - if (initial) { - explanation = tr("Provide a URL to use for push and pull actions from the current local repository.<br>This will be the default for subsequent pushes and pulls.<br>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("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"), - 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("<qt><b>Folder required</b><br><br>You asked to open \"%1\".<br>This is a file; to open a repository, you need to choose a folder.</qt>").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::askAboutUnknownFolder(QString arg) -{ - bool result = (QMessageBox::question - (this, tr("Path does not exist"), - tr("<qt><b>Path does not exist: create it?</b><br><br>You asked to open a remote repository by cloning it to \"%1\". This folder does not exist, and neither does its parent.<br><br>Would you like to create the parent folder as well?</qt>").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("<qt><b>Failed to create folder</b><br><br>Sorry, the path for the parent folder \"%1\" could not be created.</qt>").arg(dir.absolutePath())); - return false; - } - return true; - } - return false; -} - -bool MainWindow::complainAboutUnknownFolder(QString arg) -{ - QMessageBox::critical - (this, tr("Folder does not exist"), - tr("<qt><b>Folder does not exist</b><br><br>You asked to open \"%1\".<br>This folder does not exist, and it cannot be created because its parent does not exist either.</qt>").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::complainAboutInitInRepo(QString arg) -{ - QMessageBox::critical - (this, tr("Path is in existing repository"), - tr("<qt><b>Path is in an existing repository</b><br><br>You asked to initialise a repository at \"%1\".<br>This path is already inside an existing repository.</qt>").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::complainAboutInitFile(QString arg) -{ - QMessageBox::critical - (this, tr("Path is a file"), - tr("<qt><b>Path is a file</b><br><br>You asked to initialise a repository at \"%1\".<br>This is an existing file; it is only possible to initialise in folders.</qt>").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::complainAboutCloneToExisting(QString arg) -{ - QMessageBox::critical - (this, tr("Path is in existing repository"), - tr("<qt><b>Local path is in an existing repository</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is already inside an existing repository.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg))); - return false; -} - -bool MainWindow::complainAboutCloneToFile(QString arg) -{ - QMessageBox::critical - (this, tr("Path is a file"), - tr("<qt><b>Local path is a file</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is an existing file.<br>Please provide a new folder name for the local repository.</qt>").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("<qt><b>Local folder already exists</b><br><br>You asked to open a remote repository by cloning it to \"%1\", but this folder already exists and so cannot be cloned to.<br><br>Would you like to create the new folder \"%2\" instead?</qt>") - .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("<qt><b>Local folder already exists</b><br><br>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.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg))); - return ""; -} - -bool MainWindow::askToOpenParentRepo(QString arg, QString parent) -{ - return (QMessageBox::question - (this, tr("Path is inside a repository"), - tr("<qt><b>Open the repository that contains this path?</b><br><br>You asked to open \"%1\".<br>This is not the root folder of a repository.<br>But it is inside a repository, whose root is at \"%2\". <br><br>Would you like to open that repository instead?</qt>") - .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("<qt><b>Initialise a repository here?</b><br><br>You asked to open \"%1\".<br>This folder is not a Mercurial working copy.<br><br>Would you like to initialise a repository here?</qt>") - .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("<qt><b>Initialise a new repository?</b><br><br>You asked to open \"%1\".<br>This folder does not yet exist.<br><br>Would you like to create the folder and initialise a new empty repository in it?</qt>") - .arg(xmlEncode(arg)), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Ok) - == QMessageBox::Ok); -} - -bool MainWindow::askToOpenInsteadOfInit(QString arg) -{ - return (QMessageBox::question - (this, tr("Repository exists"), - tr("<qt><b>Open existing repository?</b><br><br>You asked to initialise a new repository at \"%1\".<br>This folder already contains a repository. Would you like to open it?</qt>") - .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<QString> alreadyWatched; - QStringList dl(m_fsWatcher->directories()); - foreach (QString d, dl) alreadyWatched.insert(d); - - std::deque<QString> 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("<qt><h3>%1</h3></qt>").arg(head); -} - -QString MainWindow::format3(QString head, QString intro, QString code) -{ - code = xmlEncode(code).replace("\n", "<br>") -#ifndef Q_OS_WIN32 - // The hard hyphen comes out funny on Windows - .replace("-", "‑") -#endif - .replace(" ", " "); - if (intro == "") { - return QString("<qt><h3>%1</h3><p><code>%2</code></p>") - .arg(head).arg(code); - } else if (code == "") { - return QString("<qt><h3>%1</h3><p>%2</p>") - .arg(head).arg(intro); - } else { - return QString("<qt><h3>%1</h3><p>%2</p><p><code>%3</code></p>") - .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("<qt>Successfully pushed to the remote repository at <code>%1</code>.</qt>").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<p>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.<br><br>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.<br><br>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.<br><br>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.<br><br>Or you may lack the necessary permissions on the remote repository.<br><br>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.<br>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.<br>This may indicate an installation problem.<br><br>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.<p>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.<br><br>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 <code>%1</code>.").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("<qt><h3>Update successful</h3><p>%1</p>").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("<qt><h3>Merge resolved</h3><p>Merge resolved successfully.<br>Remember to test and commit the result before making any further changes.</p>")); - 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 "<<endl; - foreach (Changeset *h, m_currentHeads) { - DEBUG << "head id = " << h->id() << endl; - } - } - m_justMerged = false; - } else if (m_currentParents.size() == 0) { - if (m_currentHeads.size() == 0) { - // No heads -> empty repo - emptyRepo = true; - } else { - // Heads, but no parents -> no working copy, e.g. we have - // just converted this repo but haven't updated in it yet. - // Uncommon but confusing; probably merits a special case - noWorkingCopy = true; - canUpdate = true; - } - m_justMerged = false; - } else { - haveMerge = true; - m_justMerged = true; - } - - m_hgIncomingAct->setEnabled(m_remoteRepoActionsEnabled); - m_hgPullAct->setEnabled(m_remoteRepoActionsEnabled); - // permit push even if no remote yet; we'll ask for one - m_hgPushAct->setEnabled(m_localRepoActionsEnabled && !emptyRepo); - - m_hgMergeAct->setEnabled(m_localRepoActionsEnabled && - (canMerge || m_hgTabs->canResolve())); - m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled && - (canUpdate && !m_hgTabs->haveChangesToCommit())); - - // Set the state field on the file status widget - - QString branchText; - if (m_currentBranch == "" || m_currentBranch == "default") { - branchText = tr("the default branch"); - } else { - branchText = tr("branch \"%1\"").arg(m_currentBranch); - } - - if (m_stateUnknown) { - if (m_workFolderPath == "") { - m_workStatus->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("<b>Awaiting merge</b> 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<QToolButton *>()) { - 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); -} - - - -
--- 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 <QMainWindow> -#include <QListWidget> -#include <QFileSystemWatcher> - -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
--- 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 <QMessageBox> -#include <QLabel> -#include <QGridLayout> -#include <QTextEdit> -#include <QDialogButtonBox> -#include <QPushButton> -#include <QApplication> -#include <QStyle> - -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("<qt><h3>%1</h3></qt>").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(); -} -
--- 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 <QString> -#include <QDialog> - -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
--- 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 <QDialogButtonBox> -#include <QToolButton> -#include <QPushButton> -#include <QFont> -#include <QDir> -#include <QFileDialog> -#include <QUrl> - -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<RecentFiles> - (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<QWidget *>(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<SelectableLabel *>(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<RecentFiles> 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(); -} - -
--- 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 <QDialog> -#include <QString> -#include <QAbstractButton> -#include <QMap> -#include <QLabel> -#include <QLineEdit> -#include <QGridLayout> -#include <QHBoxLayout> -#include <QStackedWidget> -#include <QSharedPointer> -#include <QComboBox> - -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<QString, QString> m_texts; - QMap<QString, QString> m_descriptions; - QMap<QString, ArgType> m_argTypes; - QMap<QString, QSharedPointer<RecentFiles> > m_recentFiles; - - QString m_currentChoice; - QMap<QWidget *, QString> 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
--- 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 <QScrollBar> -#include <QWheelEvent> -#include <QTimer> - -#include <cmath> - -#include <iostream> - -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" <<endl; - if (pr.x() != m_pannedRect.x()) emit pannedContentsScrolled(); - m_pannedRect = pr; - emit pannedRectChanged(pr); - } -} - -void -Panned::zoomIn() -{ - QMatrix m = matrix(); - m.scale(1.0 / 1.1, 1.0 / 1.1); - setMatrix(m); -} - -void -Panned::zoomOut() -{ - QMatrix m = matrix(); - m.scale(1.1, 1.1); - setMatrix(m); -} - -void -Panned::slotSetPannedRect(QRectF pr) -{ - centerOn(pr.center()); -// setSceneRect(pr); -// m_pannedRect = pr; -} - -void -Panned::wheelEvent(QWheelEvent *ev) -{ - if (ev->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(); -}
--- 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 <QGraphicsView> - -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 -
--- 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 <QPolygon> -#include <QMouseEvent> -#include <QColor> - -#include <iostream> - -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<QRectF> &)), - this, SLOT(slotSceneChanged(const QList<QRectF> &))); - 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<QRectF> &)), - this, SLOT(slotSceneChanged(const QList<QRectF> &))); - 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<QRectF> &) -{ - 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<QRectF> &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<PannerScene *>(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(); -} -
--- 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 <QGraphicsView> - -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<QRectF> &); - -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<QRectF> &); - virtual void drawItems(QPainter *, int, QGraphicsItem *[], - const QStyleOptionGraphicsItem []); - - bool m_clicked; - QRectF m_clickedRect; - QPoint m_clickedPoint; - - QPixmap m_cache; -}; - -#endif -
--- 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 <QFileInfo> -#include <QSettings> -#include <QRegExp> - -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<QString> 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); - } - } -} - -
--- 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 <QObject> -#include <QString> -#include <QStringList> -#include <deque> - -/** - * 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<QString> m_names; - - void read(); - void write(); - void truncateAndWrite(); -}; - -#endif
--- 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")); - - - - -}
--- 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 <QDialog> - -class RepositoryDialog : public QDialog -{ - Q_OBJECT - -public: - RepositoryDialog(QWidget *parent = 0); - - - -}; - -#endif // REPOSITORYDIALOG_H
--- 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 <iostream> -#include <QApplication> - -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); -}
--- 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 <QLabel> - -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
--- 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 <QGridLayout> -#include <QGroupBox> -#include <QDialogButtonBox> -#include <QSettings> -#include <QDir> -#include <QFileDialog> -#include <QMessageBox> - -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("<qt><b>Restore default settings?</b><br><br>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(); -} - -
--- 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 <QDialog> -#include <QLineEdit> -#include <QLabel> -#include <QPushButton> -#include <QCheckBox> -#include <QComboBox> - -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
--- /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 <QDialogButtonBox> +#include <QLabel> +#include <QTableWidget> +#include <QHeaderView> +#include <QGridLayout> + +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); +} +
--- /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 <QDialog> + +class AnnotateDialog : public QDialog +{ + Q_OBJECT + +public: + AnnotateDialog(QWidget *parent, QString output); +}; + +#endif
--- /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 <QVariant> + +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 = "<tr><td><b>%1</b> </td><td>%2</td></tr>"; + + description = "<qt><table border=0>"; + + QString c = comment().trimmed(); + c = c.replace(QRegExp("^\""), ""); + c = c.replace(QRegExp("\"$"), ""); + c = c.replace("\\\"", "\""); + c = xmlEncode(c); + c = c.replace("\\n", "<br>"); + + 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 += "</table></qt>"; + + return description; +}
--- /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 <QObject> +#include <QString> +#include <QStringList> +#include <QList> +#include <QSharedPointer> + +#include "logparser.h" + +class Changeset; + +typedef QList<Changeset *> 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
--- /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 <QTextDocument> +#include <QPainter> + +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<QPointF> 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<QPointF> 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()); +} +
--- /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 <QGraphicsItem> +#include <QFont> + +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
--- /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 <QPainter> +#include <QGraphicsScene> +#include <QGraphicsSceneMouseEvent> +#include <QMenu> +#include <QAction> +#include <QLabel> +#include <QWidgetAction> +#include <QApplication> +#include <QClipboard> + +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("<qt><b> Revision: </b>%1</qt>") + .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<ChangesetScene *>(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<QAction *>(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(); +}
--- /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 <QGraphicsObject> +#include <QFont> + +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<QAction *, QString> m_parentDiffActions; + QMap<QAction *, QString> m_summaryActions; +}; + +#endif // CHANGESETITEM_H
--- /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<ChangesetItem *>(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<ChangesetItem *>(it); + if (csit && csit->getId() == id) return csit; + } + return 0; +} + +
--- /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 <QGraphicsScene> + +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
--- /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 <QLabel> + +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("<u>%1</u>").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
--- /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; +} + + +
--- /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 <QSet> +#include <QMap> +#include <QColor> +#include <QString> + +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<QString> m_defaultNames; + QMap<QString, QColor> m_colours; + QColor m_lastColour; + + static ColourSet m_instance; +}; + +#endif
--- /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 <QFileInfo> +#include <QProcessEnvironment> +#include <QStringList> +#include <QDir> +#include <QRegExp> + +#include <sys/types.h> + +#ifdef Q_OS_WIN32 +#define _WIN32_WINNT 0x0500 +#define SECURITY_WIN32 +#include <windows.h> +#include <security.h> +#else +#include <errno.h> +#include <pwd.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <signal.h> +#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; +}
--- /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 <QString> + +#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 + +
--- /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 <QMessageBox> +#include <QInputDialog> +#include <QGridLayout> +#include <QLabel> +#include <QTextEdit> +#include <QDialogButtonBox> + +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 = "<qt>"; + else text = "<qt>" + intro + "<p>"; + + text += "<code>"; + foreach (QString file, files) { + text += " " + xmlEncode(file) + "<br>"; + } + text += "</code></qt>"; + + 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 = "<qt>" + introTextWithCount + "</qt>"; + } + 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 = "<qt>" + introTextWithCount + "</qt>"; + } + 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 = "<qt>" + introTextWithCount; + } + text += tr("<p>Please enter your comment:</qt>"); + 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; +}
--- /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 <QDialog> +#include <QWidget> +#include <QString> +#include <QStringList> +#include <QTextEdit> +#include <QPushButton> + +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
--- /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 <QPainter> + +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(); +} + +
--- /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 <QGraphicsItem> + +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
--- /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 <QPainter> +#include <QBrush> +#include <QFont> +#include <QGraphicsSceneMouseEvent> + +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(); +} + +
--- /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 <QGraphicsObject> + +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
--- /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 <QString> +#include <QUrl> +#include <QMutex> +#include <QMutexLocker> +#include <QFile> +#include <QDir> +#include <QCoreApplication> +#include <QDateTime> + +#include <cstdio> + +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() << ">"; +} +
--- /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 <QDebug> +#include <QTextStream> +#include <string> +#include <iostream> + +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 <typename T> +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 <typename T> + inline NoDebug &operator<<(const T &) { return *this; } + + inline NoDebug &operator<<(QTextStreamFunction) { return *this; } +}; + +#define DEBUG NoDebug() + +#endif /* !NDEBUG */ + +#endif /* !_DEBUG_H_ */ +
--- /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 <QMap> + +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<FileStates *>(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)); +} +
--- /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 <QStringList> +#include <QMap> +#include <QString> + +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<Activity> 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<QString, State> m_stateMap; + + void clearBuckets(); + + State charToState(QChar, bool * = 0); + QStringList *stateToBucket(State); +}; + +#endif // FILESTATES_H
--- /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 <QLabel> +#include <QListWidget> +#include <QGridLayout> +#include <QFileInfo> +#include <QApplication> +#include <QDateTime> +#include <QPushButton> +#include <QToolButton> +#include <QDir> +#include <QProcess> +#include <QCheckBox> +#include <QSettings> +#include <QAction> + +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.<br>" + "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.<br>" + "If you intended to delete them, select them and use Remove to tell the version control system about it.<br>" + "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.<br>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.<br>" +// "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,<br>" + "and so will be ignored by the version control system."); + + m_highlightExplanation = tr("Files highlighted <font color=#d40000>in red</font> " + "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("<qt><b>%1</b><br>%2<br>%3</qt>") + .arg(m_simpleLabels[s]) + .arg(m_descriptions[s]) + .arg(m_highlightExplanation); + } else { + return QString("<qt><b>%1</b><br>%2</qt>") + .arg(m_simpleLabels[s]) + .arg(m_descriptions[s]); + } + } else { + return QString("<qt><b>%1</b></qt>") + .arg(m_simpleLabels[s]); + } + settings.endGroup(); +} + +void FileStatusWidget::setNoModificationsLabelText() +{ + QSettings settings; + settings.beginGroup("Presentation"); + if (settings.value("showhelpfultext", true).toBool()) { + m_noModificationsLabel->setText + (tr("<qt>This area will list files in your working folder that you have changed.<br><br>At the moment you have no uncommitted changes.<br><br>To see changes previously made to the repository,<br>switch to the History tab.<br><br>%1</qt>") +#if defined Q_OS_MAC + .arg(tr("To open the working folder in Finder,<br>click on the “Local” folder path shown above.")) +#elif defined Q_OS_WIN32 + .arg(tr("To open the working folder in Windows Explorer,<br>click on the “Local” folder path shown above.")) +#else + .arg(tr("To open the working folder in your system file manager,<br>click the “Local” folder path shown above.")) +#endif + ); + } else { + m_noModificationsLabel->setText + (tr("<qt>You have no uncommitted changes.</qt>")); + } +} + + +void FileStatusWidget::menuActionActivated() +{ + QAction *act = qobject_cast<QAction *>(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<QListWidget *>(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<QListWidgetItem *> 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<QString> 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<QLabel *> ql = p->findChildren<QLabel *>(); + if (!ql.empty()) ql[0]->setText(text); +} +
--- /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 <QWidget> +#include <QList> + +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<FileStates::State, QString> m_simpleLabels; + QMap<FileStates::State, QString> m_descriptions; + QMap<FileStates::State, QListWidget *> m_stateListMap; + QMap<FileStates::Activity, QString> m_actionLabels; + QString m_highlightExplanation; + + QFileInfo *m_dateReference; + QStringList m_selectedFiles; + + bool m_gridlyLayout; + int m_lastGridlyCount; + QList<QWidget *> 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
--- /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 <QSettings> + +#include <iostream> + +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<QString> 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<int> 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; + } +} +
--- /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 <QSet> +#include <QMap> +#include <QPair> + +#include <exception> + +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<QString, Changeset *> IdChangesetMap; + IdChangesetMap m_changesets; + + typedef QMap<QString, ChangesetItem *> IdItemMap; + IdItemMap m_items; + + typedef QSet<int> ColumnSet; + typedef QMap<int, ColumnSet> GridAlloc; + GridAlloc m_alloc; + + typedef QPair<int, int> Range; + typedef QMap<QString, Range> BranchRangeMap; + BranchRangeMap m_branchRanges; + + typedef QMap<QString, int> BranchColumnMap; + BranchColumnMap m_branchHomes; + + typedef QSet<QString> IdSet; + IdSet m_handled; + + typedef QMap<int, QString> 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
--- /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 <QString> +#include <QStringList> + +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
--- /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 <QPushButton> +#include <QListWidget> +#include <QDialog> +#include <QLabel> +#include <QVBoxLayout> +#include <QSettings> +#include <QInputDialog> +#include <QTemporaryFile> +#include <QDir> + +#include <iostream> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#ifndef Q_OS_WIN32 +#include <unistd.h> +#include <termios.h> +#include <fcntl.h> +#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<QWidget *>(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<QWidget *>(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 +}
--- /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 <QProgressBar> +#include <QProcess> +#include <QByteArray> +#include <QRect> +#include <QFile> + +#include <deque> + +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<HgAction> ActionQueue; + ActionQueue m_queue; + HgAction m_currentAction; +}; + +#endif // HGRUNNER_H
--- /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 <QClipboard> +#include <QContextMenuEvent> +#include <QApplication> + +#include <iostream> + +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); +} +
--- /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 <QMenu> +#include <QListWidget> +#include <QGroupBox> +#include <QVBoxLayout> +#include <QCheckBox> +#include <QLabel> +#include <QTabWidget> + +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
--- /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 <iostream> + +#include <QGridLayout> + +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<QString> oldIds; + foreach (Changeset *cs, m_changesets) { + oldIds.insert(cs->id()); + } + + QSet<QString> 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<QGraphicsItem *> items = scene->items(); + foreach (QGraphicsItem *it, items) { + + ChangesetItem *csit = dynamic_cast<ChangesetItem *>(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<ChangesetScene *>(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))); +}
--- /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 <QWidget> +#include <QSet> + +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<QString> 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
--- /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 <QScrollArea> +#include <QApplication> +#include <QDialogButtonBox> +#include <QLabel> +#include <QGridLayout> +#include <QStyle> + +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("<p>%1</p><code>%2</code>") + .arg(tr("The command output was:")) + .arg(xmlEncode(text).replace("\n", "<br>")); + } else { + body = tr("<qt>Your local repository already contains all changes found in the remote repository.</qt>"); + } + scroll = false; + } else { + head = tr("There are %n change(s) ready to pull", "", csets.size()); + foreach (Changeset *cs, csets) { + body += cs->formatHtml() + "<p>"; + 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("<qt><h3>%1</h3></qt>").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); +} + +
--- /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 <QDialog> + +class IncomingDialog : public QDialog +{ + Q_OBJECT + +public: + IncomingDialog(QWidget *parent, QString incoming); +}; + +#endif
--- /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 <QStringList> +#include <QRegExp> + +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; +}
--- /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 <QObject> +#include <QString> +#include <QList> +#include <QMap> + +typedef QMap<QString, QString> LogEntry; +typedef QList<LogEntry> 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
--- /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 <QApplication> +#include <QTranslator> +#include <QDir> + +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(); +}
--- /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 <QStringList> +#include <QDir> +#include <QNetworkInterface> +#include <QHostAddress> +#include <QHostInfo> +#include <QDesktopServices> +#include <QStatusBar> +#include <QMessageBox> +#include <QMenuBar> +#include <QApplication> +#include <QToolBar> +#include <QToolButton> +#include <QSettings> +#include <QInputDialog> +#include <QWidgetAction> +#include <QRegExp> +#include <QShortcut> +#include <QUrl> +#include <QTimer> + +#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("<qt><h2>EasyMercurial v%1</h2>" +#ifdef Q_OS_MAC + "<font size=-1>" +#endif + "<p>EasyMercurial is a simple user interface for the " + "Mercurial</a> version control system.</p>" + "<h4>Credits and Copyright</h4>" + "<p>Development carried out by Chris Cannam for " + "SoundSoftware.ac.uk at the Centre for Digital Music, " + "Queen Mary, University of London.</p>" + "<p>EasyMercurial is based on HgExplorer by " + "Jari Korhonen, with thanks.</p>" + "<p style=\"margin-left: 2em;\">" + "Copyright © 2011 Queen Mary, University of London.<br>" + "Copyright © 2010 Jari Korhonen.<br>" + "Copyright © 2011 Chris Cannam." + "</p>" + "<p style=\"margin-left: 2em;\">" + "This program requires Mercurial, by Matt Mackall and others.<br>" + "This program uses Qt by Nokia.<br>" + "This program uses Nuvola icons by David Vignoni.<br>" + "This program may use KDiff3 by Joachim Eibl.<br>" + "This program may use PyQt by River Bank Computing.<br>" + "Packaging for Mercurial and other dependencies on Windows is derived from TortoiseHg by Steve Borho and others." + "</p>" + "<h4>License</h4>" + "<p>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.</p>" +#ifdef Q_OS_MAC + "</font>" +#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("<p><b>Note:</b> 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("<h3>%1</h3><p>%2%3").arg(cf) + .arg(tr("You are about to commit the following files to %1:").arg(branchText)) + .arg(subsetNote), + tr("<h3>%1</h3><p>%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("<p><b>Note:</b> 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("<qt><b>Sorry, unable to revert these files</b><br><br>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.<br><br>This is a limitation of EasyMercurial. Consider reverting all files, or using hg revert with a specific revision at the command-line instead.</qt>")); + return; + } + } + } else { + params << "revert" << "--" << files; + } + + if (ConfirmCommentDialog::confirmDangerousFilesAction + (this, + rf, + tr("<h3>%1</h3><p>%2%3").arg(rf) + .arg(tr("You are about to <b>revert</b> the following files to their previous committed state.<br><br>This will <b>throw away any changes</b> that you have made to these files but have not committed.")) + .arg(subsetNote), + tr("<h3>%1</h3><p>%2%3").arg(rf) + .arg(tr("You are about to <b>revert</b> %n file(s).<br><br>This will <b>throw away any changes</b> 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 <code>%1</code> 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 <code>%1</code> 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("<qt><h3>Pull from remote repository?</h3></qt>"), + tr("<qt><p>You are about to pull changes from the remote repository at <code>%1</code>.</p></qt>").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("<p><b>Note:</b> 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("<qt><h3>Push to remote repository?</h3></qt>"), + tr("<qt><p>You are about to push your commits to the remote repository at <code>%1</code>.</p>%2</qt>").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<QNetworkInterface> 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<iface.addressEntries().count(); j++) { + QHostAddress tmp = iface.addressEntries().at(j).ip(); + if (QAbstractSocket::IPv4Protocol == tmp.protocol()) { + ret.push_back(tmp.toString()); + } + } + } + } + return ret; +} + +void MainWindow::clearState() +{ + DEBUG << "MainWindow::clearState" << endl; + foreach (Changeset *cs, m_currentParents) delete cs; + m_currentParents.clear(); + foreach (Changeset *cs, m_currentHeads) delete cs; + m_currentHeads.clear(); + m_currentBranch = ""; + m_lastStatOutput = ""; + m_lastRevertedFiles.clear(); + m_mergeTargetRevision = ""; + m_mergeCommitComment = ""; + m_stateUnknown = true; + m_needNewLog = true; + if (m_fsWatcher) { + delete m_fsWatcherGeneralTimer; + m_fsWatcherGeneralTimer = 0; + delete m_fsWatcherRestoreTimer; + m_fsWatcherRestoreTimer = 0; + delete m_fsWatcher; + m_fsWatcher = 0; + } +} + +void MainWindow::hgServe() +{ + QStringList params; + QString msg; + + QStringList addrs = listAllUpIpV4Addresses(); + + if (addrs.empty()) { + QMessageBox::critical + (this, tr("Serve"), tr("Failed to identify an active IPv4 address")); + return; + } + + //!!! should find available port as well + + QTextStream ts(&msg); + ts << QString("<qt><p>%1</p>") + .arg(tr("Running temporary server at %n address(es):", "", addrs.size())); + foreach (QString addr, addrs) { + ts << QString("<pre> http://%1:8000</pre>").arg(xmlEncode(addr)); + } + ts << tr("<p>Press Close to stop the server and return.</p>"); + 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("<qt><big>What would you like to open?</big></qt>"), + this); + + d->addChoice("remote", + tr("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"), + tr("Open a remote Mercurial repository, by cloning from its URL into a local folder."), + MultiChoiceDialog::UrlToDirectoryArg); + + d->addChoice("local", + tr("<qt><center><img src=\":images/hglogo-64.png\"><br>Local repository</center></qt>"), + tr("Open an existing local Mercurial repository."), + MultiChoiceDialog::DirectoryArg); + + d->addChoice("init", + tr("<qt><center><img src=\":images/hdd_unmount-64.png\"><br>File folder</center></qt>"), + 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<QAction *>(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("<qt><big>Set the remote location</big></qt>"), + this); + + QString explanation; + if (initial) { + explanation = tr("Provide a URL to use for push and pull actions from the current local repository.<br>This will be the default for subsequent pushes and pulls.<br>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("<qt><center><img src=\":images/browser-64.png\"><br>Remote repository</center></qt>"), + 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("<qt><b>Folder required</b><br><br>You asked to open \"%1\".<br>This is a file; to open a repository, you need to choose a folder.</qt>").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::askAboutUnknownFolder(QString arg) +{ + bool result = (QMessageBox::question + (this, tr("Path does not exist"), + tr("<qt><b>Path does not exist: create it?</b><br><br>You asked to open a remote repository by cloning it to \"%1\". This folder does not exist, and neither does its parent.<br><br>Would you like to create the parent folder as well?</qt>").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("<qt><b>Failed to create folder</b><br><br>Sorry, the path for the parent folder \"%1\" could not be created.</qt>").arg(dir.absolutePath())); + return false; + } + return true; + } + return false; +} + +bool MainWindow::complainAboutUnknownFolder(QString arg) +{ + QMessageBox::critical + (this, tr("Folder does not exist"), + tr("<qt><b>Folder does not exist</b><br><br>You asked to open \"%1\".<br>This folder does not exist, and it cannot be created because its parent does not exist either.</qt>").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::complainAboutInitInRepo(QString arg) +{ + QMessageBox::critical + (this, tr("Path is in existing repository"), + tr("<qt><b>Path is in an existing repository</b><br><br>You asked to initialise a repository at \"%1\".<br>This path is already inside an existing repository.</qt>").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::complainAboutInitFile(QString arg) +{ + QMessageBox::critical + (this, tr("Path is a file"), + tr("<qt><b>Path is a file</b><br><br>You asked to initialise a repository at \"%1\".<br>This is an existing file; it is only possible to initialise in folders.</qt>").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::complainAboutCloneToExisting(QString arg) +{ + QMessageBox::critical + (this, tr("Path is in existing repository"), + tr("<qt><b>Local path is in an existing repository</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is already inside an existing repository.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg))); + return false; +} + +bool MainWindow::complainAboutCloneToFile(QString arg) +{ + QMessageBox::critical + (this, tr("Path is a file"), + tr("<qt><b>Local path is a file</b><br><br>You asked to open a remote repository by cloning it to the local path \"%1\".<br>This path is an existing file.<br>Please provide a new folder name for the local repository.</qt>").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("<qt><b>Local folder already exists</b><br><br>You asked to open a remote repository by cloning it to \"%1\", but this folder already exists and so cannot be cloned to.<br><br>Would you like to create the new folder \"%2\" instead?</qt>") + .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("<qt><b>Local folder already exists</b><br><br>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.<br>Please provide a different folder name for the local repository.</qt>").arg(xmlEncode(arg))); + return ""; +} + +bool MainWindow::askToOpenParentRepo(QString arg, QString parent) +{ + return (QMessageBox::question + (this, tr("Path is inside a repository"), + tr("<qt><b>Open the repository that contains this path?</b><br><br>You asked to open \"%1\".<br>This is not the root folder of a repository.<br>But it is inside a repository, whose root is at \"%2\". <br><br>Would you like to open that repository instead?</qt>") + .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("<qt><b>Initialise a repository here?</b><br><br>You asked to open \"%1\".<br>This folder is not a Mercurial working copy.<br><br>Would you like to initialise a repository here?</qt>") + .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("<qt><b>Initialise a new repository?</b><br><br>You asked to open \"%1\".<br>This folder does not yet exist.<br><br>Would you like to create the folder and initialise a new empty repository in it?</qt>") + .arg(xmlEncode(arg)), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Ok) + == QMessageBox::Ok); +} + +bool MainWindow::askToOpenInsteadOfInit(QString arg) +{ + return (QMessageBox::question + (this, tr("Repository exists"), + tr("<qt><b>Open existing repository?</b><br><br>You asked to initialise a new repository at \"%1\".<br>This folder already contains a repository. Would you like to open it?</qt>") + .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<QString> alreadyWatched; + QStringList dl(m_fsWatcher->directories()); + foreach (QString d, dl) alreadyWatched.insert(d); + + std::deque<QString> 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("<qt><h3>%1</h3></qt>").arg(head); +} + +QString MainWindow::format3(QString head, QString intro, QString code) +{ + code = xmlEncode(code).replace("\n", "<br>") +#ifndef Q_OS_WIN32 + // The hard hyphen comes out funny on Windows + .replace("-", "‑") +#endif + .replace(" ", " "); + if (intro == "") { + return QString("<qt><h3>%1</h3><p><code>%2</code></p>") + .arg(head).arg(code); + } else if (code == "") { + return QString("<qt><h3>%1</h3><p>%2</p>") + .arg(head).arg(intro); + } else { + return QString("<qt><h3>%1</h3><p>%2</p><p><code>%3</code></p>") + .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("<qt>Successfully pushed to the remote repository at <code>%1</code>.</qt>").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<p>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.<br><br>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.<br><br>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.<br><br>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.<br><br>Or you may lack the necessary permissions on the remote repository.<br><br>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.<br>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.<br>This may indicate an installation problem.<br><br>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.<p>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.<br><br>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 <code>%1</code>.").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("<qt><h3>Update successful</h3><p>%1</p>").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("<qt><h3>Merge resolved</h3><p>Merge resolved successfully.<br>Remember to test and commit the result before making any further changes.</p>")); + 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 "<<endl; + foreach (Changeset *h, m_currentHeads) { + DEBUG << "head id = " << h->id() << endl; + } + } + m_justMerged = false; + } else if (m_currentParents.size() == 0) { + if (m_currentHeads.size() == 0) { + // No heads -> empty repo + emptyRepo = true; + } else { + // Heads, but no parents -> no working copy, e.g. we have + // just converted this repo but haven't updated in it yet. + // Uncommon but confusing; probably merits a special case + noWorkingCopy = true; + canUpdate = true; + } + m_justMerged = false; + } else { + haveMerge = true; + m_justMerged = true; + } + + m_hgIncomingAct->setEnabled(m_remoteRepoActionsEnabled); + m_hgPullAct->setEnabled(m_remoteRepoActionsEnabled); + // permit push even if no remote yet; we'll ask for one + m_hgPushAct->setEnabled(m_localRepoActionsEnabled && !emptyRepo); + + m_hgMergeAct->setEnabled(m_localRepoActionsEnabled && + (canMerge || m_hgTabs->canResolve())); + m_hgUpdateAct->setEnabled(m_localRepoActionsEnabled && + (canUpdate && !m_hgTabs->haveChangesToCommit())); + + // Set the state field on the file status widget + + QString branchText; + if (m_currentBranch == "" || m_currentBranch == "default") { + branchText = tr("the default branch"); + } else { + branchText = tr("branch \"%1\"").arg(m_currentBranch); + } + + if (m_stateUnknown) { + if (m_workFolderPath == "") { + m_workStatus->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("<b>Awaiting merge</b> 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<QToolButton *>()) { + 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); +} + + + +
--- /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 <QMainWindow> +#include <QListWidget> +#include <QFileSystemWatcher> + +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
--- /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 <QMessageBox> +#include <QLabel> +#include <QGridLayout> +#include <QTextEdit> +#include <QDialogButtonBox> +#include <QPushButton> +#include <QApplication> +#include <QStyle> + +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("<qt><h3>%1</h3></qt>").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(); +} +
--- /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 <QString> +#include <QDialog> + +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
--- /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 <QDialogButtonBox> +#include <QToolButton> +#include <QPushButton> +#include <QFont> +#include <QDir> +#include <QFileDialog> +#include <QUrl> + +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<RecentFiles> + (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<QWidget *>(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<SelectableLabel *>(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<RecentFiles> 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(); +} + +
--- /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 <QDialog> +#include <QString> +#include <QAbstractButton> +#include <QMap> +#include <QLabel> +#include <QLineEdit> +#include <QGridLayout> +#include <QHBoxLayout> +#include <QStackedWidget> +#include <QSharedPointer> +#include <QComboBox> + +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<QString, QString> m_texts; + QMap<QString, QString> m_descriptions; + QMap<QString, ArgType> m_argTypes; + QMap<QString, QSharedPointer<RecentFiles> > m_recentFiles; + + QString m_currentChoice; + QMap<QWidget *, QString> 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
--- /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 <QScrollBar> +#include <QWheelEvent> +#include <QTimer> + +#include <cmath> + +#include <iostream> + +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" <<endl; + if (pr.x() != m_pannedRect.x()) emit pannedContentsScrolled(); + m_pannedRect = pr; + emit pannedRectChanged(pr); + } +} + +void +Panned::zoomIn() +{ + QMatrix m = matrix(); + m.scale(1.0 / 1.1, 1.0 / 1.1); + setMatrix(m); +} + +void +Panned::zoomOut() +{ + QMatrix m = matrix(); + m.scale(1.1, 1.1); + setMatrix(m); +} + +void +Panned::slotSetPannedRect(QRectF pr) +{ + centerOn(pr.center()); +// setSceneRect(pr); +// m_pannedRect = pr; +} + +void +Panned::wheelEvent(QWheelEvent *ev) +{ + if (ev->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(); +}
--- /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 <QGraphicsView> + +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 +
--- /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 <QPolygon> +#include <QMouseEvent> +#include <QColor> + +#include <iostream> + +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<QRectF> &)), + this, SLOT(slotSceneChanged(const QList<QRectF> &))); + 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<QRectF> &)), + this, SLOT(slotSceneChanged(const QList<QRectF> &))); + 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<QRectF> &) +{ + 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<QRectF> &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<PannerScene *>(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(); +} +
--- /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 <QGraphicsView> + +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<QRectF> &); + +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<QRectF> &); + virtual void drawItems(QPainter *, int, QGraphicsItem *[], + const QStyleOptionGraphicsItem []); + + bool m_clicked; + QRectF m_clickedRect; + QPoint m_clickedPoint; + + QPixmap m_cache; +}; + +#endif +
--- /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 <QFileInfo> +#include <QSettings> +#include <QRegExp> + +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<QString> 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); + } + } +} + +
--- /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 <QObject> +#include <QString> +#include <QStringList> +#include <deque> + +/** + * 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<QString> m_names; + + void read(); + void write(); + void truncateAndWrite(); +}; + +#endif
--- /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")); + + + + +}
--- /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 <QDialog> + +class RepositoryDialog : public QDialog +{ + Q_OBJECT + +public: + RepositoryDialog(QWidget *parent = 0); + + + +}; + +#endif // REPOSITORYDIALOG_H
--- /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 <iostream> +#include <QApplication> + +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); +}
--- /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 <QLabel> + +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
--- /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 <QGridLayout> +#include <QGroupBox> +#include <QDialogButtonBox> +#include <QSettings> +#include <QDir> +#include <QFileDialog> +#include <QMessageBox> + +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("<qt><b>Restore default settings?</b><br><br>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(); +} + +
--- /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 <QDialog> +#include <QLineEdit> +#include <QLabel> +#include <QPushButton> +#include <QCheckBox> +#include <QComboBox> + +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
--- /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 <QGridLayout> +#include <QDialogButtonBox> +#include <QSettings> + +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("<qt><big>Welcome to EasyMercurial!</qt></big><br>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("<br>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); +} + +
--- /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 <QDialog> +#include <QLineEdit> +#include <QLabel> +#include <QPushButton> + +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
--- /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 <QFontMetrics> +#include <QApplication> + +#include <iostream> + +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; +} +
--- /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 <QString> +#include <QStringList> + +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 +
--- /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 <QPainter> +#include <QGraphicsScene> +#include <QGraphicsSceneMouseEvent> +#include <QMenu> +#include <QAction> +#include <QLabel> +#include <QWidgetAction> + +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("<qt><b> Uncommitted changes</b></qt>")); + 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; +}
--- /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 <QGraphicsObject> +#include <QFont> + +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
--- /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"
--- /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 <QGridLayout> +#include <QSpacerItem> +#include <QLabel> +#include <QProcess> +#include <QDir> + +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 + } +}
--- /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 <QWidget> + +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
--- 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 <QGridLayout> -#include <QDialogButtonBox> -#include <QSettings> - -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("<qt><big>Welcome to EasyMercurial!</qt></big><br>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("<br>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); -} - -
--- 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 <QDialog> -#include <QLineEdit> -#include <QLabel> -#include <QPushButton> - -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
--- 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 <QFontMetrics> -#include <QApplication> - -#include <iostream> - -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; -} -
--- 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 <QString> -#include <QStringList> - -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 -
--- 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 <QPainter> -#include <QGraphicsScene> -#include <QGraphicsSceneMouseEvent> -#include <QMenu> -#include <QAction> -#include <QLabel> -#include <QWidgetAction> - -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("<qt><b> Uncommitted changes</b></qt>")); - 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; -}
--- 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 <QGraphicsObject> -#include <QFont> - -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
--- 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"
--- 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 <QGridLayout> -#include <QSpacerItem> -#include <QLabel> -#include <QProcess> -#include <QDir> - -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 - } -}
--- 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 <QWidget> - -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