Commit f1b52256 authored by Friedemann Kleint's avatar Friedemann Kleint

Gerrit: Add 'Details' text to dialog.

- Add a splitter and a text browser containing detailed text
  for the change using HTML links for email and URL, allowing
  for removal of 'Open', 'Copy URL' buttons.
- Remove large tooltip from list, rename methods to toHtml()
  and use them for the details text.
- Parse out reviewer email.
- Display approval level with sign.

Change-Id: I2d71a80bbe19643e5bfa9deeaaeb3650976f00b4
Reviewed-by: Orgad Shaneh's avatarOrgad Shaneh <orgads@gmail.com>
Reviewed-by: default avatarTobias Hunger <tobias.hunger@nokia.com>
parent 6e224d50
......@@ -38,13 +38,17 @@
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QSplitter>
#include <QSpacerItem>
#include <QLabel>
#include <QTextBrowser>
#include <QTreeView>
#include <QHeaderView>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QDesktopServices>
#include <QSortFilterProxyModel>
#include <QGroupBox>
#include <QUrl>
#include <QClipboard>
#include <QApplication>
......@@ -53,6 +57,8 @@
namespace Gerrit {
namespace Internal {
enum { layoutSpacing = 5 };
GerritDialog::GerritDialog(const QSharedPointer<GerritParameters> &p,
QWidget *parent)
: QDialog(parent)
......@@ -60,21 +66,26 @@ GerritDialog::GerritDialog(const QSharedPointer<GerritParameters> &p,
, m_filterModel(new QSortFilterProxyModel(this))
, m_model(new GerritModel(p, this))
, m_treeView(new QTreeView)
, m_detailsBrowser(new QTextBrowser)
, m_filterLineEdit(new Utils::FilterLineEdit)
, m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Close))
{
setWindowTitle(tr("Gerrit %1@%2").arg(p->user, p->host));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
QVBoxLayout *l = new QVBoxLayout(this);
QHBoxLayout *hl = new QHBoxLayout;
hl->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
QGroupBox *changesGroup = new QGroupBox(tr("Changes"));
QVBoxLayout *changesLayout = new QVBoxLayout(changesGroup);
changesLayout->setMargin(layoutSpacing);
QHBoxLayout *filterLayout = new QHBoxLayout;
filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored));
m_filterLineEdit->setFixedWidth(300);
hl->addWidget(m_filterLineEdit);
filterLayout->addWidget(m_filterLineEdit);
connect(m_filterLineEdit, SIGNAL(filterChanged(QString)),
m_filterModel, SLOT(setFilterFixedString(QString)));
m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
l->addLayout(hl);
l->addWidget(m_treeView);
changesLayout->addLayout(filterLayout);
changesLayout->addWidget(m_treeView);
m_filterModel->setSourceModel(m_model);
m_filterModel->setFilterRole(GerritModel::FilterRole);
m_treeView->setModel(m_filterModel);
......@@ -85,11 +96,17 @@ GerritDialog::GerritDialog(const QSharedPointer<GerritParameters> &p,
QItemSelectionModel *selectionModel = m_treeView->selectionModel();
connect(selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)),
this, SLOT(slotEnableButtons()));
this, SLOT(slotCurrentChanged()));
connect(m_treeView, SIGNAL(doubleClicked(QModelIndex)),
this, SLOT(slotDoubleClicked(QModelIndex)));
m_openButton = addActionButton(tr("Open"), SLOT(slotOpenBrowser()));
m_copyUrlButton = addActionButton(tr("Copy URL"), SLOT(slotCopyUrl()));
QGroupBox *detailsGroup = new QGroupBox(tr("Details"));
QVBoxLayout *detailsLayout = new QVBoxLayout(detailsGroup);
detailsLayout->setMargin(layoutSpacing);
m_detailsBrowser->setOpenExternalLinks(true);
m_detailsBrowser->setTextInteractionFlags(Qt::TextBrowserInteraction);
detailsLayout->addWidget(m_detailsBrowser);
m_displayButton = addActionButton(tr("Diff..."), SLOT(slotFetchDisplay()));
m_applyButton = addActionButton(tr("Apply..."), SLOT(slotFetchApply()));
m_checkoutButton = addActionButton(tr("Checkout..."), SLOT(slotFetchCheckout()));
......@@ -99,14 +116,22 @@ GerritDialog::GerritDialog(const QSharedPointer<GerritParameters> &p,
m_refreshButton, SLOT(setDisabled(bool)));
connect(m_model, SIGNAL(refreshStateChanged(bool)),
this, SLOT(slotRefreshStateChanged(bool)));
l->addWidget(m_buttonBox);
connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
slotEnableButtons();
QSplitter *splitter = new QSplitter(Qt::Vertical, this);
splitter->addWidget(changesGroup);
splitter->addWidget(detailsGroup);
splitter->setSizes(QList<int>() << 400 << 200);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(splitter);
mainLayout->addWidget(m_buttonBox);
slotCurrentChanged();
m_model->refresh();
resize(QSize(1200, 400));
resize(QSize(1200, 600));
}
QPushButton *GerritDialog::addActionButton(const QString &text, const char *buttonSlot)
......@@ -123,7 +148,7 @@ GerritDialog::~GerritDialog()
void GerritDialog::slotDoubleClicked(const QModelIndex &i)
{
if (const QStandardItem *item = itemAt(i))
openUrl(item->row());
QDesktopServices::openUrl(QUrl(m_model->change(item->row())->url));
}
void GerritDialog::slotRefreshStateChanged(bool v)
......@@ -133,12 +158,6 @@ void GerritDialog::slotRefreshStateChanged(bool v)
m_treeView->resizeColumnToContents(c);
}
void GerritDialog::slotOpenBrowser()
{
if (const QStandardItem *item = currentItem())
openUrl(item->row());
}
void GerritDialog::slotFetchDisplay()
{
if (const QStandardItem *item = currentItem())
......@@ -157,22 +176,11 @@ void GerritDialog::slotFetchCheckout()
emit fetchCheckout(m_model->change(item->row()));
}
void GerritDialog::slotCopyUrl()
{
if (const QStandardItem *item = currentItem())
QApplication::clipboard()->setText(m_model->change(item->row())->url);
}
void GerritDialog::slotRefresh()
{
m_model->refresh();
}
void GerritDialog::openUrl(int row) const
{
QDesktopServices::openUrl(QUrl(m_model->change(row)->url));
}
const QStandardItem *GerritDialog::itemAt(const QModelIndex &i, int column) const
{
if (i.isValid()) {
......@@ -191,12 +199,17 @@ const QStandardItem *GerritDialog::currentItem(int column) const
return 0;
}
void GerritDialog::slotEnableButtons()
void GerritDialog::slotCurrentChanged()
{
const bool valid = m_treeView->selectionModel()->currentIndex().isValid();
m_openButton->setEnabled(valid);
const QModelIndex current = m_treeView->selectionModel()->currentIndex();
const bool valid = current.isValid();
if (valid) {
const int row = m_filterModel->mapToSource(current).row();
m_detailsBrowser->setText(m_model->change(row)->toHtml());
} else {
m_detailsBrowser->setText(QString());
}
m_displayButton->setEnabled(valid);
m_copyUrlButton->setEnabled(valid);
m_applyButton->setEnabled(valid);
m_checkoutButton->setEnabled(valid);
}
......
......@@ -38,11 +38,13 @@
QT_BEGIN_NAMESPACE
class QTreeView;
class QLabel;
class QModelIndex;
class QSortFilterProxyModel;
class QStandardItem;
class QPushButton;
class QDialogButtonBox;
class QTextBrowser;
QT_END_NAMESPACE
namespace Utils {
......@@ -68,18 +70,15 @@ signals:
void fetchCheckout(const QSharedPointer<Gerrit::Internal::GerritChange> &);
private slots:
void slotEnableButtons();
void slotCurrentChanged();
void slotDoubleClicked(const QModelIndex &);
void slotRefreshStateChanged(bool);
void slotOpenBrowser();
void slotFetchDisplay();
void slotFetchApply();
void slotFetchCheckout();
void slotCopyUrl();
void slotRefresh();
private:
inline void openUrl(int row) const;
const QStandardItem *itemAt(const QModelIndex &i, int column = 0) const;
const QStandardItem *currentItem(int column = 0) const;
QPushButton *addActionButton(const QString &text, const char *buttonSlot);
......@@ -88,13 +87,12 @@ private:
QSortFilterProxyModel *m_filterModel;
GerritModel *m_model;
QTreeView *m_treeView;
QTextBrowser *m_detailsBrowser;
Utils::FilterLineEdit *m_filterLineEdit;
QDialogButtonBox *m_buttonBox;
QPushButton *m_openButton;
QPushButton *m_displayButton;
QPushButton *m_applyButton;
QPushButton *m_checkoutButton;
QPushButton *m_copyUrlButton;
QPushButton *m_refreshButton;
};
......
......@@ -104,7 +104,7 @@ static inline QString defaultUrl(const QSharedPointer<GerritParameters> &p, int
// Format (sorted) approvals as separate HTML table
// lines by type listing the revievers:
// "<tr><td>Code Review</td><td>John Doe: -1, ...</tr><tr>...Sanity Review: ...".
QString GerritPatchSet::approvalsToolTip() const
QString GerritPatchSet::approvalsToHtml() const
{
if (approvals.isEmpty())
return QString();
......@@ -123,7 +123,10 @@ QString GerritPatchSet::approvalsToolTip() const
} else {
str << ", ";
}
str << a.reviewer << ": " << a.approval;
str << a.reviewer;
if (!a.email.isEmpty())
str << " <a href=\"mailto:" << a.email << "\">" << a.email << "</a>";
str << ": " << forcesign << a.approval << noforcesign;
}
str << "</tr>\n";
return result;
......@@ -165,7 +168,7 @@ QString GerritPatchSet::approvalsColumn() const
for (TypeReviewMapConstIterator it = reviews.constBegin(); it != cend; ++it) {
if (!result.isEmpty())
str << ' ';
str << it.key() << ": " << it.value();
str << it.key() << ": " << forcesign << it.value() << noforcesign;
}
return result;
}
......@@ -186,23 +189,23 @@ int GerritPatchSet::approvalLevel() const
return value;
}
QString GerritChange::toolTip() const
QString GerritChange::toHtml() const
{
static const QString format = GerritModel::tr(
"<html><head/><body><table>"
"<tr><td>Subject</td><td>%1</td></tr>"
"<tr><td>Number</td><td>%2"
"<tr><td>Owner</td><td>%3 &lt;%4&gt;</td></tr>"
"<tr><td>Owner</td><td>%3 <a href=\"mailto:%4\">%4</a></td></tr>"
"<tr><td>Project</td><td>%5 (%6)</td></tr>"
"<tr><td>Status</td><td>%7, %8</td></tr>"
"<tr><td>Patch set</td><td>%9</td></tr>"
"%10"
"<tr><td>URL</td><td>%11</td></tr>"
"<tr><td>URL</td><td><a href=\"%11\">%11</a></td></tr>"
"</table></body></html>");
return format.arg(title).arg(number).arg(owner, email, project, branch)
.arg(status, lastUpdated.toString(Qt::DefaultLocaleShortDate))
.arg(currentPatchSet.patchSetNumber)
.arg(currentPatchSet.approvalsToolTip(), url);
.arg(currentPatchSet.approvalsToHtml(), url);
}
QString GerritChange::filterString() const
......@@ -535,7 +538,9 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters>
for (int a = 0; a < ac; ++a) {
const QJsonObject ao = approvalsJ.at(a).toObject();
GerritApproval a;
a.reviewer = ao.value(approvalsByKey).toObject().value(ownerNameKey).toString();
const QJsonObject approverO = ao.value(approvalsByKey).toObject();
a.reviewer = approverO.value(ownerNameKey).toString();
a.email = approverO.value(ownerEmailKey).toString();
a.approval = ao.value(approvalsValueKey).toString().toInt();
a.type = ao.value(approvalsTypeKey).toString();
a.description = ao.value(approvalsDescriptionKey).toString();
......@@ -652,13 +657,16 @@ static QList<GerritChangePtr> parseOutput(const QSharedPointer<GerritParameters>
QTC_ASSERT(oc, break );
const int value = jsonIntMember(oc, approvalsValueKey);
QString by;
QString byMail;
if (Utils::JsonValue *byV = oc->member(approvalsByKey)) {
Utils::JsonObjectValue *byO = byV->toObject();
QTC_ASSERT(byO, break );
by = jsonStringMember(byO, ownerNameKey);
byMail = jsonStringMember(byO, ownerEmailKey);
}
GerritApproval a;
a.reviewer = by;
a.email = byMail;
a.approval = value;
a.type = jsonStringMember(oc, approvalsTypeKey);
a.description = jsonStringMember(oc, approvalsDescriptionKey);
......@@ -711,6 +719,7 @@ bool changeDateLessThan(const GerritChangePtr &c1, const GerritChangePtr &c2)
void GerritModel::queryFinished(const QByteArray &output)
{
const QDate today = QDate::currentDate();
QList<GerritChangePtr> changes = parseOutput(m_parameters, output);
qStableSort(changes.begin(), changes.end(), changeDateLessThan);
foreach (const GerritChangePtr &c, changes) {
......@@ -722,7 +731,6 @@ void GerritModel::queryFinished(const QByteArray &output)
if (m_userName.isEmpty() && !m_query->currentQuery())
m_userName = c->owner;
const QVariant filterV = QVariant(c->filterString());
const QString toolTip = c->toolTip();
const QVariant changeV = qVariantFromValue(c);
QList<QStandardItem *> row;
for (int i = 0; i < GerritModel::ColumnCount; ++i) {
......@@ -730,13 +738,17 @@ void GerritModel::queryFinished(const QByteArray &output)
item->setData(changeV, GerritModel::GerritChangeRole);
item->setData(filterV, GerritModel::FilterRole);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
item->setToolTip(toolTip);
row.append(item);
}
row[NumberColumn]->setText(QString::number(c->number));
row[TitleColumn]->setText(c->title);
row[OwnerColumn]->setText(c->owner);
row[DateColumn]->setText(c->lastUpdated.toString(Qt::SystemLocaleShortDate));
// Shorten columns: Display time if it is today, else date
const QString dateString = c->lastUpdated.date() == today ?
c->lastUpdated.time().toString(Qt::SystemLocaleShortDate) :
c->lastUpdated.date().toString(Qt::SystemLocaleShortDate);
row[DateColumn]->setText(dateString);
QString project = c->project;
if (c->branch != QLatin1String("master"))
project += QLatin1String(" (") + c->branch + QLatin1Char(')');
......
......@@ -55,13 +55,14 @@ public:
QString type; // Review type
QString description; // Type description, possibly empty
QString reviewer;
QString email;
int approval;
};
class GerritPatchSet {
public:
GerritPatchSet() : patchSetNumber(1) {}
QString approvalsToolTip() const;
QString approvalsToHtml() const;
QString approvalsColumn() const;
bool hasApproval(const QString &userName) const;
int approvalLevel() const;
......@@ -77,7 +78,7 @@ public:
GerritChange() : number(0) {}
bool isValid() const { return number && !url.isEmpty() && !project.isEmpty(); }
QString toolTip() const;
QString toHtml() const;
QString filterString() const;
QStringList gitFetchArguments(const QSharedPointer<GerritParameters> &p) const;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment