1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
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
16 ** GNU Lesser General Public License Usage
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.
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.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "gitoriousprojectwidget.h"
35 #include "gitorioushostwizardpage.h"
36 #include "gitorious.h"
37 #include "ui_gitoriousprojectwidget.h"
39 #include <utils/qtcassert.h>
41 #include <QtCore/QRegExp>
42 #include <QtCore/QDebug>
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>
53 urlRole = Qt::UserRole + 1 // Project has a URL in the description
61 enum { ProjectColumn, DescriptionColumn, ColumnCount };
63 GitoriousProjectWidget::GitoriousProjectWidget(int hostIndex,
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),
73 ui->infoToolButton->setIcon(style()->standardIcon(QStyle::SP_MessageBoxInformation));
74 ui->infoToolButton->setEnabled(false);
75 connect(ui->infoToolButton, SIGNAL(clicked()), this, SLOT(slotInfo()));
77 connect(ui->filterLineEdit, SIGNAL(filterChanged(QString)), m_filterModel, SLOT(setFilterFixedString(QString)));
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)));
85 headers << tr("Project") << tr("Description");
86 m_model->setHorizontalHeaderLabels(headers);
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);
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);
108 const QModelIndex index = m_filterModel->index(0, 0);
109 ui->projectTreeView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select|QItemSelectionModel::Current|QItemSelectionModel::Rows);
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)));
118 GitoriousProjectWidget::~GitoriousProjectWidget()
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)));
126 // Map indexes back via filter
127 QStandardItem *GitoriousProjectWidget::itemFromIndex(const QModelIndex &index) const
130 return m_model->itemFromIndex(m_filterModel->mapToSource(index));
134 QStandardItem *GitoriousProjectWidget::currentItem() const
136 return itemFromIndex(ui->projectTreeView->selectionModel()->currentIndex());
139 void GitoriousProjectWidget::slotCurrentChanged(const QModelIndex ¤t, const QModelIndex & /* previous */)
141 // Any info URL to show?
143 if (current.isValid())
144 if (QStandardItem *item = itemFromIndex(current)) {
145 // Project: URL in description?
146 const QVariant urlV = item->data(urlRole);
148 url = urlV.toString();
151 ui->infoToolButton->setEnabled(!url.isEmpty());
152 ui->infoToolButton->setToolTip(url);
154 const bool isValid = current.isValid();
155 if (isValid != m_valid) {
161 void GitoriousProjectWidget::slotInfo()
163 if (const QStandardItem *item = currentItem()) {
164 const QVariant url = item->data(urlRole);
166 QDesktopServices::openUrl(QUrl(url.toString()));
170 // Create a model row for a project
171 static inline QList<QStandardItem *> projectEntry(const GitoriousProject &p)
173 enum { maxNameLength = 30 };
174 // Truncate names with colons
175 QString name = p.name;
176 const int colonPos = name.indexOf(QLatin1Char(':'));
178 name.truncate(colonPos);
179 if (name.size() > maxNameLength) {
180 name.truncate(maxNameLength);
181 name += QLatin1String("...");
183 QStandardItem *nameItem = new QStandardItem(name);
184 nameItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled);
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
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);
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
206 void GitoriousProjectWidget::setDescription(const QString &description,
207 int descriptionColumn,
208 QList<QStandardItem *> *items,
209 QString *url /* =0 */)
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);
220 descLine.truncate(dotPos);
222 descLine.truncate(MaxDescriptionLineLength);
224 descLine += QLatin1String("...");
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);
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);
245 void GitoriousProjectWidget::grabFocus()
247 ui->projectTreeView->setFocus();
250 void GitoriousProjectWidget::slotUpdateCheckBoxChanged(int state)
252 if (state == Qt::Checked)
253 slotUpdateProjects(Gitorious::instance().findByHostName(m_hostName));
256 void GitoriousProjectWidget::slotUpdateProjects(int hostIndex)
258 if (!ui->updateCheckBox->isChecked())
260 const Gitorious &gitorious = Gitorious::instance();
261 // Complete list of projects
262 if (m_hostName != gitorious.hostName(hostIndex))
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);
273 bool GitoriousProjectWidget::isValid() const
278 int GitoriousProjectWidget::hostIndex() const
280 return Gitorious::instance().findByHostName(m_hostName);
283 QSharedPointer<GitoriousProject> GitoriousProjectWidget::project() const
285 if (const QStandardItem *item = currentItem()) {
286 const int projectIndex = item->row();
287 return Gitorious::instance().hosts().at(hostIndex()).projects.at(projectIndex);
289 return QSharedPointer<GitoriousProject>(new GitoriousProject);
292 void GitoriousProjectWidget::changeEvent(QEvent *e)
294 QWidget::changeEvent(e);
296 case QEvent::LanguageChange:
297 ui->retranslateUi(this);
304 } // namespace Internal
305 } // namespace Gitorious