diff options
27 files changed, 2719 insertions, 45 deletions
diff --git a/src/plugins/git/clonewizard.cpp b/src/plugins/git/clonewizard.cpp index 03903aa4ff..9b379d18fb 100644 --- a/src/plugins/git/clonewizard.cpp +++ b/src/plugins/git/clonewizard.cpp @@ -29,8 +29,6 @@ #include "clonewizard.h" #include "clonewizardpage.h" -#include "gitplugin.h" -#include "gitclient.h" #include <vcsbase/checkoutjobs.h> #include <utils/qtcassert.h> @@ -75,18 +73,7 @@ QSharedPointer<VCSBase::AbstractCheckoutJob> CloneWizard::createJob(const QList< // Collect parameters for the clone command. const CloneWizardPage *cwp = qobject_cast<const CloneWizardPage *>(parameterPages.front()); QTC_ASSERT(cwp, return QSharedPointer<VCSBase::AbstractCheckoutJob>()) - const GitClient *client = GitPlugin::instance()->gitClient(); - QStringList args = client->binary(); - const QString workingDirectory = cwp->path(); - const QString directory = cwp->directory(); - *checkoutPath = workingDirectory + QLatin1Char('/') + directory; - args << QLatin1String("clone") << cwp->repository() << directory; - const QString binary = args.front(); - args.pop_front(); - - VCSBase::AbstractCheckoutJob *job = new VCSBase::ProcessCheckoutJob(binary, args, workingDirectory, - client->processEnvironment()); - return QSharedPointer<VCSBase::AbstractCheckoutJob>(job); + return cwp->createCheckoutJob(checkoutPath); } } // namespace Internal diff --git a/src/plugins/git/clonewizardpage.cpp b/src/plugins/git/clonewizardpage.cpp index 6f78ab278f..aef415a382 100644 --- a/src/plugins/git/clonewizardpage.cpp +++ b/src/plugins/git/clonewizardpage.cpp @@ -28,20 +28,42 @@ **************************************************************************/ #include "clonewizardpage.h" +#include "gitplugin.h" +#include "gitclient.h" + +#include <vcsbase/checkoutjobs.h> +#include <utils/qtcassert.h> namespace Git { -namespace Internal { + +struct CloneWizardPagePrivate { + CloneWizardPagePrivate(); + + const QString mainLinePostfix; + const QString gitPostFix; + const QString protocolDelimiter; +}; + +CloneWizardPagePrivate::CloneWizardPagePrivate() : + mainLinePostfix(QLatin1String("/mainline.git")), + gitPostFix(QLatin1String(".git")), + protocolDelimiter(QLatin1String("://")) +{ +} CloneWizardPage::CloneWizardPage(QWidget *parent) : VCSBase::BaseCheckoutWizardPage(parent), - m_mainLinePostfix(QLatin1String("/mainline.git")), - m_gitPostFix(QLatin1String(".git")), - m_protocolDelimiter(QLatin1String("://")) + d(new CloneWizardPagePrivate) { setSubTitle(tr("Specify repository URL, checkout directory and path.")); setRepositoryLabel(tr("Clone URL:")); } +CloneWizardPage::~CloneWizardPage() +{ + delete d; +} + QString CloneWizardPage::directoryFromRepository(const QString &urlIn) const { /* Try to figure out a good directory name from something like: @@ -51,19 +73,19 @@ QString CloneWizardPage::directoryFromRepository(const QString &urlIn) const QString url = urlIn.trimmed(); const QChar slash = QLatin1Char('/'); // remove host - const int protocolDelimiterPos = url.indexOf(m_protocolDelimiter); // "://" - const int startRepoSearchPos = protocolDelimiterPos == -1 ? 0 : protocolDelimiterPos + m_protocolDelimiter.size(); + const int protocolDelimiterPos = url.indexOf(d->protocolDelimiter); // "://" + const int startRepoSearchPos = protocolDelimiterPos == -1 ? 0 : protocolDelimiterPos + d->protocolDelimiter.size(); int repoPos = url.indexOf(QLatin1Char(':'), startRepoSearchPos); if (repoPos == -1) repoPos = url.indexOf(slash, startRepoSearchPos); if (repoPos != -1) url.remove(0, repoPos + 1); // Remove postfixes - if (url.endsWith(m_mainLinePostfix)) { - url.truncate(url.size() - m_mainLinePostfix.size()); + if (url.endsWith(d->mainLinePostfix)) { + url.truncate(url.size() - d->mainLinePostfix.size()); } else { - if (url.endsWith(m_gitPostFix)) { - url.truncate(url.size() - m_gitPostFix.size()); + if (url.endsWith(d->gitPostFix)) { + url.truncate(url.size() - d->gitPostFix.size()); } } // Check for equal parts, something like "qt/qt" -> "qt" @@ -79,5 +101,19 @@ QString CloneWizardPage::directoryFromRepository(const QString &urlIn) const return url; } -} // namespace Internal +QSharedPointer<VCSBase::AbstractCheckoutJob> CloneWizardPage::createCheckoutJob(QString *checkoutPath) const +{ + const Internal::GitClient *client = Internal::GitPlugin::instance()->gitClient(); + QStringList args = client->binary(); + const QString workingDirectory = path(); + const QString checkoutDir = directory(); + *checkoutPath = workingDirectory + QLatin1Char('/') + checkoutDir; + args << QLatin1String("clone") << repository() << checkoutDir; + const QString binary = args.front(); + args.pop_front(); + VCSBase::AbstractCheckoutJob *job = new VCSBase::ProcessCheckoutJob(binary, args, workingDirectory, + client->processEnvironment()); + return QSharedPointer<VCSBase::AbstractCheckoutJob>(job); +} + } // namespace Git diff --git a/src/plugins/git/clonewizardpage.h b/src/plugins/git/clonewizardpage.h index 4d8797e6f0..2ad6b99d84 100644 --- a/src/plugins/git/clonewizardpage.h +++ b/src/plugins/git/clonewizardpage.h @@ -32,25 +32,32 @@ #include <vcsbase/basecheckoutwizardpage.h> +#include <QtCore/QSharedPointer> + +namespace VCSBase { + class AbstractCheckoutJob; +} + namespace Git { -namespace Internal { +struct CloneWizardPagePrivate; + +// Used by gitorious as well. class CloneWizardPage : public VCSBase::BaseCheckoutWizardPage { Q_OBJECT public: - CloneWizardPage(QWidget *parent = 0); + explicit CloneWizardPage(QWidget *parent = 0); + virtual ~CloneWizardPage(); + + QSharedPointer<VCSBase::AbstractCheckoutJob> createCheckoutJob(QString *checkoutPath) const; protected: virtual QString directoryFromRepository(const QString &r) const; private: - const QString m_mainLinePostfix; - const QString m_gitPostFix; - const QString m_protocolDelimiter; + CloneWizardPagePrivate *d; }; -} // namespace Internal } // namespace Git #endif // CLONEWIZARDPAGE_H - diff --git a/src/plugins/git/git.pro b/src/plugins/git/git.pro index 618b7810b4..86556815eb 100644 --- a/src/plugins/git/git.pro +++ b/src/plugins/git/git.pro @@ -47,3 +47,4 @@ FORMS += changeselectiondialog.ui \ branchdialog.ui OTHER_FILES += ScmGit.pluginspec +include(gitorious/gitorious.pri) diff --git a/src/plugins/git/gitorious/gitorious.cpp b/src/plugins/git/gitorious/gitorious.cpp new file mode 100644 index 0000000000..c2a1787d91 --- /dev/null +++ b/src/plugins/git/gitorious/gitorious.cpp @@ -0,0 +1,600 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#include "gitorious.h" + +#include <QtCore/QDebug> +#include <QtCore/QCoreApplication> +#include <QtCore/QXmlStreamReader> +#include <QtCore/QSettings> + +#include <QtNetwork/QNetworkAccessManager> +#include <QtNetwork/QNetworkReply> + +enum { debug = 0 }; + +enum Protocol { ListCategoriesProtocol, ListProjectsProtocol }; + +static const char *protocolPropertyC = "gitoriousProtocol"; +static const char *hostNamePropertyC = "gitoriousHost"; +static const char *pagePropertyC = "requestPage"; + +static const char *settingsKeyC = "GitoriousHosts"; + +// Gitorious paginates projects as 20 per page. It starts with page 1. +enum { ProjectsPageSize = 20 }; + +// Format an URL for a XML request +static inline QUrl xmlRequest(const QString &host, const QString &request, int page = -1) +{ + QUrl url; + url.setScheme(QLatin1String("http")); + url.setHost(host); + url.setPath(QLatin1Char('/') + request); + url.addQueryItem(QLatin1String("format"), QLatin1String("xml")); + if (page >= 0) + url.addQueryItem(QLatin1String("page"), QString::number(page)); + return url; +} + +namespace Gitorious { +namespace Internal { + +GitoriousRepository::GitoriousRepository() : + type(BaselineRepository), + id(0) +{ +} + +static inline GitoriousRepository::Type repositoryType(const QString &nspace) +{ + if (nspace == QLatin1String("Repository::Namespace::BASELINE")) + return GitoriousRepository::BaselineRepository; + if (nspace == QLatin1String("Repository::Namespace::SHARED")) + return GitoriousRepository::SharedRepository; + if (nspace == QLatin1String("Repository::Namespace::PERSONAL")) + return GitoriousRepository::PersonalRepository; + return GitoriousRepository::BaselineRepository; +} + +GitoriousCategory::GitoriousCategory(const QString &n) : + name(n) +{ +} + +GitoriousHost::GitoriousHost(const QString &h, const QString &d) : + hostName(h), + description(d), + state(ProjectsQueryRunning) +{ +} + +int GitoriousHost::findCategory(const QString &n) const +{ + const int count = categories.size(); + for (int i = 0; i < count; i++) + if (categories.at(i)->name == n) + return i; + return -1; +} + +QDebug operator<<(QDebug d, const GitoriousRepository &r) +{ + QDebug nospace = d.nospace(); + nospace << "name=" << r.name << '/' << r.id << '/' << r.type << r.owner + <<" push=" << r.pushUrl << " clone=" << r.cloneUrl << " descr=" << r.description; + return d; +} + +QDebug operator<<(QDebug d, const GitoriousProject &p) +{ + QDebug nospace = d.nospace(); + nospace << " project=" << p.name << " description=" << p.description << '\n'; + foreach(const GitoriousRepository &r, p.repositories) + nospace << " " << r << '\n'; + return d; +} + +QDebug operator<<(QDebug d, const GitoriousCategory &c) +{ + d.nospace() << " category=" << c.name << '\n'; + return d; +} + +QDebug operator<<(QDebug d, const GitoriousHost &h) +{ + QDebug nospace = d.nospace(); + nospace << " Host=" << h.hostName << " description=" << h.description << '\n'; + foreach(const QSharedPointer<GitoriousCategory> &c, h.categories) + nospace << *c; + foreach(const QSharedPointer<GitoriousProject> &p, h.projects) + nospace << *p; + return d; +} + +/* GitoriousProjectReader: Helper class for parsing project list output + * \code +projects...> + <project> + <bugtracker-url> + <created-at> + <description>... </description> + <home-url> (rarely set) + <license> + <mailinglist-url> + <slug> (name) + <title>MuleFTW</title> + <owner> + <repositories> + <mainlines> // Optional + <repository> + <id> + <name> + <owner> + <clone_url> + </repository> + </mainlines> + <clones> // Optional + </clones> + </repositories> + </project> + + * \endcode */ + +class GitoriousProjectReader +{ + Q_DISABLE_COPY(GitoriousProjectReader) +public: + typedef GitoriousCategory::ProjectList ProjectList; + + GitoriousProjectReader(); + ProjectList read(const QByteArray &a, QString *errorMessage); + +private: + void readProjects(QXmlStreamReader &r); + QSharedPointer<GitoriousProject> readProject(QXmlStreamReader &r); + QList<GitoriousRepository> readRepositories(QXmlStreamReader &r); + GitoriousRepository readRepository(QXmlStreamReader &r, int defaultType = -1); + void readUnknownElement(QXmlStreamReader &r); + + const QString m_mainLinesElement; + const QString m_clonesElement; + ProjectList m_projects; +}; + +GitoriousProjectReader::GitoriousProjectReader() : + m_mainLinesElement(QLatin1String("mainlines")), + m_clonesElement(QLatin1String("clones")) +{ +} + +GitoriousProjectReader::ProjectList GitoriousProjectReader::read(const QByteArray &a, QString *errorMessage) +{ + m_projects.clear(); + QXmlStreamReader reader(a); + + while (!reader.atEnd()) { + reader.readNext(); + if (reader.isStartElement()) { + if (reader.name() == QLatin1String("projects")) { + readProjects(reader); + } else { + readUnknownElement(reader); + } + } + } + + if (reader.hasError()) { + *errorMessage = QString::fromLatin1("Error at %1:%2: %3").arg(reader.lineNumber()).arg(reader.columnNumber()).arg(reader.errorString()); + m_projects.clear(); + } + + return m_projects; +} + +bool gitoriousProjectLessThan(const QSharedPointer<GitoriousProject> &p1, const QSharedPointer<GitoriousProject> &p2) +{ + return p1->name.compare(p2->name, Qt::CaseInsensitive) < 0; +} + +void GitoriousProjectReader::readProjects(QXmlStreamReader &reader) +{ + while (!reader.atEnd()) { + reader.readNext(); + + if (reader.isEndElement()) + break; + + if (reader.isStartElement()) { + if (reader.name() == "project") { + const QSharedPointer<GitoriousProject> p = readProject(reader); + if (!p->name.isEmpty()) + m_projects.push_back(p); + } else { + readUnknownElement(reader); + } + } + } +} + +QSharedPointer<GitoriousProject> GitoriousProjectReader::readProject(QXmlStreamReader &reader) +{ + QSharedPointer<GitoriousProject> project(new GitoriousProject); + + while (!reader.atEnd()) { + reader.readNext(); + if (reader.isEndElement()) + break; + + if (reader.isStartElement()) { + const QStringRef name = reader.name(); + if (name == QLatin1String("description")) { + project->description = reader.readElementText(); + } else if (name == QLatin1String("title")) { + project->name = reader.readElementText(); + } else if (name == QLatin1String("slug") && project->name.isEmpty()) { + project->name = reader.readElementText(); + } else if (name == QLatin1String("repositories")) { + project->repositories = readRepositories(reader); + } else { + readUnknownElement(reader); + } + } + } + return project; +} + +QList<GitoriousRepository> GitoriousProjectReader::readRepositories(QXmlStreamReader &reader) +{ + QList<GitoriousRepository> repositories; + int defaultType = -1; + + // The "mainlines"/"clones" elements are not used in the + // Nokia setup, handle them optionally. + while (!reader.atEnd()) { + reader.readNext(); + + if (reader.isEndElement()) { + const QStringRef name = reader.name(); + if (name == m_mainLinesElement || name == m_clonesElement) { + defaultType = -1; + } else { + break; + } + } + + if (reader.isStartElement()) { + const QStringRef name = reader.name(); + if (reader.name() == QLatin1String("repository")) { + repositories.push_back(readRepository(reader, defaultType)); + } else if (name == m_mainLinesElement) { + defaultType = GitoriousRepository::MainLineRepository; + } else if (name == m_clonesElement) { + defaultType = GitoriousRepository::CloneRepository; + } else { + readUnknownElement(reader); + } + } + } + return repositories; +} + +GitoriousRepository GitoriousProjectReader::readRepository(QXmlStreamReader &reader, int defaultType) +{ + GitoriousRepository repository; + if (defaultType >= 0) + repository.type = static_cast<GitoriousRepository::Type>(defaultType); + + while (!reader.atEnd()) { + reader.readNext(); + + if (reader.isEndElement()) + break; + + if (reader.isStartElement()) { + const QStringRef name = reader.name(); + if (name == QLatin1String("name")) { + repository.name = reader.readElementText(); + } else if (name == QLatin1String("owner")) { + repository.owner = reader.readElementText(); + } else if (name == QLatin1String("id")) { + repository.id = reader.readElementText().toInt(); + } else if (name == QLatin1String("description")) { + repository.description = reader.readElementText(); + } else if (name == QLatin1String("push_url")) { + repository.pushUrl = reader.readElementText(); + } else if (name == QLatin1String("clone_url")) { + repository.cloneUrl = reader.readElementText(); + } else if (name == QLatin1String("namespace")) { + repository.type = repositoryType(reader.readElementText()); + } else { + readUnknownElement(reader); + } + } + } + return repository; +} + +void GitoriousProjectReader::readUnknownElement(QXmlStreamReader &reader) +{ + Q_ASSERT(reader.isStartElement()); + + while (!reader.atEnd()) { + reader.readNext(); + + if (reader.isEndElement()) + break; + + if (reader.isStartElement()) + readUnknownElement(reader); + } +} + +// --- Gitorious + +Gitorious::Gitorious() : + m_networkManager(0) +{ +} + +Gitorious &Gitorious::instance() +{ + static Gitorious gitorious; + return gitorious; +} + +void Gitorious::emitError(const QString &e) +{ + qWarning("%s\n", qPrintable(e)); + emit error(e); +} + +void Gitorious::addHost(const QString &addr, const QString &description) +{ + addHost(GitoriousHost(addr, description)); +} + +void Gitorious::addHost(const GitoriousHost &host) +{ + if (debug) + qDebug() << host; + const int index = m_hosts.size(); + m_hosts.push_back(host); + if (host.categories.empty()) { + updateCategories(index); + m_hosts.back().state = GitoriousHost::ProjectsQueryRunning; + } else { + m_hosts.back().state = GitoriousHost::ProjectsComplete; + } + if (host.projects.empty()) + updateProjectList(index); + emit hostAdded(index); +} + +void Gitorious::removeAt(int index) +{ + m_hosts.removeAt(index); + emit hostRemoved(index); +} + +int Gitorious::findByHostName(const QString &hostName) const +{ + const int size = m_hosts.size(); + for (int i = 0; i < size; i++) + if (m_hosts.at(i).hostName == hostName) + return i; + return -1; +} + +void Gitorious::setHostDescription(int index, const QString &s) +{ + m_hosts[index].description = s; +} + +QString Gitorious::hostDescription(int index) const +{ + return m_hosts.at(index).description; +} + +void Gitorious::listCategoriesReply(int index, QByteArray dataB) +{ + /* For now, parse the HTML of the projects site for "Popular Categories": + * \code + * <h4>Popular Categories:</h4> + * <ul class="..."> + * <li class="..."><a href="..."><category></a> </li> + * \endcode */ + do { + const int catIndex = dataB.indexOf("Popular Categories:"); + const int endIndex = catIndex != -1 ? dataB.indexOf("</ul>", catIndex) : -1; + if (debug) + qDebug() << "listCategoriesReply cat pos=" << catIndex << endIndex; + if (endIndex == -1) + break; + dataB.truncate(endIndex); + dataB.remove(0, catIndex); + const QString data = QString::fromUtf8(dataB); + // Cut out the contents of the anchors + QRegExp pattern = QRegExp(QLatin1String("<a href=[^>]+>([^<]+)</a>")); + Q_ASSERT(pattern.isValid()); + GitoriousHost::CategoryList &categories = m_hosts[index].categories; + for (int pos = pattern.indexIn(data) ; pos != -1; ) { + const QString cat = pattern.cap(1); + categories.push_back(QSharedPointer<GitoriousCategory>(new GitoriousCategory(cat))); + pos = pattern.indexIn(data, pos + pattern.matchedLength()); + } + } while (false); + + emit categoryListReceived(index); +} + +void Gitorious::listProjectsReply(int hostIndex, int page, const QByteArray &data) +{ + // Receive projects. + QString errorMessage; + GitoriousCategory::ProjectList projects = GitoriousProjectReader().read(data, &errorMessage); + + if (debug) { + qDebug() << "listProjectsReply" << hostName(hostIndex) + << "page=" << page << " got" << projects.size(); + if (debug > 1) + qDebug() << '\n' <<data; + } + + if (!errorMessage.isEmpty()) { + emitError(tr("Error parsing reply from '%1': %2").arg(hostName(hostIndex), errorMessage)); + if (projects.empty()) + m_hosts[hostIndex].state = GitoriousHost::Error; + } + + // Add the projects and start next request if 20 projects received + GitoriousCategory::ProjectList &hostProjects = m_hosts[hostIndex].projects; + if (!projects.empty()) + hostProjects.append(projects); + + if (projects.size() == ProjectsPageSize) { + startProjectsRequest(hostIndex, page + 1); + emit projectListPageReceived(hostIndex, page); + } else { + // We are done + m_hosts[hostIndex].state = GitoriousHost::ProjectsComplete; + emit projectListReceived(hostIndex); + } +} + +static inline int replyPage(const QNetworkReply *reply) +{ return reply->property(pagePropertyC).toInt(); } + +void Gitorious::slotReplyFinished() +{ + // Dispatch the answers via dynamic properties + if (QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender())) { + const int protocol = reply->property(protocolPropertyC).toInt(); + // Locate host by name (in case one was deleted in the meantime) + const QString hostName = reply->property(hostNamePropertyC).toString(); + const int hostIndex = findByHostName(hostName); + if (hostIndex == -1) // Entry deleted in-between? + return; + if (reply->error() == QNetworkReply::NoError) { + const QByteArray data = reply->readAll(); + switch (protocol) { + case ListProjectsProtocol: + listProjectsReply(hostIndex, replyPage(reply), data); + break; + case ListCategoriesProtocol: + listCategoriesReply(hostIndex, data); + break; + + } // switch protocol + } else { + const QString msg = tr("Request failed for '%1': %2").arg(m_hosts.at(hostIndex).hostName, reply->errorString()); + emitError(msg); + } + reply->deleteLater(); + } +} + +// Create a network request. Set dynamic properties on it to be able to +// dispatch. Use host name in case an entry is removed in-between +QNetworkReply *Gitorious::createRequest(const QUrl &url, int protocol, int hostIndex, int page) +{ + if (!m_networkManager) + m_networkManager = new QNetworkAccessManager(this); + QNetworkReply *reply = m_networkManager->get(QNetworkRequest(url)); + connect(reply, SIGNAL(finished()), this, SLOT(slotReplyFinished())); + reply->setProperty(protocolPropertyC, QVariant(protocol)); + reply->setProperty(hostNamePropertyC, QVariant(hostName(hostIndex))); + if (page >= 0) + reply->setProperty(pagePropertyC, QVariant(page)); + if (debug) + qDebug() << "createRequest" << url; + return reply; +} + +void Gitorious::updateCategories(int index) +{ + // For now, parse the HTML of the projects site for "Popular Categories": + QUrl url; + url.setScheme(QLatin1String("http")); + url.setHost(hostName(index)); + url.setPath(QLatin1String("/projects")); + createRequest(url, ListCategoriesProtocol, index); +} + +void Gitorious::updateProjectList(int hostIndex) +{ + startProjectsRequest(hostIndex); +} + +void Gitorious::startProjectsRequest(int hostIndex, int page) +{ + const QUrl url = xmlRequest(hostName(hostIndex), QLatin1String("projects"), page); + createRequest(url, ListProjectsProtocol, hostIndex, page); +} + +// Serialize hosts/descriptions as a list of "<host>|descr". +void Gitorious::saveSettings(const QString &group, QSettings *s) +{ + const QChar separator = QLatin1Char('|'); + QStringList hosts; + foreach(const GitoriousHost &h, m_hosts) { + QString entry = h.hostName; + if (!h.description.isEmpty()) { + entry += separator; + entry += h.description; + } + hosts.push_back(entry); + } + s->beginGroup(group); + s->setValue(QLatin1String(settingsKeyC), hosts); + s->endGroup(); +} + +void Gitorious::restoreSettings(const QString &group, const QSettings *s) +{ + m_hosts.clear(); + const QChar separator = QLatin1Char('|'); + const QStringList hosts = s->value(group + QLatin1Char('/') + QLatin1String(settingsKeyC), QStringList()).toStringList(); + foreach (const QString &h, hosts) { + const int sepPos = h.indexOf(separator); + if (sepPos == -1) { + addHost(GitoriousHost(h)); + } else { + addHost(GitoriousHost(h.mid(0, sepPos), h.mid(sepPos + 1))); + } + } +} + +GitoriousHost Gitorious::gitoriousOrg() +{ + return GitoriousHost(QLatin1String("gitorious.org"), tr("Open source projects that use Git.")); +} + +} // namespace Internal +} // namespace Gitorious diff --git a/src/plugins/git/gitorious/gitorious.h b/src/plugins/git/gitorious/gitorious.h new file mode 100644 index 0000000000..a296aba02d --- /dev/null +++ b/src/plugins/git/gitorious/gitorious.h @@ -0,0 +1,177 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#ifndef GITORIOUS_H +#define GITORIOUS_H + +#include <QtCore/QStringList> +#include <QtCore/QSharedPointer> +#include <QtCore/QUrl> +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE +class QNetworkAccessManager; +class QNetworkReply; +class QDebug; +class QUrl; +class QSettings; +QT_END_NAMESPACE + +namespace Gitorious { +namespace Internal { + +struct GitoriousRepository +{ + enum Type { + MainLineRepository, + CloneRepository, + BaselineRepository, // Nokia extension + SharedRepository, // Nokia extension + PersonalRepository, // Nokia extension + }; + + GitoriousRepository(); + + QString name; + QString owner; + QUrl pushUrl; + QUrl cloneUrl; + QString description; + Type type; + int id; +}; + +struct GitoriousProject +{ + QString name; + QString description; + QList<GitoriousRepository> repositories; +}; + +struct GitoriousCategory +{ + typedef QList<QSharedPointer<GitoriousProject > > ProjectList; + + GitoriousCategory(const QString &name = QString()); + + QString name; +}; + +struct GitoriousHost +{ + enum State { ProjectsQueryRunning, ProjectsComplete, Error }; + typedef QList<QSharedPointer<GitoriousCategory> > CategoryList; + typedef QList<QSharedPointer<GitoriousProject > > ProjectList; + + GitoriousHost(const QString &hostName = QString(), const QString &description = QString()); + int findCategory(const QString &) const; + + QString hostName; + QString description; + CategoryList categories; + ProjectList projects; + State state; +}; + +QDebug operator<<(QDebug d, const GitoriousRepository &r); +QDebug operator<<(QDebug d, const GitoriousProject &p); +QDebug operator<<(QDebug d, const GitoriousCategory &p); +QDebug operator<<(QDebug d, const GitoriousHost &p); + +/* Singleton that manages a list of gitorious hosts, running network queries + * in the background. It models hosts with a flat list of projects (Gitorious + * has a concept of categories, but this is not enforced, and there is no + * way to query them). + * As 24.07.2009, the only supported XML request of the host is a paginated + * "list-all-projects". */ + +class Gitorious : public QObject +{ + Q_DISABLE_COPY(Gitorious) + Q_OBJECT + +public: + static Gitorious &instance(); + + const QList<GitoriousHost> &hosts() const { return m_hosts; } + int hostCount() const { return m_hosts.size(); } + int categoryCount(int hostIndex) const { return m_hosts.at(hostIndex).categories.size(); } + int projectCount(int hostIndex) const { return m_hosts.at(hostIndex).projects.size(); } + GitoriousHost::State hostState(int hostIndex) const { return m_hosts.at(hostIndex).state; } + + // If no projects are set, start an asynchronous request querying + // the projects/categories of the host. + void addHost(const QString &addr, const QString &description = QString()); + void addHost(const GitoriousHost &host); + void removeAt(int index); + + int findByHostName(const QString &hostName) const; + QString hostName(int i) const { return m_hosts.at(i).hostName; } + QString categoryName(int hostIndex, int categoryIndex) const { return m_hosts.at(hostIndex).categories.at(categoryIndex)->name; } + + QString hostDescription(int index) const; + void setHostDescription(int index, const QString &s); + + void saveSettings(const QString &group, QSettings *s); + void restoreSettings(const QString &group, const QSettings *s); + + // Return predefined entry for "gitorious.org". + static GitoriousHost gitoriousOrg(); + +signals: + void error(const QString &); + void projectListReceived(int hostIndex); + void projectListPageReceived(int hostIndex, int page); + void categoryListReceived(int index); + void hostAdded(int index); + void hostRemoved(int index); + +public slots: + void updateProjectList(int hostIndex); + void updateCategories(int index); + +private slots: + void slotReplyFinished(); + +private: + Gitorious(); + void listProjectsReply(int hostIndex, int page, const QByteArray &data); + void listCategoriesReply(int index, QByteArray data); + void emitError(const QString &e); + QNetworkReply *createRequest(const QUrl &url, int protocol, int hostIndex, int page = -1); + void startProjectsRequest(int index, int page = 1); + + QList<GitoriousHost> m_hosts; + QNetworkAccessManager *m_networkManager; +}; + +} // namespace Internal +} // namespace Gitorious + +#endif // GITORIOUS_H diff --git a/src/plugins/git/gitorious/gitorious.pri b/src/plugins/git/gitorious/gitorious.pri new file mode 100644 index 0000000000..8678042a26 --- /dev/null +++ b/src/plugins/git/gitorious/gitorious.pri @@ -0,0 +1,22 @@ +QT += network +INCLUDEPATH+=$$PWD + +HEADERS += $$PWD/gitoriousclonewizard.h \ + $$PWD/gitorioushostwizardpage.h \ + $$PWD/gitoriousrepositorywizardpage.h \ + $$PWD/gitoriousprojectwizardpage.h \ + $$PWD/gitoriousprojectwidget.h \ + $$PWD/gitorioushostwidget.h \ + $$PWD/gitorious.h + +SOURCES += $$PWD/gitoriousclonewizard.cpp \ + $$PWD/gitorioushostwizardpage.cpp \ + $$PWD/gitoriousrepositorywizardpage.cpp \ + $$PWD/gitoriousprojectwizardpage.cpp \ + $$PWD/gitoriousprojectwidget.cpp \ + $$PWD/gitorioushostwidget.cpp \ + $$PWD/gitorious.cpp + +FORMS += $$PWD/gitorioushostwidget.ui \ + $$PWD/gitoriousrepositorywizardpage.ui \ + $$PWD/gitoriousprojectwidget.ui diff --git a/src/plugins/git/gitorious/gitoriousclonewizard.cpp b/src/plugins/git/gitorious/gitoriousclonewizard.cpp new file mode 100644 index 0000000000..3a4c309ca0 --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousclonewizard.cpp @@ -0,0 +1,111 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#include "gitoriousclonewizard.h" +#include "gitorioushostwizardpage.h" +#include "gitoriousprojectwizardpage.h" +#include "gitoriousrepositorywizardpage.h" +#include "clonewizardpage.h" + +#include <vcsbase/checkoutjobs.h> +#include <utils/qtcassert.h> + +#include <QtCore/QUrl> +#include <QtGui/QIcon> + +namespace Gitorious { +namespace Internal { + +// GitoriousCloneWizardPage: A git clone page taking its URL from the +// projects page. + +class GitoriousCloneWizardPage : public Git::CloneWizardPage { +public: + explicit GitoriousCloneWizardPage(const GitoriousRepositoryWizardPage *rp, QWidget *parent = 0); + virtual void initializePage(); + +private: + const GitoriousRepositoryWizardPage *m_repositoryPage; +}; + +GitoriousCloneWizardPage::GitoriousCloneWizardPage(const GitoriousRepositoryWizardPage *rp, QWidget *parent) : + Git::CloneWizardPage(parent), + m_repositoryPage(rp) +{ +} + +void GitoriousCloneWizardPage::initializePage() +{ + setRepository(m_repositoryPage->repositoryURL().toString()); +} + +// -------- GitoriousCloneWizard +GitoriousCloneWizard::GitoriousCloneWizard(QObject *parent) : + VCSBase::BaseCheckoutWizard(parent) +{ +} + +QIcon GitoriousCloneWizard::icon() const +{ + return QIcon(); +} + +QString GitoriousCloneWizard::description() const +{ + return tr("Clones a project from a Gitorious repository."); +} + +QString GitoriousCloneWizard::name() const +{ + return tr("Gitorious Repository Clone"); +} + +QList<QWizardPage*> GitoriousCloneWizard::createParameterPages(const QString &path) +{ + GitoriousHostWizardPage *hostPage = new GitoriousHostWizardPage; + GitoriousProjectWizardPage *projectPage = new GitoriousProjectWizardPage(hostPage); + GitoriousRepositoryWizardPage *repoPage = new GitoriousRepositoryWizardPage(projectPage); + GitoriousCloneWizardPage *clonePage = new GitoriousCloneWizardPage(repoPage); + clonePage->setPath(path); + + QList<QWizardPage*> rc; + rc << hostPage << projectPage << repoPage << clonePage; + return rc; +} + +QSharedPointer<VCSBase::AbstractCheckoutJob> GitoriousCloneWizard::createJob(const QList<QWizardPage*> ¶meterPages, + QString *checkoutPath) +{ + const Git::CloneWizardPage *cwp = qobject_cast<const Git::CloneWizardPage *>(parameterPages.back()); + QTC_ASSERT(cwp, return QSharedPointer<VCSBase::AbstractCheckoutJob>()) + return cwp->createCheckoutJob(checkoutPath); +} + +} // namespace Internal +} // namespace Gitorius diff --git a/src/plugins/git/gitorious/gitoriousclonewizard.h b/src/plugins/git/gitorious/gitoriousclonewizard.h new file mode 100644 index 0000000000..0004463b96 --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousclonewizard.h @@ -0,0 +1,60 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#ifndef GITORIOUSCLONEWIZARD_H +#define GITORIOUSCLONEWIZARD_H + +#include <vcsbase/basecheckoutwizard.h> + +namespace Gitorious { +namespace Internal { + +// GitoriousCloneWizard: A wizard allowing for browsing +// Gitorious-hosted projects. +class GitoriousCloneWizard : public VCSBase::BaseCheckoutWizard +{ +public: + explicit GitoriousCloneWizard(QObject *parent = 0); + + // IWizard + virtual QIcon icon() const; + virtual QString description() const; + virtual QString name() const; + +protected: + // BaseCheckoutWizard + virtual QList<QWizardPage*> createParameterPages(const QString &path); + virtual QSharedPointer<VCSBase::AbstractCheckoutJob> createJob(const QList<QWizardPage*> ¶meterPages, + QString *checkoutPath); +}; + +} // namespace Internal +} // namespace Gitorious + +#endif // GITORIOUSCLONEWIZARD_H diff --git a/src/plugins/git/gitorious/gitorioushostwidget.cpp b/src/plugins/git/gitorious/gitorioushostwidget.cpp new file mode 100644 index 0000000000..c01cd4c94c --- /dev/null +++ b/src/plugins/git/gitorious/gitorioushostwidget.cpp @@ -0,0 +1,319 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#include "gitorious.h" +#include "gitorioushostwidget.h" +#include "ui_gitorioushostwidget.h" + +#include <coreplugin/coreconstants.h> + +#include <QtCore/QUrl> +#include <QtCore/QDebug> +#include <QtCore/QTimer> + +#include <QtGui/QStandardItem> +#include <QtGui/QStandardItemModel> +#include <QtGui/QItemSelectionModel> +#include <QtGui/QDesktopServices> +#include <QtGui/QIcon> +#include <QtGui/QStyle> + +enum { debug = 0 }; + +enum { NewDummyEntryRole = Qt::UserRole + 1 }; + +namespace Gitorious { +namespace Internal { + +enum { HostNameColumn, ProjectCountColumn, DescriptionColumn, ColumnCount }; + +// Create a model row for a host. Make the host name editable as specified by +// flag. +static QList<QStandardItem *> hostEntry(const QString &host, + int projectCount, + const QString &description, bool isDummyEntry) +{ + const Qt::ItemFlags nonEditableFlags = (Qt::ItemIsSelectable|Qt::ItemIsEnabled); + const Qt::ItemFlags editableFlags = nonEditableFlags|Qt::ItemIsEditable; + QStandardItem *hostItem = new QStandardItem(host); + hostItem->setFlags(isDummyEntry ? editableFlags : nonEditableFlags); + // Empty for dummy, else "..." or count + QStandardItem *projectCountItem = 0; + QString countItemText; + if (!isDummyEntry) { + countItemText = projectCount ? QString::number(projectCount) : QString(QLatin1String("...")); + } + projectCountItem = new QStandardItem(countItemText); + projectCountItem->setFlags(nonEditableFlags); + QStandardItem *descriptionItem = new QStandardItem(description); + descriptionItem->setFlags(editableFlags); + QList<QStandardItem *> rc; + rc << hostItem << projectCountItem << descriptionItem; + return rc; +} + +static inline QList<QStandardItem *> hostEntry(const GitoriousHost &h) +{ + return hostEntry(h.hostName, h.projects.size(), h.description, false); +} + +GitoriousHostWidget::GitoriousHostWidget(QWidget *parent) : + QWidget(parent), + m_newHost(tr("<New Host>")), + ui(new Ui::GitoriousHostWidget), + m_model(new QStandardItemModel(0, ColumnCount)), + m_errorClearTimer(0), + m_isValid(false), + m_isHostListDirty(false) +{ + ui->setupUi(this); + ui->errorLabel->setVisible(false); + ui->browseToolButton->setIcon(style()->standardIcon(QStyle::SP_MessageBoxInformation)); + connect(ui->browseToolButton, SIGNAL(clicked()), this, SLOT(slotBrowse())); + ui->browseToolButton->setEnabled(false); + ui->deleteToolButton->setIcon(QIcon(Core::Constants::ICON_MINUS)); + connect(ui->deleteToolButton, SIGNAL(clicked()), this, SLOT(slotDelete())); + ui->deleteToolButton->setEnabled(false); + + // Model + QStringList headers; + headers << tr("Host") << tr("Projects") << tr("Description"); + m_model->setHorizontalHeaderLabels(headers); + + Gitorious &gitorious = Gitorious::instance(); + foreach( const GitoriousHost &gh, gitorious.hosts()) + m_model->appendRow(hostEntry(gh)); + appendNewDummyEntry(); + connect(m_model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(slotItemEdited(QStandardItem*))); + ui->hostView->setModel(m_model); + + // View + ui->hostView->setRootIsDecorated(false); + ui->hostView->setUniformRowHeights(true); + connect(ui->hostView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(slotCurrentChanged(QModelIndex,QModelIndex))); + + ui->hostView->setSelectionMode(QAbstractItemView::SingleSelection); + if (m_model->rowCount()) + selectRow(0); + + connect(&gitorious, SIGNAL(projectListPageReceived(int,int)), + this, SLOT(slotProjectListPageReceived(int))); + connect(&gitorious, SIGNAL(projectListReceived(int)), + this, SLOT(slotProjectListPageReceived(int))); + + connect(&gitorious, SIGNAL(error(QString)), this, SLOT(slotError(QString))); + + setMinimumWidth(700); +} + +GitoriousHostWidget::~GitoriousHostWidget() +{ + // Prevent crash? + Gitorious *gitorious = &Gitorious::instance(); + disconnect(gitorious, SIGNAL(projectListPageReceived(int,int)), + this, SLOT(slotProjectListPageReceived(int))); + disconnect(gitorious, SIGNAL(projectListReceived(int)), + this, SLOT(slotProjectListPageReceived(int))); + disconnect(gitorious, SIGNAL(error(QString)), this, SLOT(slotError(QString))); + delete ui; +} + +int GitoriousHostWidget::selectedRow() const +{ + const QModelIndex idx = ui->hostView->selectionModel()->currentIndex(); + if (idx.isValid()) + return idx.row(); + return -1; +} + +void GitoriousHostWidget::selectRow(int r) +{ + if (r >= 0 && r != selectedRow()) { + const QModelIndex index = m_model->index(r, 0); + ui->hostView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select|QItemSelectionModel::Current|QItemSelectionModel::Rows); + } +} + +void GitoriousHostWidget::appendNewDummyEntry() +{ + // Append a new entry where a host name is editable + const QList<QStandardItem *> dummyRow = hostEntry(m_newHost, 0, QString(), true); + dummyRow.front()->setData(QVariant(true), NewDummyEntryRole); + m_model->appendRow(dummyRow); +} + +void GitoriousHostWidget::slotItemEdited(QStandardItem *item) +{ + // Synchronize with Gitorious singleton. + // Did someone enter a valid host name into the dummy item? + // -> Create a new one. + const int row = item->row(); + const bool isDummyEntry = row >= Gitorious::instance().hostCount(); + switch (item->column()) { + case HostNameColumn: + if (isDummyEntry) { + Gitorious::instance().addHost(item->text(), m_model->item(row, DescriptionColumn)->text()); + item->setData(QVariant(false), NewDummyEntryRole); + m_isHostListDirty = true; + appendNewDummyEntry(); + selectRow(row); + } + break; + case ProjectCountColumn: + break; + case DescriptionColumn: + if (!isDummyEntry) { + const QString description = item->text(); + if (description != Gitorious::instance().hostDescription(row)) { + Gitorious::instance().setHostDescription(row, item->text()); + m_isHostListDirty = true; + } + } + break; + } +} + +void GitoriousHostWidget::slotProjectListPageReceived(int row) +{ + if (debug) + qDebug() << Q_FUNC_INFO << row; + // Update column + const int projectCount = Gitorious::instance().projectCount(row); + m_model->item(row, ProjectCountColumn)->setText(QString::number(projectCount)); + // If it is the currently selected host, re-check validity if not enabled + if (!m_isValid) { + const QModelIndex current = ui->hostView->selectionModel()->currentIndex(); + if (current.isValid() && current.row() == row) + checkValid(current); + } +} + +QStandardItem *GitoriousHostWidget::currentItem() const +{ + const QModelIndex idx = ui->hostView->selectionModel()->currentIndex(); + if (idx.isValid()) + return m_model->itemFromIndex(idx.column() != 0 ? idx.sibling(idx.row(), 0) : idx); + return 0; +} + +void GitoriousHostWidget::slotBrowse() +{ + if (const QStandardItem *item = currentItem()) { + const QUrl url(QLatin1String("http://") + item->text() + QLatin1Char('/')); + if (url.isValid()) + QDesktopServices::openUrl(url); + } +} + +void GitoriousHostWidget::slotDelete() +{ + const QModelIndex index = ui->hostView->selectionModel()->currentIndex(); + ui->hostView->selectionModel()->clear(); + if (index.isValid()) { + const int row = index.row(); + qDeleteAll(m_model->takeRow(row)); + Gitorious::instance().removeAt(row); + m_isHostListDirty = true; + } +} + +void GitoriousHostWidget::slotCurrentChanged(const QModelIndex ¤t, const QModelIndex & /* previous */) +{ + checkValid(current); +} + +void GitoriousHostWidget::checkValid(const QModelIndex &index) +{ + if (debug) + qDebug() << Q_FUNC_INFO << index; + bool hasSelectedHost = false; + bool hasProjects = false; + if (index.isValid()) { + // Are we on the new dummy item? + Gitorious &gitorious = Gitorious::instance(); + const int row = index.row(); + hasSelectedHost = row < gitorious.hostCount(); + hasProjects = hasSelectedHost && gitorious.projectCount(row) > 0; + } + ui->deleteToolButton->setEnabled(hasSelectedHost); + ui->browseToolButton->setEnabled(hasSelectedHost); + + const bool valid = hasSelectedHost && hasProjects; + if (valid != m_isValid) { + m_isValid = valid; + emit validChanged(); + } +} + +bool GitoriousHostWidget::isValid() const +{ + return m_isValid; +} + +bool GitoriousHostWidget::isHostListDirty() const +{ + return m_isHostListDirty; +} + +void GitoriousHostWidget::slotClearError() +{ + ui->errorLabel->setVisible(false); + ui->errorLabel->clear(); +} + +void GitoriousHostWidget::slotError(const QString &e) +{ + // Display error for a while + ui->errorLabel->setText(e); + ui->errorLabel->setVisible(true); + if (!m_errorClearTimer) { + m_errorClearTimer = new QTimer(this); + m_errorClearTimer->setSingleShot(true); + m_errorClearTimer->setInterval(5000); + connect(m_errorClearTimer, SIGNAL(timeout()), this, SLOT(slotClearError())); + } + if (!m_errorClearTimer->isActive()) + m_errorClearTimer->start(); +} + +void GitoriousHostWidget::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +} // namespace Internal +} // namespace Gitorious diff --git a/src/plugins/git/gitorious/gitorioushostwidget.h b/src/plugins/git/gitorious/gitorioushostwidget.h new file mode 100644 index 0000000000..961ae68bbe --- /dev/null +++ b/src/plugins/git/gitorious/gitorioushostwidget.h @@ -0,0 +1,103 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#ifndef GITORIOUSHOSTWIDGET_H +#define GITORIOUSHOSTWIDGET_H + +#include <QtGui/QWizardPage> +#include <QtGui/QStandardItemModel> + +QT_BEGIN_NAMESPACE +class QStandardItemModel; +class QStandardItem; +class QModelIndex; +class QTimer; + +QT_END_NAMESPACE + +namespace Gitorious { +namespace Internal { + +namespace Ui { + class GitoriousHostWidget; +} + +/* A page listing gitorious hosts with browse/add options. isValid() and the + * related change signals are provided for use within a QWizardPage. + * Connects to the signals of Gitorious and updates the project count as the + * it receives the projects. As soon as there are projects, isValid() becomes + * true. */ + +class GitoriousHostWidget : public QWidget { + Q_OBJECT +public: + GitoriousHostWidget(QWidget *parent = 0); + ~GitoriousHostWidget(); + + // Has a host selected that has projects. + bool isValid() const; + int selectedRow() const; + // hosts modified? + bool isHostListDirty() const; + +signals: + void validChanged(); + +public slots: + void selectRow(int); + +protected: + void changeEvent(QEvent *e); + +private slots: + void slotBrowse(); + void slotDelete(); + void slotCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); + void slotItemEdited(QStandardItem *item); + void slotProjectListPageReceived(int row); + void slotClearError(); + void slotError(const QString &e); + +private: + void appendNewDummyEntry(); + void checkValid(const QModelIndex ¤t); + QStandardItem *currentItem() const; + + const QString m_newHost; + + Ui::GitoriousHostWidget *ui; + QStandardItemModel *m_model; + QTimer *m_errorClearTimer; + bool m_isValid; + bool m_isHostListDirty; +}; + +} // namespace Internal +} // namespace Gitorious +#endif // GITORIOUSHOSTWIDGET_H diff --git a/src/plugins/git/gitorious/gitorioushostwidget.ui b/src/plugins/git/gitorious/gitorioushostwidget.ui new file mode 100644 index 0000000000..e9896977ec --- /dev/null +++ b/src/plugins/git/gitorious/gitorioushostwidget.ui @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Gitorious::Internal::GitoriousHostWidget</class> + <widget class="QWidget" name="Gitorious::Internal::GitoriousHostWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>356</width> + <height>265</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QTreeView" name="hostView"/> + </item> + <item> + <layout class="QVBoxLayout" name="buttonLayout"> + <item> + <widget class="QToolButton" name="browseToolButton"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="deleteToolButton"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <spacer name="buttonSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="errorLabel"> + <property name="styleSheet"> + <string notr="true">background-color: red;</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/git/gitorious/gitorioushostwizardpage.cpp b/src/plugins/git/gitorious/gitorioushostwizardpage.cpp new file mode 100644 index 0000000000..82a1c61a7d --- /dev/null +++ b/src/plugins/git/gitorious/gitorioushostwizardpage.cpp @@ -0,0 +1,100 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + + +#include "gitorioushostwizardpage.h" +#include "gitorioushostwidget.h" +#include "gitorious.h" + +#include <coreplugin/icore.h> + +#include <QtCore/QSettings> +#include <QtGui/QVBoxLayout> + +static const char *settingsGroupC = "Gitorious"; +static const char *selectionKeyC = "/SelectedHost"; + +namespace Gitorious { +namespace Internal { + +// Ensure Gitorious is populated and create widget in right order. +static GitoriousHostWidget *createHostWidget() +{ + // First time? Populate gitorious from settings. + // If there is still no host, add "gitorious.org" + Gitorious &gitorious = Gitorious::instance(); + const QSettings *settings = Core::ICore::instance()->settings(); + const QString group = QLatin1String(settingsGroupC); + if (!gitorious.hostCount()) { + gitorious.restoreSettings(group, settings); + if (!gitorious.hostCount()) + gitorious.addHost(Gitorious::gitoriousOrg()); + } + // Now create widget + GitoriousHostWidget *rc = new GitoriousHostWidget; + // Restore selection + const int selectedRow = settings->value(group + QLatin1String(selectionKeyC)).toInt(); + if (selectedRow >= 0 && selectedRow < gitorious.hostCount()) + rc->selectRow(selectedRow); + return rc; +} + +GitoriousHostWizardPage::GitoriousHostWizardPage(QWidget *parent) : + QWizardPage(parent), + m_widget(createHostWidget()) +{ + connect(m_widget, SIGNAL(validChanged()), this, SIGNAL(completeChanged())); + QVBoxLayout *lt = new QVBoxLayout; + lt->addWidget(m_widget); + setLayout(lt); + setSubTitle(tr("Select a host.")); +} + +GitoriousHostWizardPage::~GitoriousHostWizardPage() +{ + // Write out settings + selected row. + QSettings *settings = Core::ICore::instance()->settings(); + if (m_widget->isHostListDirty()) + Gitorious::instance().saveSettings(QLatin1String(settingsGroupC), settings); + if (m_widget->isValid()) + settings->setValue(QLatin1String(settingsGroupC) + QLatin1String(selectionKeyC), m_widget->selectedRow()); +} + +bool GitoriousHostWizardPage::isComplete() const +{ + return m_widget->isValid(); +} + +int GitoriousHostWizardPage::selectedHostIndex() const +{ + return m_widget->selectedRow(); +} + +} // namespace Internal +} // namespace Gitorious diff --git a/src/plugins/git/gitorious/gitorioushostwizardpage.h b/src/plugins/git/gitorious/gitorioushostwizardpage.h new file mode 100644 index 0000000000..025c69f772 --- /dev/null +++ b/src/plugins/git/gitorious/gitorioushostwizardpage.h @@ -0,0 +1,58 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#ifndef GITORIOUSHOSTWIZARDPAGE_H +#define GITORIOUSHOSTWIZARDPAGE_H + +#include <QtGui/QWizardPage> + +namespace Gitorious { +namespace Internal { + +class GitoriousHostWidget; + +/* A page listing gitorious hosts with browse/add options. */ + +class GitoriousHostWizardPage : public QWizardPage { + Q_OBJECT +public: + GitoriousHostWizardPage(QWidget *parent = 0); + virtual ~GitoriousHostWizardPage(); + + virtual bool isComplete() const; + + int selectedHostIndex() const; + +private: + GitoriousHostWidget *m_widget; +}; + +} // namespace Internal +} // namespace Gitorious +#endif // GITORIOUSHOSTWIZARDPAGE_H diff --git a/src/plugins/git/gitorious/gitoriousprojectswizardwidget.cpp b/src/plugins/git/gitorious/gitoriousprojectswizardwidget.cpp new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousprojectswizardwidget.cpp diff --git a/src/plugins/git/gitorious/gitoriousprojectwidget.cpp b/src/plugins/git/gitorious/gitoriousprojectwidget.cpp new file mode 100644 index 0000000000..6894214cb3 --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousprojectwidget.cpp @@ -0,0 +1,304 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#include "gitoriousprojectwidget.h" +#include "gitorioushostwizardpage.h" +#include "gitorious.h" +#include "ui_gitoriousprojectwidget.h" + +#include <coreplugin/coreconstants.h> +#include <utils/qtcassert.h> + +#include <QtCore/QRegExp> +#include <QtCore/QDebug> + +#include <QtGui/QStandardItemModel> +#include <QtGui/QSortFilterProxyModel> +#include <QtGui/QStandardItem> +#include <QtGui/QItemSelectionModel> +#include <QtGui/QDesktopServices> +#include <QtGui/QIcon> +#include <QtGui/QStyle> + +enum { + urlRole = Qt::UserRole + 1 // Project has a URL in the description +}; + +enum { debug = 1 }; + +namespace Gitorious { +namespace Internal { + +enum { ProjectColumn, DescriptionColumn, ColumnCount }; + +GitoriousProjectWidget::GitoriousProjectWidget(int hostIndex, + QWidget *parent) : + QWidget(parent), + m_hostName(Gitorious::instance().hostName(hostIndex)), + ui(new Ui::GitoriousProjectWidget), + m_model(new QStandardItemModel(0, ColumnCount, this)), + m_filterModel(new QSortFilterProxyModel), + m_valid(false) +{ + ui->setupUi(this); + ui->infoToolButton->setIcon(style()->standardIcon(QStyle::SP_MessageBoxInformation)); + ui->infoToolButton->setEnabled(false); + connect(ui->infoToolButton, SIGNAL(clicked()), this, SLOT(slotInfo())); + // Filter + connect(ui->filterLineEdit, SIGNAL(textChanged(QString)), m_filterModel, SLOT(setFilterFixedString(QString))); + ui->filterClearButton->setIcon(QIcon(Core::Constants::ICON_RESET)); + connect(ui->filterClearButton, SIGNAL(clicked()), ui->filterLineEdit, SLOT(clear())); + // Updater + ui->updateCheckBox->setChecked(true); + if (Gitorious::instance().hostState(hostIndex) != GitoriousHost::ProjectsQueryRunning) + ui->updateCheckBox->setVisible(false); + connect(ui->updateCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateCheckBoxChanged(int))); + // Model + QStringList headers; + headers << tr("Project") << tr("Description"); + m_model->setHorizontalHeaderLabels(headers); + // Populate the model + slotUpdateProjects(hostIndex); + // Filter on all columns + m_filterModel->setSourceModel(m_model); + m_filterModel->setFilterKeyColumn(-1); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); + ui->projectTreeView->setModel(m_filterModel); + // View + ui->projectTreeView->setAlternatingRowColors(true); + ui->projectTreeView->setRootIsDecorated(false); + ui->projectTreeView->setUniformRowHeights(true); + ui->projectTreeView->setSortingEnabled(true); + connect(ui->projectTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(slotCurrentChanged(QModelIndex,QModelIndex))); + ui->projectTreeView->setSelectionMode(QAbstractItemView::SingleSelection); + // Select first, resize columns + if (Gitorious::instance().projectCount(hostIndex)) { + for (int r = 0; r < ColumnCount; r++) + ui->projectTreeView->resizeColumnToContents(r); + // Select first + const QModelIndex index = m_filterModel->index(0, 0); + ui->projectTreeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select|QItemSelectionModel::Current|QItemSelectionModel::Rows); + } + + // Continuous update + Gitorious *gitorious = &Gitorious::instance(); + connect(gitorious, SIGNAL(projectListPageReceived(int,int)), this, SLOT(slotUpdateProjects(int))); + connect(gitorious, SIGNAL(projectListReceived(int)), this, SLOT(slotUpdateProjects(int))); +} + +GitoriousProjectWidget::~GitoriousProjectWidget() +{ + Gitorious *gitorious = &Gitorious::instance(); + disconnect(gitorious, SIGNAL(projectListPageReceived(int,int)), this, SLOT(slotUpdateProjects(int))); + disconnect(gitorious, SIGNAL(projectListReceived(int)), this, SLOT(slotUpdateProjects(int))); + delete ui; +} + +// Map indexes back via filter +QStandardItem *GitoriousProjectWidget::itemFromIndex(const QModelIndex &index) const +{ + if (index.isValid()) + return m_model->itemFromIndex(m_filterModel->mapToSource(index)); + return 0; +} + +QStandardItem *GitoriousProjectWidget::currentItem() const +{ + return itemFromIndex(ui->projectTreeView->selectionModel()->currentIndex()); +} + +void GitoriousProjectWidget::slotCurrentChanged(const QModelIndex ¤t, const QModelIndex & /* previous */) +{ + // Any info URL to show? + QString url; + if (current.isValid()) + if (QStandardItem *item = itemFromIndex(current)) { + // Project: URL in description? + const QVariant urlV = item->data(urlRole); + if (urlV.isValid()) + url = urlV.toString(); + } + + ui->infoToolButton->setEnabled(!url.isEmpty()); + ui->infoToolButton->setToolTip(url); + + const bool isValid = current.isValid(); + if (isValid != m_valid) { + m_valid = isValid; + emit validChanged(); + } +} + +void GitoriousProjectWidget::slotInfo() +{ + if (const QStandardItem *item = currentItem()) { + const QVariant url = item->data(urlRole); + if (url.isValid()) + QDesktopServices::openUrl(QUrl(url.toString())); + } +} + +// Create a model row for a project +static inline QList<QStandardItem *> projectEntry(const GitoriousProject &p) +{ + enum { maxNameLength = 30 }; + // Truncate names with colons + QString name = p.name; + const int colonPos = name.indexOf(QLatin1Char(':')); + if (colonPos != -1) + name.truncate(colonPos); + if (name.size() > maxNameLength) { + name.truncate(maxNameLength); + name += QLatin1String("..."); + } + QStandardItem *nameItem = new QStandardItem(name); + nameItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); + // Description + QStandardItem *descriptionItem = new QStandardItem; + descriptionItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); + QList<QStandardItem *> rc; + rc << nameItem << descriptionItem; + // Should the text contain an URL, store it under 'urlRole' for the info button + QString url; + GitoriousProjectWidget::setDescription(p.description, DescriptionColumn, &rc, &url); + if (!url.isEmpty()) { + const QVariant urlV = QVariant(url); + nameItem->setData(urlV, urlRole); + descriptionItem->setData(urlV, urlRole); + } + return rc; +} + +// Utility to set description column and tooltip for a row from a free +// format/HTMLish gitorious description. Make sure the description is just one +// row for the item and set a tooltip with full contents. If desired, extract +// an URL. + +void GitoriousProjectWidget::setDescription(const QString &description, + int descriptionColumn, + QList<QStandardItem *> *items, + QString *url /* =0 */) +{ + enum { MaxDescriptionLineLength = 70 }; + // Trim description to 1 sensibly long line for the item view + QString descLine = description; + const int newLinePos = descLine.indexOf(QLatin1Char('\n')); + if (newLinePos != -1) + descLine.truncate(newLinePos); + if (descLine.size() > MaxDescriptionLineLength) { + const int dotPos = descLine.lastIndexOf(QLatin1Char('.'), MaxDescriptionLineLength); + if (dotPos != -1) { + descLine.truncate(dotPos); + } else { + descLine.truncate(MaxDescriptionLineLength); + } + descLine += QLatin1String("..."); + } + items->at(descriptionColumn)->setText(descLine); + // Set a HTML tooltip to make lines wrap and the markup sprinkled within work + const QString htmlTip = QLatin1String("<html><body>") + description + QLatin1String("</body></html>"); + const int size = items->size(); + for (int i = 0; i < size; i++) + items->at(i)->setToolTip(htmlTip); + if (url) { + // Should the text contain an URL, extract + // Do not fall for "(http://XX)", strip special characters + static const QRegExp urlRegExp(QLatin1String("(http://[\\w\\.-]+/[a-zA-Z0-9/\\-&]*)")); + Q_ASSERT(urlRegExp.isValid()); + if (urlRegExp.indexIn(description) != -1) { + *url= urlRegExp.cap(1); + } else { + url->clear(); + } + } +} + +void GitoriousProjectWidget::grabFocus() +{ + ui->projectTreeView->setFocus(); +} + +void GitoriousProjectWidget::slotUpdateCheckBoxChanged(int state) +{ + if (state == Qt::Checked) + slotUpdateProjects(Gitorious::instance().findByHostName(m_hostName)); +} + +void GitoriousProjectWidget::slotUpdateProjects(int hostIndex) +{ + if (!ui->updateCheckBox->isChecked()) + return; + const Gitorious &gitorious = Gitorious::instance(); + // Complete list of projects + if (m_hostName != gitorious.hostName(hostIndex)) + return; + // Fill in missing projects + const GitoriousHost::ProjectList &projects = gitorious.hosts().at(hostIndex).projects; + const int size = projects.size(); + for (int i = m_model->rowCount(); i < size; i++) + m_model->appendRow(projectEntry(*projects.at(i))); + if (gitorious.hostState(hostIndex) == GitoriousHost::ProjectsComplete) + ui->updateCheckBox->setVisible(false); +} + +bool GitoriousProjectWidget::isValid() const +{ + return m_valid; +} + +int GitoriousProjectWidget::hostIndex() const +{ + return Gitorious::instance().findByHostName(m_hostName); +} + +QSharedPointer<GitoriousProject> GitoriousProjectWidget::project() const +{ + if (const QStandardItem *item = currentItem()) { + const int projectIndex = item->row(); + return Gitorious::instance().hosts().at(hostIndex()).projects.at(projectIndex); + } + return QSharedPointer<GitoriousProject>(new GitoriousProject); +} + +void GitoriousProjectWidget::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +} // namespace Internal +} // namespace Gitorious diff --git a/src/plugins/git/gitorious/gitoriousprojectwidget.h b/src/plugins/git/gitorious/gitoriousprojectwidget.h new file mode 100644 index 0000000000..6151b381e1 --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousprojectwidget.h @@ -0,0 +1,113 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#ifndef GITORIOUSPROJECTWIDGET_H +#define GITORIOUSPROJECTWIDGET_H + +#include <QtCore/QSharedPointer> +#include <QtGui/QWidget> + +QT_BEGIN_NAMESPACE +class QStandardItemModel; +class QStandardItem; +class QModelIndex; +class QSortFilterProxyModel; +QT_END_NAMESPACE + +namespace Gitorious { +namespace Internal { + +class GitoriousHostWizardPage; +class GitoriousProject; + +namespace Ui { + class GitoriousProjectWidget; +} + +/* Let the user select a project from a host. Displays name and description + * with tooltip and info button that opens URLs contained in the description. + * Connects to the signals of Gitorious and updates the project list as the + * it receives the projects. isValid() and signal validChanged are + * provided for use in a QWizardPage. Host matching happens via name as the + * hostIndex might change due to deleting hosts. */ +class GitoriousProjectWidget : public QWidget { + Q_OBJECT +public: + explicit GitoriousProjectWidget(int hostIndex, + QWidget *parent = 0); + ~GitoriousProjectWidget(); + + virtual bool isValid() const; + + QSharedPointer<GitoriousProject> project() const; + + QString hostName() const { return m_hostName; } + int hostIndex() const; + + // Utility to set description column and tooltip for a row from a free + // format/HTMLish gitorious description. Make sure the description is + // just one row for the item and set a tooltip with full contents. + // If desired, extract an URL. + static void setDescription(const QString &description, + int descriptionColumn, + QList<QStandardItem *> *items, + QString *url = 0); + +signals: + void validChanged(); + +public slots: + void grabFocus(); + +private slots: + void slotCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); + void slotInfo(); + void slotUpdateProjects(int hostIndex); + void slotUpdateCheckBoxChanged(int); + +protected: + void changeEvent(QEvent *e); + +private: + QStandardItem *itemFromIndex(const QModelIndex &idx) const; + QStandardItem *currentItem() const; + + const QString m_hostName; + + Ui::GitoriousProjectWidget *ui; + const GitoriousHostWizardPage *m_hostPage; + QStandardItemModel *m_model; + QSortFilterProxyModel *m_filterModel; + bool m_valid; +}; + + +} // namespace Internal +} // namespace Gitorious +#endif // GITORIOUSPROJECTWIDGET_H diff --git a/src/plugins/git/gitorious/gitoriousprojectwidget.ui b/src/plugins/git/gitorious/gitoriousprojectwidget.ui new file mode 100644 index 0000000000..f478c125b6 --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousprojectwidget.ui @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Gitorious::Internal::GitoriousProjectWidget</class> + <widget class="QWidget" name="Gitorious::Internal::GitoriousProjectWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>WizardPage</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="filterLabel"> + <property name="text"> + <string>Filter:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="filterLineEdit"/> + </item> + <item> + <widget class="QToolButton" name="filterClearButton"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QTreeView" name="projectTreeView"/> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QToolButton" name="infoToolButton"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <spacer name="buttonVerticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="updateCheckBox"> + <property name="text"> + <string>Keep updating</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/git/gitorious/gitoriousprojectwizardpage.cpp b/src/plugins/git/gitorious/gitoriousprojectwizardpage.cpp new file mode 100644 index 0000000000..c36915d4fe --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousprojectwizardpage.cpp @@ -0,0 +1,133 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#include "gitoriousprojectwizardpage.h" +#include "gitoriousprojectwidget.h" +#include "gitorioushostwizardpage.h" +#include "gitorious.h" + +#include <utils/qtcassert.h> + +#include <QtGui/QStackedWidget> +#include <QtGui/QVBoxLayout> + +namespace Gitorious { +namespace Internal { + +GitoriousProjectWizardPage::GitoriousProjectWizardPage(const GitoriousHostWizardPage *hostPage, + QWidget *parent) : + QWizardPage(parent), + m_hostPage(hostPage), + m_stackedWidget(new QStackedWidget), + m_isValid(false) +{ + QVBoxLayout *lt = new QVBoxLayout; + lt->addWidget(m_stackedWidget); + setLayout(lt); +} + +static inline QString msgChooseProject(const QString &h) +{ + return GitoriousProjectWizardPage::tr("Choose a project from '%1'").arg((h)); +} + +QString GitoriousProjectWizardPage::selectedHostName() const +{ + if (const GitoriousProjectWidget *w = currentProjectWidget()) + return w->hostName(); + return QString(); +} + +void GitoriousProjectWizardPage::initializePage() +{ + // Try to find the page by hostindex + const int hostIndex = m_hostPage->selectedHostIndex(); + const int existingStackIndex = hostIndexToStackIndex(hostIndex); + // Found? - pop up that page + if (existingStackIndex != -1) { + m_stackedWidget->setCurrentIndex(existingStackIndex); + setSubTitle(msgChooseProject(selectedHostName())); + return; + } + // Add a new page + GitoriousProjectWidget *widget = new GitoriousProjectWidget(hostIndex); + connect(widget, SIGNAL(validChanged()), this, SLOT(slotCheckValid())); + m_stackedWidget->addWidget(widget); + m_stackedWidget->setCurrentIndex(m_stackedWidget->count() - 1); + setSubTitle(msgChooseProject(widget->hostName())); + slotCheckValid(); +} + +bool GitoriousProjectWizardPage::isComplete() const +{ + return m_isValid; +} + +void GitoriousProjectWizardPage::slotCheckValid() +{ + const GitoriousProjectWidget *w = currentProjectWidget(); + const bool isValid = w ? w->isValid() : false; + if (isValid != m_isValid) { + m_isValid = isValid; + emit completeChanged(); + } +} + +QSharedPointer<GitoriousProject> GitoriousProjectWizardPage::project() const +{ + if (const GitoriousProjectWidget *w = currentProjectWidget()) + return w->project(); + return QSharedPointer<GitoriousProject>(); +} + +GitoriousProjectWidget *GitoriousProjectWizardPage::projectWidgetAt(int index) const +{ + return qobject_cast<GitoriousProjectWidget *>(m_stackedWidget->widget(index)); +} + +GitoriousProjectWidget *GitoriousProjectWizardPage::currentProjectWidget() const +{ + const int index = m_stackedWidget->currentIndex(); + if (index < 0) + return 0; + return projectWidgetAt(index); +} + +// Convert a host index to a stack index. +int GitoriousProjectWizardPage::hostIndexToStackIndex(int hostIndex) const +{ + const int count = m_stackedWidget->count(); + for(int i = 0; i < count; i++) + if (projectWidgetAt(i)->hostIndex() == hostIndex) + return i; + return -1; +} + +} // namespace Internal +} // namespace Gitorious diff --git a/src/plugins/git/gitorious/gitoriousprojectwizardpage.h b/src/plugins/git/gitorious/gitoriousprojectwizardpage.h new file mode 100644 index 0000000000..abe4b3f2d2 --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousprojectwizardpage.h @@ -0,0 +1,87 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#ifndef GITORIOUSPROJECTWIZARDPAGE_H +#define GITORIOUSPROJECTWIZARDPAGE_H + +#include <QtCore/QSharedPointer> +#include <QtGui/QWizardPage> + +QT_BEGIN_NAMESPACE +class QStackedWidget; +QT_END_NAMESPACE + +namespace Gitorious { +namespace Internal { + +class GitoriousHostWizardPage; +class GitoriousProject; +class GitoriousProjectWidget; + +namespace Ui { + class GitoriousProjectWizardPage; +} + +/* GitoriousProjectWizardPage: Let the user select a project via + * GitoriousProjectWidget. As switching back and forth hosts (repopulating + * the sorting projects model/treeviews) might get slow when the host has + * lots of projects, it manages a stack of project widgets and activates + * the one selected in the host page (or creates a new one) in + * initializePage. */ + +class GitoriousProjectWizardPage : public QWizardPage { + Q_OBJECT +public: + explicit GitoriousProjectWizardPage(const GitoriousHostWizardPage *hostPage, + QWidget *parent = 0); + + virtual void initializePage(); + virtual bool isComplete() const; + + QSharedPointer<GitoriousProject> project() const; + int selectedHostIndex() const; + QString selectedHostName() const; + +private slots: + void slotCheckValid(); + +private: + GitoriousProjectWidget *projectWidgetAt(int index) const; + GitoriousProjectWidget *currentProjectWidget() const; + int hostIndexToStackIndex(int hostIndex) const; + + const GitoriousHostWizardPage *m_hostPage; + QStackedWidget *m_stackedWidget; + bool m_isValid; + +}; + +} // namespace Internal +} // namespace Gitorious +#endif // GITORIOUSPROJECTWIZARDPAGE_H diff --git a/src/plugins/git/gitorious/gitoriousrepositorywizardpage.cpp b/src/plugins/git/gitorious/gitoriousrepositorywizardpage.cpp new file mode 100644 index 0000000000..bbe11e5b3e --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousrepositorywizardpage.cpp @@ -0,0 +1,211 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#include "gitoriousrepositorywizardpage.h" +#include "gitoriousprojectwizardpage.h" +#include "gitoriousprojectwidget.h" +#include "gitorious.h" +#include "ui_gitoriousrepositorywizardpage.h" + +#include <utils/qtcassert.h> + +#include <QtCore/QDebug> + +#include <QtGui/QStandardItemModel> +#include <QtGui/QStandardItem> +#include <QtGui/QItemSelectionModel> + +enum { TypeRole = Qt::UserRole + 1}; +enum { HeaderType, RepositoryType }; + +enum { debug = 0 }; + +namespace Gitorious { +namespace Internal { + +enum { RepositoryColumn, OwnerColumn, DescriptionColumn, ColumnCount }; + +GitoriousRepositoryWizardPage::GitoriousRepositoryWizardPage(const GitoriousProjectWizardPage *projectPage, + QWidget *parent) : + QWizardPage(parent), + ui(new Ui::GitoriousRepositoryWizardPage), + m_projectPage(projectPage), + m_model(new QStandardItemModel(0, ColumnCount)), + m_valid(false) +{ + QStringList headers; + headers << tr("Name") << tr("Owner") << tr("Description"); + m_model->setHorizontalHeaderLabels(headers); + + ui->setupUi(this); + ui->repositoryTreeView->setModel(m_model); + ui->repositoryTreeView->setUniformRowHeights(true); + ui->repositoryTreeView->setAlternatingRowColors(true); + ui->repositoryTreeView->setSelectionMode(QAbstractItemView::SingleSelection); + connect(ui->repositoryTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(slotCurrentChanged(QModelIndex,QModelIndex))); +} + +GitoriousRepositoryWizardPage::~GitoriousRepositoryWizardPage() +{ + delete ui; +} + +bool gitRepoLessThanByType(const GitoriousRepository &r1, const GitoriousRepository &r2) +{ + return r1.type < r2.type; +} + +static inline QList<QStandardItem *> headerEntry(const QString &h) +{ + QStandardItem *nameItem = new QStandardItem(h); + nameItem->setFlags(Qt::ItemIsEnabled); + nameItem->setData(QVariant(HeaderType), TypeRole); + QStandardItem *ownerItem = new QStandardItem; + ownerItem->setFlags(Qt::ItemIsEnabled); + ownerItem->setData(QVariant(HeaderType), TypeRole); + QStandardItem *descriptionItem = new QStandardItem; + descriptionItem->setFlags(Qt::ItemIsEnabled); + descriptionItem->setData(QVariant(HeaderType), TypeRole); + QList<QStandardItem *> rc; + rc << nameItem << ownerItem << descriptionItem; + return rc; +} + +static inline QList<QStandardItem *> repositoryEntry(const GitoriousRepository &r) +{ + QStandardItem *nameItem = new QStandardItem(r.name); + nameItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); + nameItem->setData(QVariant(RepositoryType), TypeRole); + QStandardItem *ownerItem = new QStandardItem(r.owner); + ownerItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); + ownerItem->setData(QVariant(RepositoryType), TypeRole); + QStandardItem *descriptionItem = new QStandardItem; + descriptionItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); + descriptionItem->setData(QVariant(RepositoryType), TypeRole); + QList<QStandardItem *> rc; + rc << nameItem << ownerItem << descriptionItem; + GitoriousProjectWidget::setDescription(r.description, DescriptionColumn, &rc); + return rc; +} + +void GitoriousRepositoryWizardPage::initializePage() +{ + // Populate the model + ui->repositoryTreeView->selectionModel()->clearSelection(); + if (const int oldRowCount = m_model->rowCount()) + m_model->removeRows(0, oldRowCount); + // fill model + const QSharedPointer<GitoriousProject> proj = m_projectPage->project(); + setSubTitle(tr("Choose a repository of the project '%1'.").arg(proj->name)); + // Create a hierarchical list by repository type, sort by type + QList<GitoriousRepository> repositories = proj->repositories; + QStandardItem *firstEntry = 0; + if (!repositories.empty()) { + int lastRepoType = -1; + QStandardItem *header = 0; + qStableSort(repositories.begin(), repositories.end(), gitRepoLessThanByType); + const QString types[GitoriousRepository::PersonalRepository + 1] = + { tr("Mainline Repositories"), tr("Clones"), tr("Baseline Repositories"), tr("Shared Project Repositories"), tr("Personal Repositories") }; + foreach(const GitoriousRepository &r, repositories) { + // New Header? + if (r.type != lastRepoType || !header) { + lastRepoType = r.type; + const QList<QStandardItem *> headerRow = headerEntry(types[r.type]); + m_model->appendRow(headerRow); + header = headerRow.front(); + } + // Repository row + const QList<QStandardItem *> row = repositoryEntry(r); + header->appendRow(row); + if (!firstEntry) + firstEntry = row.front(); + } + } + ui->repositoryTreeView->expandAll(); + for (int r = 0; r < ColumnCount; r++) + ui->repositoryTreeView->resizeColumnToContents(r); + // Select first + if (firstEntry) { + const QModelIndex idx = m_model->indexFromItem(firstEntry); + ui->repositoryTreeView->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::Select|QItemSelectionModel::Current|QItemSelectionModel::Rows); + } +} + +void GitoriousRepositoryWizardPage::slotCurrentChanged(const QModelIndex ¤t, const QModelIndex & /*previous */) +{ + const QStandardItem *item = current.isValid() ? m_model->itemFromIndex(current) : static_cast<const QStandardItem *>(0); + const bool isValid = item && item->data(TypeRole).toInt() == RepositoryType; + if (isValid != m_valid) { + m_valid = isValid; + emit completeChanged(); + } +} + +QString GitoriousRepositoryWizardPage::repositoryName() const +{ + const QModelIndex idx = ui->repositoryTreeView->selectionModel()->currentIndex(); + if (idx.isValid()) { + const QModelIndex sibling0 = idx.column() ? idx.sibling(idx.row(), 0) : idx; + if (const QStandardItem *item = m_model->itemFromIndex(sibling0)) + if (item->data(TypeRole).toInt() == RepositoryType) + return item->text(); + } + return QString(); +} + +QUrl GitoriousRepositoryWizardPage::repositoryURL() const +{ + // Find by name (as we sorted the the repositories) + const QString repoName = repositoryName(); + foreach (const GitoriousRepository &r, m_projectPage->project()->repositories) + if (r.name == repoName) + return r.cloneUrl; + return QUrl(); +} + +bool GitoriousRepositoryWizardPage::isComplete() const +{ + return m_valid; +} + +void GitoriousRepositoryWizardPage::changeEvent(QEvent *e) +{ + QWizardPage::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +} // namespace Internal +} // namespace Gitorious diff --git a/src/plugins/git/gitorious/gitoriousrepositorywizardpage.h b/src/plugins/git/gitorious/gitoriousrepositorywizardpage.h new file mode 100644 index 0000000000..f90f415d02 --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousrepositorywizardpage.h @@ -0,0 +1,80 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** +**************************************************************************/ + +#ifndef GITORIOUSREPOSITORYWIZARDPAGE_H +#define GITORIOUSREPOSITORYWIZARDPAGE_H + +#include <QtGui/QWizardPage> + +QT_BEGIN_NAMESPACE +class QStandardItemModel; +class QStandardItem; +class QModelIndex; +class QUrl; +QT_END_NAMESPACE + +namespace Gitorious { +namespace Internal { + +class GitoriousProjectWizardPage; + +namespace Ui { + class GitoriousRepositoryWizardPage; +} + +// A wizard page listing Gitorious repositories in a tree, by repository type. + +class GitoriousRepositoryWizardPage : public QWizardPage { + Q_OBJECT +public: + explicit GitoriousRepositoryWizardPage(const GitoriousProjectWizardPage *projectPage, + QWidget *parent = 0); + ~GitoriousRepositoryWizardPage(); + + virtual void initializePage(); + virtual bool isComplete() const; + + QString repositoryName() const; + QUrl repositoryURL() const; + +public slots: + void slotCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); + +protected: + void changeEvent(QEvent *e); + + Ui::GitoriousRepositoryWizardPage *ui; + const GitoriousProjectWizardPage *m_projectPage; + QStandardItemModel *m_model; + bool m_valid; +}; + +} // namespace Internal +} // namespace Gitorious +#endif // GITORIOUSREPOSITORYWIZARDPAGE_H diff --git a/src/plugins/git/gitorious/gitoriousrepositorywizardpage.ui b/src/plugins/git/gitorious/gitoriousrepositorywizardpage.ui new file mode 100644 index 0000000000..e85f590389 --- /dev/null +++ b/src/plugins/git/gitorious/gitoriousrepositorywizardpage.ui @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Gitorious::Internal::GitoriousRepositoryWizardPage</class> + <widget class="QWizardPage" name="Gitorious::Internal::GitoriousRepositoryWizardPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>WizardPage</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTreeView" name="repositoryTreeView"/> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/git/gitplugin.cpp b/src/plugins/git/gitplugin.cpp index 8359329819..14e36fcb16 100644 --- a/src/plugins/git/gitplugin.cpp +++ b/src/plugins/git/gitplugin.cpp @@ -38,6 +38,7 @@ #include "gitversioncontrol.h" #include "branchdialog.h" #include "clonewizard.h" +#include "gitoriousclonewizard.h" #include <coreplugin/icore.h> #include <coreplugin/coreconstants.h> @@ -216,6 +217,7 @@ bool GitPlugin::initialize(const QStringList &arguments, QString *errorMessage) addAutoReleasedObject(versionControl); addAutoReleasedObject(new CloneWizard); + addAutoReleasedObject(new Gitorious::Internal::GitoriousCloneWizard); //register actions Core::ActionManager *actionManager = m_core->actionManager(); diff --git a/src/plugins/vcsbase/basecheckoutwizardpage.cpp b/src/plugins/vcsbase/basecheckoutwizardpage.cpp index f9fdb1a697..c058c42555 100644 --- a/src/plugins/vcsbase/basecheckoutwizardpage.cpp +++ b/src/plugins/vcsbase/basecheckoutwizardpage.cpp @@ -63,6 +63,16 @@ void BaseCheckoutWizardPage::setRepositoryLabel(const QString &l) d->ui.repositoryLabel->setText(l); } +bool BaseCheckoutWizardPage::isRepositoryReadOnly() const +{ + return d->ui.repositoryLineEdit->isReadOnly(); +} + +void BaseCheckoutWizardPage::setRepositoryReadOnly(bool v) +{ + d->ui.repositoryLineEdit->setReadOnly(v); +} + QString BaseCheckoutWizardPage::path() const { return d->ui.pathChooser->path(); diff --git a/src/plugins/vcsbase/basecheckoutwizardpage.h b/src/plugins/vcsbase/basecheckoutwizardpage.h index adf5d60dcc..b8aab5b090 100644 --- a/src/plugins/vcsbase/basecheckoutwizardpage.h +++ b/src/plugins/vcsbase/basecheckoutwizardpage.h @@ -62,6 +62,9 @@ public: QString repository() const; void setRepository(const QString &r); + bool isRepositoryReadOnly() const; + void setRepositoryReadOnly(bool v); + virtual bool isComplete() const; protected: diff --git a/src/plugins/vcsbase/basecheckoutwizardpage.ui b/src/plugins/vcsbase/basecheckoutwizardpage.ui index 46b1edfaf7..75ac881df2 100644 --- a/src/plugins/vcsbase/basecheckoutwizardpage.ui +++ b/src/plugins/vcsbase/basecheckoutwizardpage.ui @@ -44,19 +44,6 @@ </item> </layout> </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> </layout> </widget> <customwidgets> |