OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / projectexplorer / taskwindow.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 (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 **
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 **
23 ** Other Usage
24 **
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "taskwindow.h"
34
35 #include "itaskhandler.h"
36 #include "projectexplorerconstants.h"
37 #include "task.h"
38 #include "taskhub.h"
39
40 #include <coreplugin/actionmanager/actionmanager.h>
41 #include <coreplugin/actionmanager/command.h>
42 #include <coreplugin/coreconstants.h>
43 #include <coreplugin/icontext.h>
44 #include <coreplugin/icore.h>
45 #include <extensionsystem/pluginmanager.h>
46 #include <qt4projectmanager/qt4projectmanagerconstants.h>
47
48 #include <QtCore/QDir>
49 #include <QtCore/QFileInfo>
50 #include <QtGui/QApplication>
51 #include <QtGui/QClipboard>
52 #include <QtGui/QKeyEvent>
53 #include <QtGui/QListView>
54 #include <QtGui/QPainter>
55 #include <QtGui/QStyledItemDelegate>
56 #include <QtGui/QSortFilterProxyModel>
57 #include <QtGui/QMenu>
58 #include <QtGui/QToolButton>
59
60 namespace {
61 const int ELLIPSIS_GRADIENT_WIDTH = 16;
62 }
63
64 namespace ProjectExplorer {
65 namespace Internal {
66
67 class TaskView : public QListView
68 {
69 public:
70     TaskView(QWidget *parent = 0);
71     ~TaskView();
72     void resizeEvent(QResizeEvent *e);
73     void keyPressEvent(QKeyEvent *e);
74 };
75
76 class TaskWindowContext : public Core::IContext
77 {
78 public:
79     TaskWindowContext(QWidget *widget);
80     virtual Core::Context context() const;
81     virtual QWidget *widget();
82 private:
83     QWidget *m_taskList;
84     const Core::Context m_context;
85 };
86
87 class TaskModel : public QAbstractItemModel
88 {
89 public:
90     // Model stuff
91     TaskModel();
92     QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
93     QModelIndex parent(const QModelIndex &child) const;
94     int rowCount(const QModelIndex &parent = QModelIndex()) const;
95     int columnCount(const QModelIndex &parent = QModelIndex()) const;
96     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
97     Task task(const QModelIndex &index) const;
98
99     QStringList categoryIds() const;
100     QString categoryDisplayName(const QString &categoryId) const;
101     void addCategory(const QString &categoryId, const QString &categoryName);
102
103     QList<Task> tasks(const QString &categoryId = QString()) const;
104     void addTask(const Task &task);
105     void removeTask(const Task &task);
106     void clearTasks(const QString &categoryId = QString());
107
108     int sizeOfFile(const QFont &font);
109     int sizeOfLineNumber(const QFont &font);
110     void setFileNotFound(const QModelIndex &index, bool b);
111
112     enum Roles { File = Qt::UserRole, Line, Description, FileNotFound, Type, Category, Icon, Task_t };
113
114     QIcon taskTypeIcon(Task::TaskType t) const;
115
116     int taskCount();
117     int errorTaskCount();
118
119     bool hasFile(const QModelIndex &index) const;
120
121 private:
122     QHash<QString,QString> m_categories; // category id -> display name
123     QList<Task> m_tasks;   // all tasks (in order of insertion)
124     QMap<QString,QList<Task> > m_tasksInCategory; // categoryId->tasks
125
126     QHash<QString,bool> m_fileNotFound;
127     int m_maxSizeOfFileName;
128     QString m_fileMeasurementFont;
129     const QIcon m_errorIcon;
130     const QIcon m_warningIcon;
131     int m_taskCount;
132     int m_errorTaskCount;
133     int m_sizeOfLineNumber;
134     QString m_lineMeasurementFont;
135 };
136
137 class TaskFilterModel : public QSortFilterProxyModel
138 {
139 public:
140     TaskFilterModel(TaskModel *sourceModel, QObject *parent = 0);
141
142     TaskModel *taskModel() const;
143
144     bool filterIncludesUnknowns() const { return m_includeUnknowns; }
145     void setFilterIncludesUnknowns(bool b) { m_includeUnknowns = b; invalidateFilter(); }
146
147     bool filterIncludesWarnings() const { return m_includeWarnings; }
148     void setFilterIncludesWarnings(bool b) { m_includeWarnings = b; invalidateFilter(); }
149
150     bool filterIncludesErrors() const { return m_includeErrors; }
151     void setFilterIncludesErrors(bool b) { m_includeErrors = b; invalidateFilter(); }
152
153     QStringList filteredCategories() const { return m_categoryIds; }
154     void setFilteredCategories(const QStringList &categoryIds) { m_categoryIds = categoryIds; invalidateFilter(); }
155
156     Task task(const QModelIndex &index) const
157     { return static_cast<TaskModel *>(sourceModel())->task(mapToSource(index)); }
158
159     bool hasFile(const QModelIndex &index) const
160     { return static_cast<TaskModel *>(sourceModel())->hasFile(mapToSource(index)); }
161
162 protected:
163     bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
164
165 private:
166     bool m_includeUnknowns;
167     bool m_includeWarnings;
168     bool m_includeErrors;
169     QStringList m_categoryIds;
170 };
171
172 class TaskDelegate : public QStyledItemDelegate
173 {
174     Q_OBJECT
175
176 public:
177     TaskDelegate(QObject * parent = 0);
178     ~TaskDelegate();
179     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
180     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
181
182     // TaskView uses this method if the size of the taskview changes
183     void emitSizeHintChanged(const QModelIndex &index);
184
185 public slots:
186     void currentChanged(const QModelIndex &current, const QModelIndex &previous);
187
188 private:
189     void generateGradientPixmap(int width, int height, QColor color, bool selected) const;
190
191     /*
192       Collapsed:
193       +----------------------------------------------------------------------------------------------------+
194       | TASKICONAREA  TEXTAREA                                                           FILEAREA LINEAREA |
195       +----------------------------------------------------------------------------------------------------+
196
197       Expanded:
198       +----------------------------------------------------------------------------------------------------+
199       | TASKICONICON  TEXTAREA                                                           FILEAREA LINEAREA |
200       |               more text -------------------------------------------------------------------------> |
201       +----------------------------------------------------------------------------------------------------+
202      */
203     class Positions
204     {
205     public:
206         Positions(const QStyleOptionViewItemV4 &options, TaskModel *model) :
207             m_totalWidth(options.rect.width()),
208             m_maxFileLength(model->sizeOfFile(options.font)),
209             m_maxLineLength(model->sizeOfLineNumber(options.font)),
210             m_realFileLength(m_maxFileLength),
211             m_top(options.rect.top()),
212             m_bottom(options.rect.bottom())
213         {
214             int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING;
215             if (m_maxFileLength > flexibleArea / 2)
216                 m_realFileLength = flexibleArea / 2;
217             m_fontHeight = QFontMetrics(options.font).height();
218         }
219
220         int top() const { return m_top + ITEM_MARGIN; }
221         int left() const { return ITEM_MARGIN; }
222         int right() const { return m_totalWidth - ITEM_MARGIN; }
223         int bottom() const { return m_bottom; }
224         int firstLineHeight() const { return m_fontHeight + 1; }
225         int minimumHeight() const { return taskIconHeight() + 2 * ITEM_MARGIN; }
226
227         int taskIconLeft() const { return left(); }
228         int taskIconWidth() const { return TASK_ICON_SIZE; }
229         int taskIconHeight() const { return TASK_ICON_SIZE; }
230         int taskIconRight() const { return taskIconLeft() + taskIconWidth(); }
231         QRect taskIcon() const { return QRect(taskIconLeft(), top(), taskIconWidth(), taskIconHeight()); }
232
233         int textAreaLeft() const { return taskIconRight() + ITEM_SPACING; }
234         int textAreaWidth() const { return textAreaRight() - textAreaLeft(); }
235         int textAreaRight() const { return fileAreaLeft() - ITEM_SPACING; }
236         QRect textArea() const { return QRect(textAreaLeft(), top(), textAreaWidth(), firstLineHeight()); }
237
238         int fileAreaLeft() const { return fileAreaRight() - fileAreaWidth(); }
239         int fileAreaWidth() const { return m_realFileLength; }
240         int fileAreaRight() const { return lineAreaLeft() - ITEM_SPACING; }
241         QRect fileArea() const { return QRect(fileAreaLeft(), top(), fileAreaWidth(), firstLineHeight()); }
242
243         int lineAreaLeft() const { return lineAreaRight() - lineAreaWidth(); }
244         int lineAreaWidth() const { return m_maxLineLength; }
245         int lineAreaRight() const { return right(); }
246         QRect lineArea() const { return QRect(lineAreaLeft(), top(), lineAreaWidth(), firstLineHeight()); }
247
248     private:
249         int m_totalWidth;
250         int m_maxFileLength;
251         int m_maxLineLength;
252         int m_realFileLength;
253         int m_top;
254         int m_bottom;
255         int m_fontHeight;
256
257         static const int TASK_ICON_SIZE = 16;
258         static const int ITEM_MARGIN = 2;
259         static const int ITEM_SPACING = 2 * ITEM_MARGIN;
260     };
261 };
262
263 TaskView::TaskView(QWidget *parent)
264     : QListView(parent)
265 {
266     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
267     setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
268 }
269
270 TaskView::~TaskView()
271 {
272
273 }
274
275 void TaskView::resizeEvent(QResizeEvent *e)
276 {
277     Q_UNUSED(e)
278     static_cast<TaskDelegate *>(itemDelegate())->emitSizeHintChanged(selectionModel()->currentIndex());
279 }
280
281 void TaskView::keyPressEvent(QKeyEvent *e)
282 {
283     if (!e->modifiers() && e->key() == Qt::Key_Return) {
284         emit activated(currentIndex());
285         e->accept();
286         return;
287     }
288     QListView::keyPressEvent(e);
289 }
290
291 /////
292 // TaskModel
293 /////
294
295 TaskModel::TaskModel() :
296     m_maxSizeOfFileName(0),
297     m_errorIcon(QLatin1String(":/projectexplorer/images/compile_error.png")),
298     m_warningIcon(QLatin1String(":/projectexplorer/images/compile_warning.png")),
299     m_taskCount(0),
300     m_errorTaskCount(0),
301     m_sizeOfLineNumber(0)
302 {
303 }
304
305 int TaskModel::taskCount()
306 {
307     return m_taskCount;
308 }
309
310 int TaskModel::errorTaskCount()
311 {
312     return m_errorTaskCount;
313 }
314
315 bool TaskModel::hasFile(const QModelIndex &index) const
316 {
317     int row = index.row();
318     if (!index.isValid() || row < 0 || row >= m_tasks.count())
319         return false;
320     return !m_tasks.at(row).file.isEmpty();
321 }
322
323 QIcon TaskModel::taskTypeIcon(Task::TaskType t) const
324 {
325     switch (t) {
326     case Task::Warning:
327         return m_warningIcon;
328     case Task::Error:
329         return m_errorIcon;
330     case Task::Unknown:
331         break;
332     }
333     return QIcon();
334 }
335
336 void TaskModel::addCategory(const QString &categoryId, const QString &categoryName)
337 {
338     Q_ASSERT(!categoryId.isEmpty());
339     m_categories.insert(categoryId, categoryName);
340 }
341
342 QList<Task> TaskModel::tasks(const QString &categoryId) const
343 {
344     if (categoryId.isEmpty()) {
345         return m_tasks;
346     } else {
347         return m_tasksInCategory.value(categoryId);
348     }
349 }
350
351 void TaskModel::addTask(const Task &task)
352 {
353     Q_ASSERT(m_categories.keys().contains(task.category));
354
355     if (m_tasksInCategory.contains(task.category)) {
356         m_tasksInCategory[task.category].append(task);
357     } else {
358         QList<Task> temp;
359         temp.append(task);
360         m_tasksInCategory.insert(task.category, temp);
361     }
362
363     beginInsertRows(QModelIndex(), m_tasks.size(), m_tasks.size());
364     m_tasks.append(task);
365     endInsertRows();
366
367     m_maxSizeOfFileName = 0;
368     ++m_taskCount;
369     if (task.type == Task::Error)
370         ++m_errorTaskCount;
371 }
372
373 void TaskModel::removeTask(const Task &task)
374 {
375     if (m_tasks.contains(task)) {
376         int index = m_tasks.indexOf(task);
377         beginRemoveRows(QModelIndex(), index, index);
378         m_tasks.removeAt(index);
379         --m_taskCount;
380         if (task.type == Task::Error)
381             --m_errorTaskCount;
382         endRemoveRows();
383     }
384 }
385
386 void TaskModel::clearTasks(const QString &categoryId)
387 {
388     if (categoryId.isEmpty()) {
389         if (m_tasks.size() == 0)
390             return;
391         beginRemoveRows(QModelIndex(), 0, m_tasks.size() -1);
392         m_tasks.clear();
393         m_tasksInCategory.clear();
394         m_taskCount = 0;
395         m_errorTaskCount = 0;
396         endRemoveRows();
397         m_maxSizeOfFileName = 0;
398     } else {
399         int index = 0;
400         int start = 0;
401         int subErrorTaskCount = 0;
402         while (index < m_tasks.size()) {
403             while (index < m_tasks.size() && m_tasks.at(index).category != categoryId) {
404                 ++start;
405                 ++index;
406             }
407             if (index == m_tasks.size())
408                 break;
409             while (index < m_tasks.size() && m_tasks.at(index).category == categoryId) {
410                 if (m_tasks.at(index).type == Task::Error)
411                     ++subErrorTaskCount;
412                 ++index;
413             }
414             // Index is now on the first non category
415             beginRemoveRows(QModelIndex(), start, index - 1);
416
417             for (int i = start; i < index; ++i) {
418                 m_tasksInCategory[categoryId].removeOne(m_tasks.at(i));
419             }
420
421             m_tasks.erase(m_tasks.begin() + start, m_tasks.begin() + index);
422
423             m_taskCount -= index - start;
424             m_errorTaskCount -= subErrorTaskCount;
425
426             endRemoveRows();
427             index = start;
428         }
429         // what to do with m_maxSizeOfFileName ?
430     }
431 }
432
433
434 QModelIndex TaskModel::index(int row, int column, const QModelIndex &parent) const
435 {
436     if (parent.isValid())
437         return QModelIndex();
438     return createIndex(row, column, 0);
439 }
440
441 QModelIndex TaskModel::parent(const QModelIndex &child) const
442 {
443     Q_UNUSED(child)
444     return QModelIndex();
445 }
446
447 int TaskModel::rowCount(const QModelIndex &parent) const
448 {
449     return parent.isValid() ? 0 : m_tasks.count();
450 }
451
452 int TaskModel::columnCount(const QModelIndex &parent) const
453 {
454         return parent.isValid() ? 0 : 1;
455 }
456
457 QVariant TaskModel::data(const QModelIndex &index, int role) const
458 {
459     if (!index.isValid() || index.row() >= m_tasks.size() || index.column() != 0)
460         return QVariant();
461
462     if (role == TaskModel::File) {
463         return m_tasks.at(index.row()).file;
464     } else if (role == TaskModel::Line) {
465         if (m_tasks.at(index.row()).line <= 0)
466             return QVariant();
467         else
468             return m_tasks.at(index.row()).line;
469     } else if (role == TaskModel::Description) {
470         return m_tasks.at(index.row()).description;
471     } else if (role == TaskModel::FileNotFound) {
472         return m_fileNotFound.value(m_tasks.at(index.row()).file);
473     } else if (role == TaskModel::Type) {
474         return (int)m_tasks.at(index.row()).type;
475     } else if (role == TaskModel::Category) {
476         return m_tasks.at(index.row()).category;
477     } else if (role == TaskModel::Icon) {
478         return taskTypeIcon(m_tasks.at(index.row()).type);
479     } else if (role == TaskModel::Task_t) {
480         return QVariant::fromValue(task(index));
481     }
482     return QVariant();
483 }
484
485 Task TaskModel::task(const QModelIndex &index) const
486 {
487     if (!index.isValid())
488         return Task();
489     return m_tasks.at(index.row());
490 }
491
492 QStringList TaskModel::categoryIds() const
493 {
494     return m_categories.keys();
495 }
496
497 QString TaskModel::categoryDisplayName(const QString &categoryId) const
498 {
499     return m_categories.value(categoryId);
500 }
501
502 int TaskModel::sizeOfFile(const QFont &font)
503 {
504     QString fontKey = font.key();
505     if (m_maxSizeOfFileName > 0 && fontKey == m_fileMeasurementFont)
506         return m_maxSizeOfFileName;
507
508     QFontMetrics fm(font);
509     m_fileMeasurementFont = fontKey;
510
511     foreach (const Task & t, m_tasks) {
512         QString filename = t.file;
513         const int pos = filename.lastIndexOf(QLatin1Char('/'));
514         if (pos != -1)
515             filename = filename.mid(pos +1);
516
517         m_maxSizeOfFileName = qMax(m_maxSizeOfFileName, fm.width(filename));
518     }
519     return m_maxSizeOfFileName;
520 }
521
522 int TaskModel::sizeOfLineNumber(const QFont &font)
523 {
524     QString fontKey = font.key();
525     if (m_sizeOfLineNumber == 0 || fontKey != m_lineMeasurementFont) {
526         QFontMetrics fm(font);
527         m_lineMeasurementFont = fontKey;
528         m_sizeOfLineNumber = fm.width("8888");
529     }
530     return m_sizeOfLineNumber;
531 }
532
533 void TaskModel::setFileNotFound(const QModelIndex &idx, bool b)
534 {
535     if (idx.isValid() && idx.row() < m_tasks.size()) {
536         m_fileNotFound.insert(m_tasks[idx.row()].file, b);
537         emit dataChanged(idx, idx);
538     }
539 }
540
541 /////
542 // TaskFilterModel
543 /////
544
545 TaskFilterModel::TaskFilterModel(TaskModel *sourceModel, QObject *parent)
546     : QSortFilterProxyModel(parent)
547 {
548     setSourceModel(sourceModel);
549     setDynamicSortFilter(true);
550     m_includeUnknowns = m_includeWarnings = m_includeErrors = true;
551 }
552
553 TaskModel *TaskFilterModel::taskModel() const
554 {
555     return static_cast<TaskModel*>(sourceModel());
556 }
557
558 bool TaskFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
559 {
560     bool accept = true;
561
562     QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
563     Task::TaskType type = Task::TaskType(index.data(TaskModel::Type).toInt());
564     switch (type) {
565     case Task::Unknown:
566         accept = m_includeUnknowns;
567         break;
568     case Task::Warning:
569         accept = m_includeWarnings;
570         break;
571     case Task::Error:
572         accept = m_includeErrors;
573         break;
574     }
575
576     const QString &categoryId = index.data(TaskModel::Category).toString();
577     if (m_categoryIds.contains(categoryId))
578         accept = false;
579
580     return accept;
581 }
582
583 /////
584 // TaskWindow
585 /////
586
587 class TaskWindowPrivate
588 {
589 public:
590     Internal::TaskModel *m_model;
591     Internal::TaskFilterModel *m_filter;
592     Internal::TaskView *m_listview;
593     Internal::TaskWindowContext *m_taskWindowContext;
594     QMenu *m_contextMenu;
595     QModelIndex m_contextMenuIndex;
596     ITaskHandler *m_defaultHandler;
597     QToolButton *m_filterWarningsButton;
598     QToolButton *m_categoriesButton;
599     QMenu *m_categoriesMenu;
600     TaskHub *m_taskHub;
601 };
602
603 static QToolButton *createFilterButton(QIcon icon, const QString &toolTip,
604                                        QObject *receiver, const char *slot)
605 {
606     QToolButton *button = new QToolButton;
607     button->setIcon(icon);
608     button->setToolTip(toolTip);
609     button->setCheckable(true);
610     button->setChecked(true);
611     button->setAutoRaise(true);
612     button->setEnabled(true);
613     QObject::connect(button, SIGNAL(toggled(bool)), receiver, slot);
614     return button;
615 }
616
617 TaskWindow::TaskWindow(TaskHub *taskhub) : d(new TaskWindowPrivate)
618 {
619     d->m_defaultHandler = 0;
620
621     d->m_model = new Internal::TaskModel;
622     d->m_filter = new Internal::TaskFilterModel(d->m_model);
623     d->m_listview = new Internal::TaskView;
624
625     d->m_listview->setModel(d->m_filter);
626     d->m_listview->setFrameStyle(QFrame::NoFrame);
627     d->m_listview->setWindowTitle(tr("Build Issues"));
628     d->m_listview->setSelectionMode(QAbstractItemView::SingleSelection);
629     Internal::TaskDelegate *tld = new Internal::TaskDelegate(this);
630     d->m_listview->setItemDelegate(tld);
631     d->m_listview->setWindowIcon(QIcon(QLatin1String(Qt4ProjectManager::Constants::ICON_WINDOW)));
632     d->m_listview->setContextMenuPolicy(Qt::ActionsContextMenu);
633     d->m_listview->setAttribute(Qt::WA_MacShowFocusRect, false);
634
635     d->m_taskWindowContext = new Internal::TaskWindowContext(d->m_listview);
636     d->m_taskHub = taskhub;
637
638     Core::ICore::instance()->addContextObject(d->m_taskWindowContext);
639
640     connect(d->m_listview->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
641             tld, SLOT(currentChanged(QModelIndex,QModelIndex)));
642
643     connect(d->m_listview, SIGNAL(activated(QModelIndex)),
644             this, SLOT(triggerDefaultHandler(QModelIndex)));
645
646     d->m_contextMenu = new QMenu(d->m_listview);
647     connect(d->m_contextMenu, SIGNAL(triggered(QAction*)),
648             this, SLOT(contextMenuEntryTriggered(QAction*)));
649
650     d->m_listview->setContextMenuPolicy(Qt::CustomContextMenu);
651
652     connect(d->m_listview, SIGNAL(customContextMenuRequested(QPoint)),
653             this, SLOT(showContextMenu(QPoint)));
654
655     d->m_filterWarningsButton = createFilterButton(d->m_model->taskTypeIcon(Task::Warning),
656                                                    tr("Show Warnings"),
657                                                    this, SLOT(setShowWarnings(bool)));
658
659     d->m_categoriesButton = new QToolButton;
660     d->m_categoriesButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_FILTER)));
661     d->m_categoriesButton->setToolTip(tr("Filter by categories"));
662     d->m_categoriesButton->setAutoRaise(true);
663     d->m_categoriesButton->setPopupMode(QToolButton::InstantPopup);
664
665     d->m_categoriesMenu = new QMenu(d->m_categoriesButton);
666     connect(d->m_categoriesMenu, SIGNAL(aboutToShow()), this, SLOT(updateCategoriesMenu()));
667     connect(d->m_categoriesMenu, SIGNAL(triggered(QAction*)), this, SLOT(filterCategoryTriggered(QAction*)));
668
669     d->m_categoriesButton->setMenu(d->m_categoriesMenu);
670
671     connect(d->m_taskHub, SIGNAL(categoryAdded(QString, QString)),
672             this, SLOT(addCategory(QString, QString)));
673     connect(d->m_taskHub, SIGNAL(taskAdded(ProjectExplorer::Task)),
674             this, SLOT(addTask(ProjectExplorer::Task)));
675     connect(d->m_taskHub, SIGNAL(taskRemoved(ProjectExplorer::Task)),
676             this, SLOT(removeTask(ProjectExplorer::Task)));
677     connect(d->m_taskHub, SIGNAL(tasksCleared(QString)),
678             this, SLOT(clearTasks(QString)));
679 }
680
681 TaskWindow::~TaskWindow()
682 {
683     Core::ICore::instance()->removeContextObject(d->m_taskWindowContext);
684     cleanContextMenu();
685     delete d->m_filterWarningsButton;
686     delete d->m_listview;
687     delete d->m_filter;
688     delete d->m_model;
689     delete d;
690 }
691
692 QList<QWidget*> TaskWindow::toolBarWidgets() const
693 {
694     return QList<QWidget*>() << d->m_filterWarningsButton << d->m_categoriesButton;
695 }
696
697 QWidget *TaskWindow::outputWidget(QWidget *)
698 {
699     return d->m_listview;
700 }
701
702 void TaskWindow::clearTasks(const QString &categoryId)
703 {
704     d->m_model->clearTasks(categoryId);
705
706     emit tasksChanged();
707     emit tasksCleared();
708     navigateStateChanged();
709 }
710
711 void TaskWindow::visibilityChanged(bool /* b */)
712 {
713 }
714
715 void TaskWindow::addCategory(const QString &categoryId, const QString &displayName)
716 {
717     Q_ASSERT(!categoryId.isEmpty());
718     d->m_model->addCategory(categoryId, displayName);
719 }
720
721 void TaskWindow::addTask(const Task &task)
722 {
723     d->m_model->addTask(task);
724
725     emit tasksChanged();
726     navigateStateChanged();
727 }
728
729 void TaskWindow::removeTask(const Task &task)
730 {
731     d->m_model->removeTask(task);
732
733     emit tasksChanged();
734     navigateStateChanged();
735 }
736
737 void TaskWindow::triggerDefaultHandler(const QModelIndex &index)
738 {
739     if (!index.isValid())
740         return;
741
742     // Find a default handler to use:
743     if (!d->m_defaultHandler) {
744         QList<ITaskHandler *> handlers = ExtensionSystem::PluginManager::instance()->getObjects<ITaskHandler>();
745         foreach(ITaskHandler *handler, handlers) {
746             if (handler->id() == QLatin1String(Constants::SHOW_TASK_IN_EDITOR)) {
747                 d->m_defaultHandler = handler;
748                 break;
749             }
750         }
751     }
752     Q_ASSERT(d->m_defaultHandler);
753     Task task(d->m_filter->task(index));
754     if (task.isNull())
755         return;
756
757     if (d->m_defaultHandler->canHandle(task)) {
758         d->m_defaultHandler->handle(task);
759     } else {
760         if (!QFileInfo(task.file).exists())
761             d->m_model->setFileNotFound(index, true);
762     }
763 }
764
765 void TaskWindow::showContextMenu(const QPoint &position)
766 {
767     QModelIndex index = d->m_listview->indexAt(position);
768     if (!index.isValid())
769         return;
770     d->m_contextMenuIndex = index;
771     cleanContextMenu();
772
773     Task task = d->m_filter->task(index);
774     if (task.isNull())
775         return;
776
777     QList<ITaskHandler *> handlers = ExtensionSystem::PluginManager::instance()->getObjects<ITaskHandler>();
778     foreach(ITaskHandler *handler, handlers) {
779         if (handler == d->m_defaultHandler)
780             continue;
781         QAction * action = handler->createAction(d->m_contextMenu);
782         action->setEnabled(handler->canHandle(task));
783         action->setData(qVariantFromValue(qobject_cast<QObject*>(handler)));
784         d->m_contextMenu->addAction(action);
785     }
786     d->m_contextMenu->popup(d->m_listview->mapToGlobal(position));
787 }
788
789 void TaskWindow::contextMenuEntryTriggered(QAction *action)
790 {
791     if (action->isEnabled()) {
792         Task task = d->m_filter->task(d->m_contextMenuIndex);
793         if (task.isNull())
794             return;
795
796         ITaskHandler *handler = qobject_cast<ITaskHandler*>(action->data().value<QObject*>());
797         if (!handler)
798             return;
799         handler->handle(task);
800     }
801 }
802
803 void TaskWindow::cleanContextMenu()
804 {
805     QList<QAction *> actions = d->m_contextMenu->actions();
806     qDeleteAll(actions);
807     d->m_contextMenu->clear();
808 }
809
810 void TaskWindow::setShowWarnings(bool show)
811 {
812     d->m_filter->setFilterIncludesWarnings(show);
813     d->m_filter->setFilterIncludesUnknowns(show); // "Unknowns" are often associated with warnings
814 }
815
816 void TaskWindow::updateCategoriesMenu()
817 {
818     d->m_categoriesMenu->clear();
819
820     const QStringList filteredCategories = d->m_filter->filteredCategories();
821
822     foreach (const QString &categoryId, d->m_model->categoryIds()) {
823         const QString categoryName = d->m_model->categoryDisplayName(categoryId);
824
825         QAction *action = new QAction(d->m_categoriesMenu);
826         action->setCheckable(true);
827         action->setText(categoryName);
828         action->setData(categoryId);
829         action->setChecked(!filteredCategories.contains(categoryId));
830
831         d->m_categoriesMenu->addAction(action);
832     }
833 }
834
835 void TaskWindow::filterCategoryTriggered(QAction *action)
836 {
837     QString categoryId = action->data().toString();
838     Q_ASSERT(!categoryId.isEmpty());
839
840     QStringList categories = d->m_filter->filteredCategories();
841     Q_ASSERT(d->m_filter->filteredCategories().contains(categoryId) == action->isChecked());
842
843     if (action->isChecked()) {
844         categories.removeOne(categoryId);
845     } else {
846         categories.append(categoryId);
847     }
848
849     d->m_filter->setFilteredCategories(categories);
850 }
851
852 int TaskWindow::taskCount() const
853 {
854     return d->m_model->taskCount();
855 }
856
857 int TaskWindow::errorTaskCount() const
858 {
859     return d->m_model->errorTaskCount();
860 }
861
862 int TaskWindow::priorityInStatusBar() const
863 {
864     return 90;
865 }
866
867 void TaskWindow::clearContents()
868 {
869     // clear all tasks in all displays
870     // Yeah we are that special
871     d->m_taskHub->clearTasks(QString());
872 }
873
874 bool TaskWindow::hasFocus()
875 {
876     return d->m_listview->hasFocus();
877 }
878
879 bool TaskWindow::canFocus()
880 {
881     return d->m_filter->rowCount();
882 }
883
884 void TaskWindow::setFocus()
885 {
886     if (d->m_filter->rowCount()) {
887         d->m_listview->setFocus();
888         if (d->m_listview->currentIndex() == QModelIndex()) {
889             d->m_listview->setCurrentIndex(d->m_filter->index(0,0, QModelIndex()));
890         }
891     }
892 }
893
894 bool TaskWindow::canNext()
895 {
896     return d->m_filter->rowCount();
897 }
898
899 bool TaskWindow::canPrevious()
900 {
901     return d->m_filter->rowCount();
902 }
903
904 void TaskWindow::goToNext()
905 {
906     if (!canNext())
907         return;
908     QModelIndex startIndex = d->m_listview->currentIndex();
909     QModelIndex currentIndex = startIndex;
910
911     if (startIndex.isValid()) {
912         do {
913             int row = currentIndex.row() + 1;
914             if (row == d->m_filter->rowCount())
915                 row = 0;
916             currentIndex = d->m_filter->index(row, 0);
917             if (d->m_filter->hasFile(currentIndex))
918                 break;
919         } while (startIndex != currentIndex);
920     } else {
921         currentIndex = d->m_filter->index(0, 0);
922     }
923     d->m_listview->setCurrentIndex(currentIndex);
924     triggerDefaultHandler(currentIndex);
925 }
926
927 void TaskWindow::goToPrev()
928 {
929     if (!canPrevious())
930         return;
931     QModelIndex startIndex = d->m_listview->currentIndex();
932     QModelIndex currentIndex = startIndex;
933
934     if (startIndex.isValid()) {
935         do {
936             int row = currentIndex.row() - 1;
937             if (row < 0)
938                 row = d->m_filter->rowCount() - 1;
939             currentIndex = d->m_filter->index(row, 0);
940             if (d->m_filter->hasFile(currentIndex))
941                 break;
942         } while (startIndex != currentIndex);
943     } else {
944         currentIndex = d->m_filter->index(0, 0);
945     }
946     d->m_listview->setCurrentIndex(currentIndex);
947     triggerDefaultHandler(currentIndex);
948 }
949
950 bool TaskWindow::canNavigate()
951 {
952     return true;
953 }
954
955 /////
956 // Delegate
957 /////
958
959 TaskDelegate::TaskDelegate(QObject *parent)
960     : QStyledItemDelegate(parent)
961 {
962 }
963
964 TaskDelegate::~TaskDelegate()
965 {
966 }
967
968 QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
969 {
970     QStyleOptionViewItemV4 opt = option;
971     initStyleOption(&opt, index);
972
973     QFontMetrics fm(option.font);
974     int fontHeight = fm.height();
975     int fontLeading = fm.leading();
976
977     const QAbstractItemView * view = qobject_cast<const QAbstractItemView *>(opt.widget);
978     TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel();
979     Positions positions(option, model);
980
981     QSize s;
982     s.setWidth(option.rect.width());
983     if (view->selectionModel()->currentIndex() == index) {
984         QString description = index.data(TaskModel::Description).toString();
985         // Layout the description
986         int leading = fontLeading;
987         int height = 0;
988         description.replace('\n', QChar::LineSeparator);
989         QTextLayout tl(description);
990         tl.beginLayout();
991         while (true) {
992             QTextLine line = tl.createLine();
993             if (!line.isValid())
994                 break;
995             line.setLineWidth(positions.textAreaWidth());
996             height += leading;
997             line.setPosition(QPoint(0, height));
998             height += static_cast<int>(line.height());
999         }
1000         tl.endLayout();
1001
1002         s.setHeight(height + leading + fontHeight + 3);
1003     } else {
1004         s.setHeight(fontHeight + 3);
1005     }
1006     if (s.height() < positions.minimumHeight())
1007         s.setHeight(positions.minimumHeight());
1008     return s;
1009 }
1010
1011 void TaskDelegate::emitSizeHintChanged(const QModelIndex &index)
1012 {
1013     emit sizeHintChanged(index);
1014 }
1015
1016 void TaskDelegate::currentChanged(const QModelIndex &current, const QModelIndex &previous)
1017 {
1018     emit sizeHintChanged(current);
1019     emit sizeHintChanged(previous);
1020 }
1021
1022 void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
1023 {
1024     QStyleOptionViewItemV4 opt = option;
1025     initStyleOption(&opt, index);
1026     painter->save();
1027
1028     QFontMetrics fm(opt.font);
1029     QColor backgroundColor;
1030     QColor textColor;
1031
1032     const QAbstractItemView * view = qobject_cast<const QAbstractItemView *>(opt.widget);
1033     bool selected = view->selectionModel()->currentIndex() == index;
1034
1035     if (selected) {
1036         painter->setBrush(opt.palette.highlight().color());
1037         backgroundColor = opt.palette.highlight().color();
1038     } else {
1039         painter->setBrush(opt.palette.background().color());
1040         backgroundColor = opt.palette.background().color();
1041     }
1042     painter->setPen(Qt::NoPen);
1043     painter->drawRect(opt.rect);
1044
1045     // Set Text Color
1046     if (selected)
1047         textColor = opt.palette.highlightedText().color();
1048     else
1049         textColor = opt.palette.text().color();
1050
1051     painter->setPen(textColor);
1052
1053     TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel();
1054     Positions positions(opt, model);
1055
1056     // Paint TaskIconArea:
1057     QIcon icon = index.data(TaskModel::Icon).value<QIcon>();
1058     painter->drawPixmap(positions.left(), positions.top(),
1059                         icon.pixmap(positions.taskIconWidth(), positions.taskIconHeight()));
1060
1061     // Paint TextArea:
1062     if (!selected) {
1063         // in small mode we lay out differently
1064         QString bottom = index.data(TaskModel::Description).toString().split('\n').first();
1065         painter->setClipRect(positions.textArea());
1066         painter->drawText(positions.textAreaLeft(), positions.top() + fm.ascent(), bottom);
1067         if (fm.width(bottom) > positions.textAreaWidth()) {
1068             // draw a gradient to mask the text
1069             int gradientStart = positions.textAreaRight() - ELLIPSIS_GRADIENT_WIDTH + 1;
1070             QLinearGradient lg(gradientStart, 0, gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0);
1071             lg.setColorAt(0, Qt::transparent);
1072             lg.setColorAt(1, backgroundColor);
1073             painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg);
1074         }
1075     } else {
1076         // Description
1077         QString description = index.data(TaskModel::Description).toString();
1078         // Layout the description
1079         int leading = fm.leading();
1080         int height = 0;
1081         description.replace('\n', QChar::LineSeparator);
1082         QTextLayout tl(description);
1083         tl.setAdditionalFormats(index.data(TaskModel::Task_t).value<ProjectExplorer::Task>().formats);
1084         tl.beginLayout();
1085         while (true) {
1086             QTextLine line = tl.createLine();
1087             if (!line.isValid())
1088                 break;
1089             line.setLineWidth(positions.textAreaWidth());
1090             height += leading;
1091             line.setPosition(QPoint(0, height));
1092             height += static_cast<int>(line.height());
1093         }
1094         tl.endLayout();
1095         tl.draw(painter, QPoint(positions.textAreaLeft(), positions.top()));
1096
1097         QColor mix;
1098         mix.setRgb( static_cast<int>(0.7 * textColor.red()   + 0.3 * backgroundColor.red()),
1099                 static_cast<int>(0.7 * textColor.green() + 0.3 * backgroundColor.green()),
1100                 static_cast<int>(0.7 * textColor.blue()  + 0.3 * backgroundColor.blue()));
1101         painter->setPen(mix);
1102
1103         const QString directory = QDir::toNativeSeparators(index.data(TaskModel::File).toString());
1104         int secondBaseLine = positions.top() + fm.ascent() + height + leading;
1105         if (index.data(TaskModel::FileNotFound).toBool()) {
1106             QString fileNotFound = tr("File not found: %1").arg(directory);
1107             painter->setPen(Qt::red);
1108             painter->drawText(positions.textAreaLeft(), secondBaseLine, fileNotFound);
1109         } else {
1110             painter->drawText(positions.textAreaLeft(), secondBaseLine, directory);
1111         }
1112     }
1113     painter->setPen(textColor);
1114
1115     // Paint FileArea
1116     QString file = index.data(TaskModel::File).toString();
1117     const int pos = file.lastIndexOf(QLatin1Char('/'));
1118     if (pos != -1)
1119         file = file.mid(pos +1);
1120     const int realFileWidth = fm.width(file);
1121     painter->setClipRect(positions.fileArea());
1122     painter->drawText(qMin(positions.fileAreaLeft(), positions.fileAreaRight() - realFileWidth),
1123                       positions.top() + fm.ascent(), file);
1124     if (realFileWidth > positions.fileAreaWidth()) {
1125         // draw a gradient to mask the text
1126         int gradientStart = positions.fileAreaLeft() - 1;
1127         QLinearGradient lg(gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0, gradientStart, 0);
1128         lg.setColorAt(0, Qt::transparent);
1129         lg.setColorAt(1, backgroundColor);
1130         painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg);
1131     }
1132
1133     // Paint LineArea
1134     QString lineText = index.data(TaskModel::Line).toString();
1135     painter->setClipRect(positions.lineArea());
1136     const int realLineWidth = fm.width(lineText);
1137     painter->drawText(positions.lineAreaRight() - realLineWidth, positions.top() + fm.ascent(), lineText);
1138     painter->setClipRect(opt.rect);
1139
1140     // Separator lines
1141     painter->setPen(QColor::fromRgb(150,150,150));
1142     painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());
1143     painter->restore();
1144 }
1145
1146 TaskWindowContext::TaskWindowContext(QWidget *widget)
1147   : Core::IContext(widget),
1148     m_taskList(widget),
1149     m_context(Core::Constants::C_PROBLEM_PANE)
1150 {
1151 }
1152
1153 Core::Context TaskWindowContext::context() const
1154 {
1155     return m_context;
1156 }
1157
1158 QWidget *TaskWindowContext::widget()
1159 {
1160     return m_taskList;
1161 }
1162
1163 } // namespace Internal
1164
1165 } // namespace ProjectExplorer
1166
1167 #include "taskwindow.moc"