taskwindow.cpp 30 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 35
#include "task.h"

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

#include <QtCore/QDir>
45
#include <QtCore/QFileInfo>
46
#include <QtCore/QDebug>
47 48
#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 97 98 99 100 101 102 103 104
class TaskWindowContext : public Core::IContext
{
public:
    TaskWindowContext(QWidget *widget);
    virtual QList<int> context() const;
    virtual QWidget *widget();
private:
    QWidget *m_taskList;
    QList<int> m_context;
};

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();

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

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

150
class TaskFilterModel : public QSortFilterProxyModel
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
{
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(); }

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

169 170 171 172 173 174 175
protected:
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;

private:
    bool m_includeUnknowns;
    bool m_includeWarnings;
    bool m_includeErrors;
176
    QStringList m_categoryIds;
177 178
};

con's avatar
con committed
179 180 181 182 183 184 185 186 187 188 189 190 191
TaskView::TaskView(QWidget *parent)
    : QListView(parent)
{
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}

TaskView::~TaskView()
{

}

void TaskView::resizeEvent(QResizeEvent *e)
{
192
    Q_UNUSED(e)
con's avatar
con committed
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
    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
/////

210 211 212
TaskModel::TaskModel() :
    m_maxSizeOfFileName(0),
    m_errorIcon(QLatin1String(":/projectexplorer/images/compile_error.png")),
dt's avatar
dt committed
213 214
    m_warningIcon(QLatin1String(":/projectexplorer/images/compile_warning.png")),
    m_taskCount(0),
215 216
    m_errorTaskCount(0),
    m_sizeOfLineNumber(0)
dt's avatar
dt committed
217 218 219 220 221
{

}

int TaskModel::taskCount()
con's avatar
con committed
222
{
dt's avatar
dt committed
223 224 225 226
    return m_taskCount;
}

int TaskModel::errorTaskCount()
con's avatar
con committed
227
{
dt's avatar
dt committed
228
    return m_errorTaskCount;
229 230 231 232 233 234 235 236 237 238 239 240 241
}

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
242 243
}

244 245 246 247 248 249
void TaskModel::addCategory(const QString &categoryId, const QString &categoryName)
{
    Q_ASSERT(!categoryId.isEmpty());
    m_categories.insert(categoryId, categoryName);
}

250
QList<Task> TaskModel::tasks(const QString &categoryId) const
251 252 253 254 255 256 257 258
{
    if (categoryId.isEmpty()) {
        return m_tasks;
    } else {
        return m_tasksInCategory.value(categoryId);
    }
}

259
void TaskModel::addTask(const Task &task)
con's avatar
con committed
260
{
261 262
    Q_ASSERT(m_categories.keys().contains(task.category));

dt's avatar
dt committed
263 264 265 266 267 268 269
    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
270

271 272
    beginInsertRows(QModelIndex(), m_tasks.size(), m_tasks.size());
    m_tasks.append(task);
con's avatar
con committed
273 274 275 276 277
    endInsertRows();

    QFont font;
    QFontMetrics fm(font);
    QString filename = task.file;
278
    const int pos = filename.lastIndexOf(QLatin1Char('/'));
con's avatar
con committed
279
    if (pos != -1)
280 281
        filename = task.file.mid(pos +1);

con's avatar
con committed
282
    m_maxSizeOfFileName = qMax(m_maxSizeOfFileName, fm.width(filename));
dt's avatar
dt committed
283 284 285
    ++m_taskCount;
    if (task.type == Task::Error)
        ++m_errorTaskCount;
con's avatar
con committed
286
}
287 288 289 290 291 292 293

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
294 295 296
        --m_taskCount;
        if (task.type == Task::Error)
            --m_errorTaskCount;
297 298 299
        endRemoveRows();
    }
}
300 301 302 303 304 305 306 307 308

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
309 310
        m_taskCount = 0;
        m_errorTaskCount = 0;
311 312 313
        endRemoveRows();
        m_maxSizeOfFileName = 0;
    } else {
314 315
        int index = 0;
        int start = 0;
dt's avatar
dt committed
316
        int subErrorTaskCount = 0;
317 318 319 320 321 322 323 324
        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
325 326
                if (m_tasks.at(index).type == Task::Error)
                    ++subErrorTaskCount;
327 328 329 330 331 332 333 334 335 336
                ++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);
