OSDN Git Service

Added a gitorious clone wizard.
authorFriedemann Kleint <Friedemann.Kleint@nokia.com>
Fri, 24 Jul 2009 11:41:14 +0000 (13:41 +0200)
committerFriedemann Kleint <Friedemann.Kleint@nokia.com>
Fri, 24 Jul 2009 11:41:14 +0000 (13:41 +0200)
... based on the git clone wizard. Provide a wizard for browsing
gitorious hosts.

Task-number:  44831

27 files changed:
src/plugins/git/clonewizard.cpp
src/plugins/git/clonewizardpage.cpp
src/plugins/git/clonewizardpage.h
src/plugins/git/git.pro
src/plugins/git/gitorious/gitorious.cpp [new file with mode: 0644]
src/plugins/git/gitorious/gitorious.h [new file with mode: 0644]
src/plugins/git/gitorious/gitorious.pri [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousclonewizard.cpp [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousclonewizard.h [new file with mode: 0644]
src/plugins/git/gitorious/gitorioushostwidget.cpp [new file with mode: 0644]
src/plugins/git/gitorious/gitorioushostwidget.h [new file with mode: 0644]
src/plugins/git/gitorious/gitorioushostwidget.ui [new file with mode: 0644]
src/plugins/git/gitorious/gitorioushostwizardpage.cpp [new file with mode: 0644]
src/plugins/git/gitorious/gitorioushostwizardpage.h [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousprojectswizardwidget.cpp [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousprojectwidget.cpp [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousprojectwidget.h [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousprojectwidget.ui [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousprojectwizardpage.cpp [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousprojectwizardpage.h [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousrepositorywizardpage.cpp [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousrepositorywizardpage.h [new file with mode: 0644]
src/plugins/git/gitorious/gitoriousrepositorywizardpage.ui [new file with mode: 0644]
src/plugins/git/gitplugin.cpp
src/plugins/vcsbase/basecheckoutwizardpage.cpp
src/plugins/vcsbase/basecheckoutwizardpage.h
src/plugins/vcsbase/basecheckoutwizardpage.ui

index 03903aa..9b379d1 100644 (file)
@@ -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
index 6f78ab2..aef415a 100644 (file)
 **************************************************************************/
 
 #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
index 4d8797e..2ad6b99 100644 (file)
 
 #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
-
index 618b781..8655681 100644 (file)
@@ -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 (file)
index 0000000..c2a1787
--- /dev/null
@@ -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 (file)
index 0000000..a296aba
--- /dev/null
@@ -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 (file)
index 0000000..8678042
--- /dev/null
@@ -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 (file)
index 0000000..3a4c309
--- /dev/null
@@ -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*> &parameterPages,
+                                                                    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 (file)
index 0000000..0004463
--- /dev/null
@@ -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*> &parameterPages,
+                                                                   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 (file)
index 0000000..c01cd4c
--- /dev/null
@@ -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 &current, 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 (file)
index 0000000..961ae68
--- /dev/null
@@ -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 &current, 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 &current);
+    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 (file)
index 0000000..e989697
--- /dev/null
@@ -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 (file)
index 0000000..82a1c61
--- /dev/null
@@ -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 (file)
index 0000000..025c69f
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/src/plugins/git/gitorious/gitoriousprojectwidget.cpp b/src/plugins/git/gitorious/gitoriousprojectwidget.cpp
new file mode 100644 (file)
index 0000000..6894214
--- /dev/null
@@ -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 &current, 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 (file)
index 0000000..6151b38
--- /dev/null
@@ -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 &current, 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 (file)
index 0000000..f478c12
--- /dev/null
@@ -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 (file)
index 0000000..c36915d
--- /dev/null
@@ -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 (file)
index 0000000..abe4b3f
--- /dev/null
@@ -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 (file)
index 0000000..bbe11e5
--- /dev/null
@@ -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 &current, 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 (file)
index 0000000..f90f415
--- /dev/null
@@ -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 &current, 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 (file)
index 0000000..e85f590
--- /dev/null
@@ -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>
index 8359329..14e36fc 100644 (file)
@@ -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();
index f9fdb1a..c058c42 100644 (file)
@@ -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();
index adf5d60..b8aab5b 100644 (file)
@@ -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:
index 46b1edf..75ac881 100644 (file)
      </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>