taskwindow.cpp 31.2 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11
12
13
14
** 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.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** 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.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
31
#include "taskwindow.h"

32
33
#include "itaskhandler.h"
#include "projectexplorerconstants.h"
34
#include "task.h"
dt's avatar
dt committed
35
#include "taskhub.h"
36

con's avatar
con committed
37
#include <coreplugin/actionmanager/actionmanager.h>
38
#include <coreplugin/actionmanager/command.h>
con's avatar
con committed
39
#include <coreplugin/coreconstants.h>
40
#include <coreplugin/icontext.h>
41
#include <coreplugin/icore.h>
42
#include <extensionsystem/pluginmanager.h>
43
#include <qt4projectmanager/qt4projectmanagerconstants.h>
con's avatar
con committed
44
45

#include <QtCore/QDir>
46
47
48
#include <QtCore/QFileInfo>
#include <QtGui/QApplication>
#include <QtGui/QClipboard>
con's avatar
con committed
49
50
51
#include <QtGui/QKeyEvent>
#include <QtGui/QListView>
#include <QtGui/QPainter>
52
#include <QtGui/QStyledItemDelegate>
53
#include <QtGui/QSortFilterProxyModel>
54
#include <QtGui/QMenu>
55
#include <QtGui/QToolButton>
56

con's avatar
con committed
57
namespace {
58
59
const int TASK_ICON_SIZE = 16;
const int TASK_ICON_MARGIN = 2;
con's avatar
con committed
60
61
}

