Commit 5f6a91e0 authored by Tobias Hunger's avatar Tobias Hunger
Browse files

Git: Rework branch dialog

 * Make adding new branches more discoverable
 * Make adding tracking branches more discoverable
 * Update UI

Task-number: QTCREATORBUG-4943
Task-number: QTCREATORBUG-4944
Change-Id: Idcbf5f8321a3bd04c925e33d094bb479788a7d9b
Reviewed-on: http://codereview.qt.nokia.com/588

Reviewed-by: default avatarQt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: default avatarTobias Hunger <tobias.hunger@nokia.com>
parent 91975960
#include "branchadddialog.h"
#include "ui_branchadddialog.h"
namespace Git {
namespace Internal {
BranchAddDialog::BranchAddDialog(QWidget *parent) :
QDialog(parent),
m_ui(new Ui::BranchAddDialog)
{
m_ui->setupUi(this);
}
BranchAddDialog::~BranchAddDialog()
{
delete m_ui;
}
void BranchAddDialog::setBranchName(const QString &n)
{
m_ui->branchNameEdit->setText(n);
m_ui->branchNameEdit->selectAll();
}
QString BranchAddDialog::branchName() const
{
return m_ui->branchNameEdit->text();
}
void BranchAddDialog::setTrackedBranchName(const QString &name, bool remote)
{
m_ui->trackingCheckBox->setVisible(true);
if (!name.isEmpty())
m_ui->trackingCheckBox->setText(remote ? tr("Track remote branch \'%1\'").arg(name) :
tr("Track local branch \'%1\'").arg(name));
else
m_ui->trackingCheckBox->setVisible(false);
m_ui->trackingCheckBox->setChecked(remote);
}
bool BranchAddDialog::track()
{
return m_ui->trackingCheckBox->isVisible() && m_ui->trackingCheckBox->isChecked();
}
} // namespace Internal
} // namespace Git
#ifndef BRANCHADDDIALOG_H
#define BRANCHADDDIALOG_H
#include <QDialog>
namespace Git {
namespace Internal {
namespace Ui {
class BranchAddDialog;
}
class BranchAddDialog : public QDialog
{
Q_OBJECT
public:
explicit BranchAddDialog(QWidget *parent = 0);
~BranchAddDialog();
void setBranchName(const QString &);
QString branchName() const;
void setTrackedBranchName(const QString &name, bool remote);
bool track();
private:
Ui::BranchAddDialog *m_ui;
};
} // namespace Internal
} // namespace Git
#endif // BRANCHADDDIALOG_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Git::Internal::BranchAddDialog</class>
<widget class="QDialog" name="Git::Internal::BranchAddDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>134</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="branchNameLabel">
<property name="text">
<string>Branch Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="branchNameEdit"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="trackingCheckBox">
<property name="text">
<string>CheckBox</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Git::Internal::BranchAddDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Git::Internal::BranchAddDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
......@@ -31,12 +31,14 @@
**************************************************************************/
#include "branchdialog.h"
#include "branchadddialog.h"
#include "branchmodel.h"
#include "gitclient.h"
#include "gitplugin.h"
#include "ui_branchdialog.h"
#include "stashdialog.h" // Label helpers
#include <utils/checkablemessagebox.h>
#include <vcsbase/vcsbaseoutputwindow.h>
#include <QtGui/QItemSelectionModel>
......@@ -45,42 +47,13 @@
#include <QtCore/QDebug>
enum { debug = 0 };
// Single selection helper
static inline int selectedRow(const QAbstractItemView *listView)
{
const QModelIndexList indexList = listView->selectionModel()->selectedIndexes();
if (indexList.size() == 1)
return indexList.front().row();
return -1;
}
// Helper to select a row. No sooner said then done
static inline void selectListRow(QAbstractItemView *iv, int row)
{
const QModelIndex index = iv->model()->index(row, 0);
iv->selectionModel()->select(index, QItemSelectionModel::Select);
}
namespace Git {
namespace Internal {
static inline GitClient *gitClient()
{
return GitPlugin::instance()->gitClient();
}
namespace Internal {
BranchDialog::BranchDialog(QWidget *parent) :
QDialog(parent),
m_ui(new Ui::BranchDialog),
m_checkoutButton(0),
m_diffButton(0),
m_logButton(0),
m_refreshButton(0),
m_deleteButton(0),
m_localModel(new LocalBranchModel(gitClient(), this)),
m_remoteModel(new RemoteBranchModel(gitClient(), this))
m_model(new BranchModel(GitPlugin::instance()->gitClient(), this))
{
setModal(false);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
......@@ -88,273 +61,135 @@ BranchDialog::BranchDialog(QWidget *parent) :
m_ui->setupUi(this);
m_checkoutButton = m_ui->buttonBox->addButton(tr("Checkout"), QDialogButtonBox::ActionRole);
connect(m_checkoutButton, SIGNAL(clicked()), this, SLOT(slotCheckoutSelectedBranch()));
m_diffButton = m_ui->buttonBox->addButton(tr("Diff"), QDialogButtonBox::ActionRole);
connect(m_diffButton, SIGNAL(clicked()), this, SLOT(slotDiffSelected()));
m_logButton = m_ui->buttonBox->addButton(tr("Log"), QDialogButtonBox::ActionRole);
connect(m_logButton, SIGNAL(clicked()), this, SLOT(slotLog()));
connect(m_ui->refreshButton, SIGNAL(clicked()), this, SLOT(refresh()));
connect(m_ui->addButton, SIGNAL(clicked()), this, SLOT(add()));
connect(m_ui->checkoutButton, SIGNAL(clicked()), this, SLOT(checkout()));
connect(m_ui->removeButton, SIGNAL(clicked()), this, SLOT(remove()));
connect(m_ui->diffButton, SIGNAL(clicked()), this, SLOT(diff()));
connect(m_ui->logButton, SIGNAL(clicked()), this, SLOT(log()));
m_refreshButton = m_ui->buttonBox->addButton(tr("Refresh"), QDialogButtonBox::ActionRole);
connect(m_refreshButton, SIGNAL(clicked()), this, SLOT(slotRefresh()));
m_ui->branchView->setModel(m_model);
m_deleteButton = m_ui->buttonBox->addButton(tr("Delete..."), QDialogButtonBox::ActionRole);
connect(m_deleteButton, SIGNAL(clicked()), this, SLOT(slotDeleteSelectedBranch()));
connect(m_ui->branchView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(enableButtons()));
connect(m_ui->localBranchListView, SIGNAL(doubleClicked(QModelIndex)), this,
SLOT(slotLocalBranchActivated()));
connect(m_ui->remoteBranchListView, SIGNAL(doubleClicked(QModelIndex)), this,
SLOT(slotRemoteBranchActivated(QModelIndex)));
connect(m_localModel, SIGNAL(newBranchEntered(QString)), this, SLOT(slotCreateLocalBranch(QString)));
m_ui->localBranchListView->setModel(m_localModel);
m_ui->remoteBranchListView->setModel(m_remoteModel);
connect(m_ui->localBranchListView->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(slotEnableButtons(QItemSelection)));
connect(m_ui->remoteBranchListView->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(slotEnableButtons(QItemSelection)));
slotEnableButtons();
enableButtons();
}
BranchDialog::~BranchDialog()
{
delete m_ui;
delete m_model;
m_model = 0;
}
void BranchDialog::refresh(const QString &repository, bool force)
{
if (m_repository == repository && !force)
return;
// Refresh
return;
m_repository = repository;
m_ui->repositoryLabel->setText(StashDialog::msgRepositoryLabel(m_repository));
if (m_repository.isEmpty()) {
m_localModel->clear();
m_remoteModel->clear();
} else {
QString errorMessage;
const bool success = m_localModel->refresh(m_repository, &errorMessage)
&& m_remoteModel->refresh(m_repository, &errorMessage);
if (!success)
VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
}
slotEnableButtons();
}
int BranchDialog::selectedLocalBranchIndex() const
{
return selectedRow(m_ui->localBranchListView);
}
QString errorMessage;
if (!m_model->refresh(m_repository, &errorMessage))
VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
int BranchDialog::selectedRemoteBranchIndex() const
{
return selectedRow(m_ui->remoteBranchListView);
m_ui->branchView->expandAll();
}
void BranchDialog::slotEnableButtons(const QItemSelection &selected)
void BranchDialog::enableButtons()
{
if (!selected.indexes().isEmpty()) {
if (selected.indexes().at(0).model() == m_localModel)
m_ui->remoteBranchListView->clearSelection();
else
m_ui->localBranchListView->clearSelection();
}
QModelIndex idx = selectedIndex();
const bool hasSelection = idx.isValid();
const bool currentSelected = hasSelection && idx == m_model->currentBranch();
const bool isLocal = m_model->isLocal(idx);
const bool isLeaf = m_model->isLeaf(idx);
// We can switch to or delete branches that are not current.
const int selectedLocalRow = selectedLocalBranchIndex();
const bool hasRepository = !m_repository.isEmpty();
const bool hasLocalSelection = selectedLocalRow != -1 && !m_localModel->isNewBranchRow(selectedLocalRow);
const bool otherLocalSelected = hasLocalSelection && selectedLocalRow != m_localModel->currentBranch();
const bool branchSelected = hasLocalSelection || selectedRemoteBranchIndex() != -1;
m_checkoutButton->setEnabled(otherLocalSelected);
m_diffButton->setEnabled(branchSelected);
m_logButton->setEnabled(branchSelected);
m_deleteButton->setEnabled(otherLocalSelected);
m_refreshButton->setEnabled(hasRepository);
// Also disable <New Branch> entry of list view
m_ui->localBranchListView->setEnabled(hasRepository);
m_ui->remoteBranchListView->setEnabled(hasRepository);
m_ui->removeButton->setEnabled(hasSelection && !currentSelected && isLocal && isLeaf);
m_ui->logButton->setEnabled(hasSelection && isLeaf);
m_ui->diffButton->setEnabled(hasSelection && isLeaf);
m_ui->checkoutButton->setEnabled(hasSelection && !currentSelected && isLocal && isLeaf);
}
void BranchDialog::slotRefresh()
void BranchDialog::refresh()
{
refresh(m_repository, true);
}
void BranchDialog::selectLocalBranch(const QString &b)
void BranchDialog::add()
{
// Select the newly created branch
const int row = m_localModel->findBranchByName(b);
if (row != -1)
selectListRow(m_ui->localBranchListView, row);
}
QString trackedBranch = m_model->branchName(selectedIndex());
bool isLocal = m_model->isLocal(selectedIndex());
if (trackedBranch.isEmpty()) {
trackedBranch = m_model->branchName(m_model->currentBranch());
isLocal = true;
}
bool BranchDialog::ask(const QString &title, const QString &what, bool defaultButton)
{
return QMessageBox::question(this, title, what, QMessageBox::Yes|QMessageBox::No,
defaultButton ? QMessageBox::Yes : QMessageBox::No) == QMessageBox::Yes;
}
QStringList localNames = m_model->localBranchNames();
/* Prompt to delete a local branch and do so. */
void BranchDialog::slotDeleteSelectedBranch()
{
const int idx = selectedLocalBranchIndex();
if (idx == -1)
return;
const QString name = m_localModel->branchName(idx);
if (!ask(tr("Delete Branch"), tr("Would you like to delete the branch '%1'?").arg(name), true))
return;
QString errorMessage;
bool ok = false;
do {
QString output;
QStringList args(QLatin1String("-D"));
args << name;
if (!gitClient()->synchronousBranchCmd(m_repository, args, &output, &errorMessage))
break;
if (!m_localModel->refresh(m_repository, &errorMessage))
break;
ok = true;
} while (false);
slotEnableButtons();
if (!ok)
QMessageBox::warning(this, tr("Failed to delete branch"), errorMessage);
}
QString suggestedNameBase = trackedBranch.mid(trackedBranch.lastIndexOf(QLatin1Char('/')) + 1);
QString suggestedName = suggestedNameBase;
int i = 2;
while (localNames.contains(suggestedName)) {
suggestedName = suggestedNameBase + QString::number(i);
++i;
}
void BranchDialog::slotCreateLocalBranch(const QString &branchName)
{
// Create
QString output;
QString errorMessage;
bool ok = false;
do {
if (!gitClient()->synchronousBranchCmd(m_repository, QStringList(branchName), &output, &errorMessage))
break;
if (!m_localModel->refresh(m_repository, &errorMessage))
break;
ok = true;
} while (false);
if (!ok) {
QMessageBox::warning(this, tr("Failed to create branch"), errorMessage);
return;
BranchAddDialog branchAddDialog;
branchAddDialog.setBranchName(suggestedName);
branchAddDialog.setTrackedBranchName(trackedBranch, !isLocal);
if (branchAddDialog.exec() == QDialog::Accepted && m_model) {
QModelIndex idx = m_model->addBranch(branchAddDialog.branchName(), branchAddDialog.track(), trackedBranch);
m_ui->branchView->selectionModel()->select(idx, QItemSelectionModel::Clear
| QItemSelectionModel::Select
| QItemSelectionModel::Current);
m_ui->branchView->scrollTo(idx);
}
selectLocalBranch(branchName);
}
void BranchDialog::slotLocalBranchActivated()
void BranchDialog::checkout()
{
if (m_checkoutButton->isEnabled())
m_checkoutButton->animateClick();
}
QModelIndex idx = selectedIndex();
Q_ASSERT(m_model->isLocal(idx));
void BranchDialog::slotDiffSelected()
{
int idx = selectedLocalBranchIndex();
if (idx != -1) {
gitClient()->diffBranch(m_repository, QStringList(), m_localModel->branchName(idx));
return;
}
idx = selectedRemoteBranchIndex();
if (idx != -1)
gitClient()->diffBranch(m_repository, QStringList(), m_remoteModel->branchName(idx));
m_model->checkoutBranch(idx);
}
void BranchDialog::slotLog()
/* Prompt to delete a local branch and do so. */
void BranchDialog::remove()
{
int idx = selectedLocalBranchIndex();
if (idx != -1) {
gitClient()->graphLog(m_repository, m_localModel->branchName(idx));
QModelIndex selected = selectedIndex();
Q_ASSERT(selected != m_model->currentBranch()); // otherwise the button would not be enabled!
QString branchName = m_model->branchName(selected);
if (branchName.isEmpty())
return;
}
idx = selectedRemoteBranchIndex();
if (idx != -1)
gitClient()->graphLog(m_repository, m_remoteModel->branchName(idx));
QString message = tr("Would you like to delete the branch '%1'?").arg(branchName);
bool wasMerged = m_model->branchIsMerged(selected);
if (!wasMerged)
message = tr("Would you like to delete the <b>unmerged</b> branch '%1'?").arg(branchName);
if (QMessageBox::question(this, tr("Delete Branch"), message, QMessageBox::Yes|QMessageBox::No,
wasMerged ? QMessageBox::Yes : QMessageBox::No) == QMessageBox::Yes)
m_model->removeBranch(selected);
}
/* Ask to stash away changes and then close dialog and do an asynchronous
* checkout. */
void BranchDialog::slotCheckoutSelectedBranch()
void BranchDialog::diff()
{
const int idx = selectedLocalBranchIndex();
if (idx == -1)
return;
const QString name = m_localModel->branchName(idx);
QString errorMessage;
switch (gitClient()->ensureStash(m_repository, &errorMessage)) {
case GitClient::StashUnchanged:
case GitClient::Stashed:
case GitClient::NotStashed:
break;
case GitClient::StashCanceled:
QString branchName = m_model->branchName(selectedIndex());
if (branchName.isEmpty())
return;
case GitClient::StashFailed:
QMessageBox::warning(this, tr("Failed to stash"), errorMessage);
return;
}
if (gitClient()->synchronousCheckoutBranch(m_repository, name, &errorMessage)) {
refresh(m_repository, true);
} else {
QMessageBox::warning(this, tr("Checkout failed"), errorMessage);
}
GitPlugin::instance()->gitClient()->diffBranch(m_repository, QStringList(), branchName);
}
void BranchDialog::slotRemoteBranchActivated(const QModelIndex &i)
void BranchDialog::log()
{
// Double click on a remote branch (origin/foo): Switch to matching
// local (foo) one or offer to create a tracking branch.
const QString remoteName = m_remoteModel->branchName(i.row());
// build the name of the corresponding local branch
// and look for it in the local model.
const int slashPos = remoteName.indexOf(QLatin1Char('/'));
if (slashPos == -1)
return;
const QString localBranch = remoteName.mid(slashPos + 1);
if (localBranch == QLatin1String("HEAD") || localBranch == QLatin1String("master"))
QString branchName = m_model->branchName(selectedIndex());
if (branchName.isEmpty())
return;
const int localIndex = m_localModel->findBranchByName(localBranch);
if (debug)
qDebug() << Q_FUNC_INFO << remoteName << localBranch << localIndex;
// There is a matching a local one!
if (localIndex != -1) {
// Is it the current one? Just close.
if (m_localModel->currentBranch() == localIndex) {
accept();
return;
}
// Nope, select and trigger checkout
selectListRow(m_ui->localBranchListView, localIndex);
slotLocalBranchActivated();
return;
}
// Does not exist yet. Ask to create.
const QString msg = tr("Would you like to create a local branch '%1' tracking the remote branch '%2'?").arg(localBranch, remoteName);
if (!ask(tr("Create branch"), msg, true))
return;
QStringList args(QLatin1String("--track"));
args << localBranch << remoteName;
QString errorMessage;
bool ok = false;
do {
QString output;
if (!gitClient()->synchronousBranchCmd(m_repository, args, &output, &errorMessage))
break;
if (!m_localModel->refresh(m_repository, &errorMessage))
break;
ok = true;
} while (false);
if (!ok) {
QMessageBox::warning(this, tr("Failed to create a tracking branch"), errorMessage);
return;
}
// Select it
selectLocalBranch(localBranch);
GitPlugin::instance()->gitClient()->graphLog(m_repository, branchName);
}
void BranchDialog::changeEvent(QEvent *e)
......@@ -369,5 +204,13 @@ void BranchDialog::changeEvent(QEvent *e)
}
}
QModelIndex BranchDialog::selectedIndex()
{
QModelIndexList selected = m_ui->branchView->selectionModel()->selectedIndexes();
if (selected.isEmpty())
return QModelIndex();
return selected.at(0);
}
} // namespace Internal
} // namespace Git
......@@ -48,9 +48,8 @@ namespace Ui {
class BranchDialog;