Chris@57: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@57: Chris@57: /* Chris@57: EasyMercurial Chris@57: Chris@57: Based on HgExplorer by Jari Korhonen Chris@57: Copyright (c) 2010 Jari Korhonen Chris@244: Copyright (c) 2011 Chris Cannam Chris@244: Copyright (c) 2011 Queen Mary, University of London Chris@57: Chris@57: This program is free software; you can redistribute it and/or Chris@57: modify it under the terms of the GNU General Public License as Chris@57: published by the Free Software Foundation; either version 2 of the Chris@57: License, or (at your option) any later version. See the file Chris@57: COPYING included with this distribution for more information. Chris@57: */ Chris@57: Chris@43: #include "changesetitem.h" Chris@281: #include "changesetscene.h" Chris@117: #include "changesetdetailitem.h" Chris@43: #include "changeset.h" Chris@53: #include "textabbrev.h" Chris@53: #include "colourset.h" Chris@117: #include "debug.h" Chris@43: Chris@43: #include Chris@117: #include Chris@132: #include Chris@140: #include Chris@140: #include Chris@140: #include Chris@140: #include Chris@153: #include Chris@153: #include Chris@43: Chris@53: ChangesetItem::ChangesetItem(Changeset *cs) : Chris@117: m_changeset(cs), m_detail(0), Chris@133: m_showBranch(false), m_column(0), m_row(0), m_wide(false), Chris@133: m_current(false), m_new(false) Chris@53: { Chris@53: m_font = QFont(); Chris@53: m_font.setPixelSize(11); Chris@53: m_font.setBold(false); Chris@53: m_font.setItalic(false); Chris@168: setCursor(Qt::ArrowCursor); Chris@53: } Chris@53: Chris@141: QString Chris@141: ChangesetItem::getId() Chris@141: { Chris@141: return m_changeset->id(); Chris@141: } Chris@141: Chris@43: QRectF Chris@43: ChangesetItem::boundingRect() const Chris@43: { Chris@55: int w = 100; Chris@55: if (m_wide) w = 180; Chris@250: return QRectF(-((w-50)/2 - 1), -30, w - 3, 90); Chris@43: } Chris@43: Chris@43: void Chris@119: ChangesetItem::showDetail() Chris@117: { Chris@119: if (m_detail) return; Chris@117: m_detail = new ChangesetDetailItem(m_changeset); Chris@117: m_detail->setZValue(zValue() + 1); Chris@117: scene()->addItem(m_detail); Chris@117: int w = 100; Chris@117: if (m_wide) w = 180; Chris@124: int h = 80; Chris@124: // m_detail->moveBy(x() - (m_detail->boundingRect().width() - 50) / 2, Chris@124: // y() + 60); Chris@124: m_detail->moveBy(x() + (w + 50) / 2 + 10 + 0.5, Chris@124: y() - (m_detail->boundingRect().height() - h) / 2 + 0.5); Chris@119: emit detailShown(); Chris@119: } Chris@119: Chris@119: void Chris@119: ChangesetItem::hideDetail() Chris@119: { Chris@124: if (!m_detail) return; Chris@124: scene()->removeItem(m_detail); Chris@119: delete m_detail; Chris@119: m_detail = 0; Chris@119: emit detailHidden(); Chris@119: } Chris@119: Chris@119: void Chris@119: ChangesetItem::mousePressEvent(QGraphicsSceneMouseEvent *e) Chris@119: { Chris@119: DEBUG << "ChangesetItem::mousePressEvent" << endl; Chris@132: if (e->button() == Qt::LeftButton) { Chris@132: if (m_detail) { Chris@132: hideDetail(); Chris@132: } else { Chris@132: showDetail(); Chris@132: } Chris@140: } else if (e->button() == Qt::RightButton) { Chris@141: if (m_detail) { Chris@141: hideDetail(); Chris@141: } Chris@140: activateMenu(); Chris@119: } Chris@117: } Chris@117: Chris@117: void Chris@140: ChangesetItem::activateMenu() Chris@140: { Chris@153: m_parentDiffActions.clear(); Chris@288: m_summaryActions.clear(); Chris@153: Chris@140: QMenu *menu = new QMenu; Chris@165: QLabel *label = new QLabel(tr(" Revision: %1") Chris@165: .arg(Changeset::hashOf(m_changeset->id()))); Chris@141: QWidgetAction *wa = new QWidgetAction(menu); Chris@140: wa->setDefaultWidget(label); Chris@140: menu->addAction(wa); Chris@140: menu->addSeparator(); Chris@141: Chris@153: QAction *copyId = menu->addAction(tr("Copy identifier to clipboard")); Chris@153: connect(copyId, SIGNAL(triggered()), this, SLOT(copyIdActivated())); Chris@141: Chris@281: QStringList parents = m_changeset->parents(); Chris@153: Chris@288: QString leftId, rightId; Chris@288: bool havePositions = false; Chris@288: Chris@281: if (parents.size() > 1) { Chris@281: ChangesetScene *cs = dynamic_cast(scene()); Chris@281: if (cs && parents.size() == 2) { Chris@281: ChangesetItem *i0 = cs->getItemById(parents[0]); Chris@281: ChangesetItem *i1 = cs->getItemById(parents[1]); Chris@281: if (i0 && i1) { Chris@281: if (i0->x() < i1->x()) { Chris@281: leftId = parents[0]; Chris@281: rightId = parents[1]; Chris@281: } else { Chris@281: leftId = parents[1]; Chris@281: rightId = parents[0]; Chris@281: } Chris@281: havePositions = true; Chris@281: } Chris@281: } Chris@288: } Chris@281: Chris@288: if (parents.size() > 1) { Chris@288: if (havePositions) { Chris@288: QAction *stat = menu->addAction(tr("Summarise changes from left parent")); Chris@288: connect(stat, SIGNAL(triggered()), this, SLOT(showSummaryToParentActivated())); Chris@288: m_summaryActions[stat] = leftId; Chris@288: Chris@288: stat = menu->addAction(tr("Summarise changes from right parent")); Chris@288: connect(stat, SIGNAL(triggered()), this, SLOT(showSummaryToParentActivated())); Chris@288: m_summaryActions[stat] = rightId; Chris@288: } else { Chris@288: Chris@288: foreach (QString parentId, parents) { Chris@288: QString text = tr("Summarise changes from parent %1").arg(Changeset::hashOf(parentId)); Chris@288: QAction *stat = menu->addAction(text); Chris@288: connect(stat, SIGNAL(triggered()), this, SLOT(showSummaryToParentActivated())); Chris@288: m_summaryActions[stat] = parentId; Chris@288: } Chris@288: Chris@288: } Chris@288: } else { Chris@288: QAction *stat = menu->addAction(tr("Summarise changes")); Chris@288: connect(stat, SIGNAL(triggered()), this, SLOT(showSummaryToParentActivated())); Chris@288: } Chris@288: Chris@288: menu->addSeparator(); Chris@288: Chris@288: if (parents.size() > 1) { Chris@281: if (havePositions) { Chris@281: Chris@288: QAction *diff = menu->addAction(tr("Diff to left parent")); Chris@288: connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); Chris@288: m_parentDiffActions[diff] = leftId; Chris@281: Chris@288: diff = menu->addAction(tr("Diff to right parent")); Chris@288: connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); Chris@288: m_parentDiffActions[diff] = rightId; Chris@281: Chris@281: } else { Chris@281: Chris@281: foreach (QString parentId, parents) { Chris@288: QString text = tr("Diff to parent %1").arg(Changeset::hashOf(parentId)); Chris@288: QAction *diff = menu->addAction(text); Chris@288: connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); Chris@288: m_parentDiffActions[diff] = parentId; Chris@281: } Chris@153: } Chris@153: Chris@153: } else { Chris@153: Chris@288: QAction *diff = menu->addAction(tr("Diff to parent")); Chris@288: connect(diff, SIGNAL(triggered()), this, SLOT(diffToParentActivated())); Chris@153: } Chris@153: Chris@153: QAction *diffCurrent = menu->addAction(tr("Diff to current working folder")); Chris@141: connect(diffCurrent, SIGNAL(triggered()), this, SLOT(diffToCurrentActivated())); Chris@141: Chris@140: menu->addSeparator(); Chris@141: Chris@153: QAction *update = menu->addAction(tr("Update to this revision")); Chris@153: connect(update, SIGNAL(triggered()), this, SLOT(updateActivated())); Chris@153: Chris@140: QAction *merge = menu->addAction(tr("Merge from here to current")); Chris@141: connect(merge, SIGNAL(triggered()), this, SLOT(mergeActivated())); Chris@153: Chris@153: menu->addSeparator(); Chris@153: Chris@153: QAction *tag = menu->addAction(tr("Add tag...")); Chris@141: connect(tag, SIGNAL(triggered()), this, SLOT(tagActivated())); Chris@141: Chris@148: menu->exec(QCursor::pos()); Chris@148: Chris@141: ungrabMouse(); Chris@140: } Chris@140: Chris@153: void Chris@153: ChangesetItem::copyIdActivated() Chris@153: { Chris@153: QClipboard *clipboard = QApplication::clipboard(); Chris@153: clipboard->setText(Changeset::hashOf(m_changeset->id())); Chris@153: } Chris@153: Chris@153: void ChangesetItem::diffToParentActivated() Chris@153: { Chris@153: QAction *a = qobject_cast(sender()); Chris@153: QString parentId; Chris@153: if (m_parentDiffActions.contains(a)) { Chris@153: parentId = m_parentDiffActions[a]; Chris@153: DEBUG << "ChangesetItem::diffToParentActivated: specific parent " Chris@153: << parentId << " selected" << endl; Chris@153: } else { Chris@153: parentId = m_changeset->parents()[0]; Chris@153: DEBUG << "ChangesetItem::diffToParentActivated: " Chris@153: << "no specific parent selected, using first parent " Chris@153: << parentId << endl; Chris@153: } Chris@153: Chris@153: emit diffToParent(getId(), parentId); Chris@153: } Chris@153: Chris@288: void ChangesetItem::showSummaryToParentActivated() Chris@288: { Chris@288: QAction *a = qobject_cast(sender()); Chris@288: QString parentId; Chris@288: if (m_summaryActions.contains(a)) { Chris@288: parentId = m_summaryActions[a]; Chris@288: DEBUG << "ChangesetItem::showSummaryToParentActivated: specific parent " Chris@288: << parentId << " selected" << endl; Chris@288: } else { Chris@288: parentId = m_changeset->parents()[0]; Chris@288: DEBUG << "ChangesetItem::showSummaryToParentActivated: " Chris@288: << "no specific parent selected, using first parent " Chris@288: << parentId << endl; Chris@288: } Chris@288: Chris@288: emit showSummaryToParent(getId(), parentId); Chris@288: } Chris@288: Chris@141: void ChangesetItem::updateActivated() { emit updateTo(getId()); } Chris@141: void ChangesetItem::diffToCurrentActivated() { emit diffToCurrent(getId()); } Chris@141: void ChangesetItem::mergeActivated() { emit mergeFrom(getId()); } Chris@141: void ChangesetItem::tagActivated() { emit tag(getId()); } Chris@141: Chris@140: void Chris@288: ChangesetItem::paint(QPainter *paint, const QStyleOptionGraphicsItem *, QWidget *) Chris@43: { Chris@53: paint->save(); Chris@53: Chris@53: ColourSet *colourSet = ColourSet::instance(); Chris@53: QColor branchColour = colourSet->getColourFor(m_changeset->branch()); Chris@128: QColor userColour = colourSet->getColourFor(m_changeset->author()); Chris@53: Chris@53: QFont f(m_font); Chris@53: Chris@54: QTransform t = paint->worldTransform(); Chris@53: float scale = std::min(t.m11(), t.m22()); Chris@53: if (scale > 1.0) { Chris@53: int ps = int((f.pixelSize() / scale) + 0.5); Chris@53: if (ps < 8) ps = 8; Chris@53: f.setPixelSize(ps); Chris@53: } Chris@54: Chris@250: bool showText = (scale >= 0.2); Chris@250: bool showProperLines = (scale >= 0.1); Chris@250: Chris@250: if (!showProperLines) { Chris@54: paint->setPen(QPen(branchColour, 0)); Chris@54: } else { Chris@54: paint->setPen(QPen(branchColour, 2)); Chris@54: } Chris@53: Chris@53: paint->setFont(f); Chris@53: QFontMetrics fm(f); Chris@53: int fh = fm.height(); Chris@55: Chris@55: int width = 100; Chris@55: if (m_wide) width = 180; Chris@55: int x0 = -((width - 50) / 2 - 1); Chris@55: Chris@250: int textwid = width - 7; Chris@250: Chris@250: QString comment; Chris@250: QStringList lines; Chris@250: int lineCount = 3; Chris@250: Chris@250: if (showText) { Chris@250: Chris@250: comment = m_changeset->comment().trimmed(); Chris@250: comment = comment.replace("\\n", " "); Chris@250: comment = comment.replace(QRegExp("^\"\\s*\\**\\s*"), ""); Chris@250: comment = comment.replace(QRegExp("\"$"), ""); Chris@250: comment = comment.replace("\\\"", "\""); Chris@250: Chris@250: comment = TextAbbrev::abbreviate(comment, fm, textwid, Chris@250: TextAbbrev::ElideEnd, "...", 3); Chris@250: // abbreviate() changes this (ouch!), restore it Chris@250: textwid = width - 5; Chris@250: Chris@250: lines = comment.split('\n'); Chris@250: lineCount = lines.size(); Chris@250: Chris@250: if (lineCount < 2) lineCount = 2; Chris@250: } Chris@250: Chris@250: int height = (lineCount + 1) * fh + 2; Chris@56: QRectF r(x0, 0, width - 3, height); Chris@250: Chris@250: if (showProperLines) { Chris@250: Chris@250: paint->setBrush(Qt::white); Chris@250: Chris@250: if (m_current) { Chris@250: paint->drawRect(QRectF(x0 - 4, -4, width + 5, height + 8)); Chris@250: } Chris@250: Chris@250: if (m_new) { Chris@250: paint->save(); Chris@250: paint->setPen(Qt::yellow); Chris@250: paint->setBrush(Qt::NoBrush); Chris@250: paint->drawRect(QRectF(x0 - 2, -2, width + 1, height + 4)); Chris@250: paint->restore(); Chris@250: } Chris@250: } Chris@250: Chris@53: paint->drawRect(r); Chris@53: Chris@250: if (!showText) { Chris@53: paint->restore(); Chris@53: return; Chris@53: } Chris@53: Chris@55: paint->fillRect(QRectF(x0 + 0.5, 0.5, width - 4, fh - 0.5), Chris@55: QBrush(userColour)); Chris@53: Chris@53: paint->setPen(QPen(Qt::white)); Chris@53: Chris@250: QString person = TextAbbrev::abbreviate(m_changeset->authorName(), Chris@250: fm, textwid); Chris@55: paint->drawText(x0 + 3, fm.ascent(), person); Chris@53: Chris@53: paint->setPen(QPen(Qt::black)); Chris@53: Chris@147: QStringList tags = m_changeset->tags(); Chris@147: if (!tags.empty()) { Chris@147: QStringList nonTipTags; Chris@147: foreach (QString t, tags) { Chris@147: // I'm not convinced that showing the tip tag really Chris@147: // works; I think perhaps it confuses as much as it Chris@147: // illuminates. But also, our current implementation Chris@147: // doesn't interact well with it because it moves -- it's Chris@147: // the one thing that can actually (in normal use) change Chris@147: // inside an existing changeset record even during an Chris@147: // incremental update Chris@147: if (t != "tip") nonTipTags.push_back(t); Chris@147: } Chris@147: if (!nonTipTags.empty()) { Chris@147: QString tagText = nonTipTags.join(" ").trimmed(); Chris@147: int tw = fm.width(tagText); Chris@147: paint->fillRect(QRectF(x0 + width - 8 - tw, 1, tw + 4, fh - 1), Chris@147: QBrush(Qt::yellow)); Chris@147: paint->drawText(x0 + width - 6 - tw, fm.ascent(), tagText); Chris@147: } Chris@128: } Chris@128: Chris@74: if (m_showBranch) { Chris@56: // write branch name Chris@153: paint->save(); Chris@56: f.setBold(true); Chris@56: paint->setFont(f); Chris@153: paint->setPen(QPen(branchColour)); Chris@56: QString branch = m_changeset->branch(); Chris@74: if (branch == "") branch = "default"; Chris@56: int wid = width - 3; Chris@56: branch = TextAbbrev::abbreviate(branch, QFontMetrics(f), wid); Chris@56: paint->drawText(x0, -fh + fm.ascent() - 4, branch); Chris@56: f.setBold(false); Chris@153: paint->restore(); Chris@56: } Chris@56: Chris@53: paint->setFont(f); Chris@53: Chris@53: for (int i = 0; i < lines.size(); ++i) { Chris@55: paint->drawText(x0 + 3, i * fh + fh + fm.ascent(), lines[i].trimmed()); Chris@53: } Chris@53: Chris@53: paint->restore(); Chris@43: }