forked from qt-creator/qt-creator
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 <orgads@gmail.com> Reviewed-by: Tobias Hunger <tobias.hunger@nokia.com>
This commit is contained in:
@@ -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 <%4></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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user