337

dt's avatar
dt committed
338 339
            m_taskCount -= index - start;
            m_errorTaskCount -= subErrorTaskCount;
340 341

            endRemoveRows();
342
            index = start;
343 344 345
        }
        // what to do with m_maxSizeOfFileName ?
    }
con's avatar
con committed
346 347 348 349 350 351 352 353 354 355 356 357
}


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
{
358
    Q_UNUSED(child)
con's avatar
con committed
359 360 361 362 363
    return QModelIndex();
}

int TaskModel::rowCount(const QModelIndex &parent) const
{
364
    return parent.isValid() ? 0 : m_tasks.count();
con's avatar
con committed
365 366 367 368 369 370 371 372 373
}

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

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

377
    if (role == TaskModel::File) {
378
        return m_tasks.at(index.row()).file;
379 380 381 382 383 384
    } 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) {
385
        return m_tasks.at(index.row()).description;
386
    } else if (role == TaskModel::FileNotFound) {
387
        return m_fileNotFound.value(m_tasks.at(index.row()).file);
388
    } else if (role == TaskModel::Type) {
389
        return (int)m_tasks.at(index.row()).type;
390
    } else if (role == TaskModel::Category) {
391
        return m_tasks.at(index.row()).category;
392
    } else if (role == TaskModel::Icon) {
393
        return taskTypeIcon(m_tasks.at(index.row()).type);
dt's avatar
dt committed
394
    } else if (role == TaskModel::Task_t) {
395
        return QVariant::fromValue(task(index));
396
    }
con's avatar
con committed
397 398 399
    return QVariant();
}

400 401 402 403 404
Task TaskModel::task(const QModelIndex &index) const
{
    return m_tasks.at(index.row());
}

405 406 407 408 409 410 411 412 413 414
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
415 416 417 418 419 420 421
int TaskModel::sizeOfFile()
{
    return m_maxSizeOfFileName;
}

int TaskModel::sizeOfLineNumber()
{
422 423 424 425 426 427
    if (m_sizeOfLineNumber == 0) {
        QFont font;
        QFontMetrics fm(font);
        m_sizeOfLineNumber = fm.width("8888");
    }
    return m_sizeOfLineNumber;
con's avatar
con committed
428 429 430 431
}

void TaskModel::setFileNotFound(const QModelIndex &idx, bool b)
{
432 433
    if (idx.isValid() && idx.row() < m_tasks.size()) {
        m_fileNotFound.insert(m_tasks[idx.row()].file, b);
con's avatar
con committed
434 435 436 437
        emit dataChanged(idx, idx);
    }
}

438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
/////
// 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
{
457 458
    bool accept = true;

459
    QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
460
    Task::TaskType type = Task::TaskType(index.data(TaskModel::Type).toInt());
461
    switch (type) {
462
    case Task::Unknown:
463 464
        accept = m_includeUnknowns;
        break;
465
    case Task::Warning:
466 467
        accept = m_includeWarnings;
        break;
468
    case Task::Error:
469 470
        accept = m_includeErrors;
        break;
471 472
    }

473 474 475 476 477
    const QString &categoryId = index.data(TaskModel::Category).toString();
    if (m_categoryIds.contains(categoryId))
        accept = false;

    return accept;
478 479
}

480 481
} // namespace Internal

con's avatar
con committed
482 483 484 485
/////
// TaskWindow
/////

486 487 488
class TaskWindowPrivate
{
public:
489 490 491 492
    Internal::TaskModel *m_model;
    Internal::TaskFilterModel *m_filter;
    Internal::TaskView *m_listview;
    Internal::TaskWindowContext *m_taskWindowContext;
493 494 495
    QMenu *m_contextMenu;
    QModelIndex m_contextMenuIndex;
    ITaskHandler *m_defaultHandler;
496 497 498 499 500
    QToolButton *m_filterWarningsButton;
    QToolButton *m_categoriesButton;
    QMenu *m_categoriesMenu;
};

501
static QToolButton *createFilterButton(QIcon icon, const QString &toolTip,
502 503 504
                                       QObject *receiver, const char *slot)
{
    QToolButton *button = new QToolButton;
505
    button->setIcon(icon);
506 507 508 509 510 511 512 513 514
    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;
}