62
63
64
65
66
67
68
69
70
71
72
namespace ProjectExplorer {
namespace Internal {

class TaskView : public QListView
{
public:
    TaskView(QWidget *parent = 0);
    ~TaskView();
    void resizeEvent(QResizeEvent *e);
    void keyPressEvent(QKeyEvent *e);
};
con's avatar
con committed
73

74
class TaskDelegate : public QStyledItemDelegate
con's avatar
con committed
75
{
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
    Q_OBJECT
public:
    TaskDelegate(QObject * parent = 0);
    ~TaskDelegate();
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;

    // TaskView uses this method if the size of the taskview changes
    void emitSizeHintChanged(const QModelIndex &index);

public slots:
    void currentChanged(const QModelIndex &current, const QModelIndex &previous);

private:
    void generateGradientPixmap(int width, int height, QColor color, bool selected) const;
con's avatar
con committed
91
92
};

93
94
95
96
class TaskWindowContext : public Core::IContext
{
public:
    TaskWindowContext(QWidget *widget);
97
    virtual Core::Context context() const;
98
99
100
    virtual QWidget *widget();
private:
    QWidget *m_taskList;
101
    const Core::Context m_context;
102
103
104
};

class TaskModel : public QAbstractItemModel
con's avatar
con committed
105
106
107
108
109
110
111
112
113
{
public:
    // Model stuff
    TaskModel();
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
    QModelIndex parent(const QModelIndex &child) const;
    int rowCount(const QModelIndex &parent = QModelIndex()) const;
    int columnCount(const QModelIndex &parent = QModelIndex()) const;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
114
    Task task(const QModelIndex &index) const;
115

116
117
    QStringList categoryIds() const;
    QString categoryDisplayName(const QString &categoryId) const;
118
119
    void addCategory(const QString &categoryId, const QString &categoryName);

120
121
    QList<Task> tasks(const QString &categoryId = QString()) const;
    void addTask(const Task &task);
122
    void removeTask(const Task &task);
123
124
    void clearTasks(const QString &categoryId = QString());

con's avatar
con committed
125
126
127
128
    int sizeOfFile();
    int sizeOfLineNumber();
    void setFileNotFound(const QModelIndex &index, bool b);

129
    enum Roles { File = Qt::UserRole, Line, Description, FileNotFound, Type, Category, Icon, Task_t };
130

131
132
    QIcon taskTypeIcon(Task::TaskType t) const;

dt's avatar
dt committed
133
134
135
    int taskCount();
    int errorTaskCount();

136
137
    bool hasFile(const QModelIndex &index) const;

con's avatar
con committed
138
private:
139
    QHash<QString,QString> m_categories; // category id -> display name
140
141
    QList<Task> m_tasks;   // all tasks (in order of insertion)
    QMap<QString,QList<Task> > m_tasksInCategory; // categoryId->tasks
142
143

    QHash<QString,bool> m_fileNotFound;
con's avatar
con committed
144
    int m_maxSizeOfFileName;
145
146
    const QIcon m_errorIcon;
    const QIcon m_warningIcon;
dt's avatar
dt committed
147
148
    int m_taskCount;
    int m_errorTaskCount;
149
    int m_sizeOfLineNumber;
con's avatar
con committed
150
151
};

152
class TaskFilterModel : public QSortFilterProxyModel
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
{
public:
    TaskFilterModel(TaskModel *sourceModel, QObject *parent = 0);

    TaskModel *taskModel() const;

    bool filterIncludesUnknowns() const { return m_includeUnknowns; }
    void setFilterIncludesUnknowns(bool b) { m_includeUnknowns = b; invalidateFilter(); }

    bool filterIncludesWarnings() const { return m_includeWarnings; }
    void setFilterIncludesWarnings(bool b) { m_includeWarnings = b; invalidateFilter(); }

    bool filterIncludesErrors() const { return m_includeErrors; }
    void setFilterIncludesErrors(bool b) { m_includeErrors = b; invalidateFilter(); }

168
169
170
    QStringList filteredCategories() const { return m_categoryIds; }
    void setFilteredCategories(const QStringList &categoryIds) { m_categoryIds = categoryIds; invalidateFilter(); }

171
172
173
    Task task(const QModelIndex &index) const
    { return static_cast<TaskModel *>(sourceModel())->task(mapToSource(index)); }

174
175
176
    bool hasFile(const QModelIndex &index) const
    { return static_cast<TaskModel *>(sourceModel())->hasFile(mapToSource(index)); }

177
178
179
180
181
182
183
protected:
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;

private:
    bool m_includeUnknowns;
    bool m_includeWarnings;
    bool m_includeErrors;
184
    QStringList m_categoryIds;
185
186
};

con's avatar
con committed
187
188
189
190
191
192
193
194
195
196
197
198
199
TaskView::TaskView(QWidget *parent)
    : QListView(parent)
{
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}

TaskView::~TaskView()
{

}

void TaskView::resizeEvent(QResizeEvent *e)
{
200
    Q_UNUSED(e)
con's avatar
con committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    static_cast<TaskDelegate *>(itemDelegate())->emitSizeHintChanged(selectionModel()->currentIndex());
}

void TaskView::keyPressEvent(QKeyEvent *e)
{
    if (!e->modifiers() && e->key() == Qt::Key_Return) {
        emit activated(currentIndex());
        e->accept();
        return;
    }
    QListView::keyPressEvent(e);
}

/////
// TaskModel
/////

218
219
220
TaskModel::TaskModel() :
    m_maxSizeOfFileName(0),
    m_errorIcon(QLatin1String(":/projectexplorer/images/compile_error.png")),
dt's avatar
dt committed
221
222
    m_warningIcon(QLatin1String(":/projectexplorer/images/compile_warning.png")),
    m_taskCount(0),
223
224
    m_errorTaskCount(0),
    m_sizeOfLineNumber(0)
dt's avatar
dt committed
225
226
227
228
229
{

}

int TaskModel::taskCount()
con's avatar
con committed
230
{
dt's avatar
dt committed
231
232
233
234
    return m_taskCount;
}

int TaskModel::errorTaskCount()
con's avatar
con committed
235
{
dt's avatar
dt committed
236
    return m_errorTaskCount;
237
238
}

239
240
241
242
243
244
245
246
bool TaskModel::hasFile(const QModelIndex &index) const
{
    int row = index.row();
    if (!index.isValid() || row < 0 || row >= m_tasks.count())
        return false;
    return !m_tasks.at(row).file.isEmpty();
}

247
248
249
250
251
252
253
254
255
256
257
QIcon TaskModel::taskTypeIcon(Task::TaskType t) const
{
    switch (t) {
    case Task::Warning:
        return m_warningIcon;
    case Task::Error:
        return m_errorIcon;
    case Task::Unknown:
        break;
    }
    return QIcon();
con's avatar
con committed
258
259
}

260
261
262
263
264
265
void TaskModel::addCategory(const QString &categoryId, const QString &categoryName)
{
    Q_ASSERT(!categoryId.isEmpty());
    m_categories.insert(categoryId, categoryName);
}

266
QList<Task> TaskModel::tasks(const QString &categoryId) const
267
268
269
270
271
272
273
274
{
    if (categoryId.isEmpty()) {
        return m_tasks;
    } else {
        return m_tasksInCategory.value(categoryId);
    }
}

275
void TaskModel::addTask(const Task &task)
con's avatar
con committed
276
{
277
278
    Q_ASSERT(m_categories.keys().contains(task.category));

dt's avatar
dt committed
279
280
281
282
283
284
285
    if (m_tasksInCategory.contains(task.category)) {
        m_tasksInCategory[task.category].append(task);
    } else {
        QList<Task> temp;
        temp.append(task);
        m_tasksInCategory.insert(task.category, temp);
    }
con's avatar
con committed
286

287
288
    beginInsertRows(QModelIndex(), m_tasks.size(), m_tasks.size());
    m_tasks.append(task);
con's avatar
con committed
289
290
291
292
293
    endInsertRows();

    QFont font;
    QFontMetrics fm(font);
    QString filename = task.file;
294
    const int pos = filename.lastIndexOf(QLatin1Char('/'));
con's avatar
con committed
295
    if (pos != -1)
296
297
        filename = task.file.mid(pos +1);

con's avatar
con committed
298
    m_maxSizeOfFileName = qMax(m_maxSizeOfFileName, fm.width(filename));
dt's avatar
dt committed
299
300
301
    ++m_taskCount;
    if (task.type == Task::Error)
        ++m_errorTaskCount;
con's avatar
con committed
302
}
303
304
305
306
307
308
309

void TaskModel::removeTask(const Task &task)
{
    if (m_tasks.contains(task)) {
        int index = m_tasks.indexOf(task);
        beginRemoveRows(QModelIndex(), index, index);
        m_tasks.removeAt(index);
dt's avatar
dt committed
310
311
312
        --m_taskCount;
        if (task.type == Task::Error)
            --m_errorTaskCount;
313
314
315
        endRemoveRows();
    }
}
316
317
318
319
320
321
322
323
324

void TaskModel::clearTasks(const QString &categoryId)
{
    if (categoryId.isEmpty()) {
        if (m_tasks.size() == 0)
            return;
        beginRemoveRows(QModelIndex(), 0, m_tasks.size() -1);
        m_tasks.clear();
        m_tasksInCategory.clear();
dt's avatar
dt committed
325
326
        m_taskCount = 0;
        m_errorTaskCount = 0;
327
328
329
        endRemoveRows();
        m_maxSizeOfFileName = 0;
    } else {
330
331
        int index = 0;
        int start = 0;
dt's avatar
dt committed
332
        int subErrorTaskCount = 0;
333
334
335
336
337
338
339
340
        while (index < m_tasks.size()) {
            while (index < m_tasks.size() && m_tasks.at(index).category != categoryId) {
                ++start;
                ++index;
            }
            if (index == m_tasks.size())
                break;
            while (index < m_tasks.size() && m_tasks.at(index).category == categoryId) {
dt's avatar
dt committed
341
342
                if (m_tasks.at(index).type == Task::Error)
                    ++subErrorTaskCount;
343
344
345
346
347
348
349
350
351
352
                ++index;
            }
            // Index is now on the first non category
            beginRemoveRows(QModelIndex(), start, index - 1);

            for (int i = start; i < index; ++i) {
                m_tasksInCategory[categoryId].removeOne(m_tasks.at(i));
            }

            m_tasks.erase(m_tasks.begin() + start, m_tasks.begin() + index);
353

dt's avatar
dt committed
354
355
            m_taskCount -= index - start;
            m_errorTaskCount -= subErrorTaskCount;
356
357

            endRemoveRows();
358
            index = start;
359
360
361
        }
        // what to do with m_maxSizeOfFileName ?
    }
con's avatar
con committed
362
363
364
365
366
367
368
369
370
371
372
373
}


QModelIndex TaskModel::index(int row, int column, const QModelIndex &parent) const
{
    if (parent.isValid())
        return QModelIndex();
    return createIndex(row, column, 0);
}

QModelIndex TaskModel::parent(const QModelIndex &child) const
{
374
    Q_UNUSED(child)
con's avatar
con committed
375
376
377
378
379
    return QModelIndex();
}

int TaskModel::rowCount(const QModelIndex &parent) const
{
380
    return parent.isValid() ? 0 : m_tasks.count();
con's avatar
con committed
381
382
383
384
385
386
387
388
389
}

int TaskModel::columnCount(const QModelIndex &parent) const
{
        return parent.isValid() ? 0 : 1;
}

QVariant TaskModel::data(const QModelIndex &index, int role) const
{
390
    if (!index.isValid() || index.row() >= m_tasks.size() || index.column() != 0)
con's avatar
con committed
391
392
        return QVariant();

393
    if (role == TaskModel::File) {
394
        return m_tasks.at(index.row()).file;
395
396
397
398
399
400
    } else if (role == TaskModel::Line) {
        if (m_tasks.at(index.row()).line <= 0)
            return QVariant();
        else
            return m_tasks.at(index.row()).line;
    } else if (role == TaskModel::Description) {
401
        return m_tasks.at(index.row()).description;
402
    } else if (role == TaskModel::FileNotFound) {
403
        return m_fileNotFound.value(m_tasks.at(index.row()).file);
404
    } else if (role == TaskModel::Type) {
405
        return (int)m_tasks.at(index.row()).type;
406
    } else if (role == TaskModel::Category) {
407
        return m_tasks.at(index.row()).category;
408
    } else if (role == TaskModel::Icon) {
409
        return taskTypeIcon(m_tasks.at(index.row()).type);
dt's avatar
dt committed
410
    } else if (role == TaskModel::Task_t) {
411
        return QVariant::fromValue(task(index));
412
    }
con's avatar
con committed
413
414
415
    return QVariant();
}

416
417
Task TaskModel::task(const QModelIndex &index) const
{
418
419
    if (!index.isValid())
        return Task();
420
421
422
    return m_tasks.at(index.row());
}

423
424
425
426
427
428
429
430
431
432
QStringList TaskModel::categoryIds() const
{
    return m_categories.keys();
}

QString TaskModel::categoryDisplayName(const QString &categoryId) const
{
    return m_categories.value(categoryId);
}

con's avatar
con committed
433
434
435
436
437
438
439
int TaskModel::sizeOfFile()
{
    return m_maxSizeOfFileName;
}

int TaskModel::sizeOfLineNumber()
{
440
441
442
443
444
445
    if (m_sizeOfLineNumber == 0) {
        QFont font;
        QFontMetrics fm(font);
        m_sizeOfLineNumber = fm.width("8888");
    }
    return m_sizeOfLineNumber;
con's avatar
con committed
446
447
448
449
}

void TaskModel::setFileNotFound(const QModelIndex &idx, bool b)
{
450
451
    if (idx.isValid() && idx.row() < m_tasks.size()) {
        m_fileNotFound.insert(m_tasks[idx.row()].file, b);
con's avatar
con committed
452
453
454
455
        emit dataChanged(idx, idx);
    }
}

456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
/////
// TaskFilterModel
/////

TaskFilterModel::TaskFilterModel(TaskModel *sourceModel, QObject *parent)
    : QSortFilterProxyModel(parent)
{
    setSourceModel(sourceModel);
    setDynamicSortFilter(true);
    m_includeUnknowns = m_includeWarnings = m_includeErrors = true;
}

TaskModel *TaskFilterModel::taskModel() const
{
    return static_cast<TaskModel*>(sourceModel());
}

bool TaskFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
475
476
    bool accept = true;

477
    QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
478
    Task::TaskType type = Task::TaskType(index.data(TaskModel::Type).toInt());
479
    switch (type) {
480
    case Task::Unknown:
481
482
        accept = m_includeUnknowns;
        break;
483
    case Task::Warning:
484
485
        accept = m_includeWarnings;
        break;
486
    case Task::Error:
487
488
        accept = m_includeErrors;
        break;
489
490
    }

491
492
493
494
495
    const QString &categoryId = index.data(TaskModel::Category).toString();
    if (m_categoryIds.contains(categoryId))
        accept = false;

