/* Copyright 2013-2018 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "InstanceDelegate.h" #include #include #include #include #include #include #include "GroupView.h" #include "BaseInstance.h" #include "InstanceList.h" #include #include // Origin: Qt static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, qreal &widthUsed) { height = 0; widthUsed = 0; textLayout.beginLayout(); QString str = textLayout.text(); while (true) { QTextLine line = textLayout.createLine(); if (!line.isValid()) break; if (line.textLength() == 0) break; line.setLineWidth(lineWidth); line.setPosition(QPointF(0, height)); height += line.height(); widthUsed = qMax(widthUsed, line.naturalTextWidth()); } textLayout.endLayout(); } ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) { } void drawSelectionRect(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) { if ((option.state & QStyle::State_Selected)) painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); else { QColor backgroundColor = option.palette.color(QPalette::Background); backgroundColor.setAlpha(160); painter->fillRect(rect, QBrush(backgroundColor)); } } void drawFocusRect(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) { if (!(option.state & QStyle::State_HasFocus)) return; QStyleOptionFocusRect opt; opt.direction = option.direction; opt.fontMetrics = option.fontMetrics; opt.palette = option.palette; opt.rect = rect; // opt.state = option.state | QStyle::State_KeyboardFocusChange | // QStyle::State_Item; auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; opt.backgroundColor = option.palette.color(col); // Apparently some widget styles expect this hint to not be set painter->setRenderHint(QPainter::Antialiasing, false); QStyle *style = option.widget ? option.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); painter->setRenderHint(QPainter::Antialiasing); } // TODO this can be made a lot prettier void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItem &option, const int value, const int maximum) { if (maximum == 0 || value == maximum) { return; } painter->save(); qreal percent = (qreal)value / (qreal)maximum; QColor color = option.palette.color(QPalette::Dark); color.setAlphaF(0.70f); painter->setBrush(color); painter->setPen(QPen(QBrush(), 0)); painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); painter->restore(); } void drawBadges(QPainter *painter, const QStyleOptionViewItem &option, BaseInstance *instance, QIcon::Mode mode, QIcon::State state) { QList pixmaps; if (instance->isRunning()) { pixmaps.append("status-running"); } else if (instance->hasCrashed() || instance->hasVersionBroken()) { pixmaps.append("status-bad"); } if (instance->hasUpdateAvailable()) { pixmaps.append("checkupdate"); } static const int itemSide = 24; static const int spacing = 1; const int itemsPerRow = qMax(1, qFloor(double(option.rect.width() + spacing) / double(itemSide + spacing))); const int rows = qCeil((double)pixmaps.size() / (double)itemsPerRow); QListIterator it(pixmaps); painter->translate(option.rect.topLeft()); for (int y = 0; y < rows; ++y) { for (int x = 0; x < itemsPerRow; ++x) { if (!it.hasNext()) { return; } // FIXME: inject this. auto icon = XdgIcon::fromTheme(it.next()); // opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); const QPixmap pixmap; // itemSide QRect badgeRect( option.rect.width() - x * itemSide + qMax(x - 1, 0) * spacing - itemSide, y * itemSide + qMax(y - 1, 0) * spacing, itemSide, itemSide ); icon.paint(painter, badgeRect, Qt::AlignCenter, mode, state); } } painter->translate(-option.rect.topLeft()); } static QSize viewItemTextSize(const QStyleOptionViewItem *option) { QStyle *style = option->widget ? option->widget->style() : QApplication::style(); QTextOption textOption; textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); QTextLayout textLayout; textLayout.setTextOption(textOption); textLayout.setFont(option->font); textLayout.setText(option->text); const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; QRect bounds(0, 0, 100 - 2 * textMargin, 600); qreal height = 0, widthUsed = 0; viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); const QSize size(qCeil(widthUsed), qCeil(height)); return QSize(size.width() + 2 * textMargin, size.height()); } void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); painter->save(); painter->setClipRect(opt.rect); opt.features |= QStyleOptionViewItem::WrapText; opt.text = index.data().toString(); opt.textElideMode = Qt::ElideRight; opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); // const int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); const int iconSize = 48; QRect iconbox = opt.rect; const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; QRect textRect = opt.rect; QRect textHighlightRect = textRect; // clip the decoration on top, remove width padding textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); textHighlightRect.adjust(0, iconSize + 5, 0, 0); // draw background { // FIXME: unused // QSize textSize = viewItemTextSize ( &opt ); QPalette::ColorGroup cg; QStyleOptionViewItem opt2(opt); if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) { if (!(opt.state & QStyle::State_Active)) cg = QPalette::Inactive; else cg = QPalette::Normal; } else { cg = QPalette::Disabled; } opt2.palette.setCurrentColorGroup(cg); // fill in background, if any if (opt.backgroundBrush.style() != Qt::NoBrush) { QPointF oldBO = painter->brushOrigin(); painter->setBrushOrigin(opt.rect.topLeft()); painter->fillRect(opt.rect, opt.backgroundBrush); painter->setBrushOrigin(oldBO); } drawSelectionRect(painter, opt2, textHighlightRect); /* if (opt.showDecorationSelected) { drawSelectionRect(painter, opt2, opt.rect); drawFocusRect(painter, opt2, opt.rect); // painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); } else { // if ( opt.state & QStyle::State_Selected ) { // QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText, opt, // opt.widget ); // painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, // QPalette::Highlight ) ); drawSelectionRect(painter, opt2, textHighlightRect); drawFocusRect(painter, opt2, textHighlightRect); } } */ } // icon mode and state, also used for badges QIcon::Mode mode = QIcon::Normal; if (!(opt.state & QStyle::State_Enabled)) mode = QIcon::Disabled; else if (opt.state & QStyle::State_Selected) mode = QIcon::Selected; QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; // draw the icon { iconbox.setHeight(iconSize); opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); } // set the text colors QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) cg = QPalette::Inactive; if (opt.state & QStyle::State_Selected) { painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); } else { painter->setPen(opt.palette.color(cg, QPalette::Text)); } // draw the text QTextOption textOption; textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); textOption.setTextDirection(opt.direction); textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); QTextLayout textLayout; textLayout.setTextOption(textOption); textLayout.setFont(opt.font); textLayout.setText(opt.text); qreal width, height; viewItemTextLayout(textLayout, textRect.width(), height, width); const int lineCount = textLayout.lineCount(); const QRect layoutRect = QStyle::alignedRect( opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); const QPointF position = layoutRect.topLeft(); for (int i = 0; i < lineCount; ++i) { const QTextLine line = textLayout.lineAt(i); line.draw(painter, position); } // FIXME: this really has no business of being here. Make generic. auto instance = (BaseInstance*)index.data(InstanceList::InstancePointerRole) .value(); if (instance) { drawBadges(painter, opt, instance, mode, state); } drawProgressOverlay(painter, opt, index.data(GroupViewRoles::ProgressValueRole).toInt(), index.data(GroupViewRoles::ProgressMaximumRole).toInt()); painter->restore(); } QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); opt.features |= QStyleOptionViewItem::WrapText; opt.text = index.data().toString(); opt.textElideMode = Qt::ElideRight; opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables QSize szz = viewItemTextSize(&opt); height += szz.height(); // FIXME: maybe the icon items could scale and keep proportions? QSize sz(100, height); return sz; } void ListViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const { const int iconSize = 48; QRect textRect = option.rect; textRect.adjust(0, iconSize + 5, 0, 0); editor->setGeometry(textRect); } QWidget * ListViewDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { auto *le = new QLineEdit(parent); le->setFrame(false); return le; }