515
TaskWindow::TaskWindow() : d(new TaskWindowPrivate)
con's avatar
con committed
516
{
517
    d->m_defaultHandler = 0;
con's avatar
con committed
518

519 520 521
    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
522

523 524 525 526 527 528 529 530 531
    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);
    d->m_listview->setWindowIcon(QIcon(":/qt4projectmanager/images/window.png"));
    d->m_listview->setContextMenuPolicy(Qt::ActionsContextMenu);
    d->m_listview->setAttribute(Qt::WA_MacShowFocusRect, false);
532

533
    d->m_taskWindowContext = new Internal::TaskWindowContext(d->m_listview);
534
    Core::ICore::instance()->addContextObject(d->m_taskWindowContext);
con's avatar
con committed
535

536
    connect(d->m_listview->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
537
            tld, SLOT(currentChanged(QModelIndex,QModelIndex)));
con's avatar
con committed
538

539
    connect(d->m_listview, SIGNAL(activated(QModelIndex)),
540
            this, SLOT(triggerDefaultHandler(QModelIndex)));
541
    connect(d->m_listview, SIGNAL(clicked(QModelIndex)),
542 543 544 545 546 547 548 549 550 551
            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)));
552

553
    d->m_filterWarningsButton = createFilterButton(taskTypeIcon(Task::Warning),
554
                                                tr("Show Warnings"),
555 556
                                                this, SLOT(setShowWarnings(bool)));

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

561 562 563 564 565 566
    d->m_categoriesButton = new QToolButton;
    d->m_categoriesButton->setIcon(QIcon(":/projectexplorer/images/filtericon.png"));
    d->m_categoriesButton->setToolTip(tr("Filter by categories"));
    d->m_categoriesButton->setAutoRaise(true);
    d->m_categoriesButton->setPopupMode(QToolButton::InstantPopup);
    d->m_categoriesButton->setMenu(d->m_categoriesMenu);
567

568 569
    qRegisterMetaType<ProjectExplorer::Task>("ProjectExplorer::Task");
    qRegisterMetaType<QList<ProjectExplorer::Task> >("QList<ProjectExplorer::Task>");
con's avatar
con committed
570 571 572 573
}

TaskWindow::~TaskWindow()
{
574
    Core::ICore::instance()->removeContextObject(d->m_taskWindowContext);
575
    cleanContextMenu();
576 577 578 579 580
    delete d->m_filterWarningsButton;
    delete d->m_listview;
    delete d->m_filter;
    delete d->m_model;
    delete d;
con's avatar
con committed
581 582 583 584
}

QList<QWidget*> TaskWindow::toolBarWidgets() const
{
585
    return QList<QWidget*>() << d->m_filterWarningsButton << d->m_categoriesButton;
con's avatar
con committed
586 587 588 589
}

QWidget *TaskWindow::outputWidget(QWidget *)
{
590
    return d->m_listview;
con's avatar
con committed
591 592
}

593
void TaskWindow::clearTasks(const QString &categoryId)
con's avatar
con committed
594
{
595
    d->m_model->clearTasks(categoryId);
596

con's avatar
con committed
597
    emit tasksChanged();
598
    navigateStateChanged();
con's avatar
con committed
599 600 601 602 603 604
}

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

605 606 607
void TaskWindow::addCategory(const QString &categoryId, const QString &displayName)
{
    Q_ASSERT(!categoryId.isEmpty());
608
    d->m_model->addCategory(categoryId, displayName);
609 610 611
}

void TaskWindow::addTask(const Task &task)
con's avatar
con committed
612
{
613
    d->m_model->addTask(task);
614

con's avatar
con committed
615
    emit tasksChanged();
616
    navigateStateChanged();
con's avatar
con committed
617 618
}

619 620
void TaskWindow::removeTask(const Task &task)
{
621
    d->m_model->removeTask(task);
622 623 624 625 626

    emit tasksChanged();
    navigateStateChanged();
}

627
void TaskWindow::triggerDefaultHandler(const QModelIndex &index)
con's avatar
con committed
628 629 630 631
{
    if (!index.isValid())
        return;

632 633 634 635 636 637 638 639 640 641 642
    // 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);
643
    Task task(d->m_model->task(index));
644 645 646 647 648
    if (d->m_defaultHandler->canHandle(task)) {
        d->m_defaultHandler->handle(task);
    } else {
        if (!QFileInfo(task.file).exists())
            d->m_model->setFileNotFound(index, true);
649
    }
con's avatar
con committed
650 651
}