    return accept;
496
497
}

con's avatar
con committed
498
499
500
501
/////
// TaskWindow
/////

502
503
504
class TaskWindowPrivate
{
public:
505
506
507
508
    Internal::TaskModel *m_model;
    Internal::TaskFilterModel *m_filter;
    Internal::TaskView *m_listview;
    Internal::TaskWindowContext *m_taskWindowContext;
509
510
511
    QMenu *m_contextMenu;
    QModelIndex m_contextMenuIndex;
    ITaskHandler *m_defaultHandler;
512
513
514
    QToolButton *m_filterWarningsButton;
    QToolButton *m_categoriesButton;
    QMenu *m_categoriesMenu;
dt's avatar
dt committed
515
    TaskHub *m_taskHub;
516
517
};

518
519
520
521
522
523
524
525
526
527
528
529
530
531
static QToolButton *createFilterButton(QIcon icon, const QString &toolTip,
                                       QObject *receiver, const char *slot)
{
    QToolButton *button = new QToolButton;
    button->setIcon(icon);
    button->setToolTip(toolTip);
    button->setCheckable(true);
    button->setChecked(true);
    button->setAutoRaise(true);
    button->setEnabled(true);
    QObject::connect(button, SIGNAL(toggled(bool)), receiver, slot);
    return button;
}

dt's avatar
dt committed
532
TaskWindow::TaskWindow(TaskHub *taskhub) : d(new TaskWindowPrivate)
con's avatar
con committed
533
{
534
    d->m_defaultHandler = 0;
con's avatar
con committed
535

536
537
538
    d->m_model = new Internal::TaskModel;
    d->m_filter = new Internal::TaskFilterModel(d->m_model);
    d->m_listview = new Internal::TaskView;
con's avatar
con committed
539

540
541
542
543
544
545
    d->m_listview->setModel(d->m_filter);
    d->m_listview->setFrameStyle(QFrame::NoFrame);
    d->m_listview->setWindowTitle(tr("Build Issues"));
    d->m_listview->setSelectionMode(QAbstractItemView::SingleSelection);
    Internal::TaskDelegate *tld = new Internal::TaskDelegate(this);
    d->m_listview->setItemDelegate(tld);
546
    d->m_listview->setWindowIcon(QIcon(QLatin1String(Qt4ProjectManager::Constants::ICON_WINDOW)));
547
548
    d->m_listview->setContextMenuPolicy(Qt::ActionsContextMenu);
    d->m_listview->setAttribute(Qt::WA_MacShowFocusRect, false);
549

550
    d->m_taskWindowContext = new Internal::TaskWindowContext(d->m_listview);
dt's avatar
dt committed
551
552
    d->m_taskHub = taskhub;

553
    Core::ICore::instance()->addContextObject(d->m_taskWindowContext);
con's avatar
con committed
554

555
    connect(d->m_listview->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
556
            tld, SLOT(currentChanged(QModelIndex,QModelIndex)));
con's avatar
con committed
557

558
    connect(d->m_listview, SIGNAL(activated(QModelIndex)),
559
560
561
562
563
564
565
566
567
568
            this, SLOT(triggerDefaultHandler(QModelIndex)));

    d->m_contextMenu = new QMenu(d->m_listview);
    connect(d->m_contextMenu, SIGNAL(triggered(QAction*)),
            this, SLOT(contextMenuEntryTriggered(QAction*)));

    d->m_listview->setContextMenuPolicy(Qt::CustomContextMenu);

    connect(d->m_listview, SIGNAL(customContextMenuRequested(QPoint)),
            this, SLOT(showContextMenu(QPoint)));
569

570
571
572
573
574
575
576
577
578
    d->m_filterWarningsButton = createFilterButton(d->m_model->taskTypeIcon(Task::Warning),
                                                   tr("Show Warnings"),
                                                   this, SLOT(setShowWarnings(bool)));

    d->m_categoriesButton = new QToolButton;
    d->m_categoriesButton->setIcon(QIcon(QLatin1String(Core::Constants::ICON_FILTER)));
    d->m_categoriesButton->setToolTip(tr("Filter by categories"));
    d->m_categoriesButton->setAutoRaise(true);
    d->m_categoriesButton->setPopupMode(QToolButton::InstantPopup);
dt's avatar
dt committed
579
580
581
582
583

    d->m_categoriesMenu = new QMenu(d->m_categoriesButton);
    connect(d->m_categoriesMenu, SIGNAL(aboutToShow()), this, SLOT(updateCategoriesMenu()));
    connect(d->m_categoriesMenu, SIGNAL(triggered(QAction*)), this, SLOT(filterCategoryTriggered(QAction*)));

584
    d->m_categoriesButton->setMenu(d->m_categoriesMenu);
585

dt's avatar
dt committed
586
587
588
589
590
591
592
593
    connect(d->m_taskHub, SIGNAL(categoryAdded(QString, QString)),
            this, SLOT(addCategory(QString, QString)));
    connect(d->m_taskHub, SIGNAL(taskAdded(ProjectExplorer::Task)),
            this, SLOT(addTask(ProjectExplorer::Task)));
    connect(d->m_taskHub, SIGNAL(taskRemoved(ProjectExplorer::Task)),
            this, SLOT(removeTask(ProjectExplorer::Task)));
    connect(d->m_taskHub, SIGNAL(tasksCleared(QString)),
            this, SLOT(clearTasks(QString)));
con's avatar
con committed
594
595
596
597
}

TaskWindow::~TaskWindow()
{
598
    Core::ICore::instance()->removeContextObject(d->m_taskWindowContext);
599
    cleanContextMenu();
600
    delete d->m_filterWarningsButton;
601
602
603
604
    delete d->m_listview;
    delete d->m_filter;
    delete d->m_model;
    delete d;
con's avatar
con committed
605
606
607
608
}

QList<QWidget*> TaskWindow::toolBarWidgets() const
{
609
    return QList<QWidget*>() << d->m_filterWarningsButton << d->m_categoriesButton;
con's avatar
con committed
610
611
612
613
}

QWidget *TaskWindow::outputWidget(QWidget *)
{
614
    return d->m_listview;
con's avatar
con committed
615
616
}

617
void TaskWindow::clearTasks(const QString &categoryId)
con's avatar
con committed
618
{
619
    d->m_model->clearTasks(categoryId);
620

con's avatar
con committed
621
    emit tasksChanged();
dt's avatar
dt committed
622
    emit tasksCleared();
623
    navigateStateChanged();
con's avatar
con committed
624
625
626
627
628
629
}

void TaskWindow::visibilityChanged(bool /* b */)
{
}

630
631
632
void TaskWindow::addCategory(const QString &categoryId, const QString &displayName)
{
    Q_ASSERT(!categoryId.isEmpty());
633
    d->m_model->addCategory(categoryId, displayName);
634
635
636
}

void TaskWindow::addTask(const Task &task)
con's avatar
con committed
637
{
638
    d->m_model->addTask(task);
639

con's avatar
con committed
640
    emit tasksChanged();
641
    navigateStateChanged();
con's avatar
con committed
642
643
}

644
645
void TaskWindow::removeTask(const Task &task)
{
646
    d->m_model->removeTask(task);
647
648
649
650
651

    emit tasksChanged();
    navigateStateChanged();
}

652
void TaskWindow::triggerDefaultHandler(const QModelIndex &index)
con's avatar
con committed
653
654
655
656
{
    if (!index.isValid())
        return;

657
658
659
660
661
662
663
664
665
666
667
    // Find a default handler to use:
    if (!d->m_defaultHandler) {
        QList<ITaskHandler *> handlers = ExtensionSystem::PluginManager::instance()->getObjects<ITaskHandler>();
        foreach(ITaskHandler *handler, handlers) {
            if (handler->id() == QLatin1String(Constants::SHOW_TASK_IN_EDITOR)) {
                d->m_defaultHandler = handler;
                break;
            }
        }
    }
    Q_ASSERT(d->m_defaultHandler);
668
669
670
671
    Task task(d->m_filter->task(index));
    if (task.isNull())
        return;

672
673
674
675
676
    if (d->m_defaultHandler->canHandle(task)) {
        d->m_defaultHandler->handle(task);
    } else {
        if (!QFileInfo(task.file).exists())
            d->m_model->setFileNotFound(index, true);
677
    }
con's avatar
con committed
678
679
}

680
void TaskWindow::showContextMenu(const QPoint &position)
681
{
682
    QModelIndex index = d->m_listview->indexAt(position);
683
684
    if (!index.isValid())
        return;
685
686
687
    d->m_contextMenuIndex = index;
    cleanContextMenu();

688
689
690
    Task task = d->m_filter->task(index);
    if (task.isNull())
        return;
691
692
693
694
695
696
697
698
699
700
701

    QList<ITaskHandler *> handlers = ExtensionSystem::PluginManager::instance()->getObjects<ITaskHandler>();
    foreach(ITaskHandler *handler, handlers) {
        if (handler == d->m_defaultHandler)
            continue;
        QAction * action = handler->createAction(d->m_contextMenu);
        action->setEnabled(handler->canHandle(task));
        action->setData(qVariantFromValue(qobject_cast<QObject*>(handler)));
        d->m_contextMenu->addAction(action);
    }
    d->m_contextMenu->popup(d->m_listview->mapToGlobal(position));
702
703
}

704
void TaskWindow::contextMenuEntryTriggered(QAction *action)
705
{
706
    if (action->isEnabled()) {
707
708
709
710
        Task task = d->m_filter->task(d->m_contextMenuIndex);
        if (task.isNull())
            return;

711
712
713
714
        ITaskHandler *handler = qobject_cast<ITaskHandler*>(action->data().value<QObject*>());
        if (!handler)
            return;
        handler->handle(task);
715
    }
716
}
717

718
719
720
721
722
void TaskWindow::cleanContextMenu()
{
    QList<QAction *> actions = d->m_contextMenu->actions();
    qDeleteAll(actions);
    d->m_contextMenu->clear();
723
724
}

725
726
void TaskWindow::setShowWarnings(bool show)
{
727
728
    d->m_filter->setFilterIncludesWarnings(show);
    d->m_filter->setFilterIncludesUnknowns(show); // "Unknowns" are often associated with warnings
729
730
}

731
732
void TaskWindow::updateCategoriesMenu()
{
733
    d->m_categoriesMenu->clear();
734

735
    const QStringList filteredCategories = d->m_filter->filteredCategories();
736

737
738
    foreach (const QString &categoryId, d->m_model->categoryIds()) {
        const QString categoryName = d->m_model->categoryDisplayName(categoryId);
739

740
        QAction *action = new QAction(d->m_categoriesMenu);
741
742
743
744
745
        action->setCheckable(true);
        action->setText(categoryName);
        action->setData(categoryId);
        action->setChecked(!filteredCategories.contains(categoryId));

746
        d->m_categoriesMenu->addAction(action);
747
748
749
    }
}

750
void TaskWindow::filterCategoryTriggered(QAction *action)
751
{
752
753
    QString categoryId = action->data().toString();
    Q_ASSERT(!categoryId.isEmpty());
754

755
756
    QStringList categories = d->m_filter->filteredCategories();
    Q_ASSERT(d->m_filter->filteredCategories().contains(categoryId) == action->isChecked());
Tobias Hunger's avatar
Tobias Hunger committed
757

758
759
    if (action->isChecked()) {
        categories.removeOne(categoryId);
760
    } else {
761
        categories.append(categoryId);
Tobias Hunger's avatar
Tobias Hunger committed
762
    }
763
764

    d->m_filter->setFilteredCategories(categories);
765
766
}

dt's avatar
dt committed
767
int TaskWindow::taskCount() const
con's avatar
con committed
768
{
con's avatar
con committed
769
    return d->m_model->taskCount();
con's avatar
con committed
770
771
}

dt's avatar
dt committed
772
int TaskWindow::errorTaskCount() const
con's avatar
con committed
773
{
con's avatar
con committed
774
    return d->m_model->errorTaskCount();
con's avatar
con committed
775
776
777
778
779
780
781
}

int TaskWindow::priorityInStatusBar() const
{
    return 90;
}

782
783
void TaskWindow::clearContents()
{
dt's avatar
dt committed
784
785
786
    // clear all tasks in all displays
    // Yeah we are that special
    d->m_taskHub->clearTasks(QString());
787
788
}

con's avatar
con committed
789
790
bool TaskWindow::hasFocus()
{
791
    return d->m_listview->hasFocus();
con's avatar
con committed
792
793
794
795
}

bool TaskWindow::canFocus()
{
796
    return d->m_filter->rowCount();
con's avatar
con committed
797
798
799
800
}

void TaskWindow::setFocus()
{
801
802
803
804
    if (d->m_filter->rowCount()) {
        d->m_listview->setFocus();
        if (d->m_listview->currentIndex() == QModelIndex()) {
            d->m_listview->setCurrentIndex(d->m_filter->index(0,0, QModelIndex()));
con's avatar
con committed
805
806
807
808
        }
    }
}

809
810
bool TaskWindow::canNext()
{
811
    return d->m_filter->rowCount();
812
813
814
815
}

bool TaskWindow::canPrevious()
{
816
    return d->m_filter->rowCount();
817
818
819
820
}

void TaskWindow::goToNext()
{
821
    if (!canNext())
822
        return;
823
824
825
826
827
828
829
830
831
832
833
834
    QModelIndex startIndex = d->m_listview->currentIndex();
    QModelIndex currentIndex = startIndex;

    if (startIndex.isValid()) {
        do {
            int row = currentIndex.row() + 1;
            if (row == d->m_filter->rowCount())
                row = 0;
            currentIndex = d->m_filter->index(row, 0);
            if (d->m_filter->hasFile(currentIndex))
                break;
        } while (startIndex != currentIndex);
835
    } else {
836
        currentIndex = d->m_filter->index(0, 0);
837
    }
838
    d->m_listview->setCurrentIndex(currentIndex);
839
    triggerDefaultHandler(currentIndex);
840
841
842
843
}

void TaskWindow::goToPrev()
{
844
    if (!canPrevious())
845
        return;
846
847
848
849
850
851
852
853
854
855
856
857
    QModelIndex startIndex = d->m_listview->currentIndex();
    QModelIndex currentIndex = startIndex;

    if (startIndex.isValid()) {
        do {
            int row = currentIndex.row() - 1;
            if (row < 0)
                row = d->m_filter->rowCount() - 1;
            currentIndex = d->m_filter->index(row, 0);
            if (d->m_filter->hasFile(currentIndex))
                break;
        } while (startIndex != currentIndex);
858
    } else {
859
        currentIndex = d->m_filter->index(0, 0);
860
    }
861
    d->m_listview->setCurrentIndex(currentIndex);
862
    triggerDefaultHandler(currentIndex);
863
864
865
866
867
868
869
}

bool TaskWindow::canNavigate()
{
    return true;
}

con's avatar
con committed
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
/////
// Delegate
/////

TaskDelegate::TaskDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{
}

TaskDelegate::~TaskDelegate()
{
}

QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItemV4 opt = option;
    initStyleOption(&opt, index);

