diff --git a/src/plugins/core/notify/constants.h b/src/plugins/core/notify/constants.h index e27396895..d6ba1d21f 100644 --- a/src/plugins/core/notify/constants.h +++ b/src/plugins/core/notify/constants.h @@ -7,6 +7,8 @@ #include "notificationentity.h" +#include + typedef std::shared_ptr EntityPtr; static const int ItemSpacing = 10; //消息通知内部Space @@ -16,4 +18,17 @@ static const int BubbleEntities = 5; static const char DefaultButtonField[] = "_default"; static const char NotificationContentName[] = "NotificationContent"; +enum NotificationRole { + kActionsRole = Qt::UserRole + 1, + kSourceRole, + kEntityRole +}; + +struct ActionBtuuonInfo +{ + QString id; + QString text; + QRect rect; +}; + #endif // CONSTANTS_H diff --git a/src/plugins/core/notify/gui/itemdelegate.cpp b/src/plugins/core/notify/gui/itemdelegate.cpp index 757e5861f..3dcf7aa1e 100644 --- a/src/plugins/core/notify/gui/itemdelegate.cpp +++ b/src/plugins/core/notify/gui/itemdelegate.cpp @@ -4,77 +4,317 @@ #include "itemdelegate.h" #include "notificationlistview.h" -#include "notificationitemwidget.h" -#include "notify/constants.h" #include #include +#include +#include +#include +#ifdef DTKWIDGET_CLASS_DPaletteHelper +# include +#endif #include #include #include +#include DWIDGET_USE_NAMESPACE +constexpr int kRadius { 8 }; +constexpr int kNotificationSourceHeight { 15 }; +constexpr int kNotificationLineWidth { 388 }; +constexpr int kNotificationMessageWidth { 310 }; +constexpr int kNotificationActionHeight { 32 }; +constexpr int kNotificationSpacing { 10 }; + ItemDelegate::ItemDelegate(NotificationListView *view, QObject *parent) : QStyledItemDelegate(parent), view(view) { } -QWidget *ItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - Q_UNUSED(option) + if (!index.isValid()) + return QStyledItemDelegate::paint(painter, option, index); - EntityPtr notify = index.data().value(); - if (!notify) - return nullptr; + QStyleOptionViewItem opt = option; + QStyledItemDelegate::initStyleOption(&opt, index); - DFrame *frame = new DFrame(parent); - frame->setBackgroundRole(DPalette::ItemBackground); - frame->setFrameRounded(true); - - QHBoxLayout *layout = new QHBoxLayout(frame); - layout->setContentsMargins(10, 10, 10, 10); + painter->setRenderHint(QPainter::Antialiasing); + drawBackground(painter, opt); + auto iconRect = drawIcon(painter, opt, index); + drawDisplayText(painter, opt, index, iconRect); + drawActionButton(painter, option, index); +} - NotificationItemWidget *item = new NotificationItemWidget(parent, notify); - layout->addWidget(item); +bool ItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) +{ + if (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress) { + Q_EMIT model->layoutChanged({ QPersistentModelIndex(index) }); + return false; + } else if (event->type() == QEvent::MouseButtonRelease) { + const QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::LeftButton) { + auto entity = index.data(kEntityRole).value(); + QPoint mousePos = view->mapFromGlobal(QCursor::pos()); + const auto &closeRect = closeButtonRect(option.rect); + if (closeRect.contains(mousePos)) { + Q_EMIT view->processed(entity); + return true; + } - connect(item, &NotificationItemWidget::actionInvoked, view, std::bind(&NotificationListView::actionInvoked, view, notify, std::placeholders::_1)); - connect(item, &NotificationItemWidget::processed, view, &NotificationListView::processed); + const auto &actInfoList = actionInfoList(option, index); + for (const auto &info : actInfoList) { + if (info.rect.contains(mousePos)) { + Q_EMIT view->actionInvoked(entity, info.id); + Q_EMIT view->processed(entity); + return true; + } + } + } + } - return frame; + return false; } QSize ItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - EntityPtr notify = index.data().value(); - Q_ASSERT(notify); - - int height = 32; - QFontMetrics fontMetrics(qApp->font()); - if (!notify->message().isEmpty()) { - QRect boundingRect = fontMetrics.boundingRect(0, 0, 300, 0, Qt::TextWordWrap, notify->message()); - height += boundingRect.height() + 20; - } + int height = kNotificationSpacing * 2; + QString message = index.data(Qt::DisplayRole).toString(); + auto f = DFontSizeManager::instance()->get(DFontSizeManager::T5); + QFontMetrics fontMetrics(f); + QRect boundingRect = fontMetrics.boundingRect(0, 0, kNotificationMessageWidth, 0, Qt::TextWordWrap, message); + height += boundingRect.height(); - if (!notify->name().isEmpty()) { - QRect boundingRect = fontMetrics.boundingRect(0, 0, 300, 0, Qt::TextWordWrap, notify->name()); - height += boundingRect.height() + 10; + QString source = index.data(kSourceRole).toString(); + if (!source.isEmpty()) { + height += kNotificationSourceHeight + kNotificationSpacing; } - if (!notify->actions().isEmpty()) { - height += 34; + auto actList = index.data(kActionsRole).toStringList(); + if (!actList.isEmpty()) { + height += kNotificationActionHeight; } - return { view->width(), height }; + return { kNotificationLineWidth, height }; } -void ItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const +void ItemDelegate::drawBackground(QPainter *painter, const QStyleOptionViewItem &option) const { - Q_UNUSED(index) + painter->save(); +#ifdef DTKWIDGET_CLASS_DPaletteHelper + DPalette palette = DPaletteHelper::instance()->palette(option.widget); +#else + DPalette palette = DGuiApplicationHelper::instance()->applicationPalette(); +#endif + painter->setBrush(palette.brush(DPalette::ItemBackground)); + painter->setPen(Qt::NoPen); + painter->drawRoundedRect(option.rect, kRadius, kRadius); + painter->restore(); +} +QRect ItemDelegate::drawIcon(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ QRect rect = option.rect; - QSize size = sizeHint(option, index); - editor->setGeometry(rect.x(), rect.y(), size.width(), size.height() - ItemSpacing); + rect.setSize(view->iconSize()); + rect.moveLeft(rect.left() + kNotificationSpacing); + rect.moveTop(rect.top() + kNotificationSpacing); + + auto px = option.icon.pixmap(view->iconSize()); + px.setDevicePixelRatio(qApp->devicePixelRatio()); + + painter->drawPixmap(rect, px); + return rect; +} + +QRect ItemDelegate::drawDisplayText(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const QRect &iconRect) const +{ + QRect textRect = drawNotificationText(painter, option, index, iconRect); + textRect = drawSourceText(painter, option, index, textRect); + return textRect; +} + +QRect ItemDelegate::drawNotificationText(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const QRect &rect) const +{ + QRect textRect = option.rect; + textRect.setLeft(rect.right() + kNotificationSpacing); + textRect.moveTop(textRect.top() + kNotificationSpacing - 4); + const auto &btnRect = drawCloseButton(painter, option); + textRect.setRight(btnRect.left() - kNotificationSpacing); + + QTextOption textOption; + textOption.setWrapMode(QTextOption::WordWrap); + textOption.setTextDirection(option.direction); + textOption.setAlignment(QStyle::visualAlignment(option.direction, Qt::AlignLeft | Qt::AlignVCenter)); + + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(option.font); + textLayout.setText(option.text); + + QSizeF textLayoutSize = doTextLayout(&textLayout, textRect.width()); + textRect.setSize(textLayoutSize.toSize()); + textLayout.draw(painter, textRect.topLeft()); + return textRect; +} + +QRect ItemDelegate::drawSourceText(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const QRect &rect) const +{ + QString sourceText = index.data(kSourceRole).toString(); + if (sourceText.isEmpty()) + return rect; + + QRect textRect = option.rect; + textRect.setLeft(rect.left()); + textRect.moveTop(rect.bottom() + kNotificationSpacing - 4); + + QTextOption textOption; + textOption.setWrapMode(QTextOption::NoWrap); + textOption.setTextDirection(option.direction); + textOption.setAlignment(QStyle::visualAlignment(option.direction, Qt::AlignLeft | Qt::AlignVCenter)); + + QTextLayout textLayout; + textLayout.setTextOption(textOption); + textLayout.setFont(DFontSizeManager::instance()->get(DFontSizeManager::T7, option.font)); + textLayout.setText(sourceText); + + QSizeF textLayoutSize = doTextLayout(&textLayout, textRect.width()); + if (textRect.width() < textLayoutSize.width()) { + QString displayText = option.fontMetrics.elidedText(sourceText, Qt::ElideRight, textRect.width()); + textLayout.setText(displayText); + textLayoutSize = doTextLayout(&textLayout, textRect.width()); + } + +#ifdef DTKWIDGET_CLASS_DPaletteHelper + DPalette palette = DPaletteHelper::instance()->palette(option.widget); +#else + DPalette palette = DGuiApplicationHelper::instance()->applicationPalette(); +#endif + painter->save(); + painter->setPen(palette.color(DPalette::TextTips)); + textLayout.draw(painter, textRect.topLeft()); + painter->restore(); + + return textRect; +} + +QRect ItemDelegate::drawCloseButton(QPainter *painter, const QStyleOptionViewItem &option) const +{ + const auto &rect = closeButtonRect(option.rect); + if (option.state & QStyle::State_MouseOver) { + DStyleOptionButton opt; + opt.icon = DStyle::standardIcon(option.widget->style(), DStyle::SP_CloseButton); + opt.iconSize = rect.size(); + opt.rect = rect; + opt.state = QStyle::State_Enabled; + opt.features |= DStyleOptionButton::Flat; + opt.init(option.widget); + + QPoint mousePos = view->mapFromGlobal(QCursor::pos()); + bool isHovered = rect.contains(mousePos); + if (option.state & QStyle::State_MouseOver && isHovered) { + opt.state |= QStyle::State_MouseOver; + // pressed + if (QApplication::mouseButtons() & Qt::LeftButton) + opt.state |= QStyle::State_Sunken; + } + + painter->save(); + DStyle::drawControl(option.widget->style(), DStyle::CE_IconButton, &opt, painter, option.widget); + painter->restore(); + } + + return rect; +} + +void ItemDelegate::drawActionButton(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + const auto &actInfoList = actionInfoList(option, index); + for (const auto &info : actInfoList) { + DStyleOptionButton opt; + opt.text = info.text; + opt.rect = info.rect; + opt.state = QStyle::State_Enabled; + opt.init(option.widget); + + QPoint mousePos = view->mapFromGlobal(QCursor::pos()); + bool isHovered = info.rect.contains(mousePos); + if (option.state & QStyle::State_MouseOver && isHovered) { + opt.state |= QStyle::State_MouseOver; + // pressed + if (QApplication::mouseButtons() & Qt::LeftButton) + opt.state |= QStyle::State_Sunken; + } + + painter->save(); + if (info.id.endsWith(DefaultButtonField)) { + opt.features |= QStyleOptionButton::ButtonFeature(DStyleOptionButton::SuggestButton); + QColor startColor = option.palette.color(QPalette::Highlight); + QColor endColor = DGuiApplicationHelper::adjustColor(startColor, 0, 0, +10, 0, 0, 0, 0); + + opt.palette.setBrush(QPalette::Light, QBrush(endColor)); + opt.palette.setBrush(QPalette::Dark, QBrush(startColor)); + opt.palette.setBrush(QPalette::ButtonText, option.palette.highlightedText()); + } + + option.widget->style()->drawControl(QStyle::CE_PushButton, &opt, painter, option.widget); + painter->restore(); + } +} + +QSizeF ItemDelegate::doTextLayout(QTextLayout *textLayout, int width) const +{ + qreal height = 0; + qreal widthUsed = 0; + textLayout->beginLayout(); + while (true) { + QTextLine line = textLayout->createLine(); + if (!line.isValid()) + break; + line.setLineWidth(width); + line.setPosition(QPointF(0, height)); + height += line.height(); + widthUsed = qMax(widthUsed, line.naturalTextWidth()); + } + textLayout->endLayout(); + return QSizeF(widthUsed, height); +} + +QList ItemDelegate::actionInfoList(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + auto actList = index.data(kActionsRole).toStringList(); + if (actList.isEmpty()) + return {}; + + QList actInfoList; + ActionBtuuonInfo info; + QRect tmpRect = option.rect; + for (int i = actList.size() - 1; i >= 0; --i) { + if (i % 2 == 0) { + info.id = actList[i]; + actInfoList.append(info); + } else { + info.text = actList[i]; + info.rect = tmpRect; + auto textRect = option.fontMetrics.boundingRect(0, 0, tmpRect.width(), 0, Qt::TextSingleLine, info.text); + textRect.adjust(-15, -5, 15, 5); + info.rect.setSize(textRect.size()); + info.rect.moveRight(tmpRect.right() - kNotificationSpacing); + info.rect.moveBottom(option.rect.bottom() - kNotificationSpacing); + tmpRect.moveRight(info.rect.left()); + } + } + + return actInfoList; +} + +QRect ItemDelegate::closeButtonRect(const QRect &itemRect) const +{ + QRect rect = itemRect; + rect.setSize({ 24, 24 }); + rect.moveRight(itemRect.right() - kNotificationSpacing); + rect.moveTop(itemRect.top() + kNotificationSpacing - 4); + return rect; } diff --git a/src/plugins/core/notify/gui/itemdelegate.h b/src/plugins/core/notify/gui/itemdelegate.h index 7234ba18d..fcff9082f 100644 --- a/src/plugins/core/notify/gui/itemdelegate.h +++ b/src/plugins/core/notify/gui/itemdelegate.h @@ -5,7 +5,10 @@ #ifndef ITEMDELEGATE_H #define ITEMDELEGATE_H +#include "notify/constants.h" + #include +#include class NotificationListView; class ItemDelegate : public QStyledItemDelegate @@ -13,11 +16,23 @@ class ItemDelegate : public QStyledItemDelegate public: explicit ItemDelegate(NotificationListView *view, QObject *parent = nullptr); - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: + void drawBackground(QPainter *painter, const QStyleOptionViewItem &option) const; + QRect drawIcon(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QRect drawDisplayText(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const QRect &iconRect) const; + QRect drawNotificationText(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const QRect &rect) const; + QRect drawSourceText(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const QRect &rect) const; + QRect drawCloseButton(QPainter *painter, const QStyleOptionViewItem &option) const; + void drawActionButton(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + QSizeF doTextLayout(QTextLayout *textLayout, int width) const; + QList actionInfoList(const QStyleOptionViewItem &option, const QModelIndex &index) const; + QRect closeButtonRect(const QRect &itemRect) const; + NotificationListView *view { nullptr }; }; diff --git a/src/plugins/core/notify/gui/notificationcenterwidget.cpp b/src/plugins/core/notify/gui/notificationcenterwidget.cpp index f1685d471..0e1524192 100644 --- a/src/plugins/core/notify/gui/notificationcenterwidget.cpp +++ b/src/plugins/core/notify/gui/notificationcenterwidget.cpp @@ -48,9 +48,11 @@ void NotificationCenterWidgetPrivate::initUI() q->setBackgroundRole(QPalette::Base); QVBoxLayout *mainLayout = new QVBoxLayout(q); - mainLayout->setContentsMargins(10, 10, 10, 10); + mainLayout->setContentsMargins(5, 10, 5, 10); + mainLayout->setSpacing(5); QLabel *titleLabel = new QLabel(NotificationCenterWidget::tr("Notification"), q); + DFontSizeManager::instance()->bind(titleLabel, DFontSizeManager::T5); hideBtn = new DToolButton(q); hideBtn->setIconSize({ 16, 16 }); hideBtn->setIcon(QIcon::fromTheme("hide")); @@ -60,7 +62,7 @@ void NotificationCenterWidgetPrivate::initUI() clearBtn->setIcon(QIcon::fromTheme("clear_history")); QHBoxLayout *titleLayout = new QHBoxLayout; - titleLayout->setContentsMargins(10, 0, 10, 0); + titleLayout->setContentsMargins(15, 0, 15, 0); titleLayout->addWidget(titleLabel); titleLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding)); titleLayout->addWidget(hideBtn); diff --git a/src/plugins/core/notify/gui/notificationlistview.cpp b/src/plugins/core/notify/gui/notificationlistview.cpp index 3b91150e6..b3d8dd0fc 100644 --- a/src/plugins/core/notify/gui/notificationlistview.cpp +++ b/src/plugins/core/notify/gui/notificationlistview.cpp @@ -11,15 +11,10 @@ DWIDGET_USE_NAMESPACE NotificationListView::NotificationListView(QWidget *parent) : DListView(parent) { - setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - QScroller::grabGesture(this, QScroller::LeftMouseButtonGesture); - QScroller *scroller = QScroller::scroller(this); - QScrollerProperties sp; - sp.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff); - scroller->setScrollerProperties(sp); + setIconSize({ 16, 16 }); + setSpacing(5); } NotificationListView::~NotificationListView() { - } diff --git a/src/plugins/core/notify/gui/notificationmodel.cpp b/src/plugins/core/notify/gui/notificationmodel.cpp index 33595ad34..0cc0d7822 100644 --- a/src/plugins/core/notify/gui/notificationmodel.cpp +++ b/src/plugins/core/notify/gui/notificationmodel.cpp @@ -53,22 +53,38 @@ QVariant NotificationModel::data(const QModelIndex &index, int role) const } EntityPtr entity = d->getEntityByRow(index.row()); - if (entity) - return QVariant::fromValue(entity); + if (entity) { + switch (role) { + case Qt::DecorationRole: { + auto type = entity->type(); + switch (type) { + case NotificationEntity::Information: + return QIcon::fromTheme("notification_info"); + case NotificationEntity::Warning: + return QIcon::fromTheme("notification_warning"); + case NotificationEntity::Error: + return QIcon::fromTheme("notification_error"); + } + } + case Qt::DisplayRole: + return entity->message(); + case kActionsRole: + return entity->actions(); + case kSourceRole: { + QString source = entity->name(); + if (!source.isEmpty()) + return tr("Source: %1").arg(source); + } + case kEntityRole: + return qVariantFromValue(entity); + default: + break; + } + } return QVariant(); } -Qt::ItemFlags NotificationModel::flags(const QModelIndex &index) const -{ - if (index.isValid()) { - if (d->view) - d->view->openPersistentEditor(index); - return QAbstractListModel::flags(index) | Qt::ItemIsEditable; - } - return QAbstractListModel::flags(index); -} - void NotificationModel::setNotifications(const QList &datas) { beginResetModel(); diff --git a/src/plugins/core/notify/gui/notificationmodel.h b/src/plugins/core/notify/gui/notificationmodel.h index a81c6a93c..7bbc03851 100644 --- a/src/plugins/core/notify/gui/notificationmodel.h +++ b/src/plugins/core/notify/gui/notificationmodel.h @@ -20,7 +20,6 @@ class NotificationModel : public QAbstractListModel int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; public Q_SLOTS: void setNotifications(const QList &datas);