652
void TaskWindow::showContextMenu(const QPoint &position)
653
{
654
    QModelIndex index = d->m_listview->indexAt(position);
655 656
    if (!index.isValid())
        return;
657 658 659
    d->m_contextMenuIndex = index;
    cleanContextMenu();

660
    Task task = d->m_model->task(index);
661 662 663 664 665 666 667 668 669 670 671

    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));
672 673
}

674
void TaskWindow::contextMenuEntryTriggered(QAction *action)
675
{
676
    if (action->isEnabled()) {
677
        Task task = d->m_model->task(d->m_contextMenuIndex);
678 679 680 681
        ITaskHandler *handler = qobject_cast<ITaskHandler*>(action->data().value<QObject*>());
        if (!handler)
            return;
        handler->handle(task);
682
    }
683
}
684

685 686 687 688 689
void TaskWindow::cleanContextMenu()
{
    QList<QAction *> actions = d->m_contextMenu->actions();
    qDeleteAll(actions);
    d->m_contextMenu->clear();
690 691
}

692 693
void TaskWindow::setShowWarnings(bool show)
{
694 695
    d->m_filter->setFilterIncludesWarnings(show);
    d->m_filter->setFilterIncludesUnknowns(show); // "Unknowns" are often associated with warnings
696 697
}

698 699
void TaskWindow::updateCategoriesMenu()
{
700
    d->m_categoriesMenu->clear();
701

702
    const QStringList filteredCategories = d->m_filter->filteredCategories();
703

704 705
    foreach (const QString &categoryId, d->m_model->categoryIds()) {
        const QString categoryName = d->m_model->categoryDisplayName(categoryId);
706

707
        QAction *action = new QAction(d->m_categoriesMenu);
708 709 710 711 712
        action->setCheckable(true);
        action->setText(categoryName);
        action->setData(categoryId);
        action->setChecked(!filteredCategories.contains(categoryId));

713
        d->m_categoriesMenu->addAction(action);
714 715 716 717 718 719 720 721
    }
}

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

722 723
    QStringList categories = d->m_filter->filteredCategories();
    Q_ASSERT(d->m_filter->filteredCategories().contains(categoryId) == action->isChecked());
724 725 726 727 728 729 730

    if (action->isChecked()) {
        categories.removeOne(categoryId);
    } else {
        categories.append(categoryId);
    }

731
    d->m_filter->setFilteredCategories(categories);
732 733
}

dt's avatar
dt committed
734
int TaskWindow::taskCount() const
con's avatar
con committed
735
{
con's avatar
con committed
736
    return d->m_model->taskCount();
con's avatar
con committed
737 738
}

dt's avatar
dt committed
739
int TaskWindow::errorTaskCount() const
con's avatar
con committed
740
{
con's avatar
con committed
741
    return d->m_model->errorTaskCount();
con's avatar
con committed
742 743 744 745 746 747 748
}

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

749 750 751 752 753
void TaskWindow::clearContents()
{
    clearTasks();
}

con's avatar
con committed
754 755
bool TaskWindow::hasFocus()
{
756
    return d->m_listview->hasFocus();
con's avatar
con committed
757 758 759 760
}

bool TaskWindow::canFocus()
{
761
    return d->m_filter->rowCount();
con's avatar
con committed
762 763 764 765
}

void TaskWindow::setFocus()
{
766 767 768 769
    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
770 771 772 773
        }
    }
}

774 775
bool TaskWindow::canNext()
{
776
    return d->m_filter->rowCount();
777 778 779 780
}

bool TaskWindow::canPrevious()
{
781
    return d->m_filter->rowCount();
782 783 784 785
}

void TaskWindow::goToNext()
{
786
    if (!d->m_filter->rowCount())
787
        return;
788
    QModelIndex currentIndex = d->m_listview->currentIndex();
789 790
    if (currentIndex.isValid()) {
        int row = currentIndex.row() + 1;
791
        if (row == d->m_filter->rowCount())
792
            row = 0;
793
        currentIndex = d->m_filter->index(row, 0);
794
    } else {
795
        currentIndex = d->m_filter->index(0, 0);
796
    }
797
    d->m_listview->setCurrentIndex(currentIndex);
798
    triggerDefaultHandler(currentIndex);
799 800 801 802
}