    QFontMetrics fm(option.font);
889
890
891
    int fontHeight = fm.height();
    int fontLeading = fm.leading();

con's avatar
con committed
892
893
894
    QSize s;
    s.setWidth(option.rect.width());
    const QAbstractItemView * view = qobject_cast<const QAbstractItemView *>(opt.widget);
895
    TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel();
con's avatar
con committed
896
897
898
899
    int width = opt.rect.width() - model->sizeOfFile() - model->sizeOfLineNumber() - 12 - 22;
    if (view->selectionModel()->currentIndex() == index) {
        QString description = index.data(TaskModel::Description).toString();
        // Layout the description
900
        int leading = fontLeading;
con's avatar
con committed
901
        int height = 0;
dt's avatar
dt committed
902
        description.replace('\n', QChar::LineSeparator);
con's avatar
con committed
903
904
        QTextLayout tl(description);
        tl.beginLayout();
hjk's avatar
hjk committed
905
        while (true) {
con's avatar
con committed
906
907
908
909
910
911
912
913
914
915
            QTextLine line = tl.createLine();
            if (!line.isValid())
                break;
            line.setLineWidth(width);
            height += leading;
            line.setPosition(QPoint(0, height));
            height += static_cast<int>(line.height());
        }
        tl.endLayout();

916
        s.setHeight(height + leading + fontHeight + 3);
con's avatar
con committed
917
    } else {
918
        s.setHeight(fontHeight + 3);
con's avatar
con committed
919
    }
con's avatar
con committed
920
921
    if (s.height() < TASK_ICON_SIZE + 2 * TASK_ICON_MARGIN)
        s.setHeight(TASK_ICON_SIZE + 2 * TASK_ICON_MARGIN);
con's avatar
con committed
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
    return s;
}

