OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / git / gitorious / gitoriousprojectwidget.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "gitoriousprojectwidget.h"
35 #include "gitorioushostwizardpage.h"
36 #include "gitorious.h"
37 #include "ui_gitoriousprojectwidget.h"
38
39 #include <utils/qtcassert.h>
40
41 #include <QtCore/QRegExp>
42 #include <QtCore/QDebug>
43
44 #include <QtGui/QStandardItemModel>
45 #include <QtGui/QSortFilterProxyModel>
46 #include <QtGui/QStandardItem>
47 #include <QtGui/QItemSelectionModel>
48 #include <QtGui/QDesktopServices>
49 #include <QtGui/QIcon>
50 #include <QtGui/QStyle>
51
52 enum {
53     urlRole = Qt::UserRole + 1  // Project has a URL in the description
54 };
55
56 enum { debug = 1 };
57
58 namespace Gitorious {
59 namespace Internal {
60
61 enum { ProjectColumn, DescriptionColumn, ColumnCount };
62
63 GitoriousProjectWidget::GitoriousProjectWidget(int hostIndex,
64                                                QWidget *parent) :
65     QWidget(parent),
66     m_hostName(Gitorious::instance().hostName(hostIndex)),
67     ui(new Ui::GitoriousProjectWidget),
68     m_model(new QStandardItemModel(0, ColumnCount, this)),
69     m_filterModel(new QSortFilterProxyModel),
70     m_valid(false)
71 {
72     ui->setupUi(this);
73     ui->infoToolButton->setIcon(style()->standardIcon(QStyle::SP_MessageBoxInformation));
74     ui->infoToolButton->setEnabled(false);
75     connect(ui->infoToolButton, SIGNAL(clicked()), this, SLOT(slotInfo()));
76     // Filter
77     connect(ui->filterLineEdit, SIGNAL(filterChanged(QString)), m_filterModel, SLOT(setFilterFixedString(QString)));
78     // Updater
79     ui->updateCheckBox->setChecked(true);
80     if (Gitorious::instance().hostState(hostIndex) != GitoriousHost::ProjectsQueryRunning)
81         ui->updateCheckBox->setVisible(false);
82     connect(ui->updateCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateCheckBoxChanged(int)));
83     // Model
84     QStringList headers;
85     headers << tr("Project") << tr("Description");
86     m_model->setHorizontalHeaderLabels(headers);
87     // Populate the model
88     slotUpdateProjects(hostIndex);
89     // Filter on all columns
90     m_filterModel->setSourceModel(m_model);
91     m_filterModel->setFilterKeyColumn(-1);
92     m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
93     m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
94     ui->projectTreeView->setModel(m_filterModel);
95     // View
96     ui->projectTreeView->setAlternatingRowColors(true);
97     ui->projectTreeView->setRootIsDecorated(false);
98     ui->projectTreeView->setUniformRowHeights(true);
99     ui->projectTreeView->setSortingEnabled(true);
100     connect(ui->projectTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
101             this, SLOT(slotCurrentChanged(QModelIndex,QModelIndex)));
102     ui->projectTreeView->setSelectionMode(QAbstractItemView::SingleSelection);
103     // Select first, resize columns
104     if (Gitorious::instance().projectCount(hostIndex)) {
105         for (int r = 0; r < ColumnCount; r++)
106             ui->projectTreeView->resizeColumnToContents(r);
107         // Select first
108         const QModelIndex index = m_filterModel->index(0, 0);
109         ui->projectTreeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select|QItemSelectionModel::Current|QItemSelectionModel::Rows);
110     }
111
112     // Continuous update
113     Gitorious *gitorious = &Gitorious::instance();
114     connect(gitorious, SIGNAL(projectListPageReceived(int,int)), this, SLOT(slotUpdateProjects(int)));
115     connect(gitorious, SIGNAL(projectListReceived(int)), this, SLOT(slotUpdateProjects(int)));
116 }
117
118 GitoriousProjectWidget::~GitoriousProjectWidget()
119 {
120     Gitorious *gitorious = &Gitorious::instance();
121     disconnect(gitorious, SIGNAL(projectListPageReceived(int,int)), this, SLOT(slotUpdateProjects(int)));
122     disconnect(gitorious, SIGNAL(projectListReceived(int)), this, SLOT(slotUpdateProjects(int)));
123     delete ui;
124 }
125
126 // Map indexes back via filter
127 QStandardItem *GitoriousProjectWidget::itemFromIndex(const QModelIndex &index) const
128 {
129     if (index.isValid())
130         return m_model->itemFromIndex(m_filterModel->mapToSource(index));
131     return 0;
132 }
133
134 QStandardItem *GitoriousProjectWidget::currentItem() const
135 {
136     return itemFromIndex(ui->projectTreeView->selectionModel()->currentIndex());
137 }
138
139 void GitoriousProjectWidget::slotCurrentChanged(const QModelIndex &current, const QModelIndex & /* previous */)
140 {
141     // Any info URL to show?
142     QString url;
143     if (current.isValid())
144         if (QStandardItem *item = itemFromIndex(current)) {
145         // Project: URL in description?
146         const QVariant urlV = item->data(urlRole);
147         if (urlV.isValid())
148             url = urlV.toString();
149     }
150
151     ui->infoToolButton->setEnabled(!url.isEmpty());
152     ui->infoToolButton->setToolTip(url);
153
154     const bool isValid = current.isValid();
155     if (isValid != m_valid) {
156         m_valid = isValid;
157         emit validChanged();
158     }
159 }
160
161 void GitoriousProjectWidget::slotInfo()
162 {
163     if (const QStandardItem *item = currentItem()) {
164         const QVariant url = item->data(urlRole);
165         if (url.isValid())
166             QDesktopServices::openUrl(QUrl(url.toString()));
167     }
168 }
169
170 // Create a model row for a project
171 static inline QList<QStandardItem *> projectEntry(const GitoriousProject &p)
172 {
173     enum { maxNameLength = 30 };
174     // Truncate names with colons
175     QString name = p.name;
176     const int colonPos = name.indexOf(QLatin1Char(':'));
177     if (colonPos != -1)
178         name.truncate(colonPos);
179     if (name.size() > maxNameLength) {
180         name.truncate(maxNameLength);
181         name += QLatin1String("...");
182     }
183     QStandardItem *nameItem = new QStandardItem(name);
184     nameItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
185     // Description
186     QStandardItem *descriptionItem = new QStandardItem;
187     descriptionItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
188     QList<QStandardItem *> rc;
189     rc << nameItem << descriptionItem;
190     // Should the text contain an URL, store it under 'urlRole' for the info button
191     QString url;
192     GitoriousProjectWidget::setDescription(p.description, DescriptionColumn, &rc, &url);
193     if (!url.isEmpty()) {
194         const QVariant urlV = QVariant(url);
195         nameItem->setData(urlV, urlRole);
196         descriptionItem->setData(urlV, urlRole);
197     }
198     return rc;
199 }
200
201 // Utility to set description column and tooltip for a row from a free
202 // format/HTMLish gitorious description. Make sure the description is just one
203 // row for the item and set a tooltip with full contents. If desired, extract
204 // an URL.
205
206 void GitoriousProjectWidget::setDescription(const QString &description,
207                                                 int descriptionColumn,
208                                                 QList<QStandardItem *> *items,
209                                                 QString *url /* =0 */)
210 {
211     enum { MaxDescriptionLineLength = 70 };
212     // Trim description to 1 sensibly long line for the item view
213     QString descLine = description;
214     const int newLinePos = descLine.indexOf(QLatin1Char('\n'));
215     if (newLinePos != -1)
216         descLine.truncate(newLinePos);
217     if (descLine.size() > MaxDescriptionLineLength) {
218         const int dotPos = descLine.lastIndexOf(QLatin1Char('.'), MaxDescriptionLineLength);
219         if (dotPos != -1) {
220             descLine.truncate(dotPos);
221         } else {
222             descLine.truncate(MaxDescriptionLineLength);
223         }
224         descLine += QLatin1String("...");
225     }
226     items->at(descriptionColumn)->setText(descLine);
227     // Set a HTML tooltip to make lines wrap and the markup sprinkled within work
228     const QString htmlTip = QLatin1String("<html><body>") + description + QLatin1String("</body></html>");
229     const int size = items->size();
230     for (int i = 0; i < size; i++)
231         items->at(i)->setToolTip(htmlTip);
232     if (url) {
233         // Should the text contain an URL, extract
234         // Do not fall for "(http://XX)", strip special characters
235         static const QRegExp urlRegExp(QLatin1String("(http://[\\w\\.-]+/[a-zA-Z0-9/\\-&]*)"));
236         Q_ASSERT(urlRegExp.isValid());
237         if (urlRegExp.indexIn(description) != -1) {
238             *url= urlRegExp.cap(1);
239         } else {
240             url->clear();
241         }
242     }
243 }
244
245 void GitoriousProjectWidget::grabFocus()
246 {
247     ui->projectTreeView->setFocus();
248 }
249
250 void GitoriousProjectWidget::slotUpdateCheckBoxChanged(int state)
251 {
252     if (state == Qt::Checked)
253         slotUpdateProjects(Gitorious::instance().findByHostName(m_hostName));
254 }
255
256 void GitoriousProjectWidget::slotUpdateProjects(int hostIndex)
257 {
258     if (!ui->updateCheckBox->isChecked())
259         return;
260     const Gitorious &gitorious = Gitorious::instance();
261     // Complete list of projects
262     if (m_hostName != gitorious.hostName(hostIndex))
263         return;
264     // Fill in missing projects
265     const GitoriousHost::ProjectList &projects = gitorious.hosts().at(hostIndex).projects;
266     const int size = projects.size();
267     for (int i = m_model->rowCount(); i < size; i++)
268         m_model->appendRow(projectEntry(*projects.at(i)));
269     if (gitorious.hostState(hostIndex) == GitoriousHost::ProjectsComplete)
270         ui->updateCheckBox->setVisible(false);
271 }
272
273 bool GitoriousProjectWidget::isValid() const
274 {
275     return m_valid;
276 }
277
278 int GitoriousProjectWidget::hostIndex() const
279 {
280     return Gitorious::instance().findByHostName(m_hostName);
281 }
282
283 QSharedPointer<GitoriousProject> GitoriousProjectWidget::project() const
284 {
285     if (const QStandardItem *item = currentItem()) {
286         const int projectIndex = item->row();
287         return Gitorious::instance().hosts().at(hostIndex()).projects.at(projectIndex);
288     }
289     return QSharedPointer<GitoriousProject>(new GitoriousProject);
290 }
291
292 void GitoriousProjectWidget::changeEvent(QEvent *e)
293 {
294     QWidget::changeEvent(e);
295     switch (e->type()) {
296     case QEvent::LanguageChange:
297         ui->retranslateUi(this);
298         break;
299     default:
300         break;
301     }
302 }
303
304 } // namespace Internal
305 } // namespace Gitorious