void TaskWindow::goToPrev()
{
803
    if (!d->m_filter->rowCount())
804
        return;
805
    QModelIndex currentIndex = d->m_listview->currentIndex();
806 807 808
    if (currentIndex.isValid()) {
        int row = currentIndex.row() -1;
        if (row < 0)
809 810
            row = d->m_filter->rowCount() - 1;
        currentIndex = d->m_filter->index(row, 0);
811
    } else {
812
        currentIndex = d->m_filter->index(d->m_filter->rowCount()-1, 0);
813
    }
814
    d->m_listview->setCurrentIndex(currentIndex);
815
    triggerDefaultHandler(currentIndex);
816 817 818 819 820 821 822
}

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

823
QIcon TaskWindow::taskTypeIcon(int t) const
824
{
825
    return d->m_model->taskTypeIcon(static_cast<Task::TaskType>(t));
826 827
}

828
namespace Internal {
con's avatar
con committed
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
/////
// 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);
848 849 850
    int fontHeight = fm.height();
    int fontLeading = fm.leading();

con's avatar
con committed
851 852 853
    QSize s;
    s.setWidth(option.rect.width());
    const QAbstractItemView * view = qobject_cast<const QAbstractItemView *>(opt.widget);
854
    TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel();
con's avatar
con committed
855 856 857 858
    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
859
        int leading = fontLeading;
con's avatar
con committed
860
        int height = 0;
dt's avatar
dt committed
861
        description.replace('\n', QChar::LineSeparator);
con's avatar
con committed
862 863
        QTextLayout tl(description);
        tl.beginLayout();
hjk's avatar
hjk committed
864
        while (true) {
con's avatar
con committed
865 866 867 868 869 870 871 872 873 874
            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();

875
        s.setHeight(height + leading + fontHeight + 3);
con's avatar
con committed
876
    } else {
877
        s.setHeight(fontHeight + 3);
con's avatar
con committed
878
    }
con's avatar
con committed
879 880
    if (s.height() < TASK_ICON_SIZE + 2 * TASK_ICON_MARGIN)
        s.setHeight(TASK_ICON_SIZE + 2 * TASK_ICON_MARGIN);
con's avatar
con committed
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925
    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);

926
    TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel();
927
    QIcon icon = index.data(TaskModel::Icon).value<QIcon>();
con's avatar
con committed
928
    painter->drawPixmap(TASK_ICON_MARGIN, opt.rect.top() + TASK_ICON_MARGIN, icon.pixmap(TASK_ICON_SIZE, TASK_ICON_SIZE));
con's avatar
con committed
929 930

    int width = opt.rect.width() - model->sizeOfFile() - model->sizeOfLineNumber() - 12 - 22;
931
    if (!selected) {
con's avatar
con committed
932
        // in small mode we lay out differently
dt's avatar
dt committed
933
        QString bottom = index.data(TaskModel::Description).toString().split('\n').first();
con's avatar
con committed
934 935 936
        painter->drawText(22, 2 + opt.rect.top() + fm.ascent(), bottom);
        if (fm.width(bottom) > width) {
            // draw a gradient to mask the text
937
            int gwidth = opt.rect.right() + 1 - width;
con's avatar
con committed
938 939 940 941 942 943 944 945
            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 {
946
        // Description
con's avatar
con committed
947 948 949 950
        QString description = index.data(TaskModel::Description).toString();
        // Layout the description
        int leading = fm.leading();
        int height = 0;
dt's avatar
dt committed
951
        description.replace('\n', QChar::LineSeparator);
con's avatar
con committed
952
        QTextLayout tl(description);
dt's avatar
dt committed
953
        tl.setAdditionalFormats(index.data(TaskModel::Task_t).value<ProjectExplorer::Task>().formats);
con's avatar
con committed
954
        tl.beginLayout();
hjk's avatar
hjk committed
955
        while (true) {
con's avatar
con committed
956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973
            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);

974
        const QString directory = QDir::toNativeSeparators(index.data(TaskModel::File).toString());