void TaskDelegate::emitSizeHintChanged(const QModelIndex &index)
{
    emit sizeHintChanged(index);
}

void TaskDelegate::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
    emit sizeHintChanged(current);
    emit sizeHintChanged(previous);
}

void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItemV4 opt = option;
    initStyleOption(&opt, index);
    painter->save();

    QFontMetrics fm(opt.font);
    QColor backgroundColor;
    QColor textColor;

    const QAbstractItemView * view = qobject_cast<const QAbstractItemView *>(opt.widget);
    bool selected = view->selectionModel()->currentIndex() == index;

    if (selected) {
        painter->setBrush(opt.palette.highlight().color());
        backgroundColor = opt.palette.highlight().color();
    } else {
        painter->setBrush(opt.palette.background().color());
        backgroundColor = opt.palette.background().color();
    }
    painter->setPen(Qt::NoPen);
    painter->drawRect(opt.rect);

    // Set Text Color
    if (selected)
        textColor = opt.palette.highlightedText().color();
    else
        textColor = opt.palette.text().color();

    painter->setPen(textColor);

967
    TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel();
968
    QIcon icon = index.data(TaskModel::Icon).value<QIcon>();
con's avatar
con committed
969
    painter->drawPixmap(TASK_ICON_MARGIN, opt.rect.top() + TASK_ICON_MARGIN, icon.pixmap(TASK_ICON_SIZE, TASK_ICON_SIZE));
