From cda261db58e7cba38f232115950103d21decaf27 Mon Sep 17 00:00:00 2001 From: Tobias Hunger <tobias.hunger@nokia.com> Date: Wed, 20 Oct 2010 11:33:23 +0200 Subject: [PATCH] TaskWindow: Improve delegate Improve the delegate used to paint the tasks inside the taskwindow. * Limit maximum size of the area used to paint the filename. * Fix glitch where the filename is painted over the line number * Reduce size of gradients painted, use clipping instead. Task-number: QTCREATORBUG-2757 --- src/plugins/projectexplorer/taskwindow.cpp | 229 +++++++++++++++------ 1 file changed, 164 insertions(+), 65 deletions(-) diff --git a/src/plugins/projectexplorer/taskwindow.cpp b/src/plugins/projectexplorer/taskwindow.cpp index 08b973a804f..317da297f8e 100644 --- a/src/plugins/projectexplorer/taskwindow.cpp +++ b/src/plugins/projectexplorer/taskwindow.cpp @@ -55,8 +55,7 @@ #include <QtGui/QToolButton> namespace { -const int TASK_ICON_SIZE = 16; -const int TASK_ICON_MARGIN = 2; +const int ELLIPSIS_GRADIENT_WIDTH = 16; } namespace ProjectExplorer { @@ -71,25 +70,6 @@ public: void keyPressEvent(QKeyEvent *e); }; -class TaskDelegate : public QStyledItemDelegate -{ - 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 ¤t, const QModelIndex &previous); - -private: - void generateGradientPixmap(int width, int height, QColor color, bool selected) const; -}; - class TaskWindowContext : public Core::IContext { public: @@ -122,8 +102,8 @@ public: void removeTask(const Task &task); void clearTasks(const QString &categoryId = QString()); - int sizeOfFile(); - int sizeOfLineNumber(); + int sizeOfFile(const QFont &font); + int sizeOfLineNumber(const QFont &font); void setFileNotFound(const QModelIndex &index, bool b); enum Roles { File = Qt::UserRole, Line, Description, FileNotFound, Type, Category, Icon, Task_t }; @@ -142,11 +122,13 @@ private: QHash<QString,bool> m_fileNotFound; int m_maxSizeOfFileName; + QString m_fileMeasurementFont; const QIcon m_errorIcon; const QIcon m_warningIcon; int m_taskCount; int m_errorTaskCount; int m_sizeOfLineNumber; + QString m_lineMeasurementFont; }; class TaskFilterModel : public QSortFilterProxyModel @@ -184,6 +166,97 @@ private: QStringList m_categoryIds; }; +class TaskDelegate : public QStyledItemDelegate +{ + 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 ¤t, const QModelIndex &previous); + +private: + void generateGradientPixmap(int width, int height, QColor color, bool selected) const; + + /* + Collapsed: + +----------------------------------------------------------------------------------------------------+ + | TASKICONAREA TEXTAREA FILEAREA LINEAREA | + +----------------------------------------------------------------------------------------------------+ + + Expanded: + +----------------------------------------------------------------------------------------------------+ + | TASKICONICON TEXTAREA FILEAREA LINEAREA | + | more text -------------------------------------------------------------------------> | + +----------------------------------------------------------------------------------------------------+ + */ + class Positions + { + public: + Positions(const QStyleOptionViewItemV4 &options, TaskModel *model) : + m_totalWidth(options.rect.width()), + m_maxFileLength(model->sizeOfFile(options.font)), + m_maxLineLength(model->sizeOfLineNumber(options.font)), + m_realFileLength(m_maxFileLength), + m_top(options.rect.top()), + m_bottom(options.rect.bottom()) + { + int flexibleArea = lineAreaLeft() - textAreaLeft() - ITEM_SPACING; + if (m_maxFileLength > flexibleArea / 2) + m_realFileLength = flexibleArea / 2; + m_fontHeight = QFontMetrics(options.font).height(); + } + + int top() const { return m_top + ITEM_MARGIN; } + int left() const { return ITEM_MARGIN; } + int right() const { return m_totalWidth - ITEM_MARGIN; } + int bottom() const { return m_bottom; } + int firstLineHeight() const { return m_fontHeight + 1; } + int minimumHeight() const { return taskIconHeight() + 2 * ITEM_MARGIN; } + + int taskIconLeft() const { return left(); } + int taskIconWidth() const { return TASK_ICON_SIZE; } + int taskIconHeight() const { return TASK_ICON_SIZE; } + int taskIconRight() const { return taskIconLeft() + taskIconWidth(); } + QRect taskIcon() const { return QRect(taskIconLeft(), top(), taskIconWidth(), taskIconHeight()); } + + int textAreaLeft() const { return taskIconRight() + ITEM_SPACING; } + int textAreaWidth() const { return textAreaRight() - textAreaLeft(); } + int textAreaRight() const { return fileAreaLeft() - ITEM_SPACING; } + QRect textArea() const { return QRect(textAreaLeft(), top(), textAreaWidth(), firstLineHeight()); } + + int fileAreaLeft() const { return fileAreaRight() - fileAreaWidth(); } + int fileAreaWidth() const { return m_realFileLength; } + int fileAreaRight() const { return lineAreaLeft() - ITEM_SPACING; } + QRect fileArea() const { return QRect(fileAreaLeft(), top(), fileAreaWidth(), firstLineHeight()); } + + int lineAreaLeft() const { return lineAreaRight() - lineAreaWidth(); } + int lineAreaWidth() const { return m_maxLineLength; } + int lineAreaRight() const { return right(); } + QRect lineArea() const { return QRect(lineAreaLeft(), top(), lineAreaWidth(), firstLineHeight()); } + + private: + int m_totalWidth; + int m_maxFileLength; + int m_maxLineLength; + int m_realFileLength; + int m_top; + int m_bottom; + int m_fontHeight; + + static const int TASK_ICON_SIZE = 16; + static const int ITEM_MARGIN = 2; + static const int ITEM_SPACING = 2 * ITEM_MARGIN; + }; +}; + TaskView::TaskView(QWidget *parent) : QListView(parent) { @@ -223,7 +296,6 @@ TaskModel::TaskModel() : m_errorTaskCount(0), m_sizeOfLineNumber(0) { - } int TaskModel::taskCount() @@ -288,14 +360,7 @@ void TaskModel::addTask(const Task &task) m_tasks.append(task); endInsertRows(); - QFont font; - QFontMetrics fm(font); - QString filename = task.file; - const int pos = filename.lastIndexOf(QLatin1Char('/')); - if (pos != -1) - filename = task.file.mid(pos +1); - - m_maxSizeOfFileName = qMax(m_maxSizeOfFileName, fm.width(filename)); + m_maxSizeOfFileName = 0; ++m_taskCount; if (task.type == Task::Error) ++m_errorTaskCount; @@ -430,16 +495,32 @@ QString TaskModel::categoryDisplayName(const QString &categoryId) const return m_categories.value(categoryId); } -int TaskModel::sizeOfFile() +int TaskModel::sizeOfFile(const QFont &font) { + QString fontKey = font.key(); + if (m_maxSizeOfFileName > 0 && fontKey == m_fileMeasurementFont) + return m_maxSizeOfFileName; + + QFontMetrics fm(font); + m_fileMeasurementFont = fontKey; + + foreach (const Task & t, m_tasks) { + QString filename = t.file; + const int pos = filename.lastIndexOf(QLatin1Char('/')); + if (pos != -1) + filename = filename.mid(pos +1); + + m_maxSizeOfFileName = qMax(m_maxSizeOfFileName, fm.width(filename)); + } return m_maxSizeOfFileName; } -int TaskModel::sizeOfLineNumber() +int TaskModel::sizeOfLineNumber(const QFont &font) { - if (m_sizeOfLineNumber == 0) { - QFont font; + QString fontKey = font.key(); + if (m_sizeOfLineNumber == 0 || fontKey != m_lineMeasurementFont) { QFontMetrics fm(font); + m_lineMeasurementFont = fontKey; m_sizeOfLineNumber = fm.width("8888"); } return m_sizeOfLineNumber; @@ -889,11 +970,12 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd int fontHeight = fm.height(); int fontLeading = fm.leading(); - QSize s; - s.setWidth(option.rect.width()); const QAbstractItemView * view = qobject_cast<const QAbstractItemView *>(opt.widget); TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel(); - int width = opt.rect.width() - model->sizeOfFile() - model->sizeOfLineNumber() - 12 - 22; + Positions positions(option, model); + + QSize s; + s.setWidth(option.rect.width()); if (view->selectionModel()->currentIndex() == index) { QString description = index.data(TaskModel::Description).toString(); // Layout the description @@ -906,7 +988,7 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd QTextLine line = tl.createLine(); if (!line.isValid()) break; - line.setLineWidth(width); + line.setLineWidth(positions.textAreaWidth()); height += leading; line.setPosition(QPoint(0, height)); height += static_cast<int>(line.height()); @@ -917,8 +999,8 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd } else { s.setHeight(fontHeight + 3); } - if (s.height() < TASK_ICON_SIZE + 2 * TASK_ICON_MARGIN) - s.setHeight(TASK_ICON_SIZE + 2 * TASK_ICON_MARGIN); + if (s.height() < positions.minimumHeight()) + s.setHeight(positions.minimumHeight()); return s; } @@ -965,23 +1047,26 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, painter->setPen(textColor); TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel(); + Positions positions(opt, model); + + // Paint TaskIconArea: QIcon icon = index.data(TaskModel::Icon).value<QIcon>(); - painter->drawPixmap(TASK_ICON_MARGIN, opt.rect.top() + TASK_ICON_MARGIN, icon.pixmap(TASK_ICON_SIZE, TASK_ICON_SIZE)); + painter->drawPixmap(positions.left(), positions.top(), + icon.pixmap(positions.taskIconWidth(), positions.taskIconHeight())); - int width = opt.rect.width() - model->sizeOfFile() - model->sizeOfLineNumber() - 12 - 22; + // Paint TextArea: if (!selected) { // in small mode we lay out differently QString bottom = index.data(TaskModel::Description).toString().split('\n').first(); - painter->drawText(22, 2 + opt.rect.top() + fm.ascent(), bottom); - if (fm.width(bottom) > width) { + painter->setClipRect(positions.textArea()); + painter->drawText(positions.textAreaLeft(), positions.top() + fm.ascent(), bottom); + if (fm.width(bottom) > positions.textAreaWidth()) { // draw a gradient to mask the text - int gwidth = opt.rect.right() + 1 - width; - 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); + int gradientStart = positions.textAreaRight() - ELLIPSIS_GRADIENT_WIDTH + 1; + QLinearGradient lg(gradientStart, 0, gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0); + lg.setColorAt(0, Qt::transparent); + lg.setColorAt(1, backgroundColor); + painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg); } } else { // Description @@ -997,14 +1082,13 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, QTextLine line = tl.createLine(); if (!line.isValid()) break; - line.setLineWidth(width); + line.setLineWidth(positions.textAreaWidth()); 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); + tl.draw(painter, QPoint(positions.textAreaLeft(), positions.top())); QColor mix; mix.setRgb( static_cast<int>(0.7 * textColor.red() + 0.3 * backgroundColor.red()), @@ -1013,27 +1097,42 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, painter->setPen(mix); const QString directory = QDir::toNativeSeparators(index.data(TaskModel::File).toString()); - int secondBaseLine = 2 + fm.ascent() + opt.rect.top() + height + leading; //opt.rect.top() + fm.ascent() + fm.height() + 6; + int secondBaseLine = positions.top() + fm.ascent() + height + leading; if (index.data(TaskModel::FileNotFound).toBool()) { QString fileNotFound = tr("File not found: %1").arg(directory); painter->setPen(Qt::red); - painter->drawText(22, secondBaseLine, fileNotFound); + painter->drawText(positions.textAreaLeft(), secondBaseLine, fileNotFound); } else { - painter->drawText(22, secondBaseLine, directory); + painter->drawText(positions.textAreaLeft(), secondBaseLine, directory); } } - painter->setPen(textColor); - // Assemble string for the right side - // just filename + linenumer + + // Paint FileArea QString file = index.data(TaskModel::File).toString(); const int pos = file.lastIndexOf(QLatin1Char('/')); if (pos != -1) file = file.mid(pos +1); - painter->drawText(width + 22 + 4, 2 + opt.rect.top() + fm.ascent(), file); + const int realFileWidth = fm.width(file); + painter->setClipRect(positions.fileArea()); + painter->drawText(qMin(positions.fileAreaLeft(), positions.fileAreaRight() - realFileWidth), + positions.top() + fm.ascent(), file); + if (realFileWidth > positions.fileAreaWidth()) { + // draw a gradient to mask the text + int gradientStart = positions.fileAreaLeft() - 1; + QLinearGradient lg(gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0, gradientStart, 0); + lg.setColorAt(0, Qt::transparent); + lg.setColorAt(1, backgroundColor); + painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg); + } + + // Paint LineArea + QString lineText = index.data(TaskModel::Line).toString(); + painter->setClipRect(positions.lineArea()); + const int realLineWidth = fm.width(lineText); + painter->drawText(positions.lineAreaRight() - realLineWidth, positions.top() + fm.ascent(), lineText); + painter->setClipRect(opt.rect); - 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()); -- GitLab