con's avatar
con committed
970
971

    int width = opt.rect.width() - model->sizeOfFile() - model->sizeOfLineNumber() - 12 - 22;
972
    if (!selected) {
con's avatar
con committed
973
        // in small mode we lay out differently
dt's avatar
dt committed
974
        QString bottom = index.data(TaskModel::Description).toString().split('\n').first();
con's avatar
con committed
975
976
977
        painter->drawText(22, 2 + opt.rect.top() + fm.ascent(), bottom);
        if (fm.width(bottom) > width) {
            // draw a gradient to mask the text
978
            int gwidth = opt.rect.right() + 1 - width;
con's avatar
con committed
979
980
981
982
983
984
985
986
            QLinearGradient lg(QPoint(width, 0), QPoint(width+gwidth, 0));
            QColor c = backgroundColor;
            c.setAlpha(0);
            lg.setColorAt(0, c);
            lg.setColorAt(20.0/gwidth, backgroundColor);
            painter->fillRect(width, 2 + opt.rect.top(), gwidth, fm.height() + 1, lg);
        }
    } else {
987
        // Description
con's avatar
con committed
988
989
990
991
        QString description = index.data(TaskModel::Description).toString();
        // Layout the description
        int leading = fm.leading();
        int height = 0;
dt's avatar
dt committed
992
        description.replace('\n', QChar::LineSeparator);
con's avatar
con committed
993
        QTextLayout tl(description);
dt's avatar
dt committed
994
        tl.setAdditionalFormats(index.data(TaskModel::Task_t).value<ProjectExplorer::Task>().formats);
con's avatar
con committed
995
        tl.beginLayout();
hjk's avatar
hjk committed
996
        while (true) {
con's avatar
con committed
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
            QTextLine line = tl.createLine();
            if (!line.isValid())
                break;
            line.setLineWidth(width);
            height += leading;
            line.setPosition(QPoint(0, height));
            height += static_cast<int>(line.height());
        }
        tl.endLayout();
        tl.draw(painter, QPoint(22, 2 + opt.rect.top()));
        //painter->drawText(22, 2 + opt.rect.top() + fm.ascent(), description);

        QColor mix;
        mix.setRgb( static_cast<int>(0.7 * textColor.red()   + 0.3 * backgroundColor.red()),
                static_cast<int>(0.7 * textColor.green() + 0.3 * backgroundColor.green()),
                static_cast<int>(0.7 * textColor.blue()  + 0.3 * backgroundColor.blue()));
        painter->setPen(mix);

1015
        const QString directory = QDir::toNativeSeparators(index.data(TaskModel::File).toString());
con's avatar
con committed
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
        int secondBaseLine = 2 + fm.ascent() + opt.rect.top() + height + leading; //opt.rect.top() + fm.ascent() + fm.height() + 6;
        if (index.data(TaskModel::FileNotFound).toBool()) {
            QString fileNotFound = tr("File not found: %1").arg(directory);
            painter->setPen(Qt::red);
            painter->drawText(22, secondBaseLine, fileNotFound);
        } else {
            painter->drawText(22, secondBaseLine, directory);
        }
    }

    painter->setPen(textColor);
    // Assemble string for the right side
    // just filename + linenumer
    QString file = index.data(TaskModel::File).toString();
1030
    const int pos = file.lastIndexOf(QLatin1Char('/'));
con's avatar
con committed
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
    if (pos != -1)
        file = file.mid(pos +1);
    painter->drawText(width + 22 + 4, 2 + opt.rect.top() + fm.ascent(), file);

    QString topRight = index.data(TaskModel::Line).toString();
    painter->drawText(opt.rect.right() - fm.width(topRight) - 6 , 2 + opt.rect.top() + fm.ascent(), topRight);
    // Separator lines
    painter->setPen(QColor::fromRgb(150,150,150));
    painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());
    painter->restore();
}
1042
1043

TaskWindowContext::TaskWindowContext(QWidget *widget)
1044
1045
1046
  : Core::IContext(widget),
    m_taskList(widget),
    m_context(Core::Constants::C_PROBLEM_PANE)
1047
1048
1049
{
}

1050
Core::Context TaskWindowContext::context() const
1051
1052
1053
1054
1055
1056
1057
1058
1059
{
    return m_context;
}

QWidget *TaskWindowContext::widget()
{
    return m_taskList;
}

1060
} // namespace Internal
1061

1062
1063
} // namespace ProjectExplorer

1064
#include "taskwindow.moc"