diff --git a/visualization/motion_planning_tasks/src/CMakeLists.txt b/visualization/motion_planning_tasks/src/CMakeLists.txt index c64d6114..b1dc4178 100644 --- a/visualization/motion_planning_tasks/src/CMakeLists.txt +++ b/visualization/motion_planning_tasks/src/CMakeLists.txt @@ -8,6 +8,7 @@ qt_wrap_ui(UIC_FILES add_library(${MOVEIT_LIB_NAME} factory_model.cpp + icons.cpp job_queue.cpp local_task_model.cpp meta_task_list_model.cpp diff --git a/visualization/motion_planning_tasks/src/icons.cpp b/visualization/motion_planning_tasks/src/icons.cpp new file mode 100644 index 00000000..1d05a297 --- /dev/null +++ b/visualization/motion_planning_tasks/src/icons.cpp @@ -0,0 +1,19 @@ +#include "icons.h" +#include + +using namespace moveit_rviz_plugin::utils; + +namespace moveit_rviz_plugin { namespace icons { + +const Icon CONNECT({ + {QLatin1String(":icons/connectarrow.png"), QColor::fromRgba(0xff000000)}}, Icon::Tint); +const Icon FORWARD({ + {QLatin1String(":icons/downarrow.png"), QColor::fromRgba(0xff000000)}}, Icon::Tint); +const Icon BACKWARD({ + {QLatin1String(":icons/uparrow.png"), QColor::fromRgba(0xff000000)}}, Icon::Tint); +const Icon BOTHWAY({ + {QLatin1String(":icons/updownarrow.png"), QColor::fromRgba(0xff000000)}}, Icon::Tint); +const Icon GENERATE({ + {QLatin1String(":icons/generatearrow.png"), QColor::fromRgba(0xff000000)}}, Icon::Tint); + +} } diff --git a/visualization/motion_planning_tasks/src/icons.h b/visualization/motion_planning_tasks/src/icons.h new file mode 100644 index 00000000..aa696adb --- /dev/null +++ b/visualization/motion_planning_tasks/src/icons.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace moveit_rviz_plugin { namespace icons { + +extern const moveit_rviz_plugin::utils::Icon CONNECT; +extern const moveit_rviz_plugin::utils::Icon FORWARD; +extern const moveit_rviz_plugin::utils::Icon BACKWARD; +extern const moveit_rviz_plugin::utils::Icon BOTHWAY; +extern const moveit_rviz_plugin::utils::Icon GENERATE; + +} } diff --git a/visualization/motion_planning_tasks/src/icons/connectarrow.png b/visualization/motion_planning_tasks/src/icons/connectarrow.png new file mode 100644 index 00000000..b709b9c9 Binary files /dev/null and b/visualization/motion_planning_tasks/src/icons/connectarrow.png differ diff --git a/visualization/motion_planning_tasks/src/icons/downarrow.png b/visualization/motion_planning_tasks/src/icons/downarrow.png new file mode 100644 index 00000000..222c1ddf Binary files /dev/null and b/visualization/motion_planning_tasks/src/icons/downarrow.png differ diff --git a/visualization/motion_planning_tasks/src/icons/generatearrow.png b/visualization/motion_planning_tasks/src/icons/generatearrow.png new file mode 100644 index 00000000..8df7a986 Binary files /dev/null and b/visualization/motion_planning_tasks/src/icons/generatearrow.png differ diff --git a/visualization/motion_planning_tasks/src/icons/uparrow.png b/visualization/motion_planning_tasks/src/icons/uparrow.png new file mode 100644 index 00000000..0781d0e5 Binary files /dev/null and b/visualization/motion_planning_tasks/src/icons/uparrow.png differ diff --git a/visualization/motion_planning_tasks/src/icons/updownarrow.png b/visualization/motion_planning_tasks/src/icons/updownarrow.png new file mode 100644 index 00000000..46da5a23 Binary files /dev/null and b/visualization/motion_planning_tasks/src/icons/updownarrow.png differ diff --git a/visualization/motion_planning_tasks/src/remote_task_model.cpp b/visualization/motion_planning_tasks/src/remote_task_model.cpp index 59a14801..b6e07f98 100644 --- a/visualization/motion_planning_tasks/src/remote_task_model.cpp +++ b/visualization/motion_planning_tasks/src/remote_task_model.cpp @@ -200,6 +200,10 @@ QVariant RemoteTaskModel::data(const QModelIndex &index, int role) const if (index.column() == 0 && !index.parent().isValid()) return (flags_ & IS_DESTROYED) ? QColor(Qt::red) : QApplication::palette().text().color(); break; + case Qt::DecorationRole: + if (index.column() == 0 && index.parent().isValid()) + return flowIcon(n->interface_flags_); + break; } return BaseTaskModel::data(index, role); diff --git a/visualization/motion_planning_tasks/src/resources.qrc b/visualization/motion_planning_tasks/src/resources.qrc index d08caa5c..499e5a2c 100644 --- a/visualization/motion_planning_tasks/src/resources.qrc +++ b/visualization/motion_planning_tasks/src/resources.qrc @@ -6,5 +6,10 @@ icons/new-stage.png icons/success.png icons/tasks.png + icons/connectarrow.png + icons/downarrow.png + icons/generatearrow.png + icons/uparrow.png + icons/updownarrow.png diff --git a/visualization/motion_planning_tasks/src/task_list_model.cpp b/visualization/motion_planning_tasks/src/task_list_model.cpp index e4b81f6d..f95268d4 100644 --- a/visualization/motion_planning_tasks/src/task_list_model.cpp +++ b/visualization/motion_planning_tasks/src/task_list_model.cpp @@ -38,6 +38,7 @@ #include "local_task_model.h" #include "remote_task_model.h" #include "factory_model.h" +#include "icons.h" #include #include @@ -48,6 +49,8 @@ #include #include +using namespace moveit::task_constructor; + namespace moveit_rviz_plugin { static const std::string LOGNAME("TaskListModel"); @@ -109,6 +112,23 @@ Qt::ItemFlags BaseTaskModel::flags(const QModelIndex &index) const return flags; } +QVariant BaseTaskModel::flowIcon(moveit::task_constructor::InterfaceFlags f) +{ + static const QIcon CONNECT_ICON = icons::CONNECT.icon(); + static const QIcon FORWARD_ICON = icons::FORWARD.icon(); + static const QIcon BACKWARD_ICON = icons::BACKWARD.icon(); + static const QIcon BOTHWAY_ICON = icons::BOTHWAY.icon(); + static const QIcon GENERATE_ICON = icons::GENERATE.icon(); + + if (f == InterfaceFlags(CONNECT)) return CONNECT_ICON; + if (f == InterfaceFlags(PROPAGATE_FORWARDS)) return FORWARD_ICON; + if (f == InterfaceFlags(PROPAGATE_BACKWARDS)) return BACKWARD_ICON; + if (f == PROPAGATE_BOTHWAYS) return BOTHWAY_ICON; + if (f == InterfaceFlags(GENERATE)) return GENERATE_ICON; + + return QVariant(); +} + StageFactoryPtr getStageFactory() { diff --git a/visualization/motion_planning_tasks/src/task_list_model.h b/visualization/motion_planning_tasks/src/task_list_model.h index 29950221..753e5f4a 100644 --- a/visualization/motion_planning_tasks/src/task_list_model.h +++ b/visualization/motion_planning_tasks/src/task_list_model.h @@ -84,6 +84,7 @@ public: virtual void setStageFactory(const StageFactoryPtr &factory) {} Qt::ItemFlags flags(const QModelIndex &index) const override; unsigned int taskFlags() const { return flags_; } + static QVariant flowIcon(moveit::task_constructor::InterfaceFlags f); /// get solution model for given stage index virtual QAbstractItemModel* getSolutionModel(const QModelIndex& index) = 0; diff --git a/visualization/motion_planning_tasks/utils/CMakeLists.txt b/visualization/motion_planning_tasks/utils/CMakeLists.txt index c9de4164..ae3533c8 100644 --- a/visualization/motion_planning_tasks/utils/CMakeLists.txt +++ b/visualization/motion_planning_tasks/utils/CMakeLists.txt @@ -3,6 +3,7 @@ set(MOVEIT_LIB_NAME motion_planning_tasks_utils) set(SOURCES flat_merge_proxy_model.cpp tree_merge_proxy_model.cpp + icon.cpp ) add_library(${MOVEIT_LIB_NAME} SHARED ${SOURCES}) diff --git a/visualization/motion_planning_tasks/utils/icon.cpp b/visualization/motion_planning_tasks/utils/icon.cpp new file mode 100644 index 00000000..245c4980 --- /dev/null +++ b/visualization/motion_planning_tasks/utils/icon.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is adapted from Qt Creator (replacing theme stuff by QColor) +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "icon.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace moveit_rviz_plugin { namespace utils { + +static const qreal PunchEdgeWidth = 0.5; +static const qreal PunchEdgeIntensity = 0.6; + +static QPixmap maskToColorAndAlpha(const QPixmap &mask, const QColor &color) +{ + QImage result(mask.toImage().convertToFormat(QImage::Format_ARGB32)); + result.setDevicePixelRatio(mask.devicePixelRatio()); + QRgb *bitsStart = reinterpret_cast(result.bits()); + const QRgb *bitsEnd = bitsStart + result.width() * result.height(); + const QRgb tint = color.rgb() & 0x00ffffff; + const QRgb alpha = QRgb(color.alpha()); + for (QRgb *pixel = bitsStart; pixel < bitsEnd; ++pixel) { + QRgb pixelAlpha = (((~*pixel) & 0xff) * alpha) >> 8; + *pixel = (pixelAlpha << 24) | tint; + } + return QPixmap::fromImage(result); +} + +typedef QPair MaskAndColor; +typedef QList MasksAndColors; +static MasksAndColors masksAndColors(const Icon &icon, int dpr) +{ + MasksAndColors result; + for (const IconMaskAndColor &i: icon) { + const QString &fileName = i.first; + const QColor color = i.second; + result.append(qMakePair(QPixmap(fileName), color)); + } + return result; +} + +static void smearPixmap(QPainter *painter, const QPixmap &pixmap, qreal radius) +{ + const qreal nagative = -radius - 0.01; // Workaround for QPainter rounding behavior + const qreal positive = radius; + painter->drawPixmap(QPointF(nagative, nagative), pixmap); + painter->drawPixmap(QPointF(0, nagative), pixmap); + painter->drawPixmap(QPointF(positive, nagative), pixmap); + painter->drawPixmap(QPointF(positive, 0), pixmap); + painter->drawPixmap(QPointF(positive, positive), pixmap); + painter->drawPixmap(QPointF(0, positive), pixmap); + painter->drawPixmap(QPointF(nagative, positive), pixmap); + painter->drawPixmap(QPointF(nagative, 0), pixmap); +} + +static QPixmap combinedMask(const MasksAndColors &masks, Icon::IconStyleOptions style) +{ + if (masks.count() == 1) + return masks.first().first; + + QPixmap result(masks.first().first); + QPainter p(&result); + p.setCompositionMode(QPainter::CompositionMode_Darken); + auto maskImage = masks.constBegin(); + maskImage++; + for (;maskImage != masks.constEnd(); ++maskImage) { + if (style & Icon::PunchEdges) { + p.save(); + p.setOpacity(PunchEdgeIntensity); + p.setCompositionMode(QPainter::CompositionMode_Lighten); + smearPixmap(&p, maskToColorAndAlpha((*maskImage).first, Qt::white), PunchEdgeWidth); + p.restore(); + } + p.drawPixmap(0, 0, (*maskImage).first); + } + p.end(); + return result; +} + +static QPixmap masksToIcon(const MasksAndColors &masks, const QPixmap &combinedMask, Icon::IconStyleOptions style) +{ + QPixmap result(combinedMask.size()); + result.setDevicePixelRatio(combinedMask.devicePixelRatio()); + result.fill(Qt::transparent); + QPainter p(&result); + + for (MasksAndColors::const_iterator maskImage = masks.constBegin(); + maskImage != masks.constEnd(); ++maskImage) { + if (style & Icon::PunchEdges && maskImage != masks.constBegin()) { + // Punch a transparent outline around an overlay. + p.save(); + p.setOpacity(PunchEdgeIntensity); + p.setCompositionMode(QPainter::CompositionMode_DestinationOut); + smearPixmap(&p, maskToColorAndAlpha((*maskImage).first, Qt::white), PunchEdgeWidth); + p.restore(); + } + p.drawPixmap(0, 0, maskToColorAndAlpha((*maskImage).first, (*maskImage).second)); + } + + if (style & Icon::DropShadow) { + const QPixmap shadowMask = maskToColorAndAlpha(combinedMask, Qt::black); + p.setCompositionMode(QPainter::CompositionMode_DestinationOver); + p.setOpacity(0.05); + p.drawPixmap(QPointF(0, -0.501), shadowMask); + p.drawPixmap(QPointF(-0.501, 0), shadowMask); + p.drawPixmap(QPointF(0.5, 0), shadowMask); + p.drawPixmap(QPointF(0.5, 0.5), shadowMask); + p.drawPixmap(QPointF(-0.501, 0.5), shadowMask); + p.setOpacity(0.2); + p.drawPixmap(0, 1, shadowMask); + } + + p.end(); + + return result; +} + +static QPixmap combinedPlainPixmaps(const QVector &images) +{ + QPixmap result(images.first().first); + auto pixmap = images.constBegin(); + pixmap++; + for (;pixmap != images.constEnd(); ++pixmap) { + const QPixmap overlay((*pixmap).first); + result.paintEngine()->painter()->drawPixmap(0, 0, overlay); + } + return result; +} + +Icon::Icon() +{ +} + +Icon::Icon(std::initializer_list args, Icon::IconStyleOptions style) + : QVector(args) + , m_style(style) +{ +} + +Icon::Icon(const QString &imageFileName) + : m_style(None) +{ + append({imageFileName, QColor()}); +} + +QIcon Icon::icon() const +{ + if (isEmpty()) { + return QIcon(); + } else if (m_style == None) { + return QIcon(combinedPlainPixmaps(*this)); + } else { + QIcon result; + const int maxDpr = qRound(qApp->devicePixelRatio()); + for (int dpr = 1; dpr <= maxDpr; dpr++) { + const MasksAndColors masks = masksAndColors(*this, dpr); + const QPixmap combined_mask = combinedMask(masks, m_style); + result.addPixmap(masksToIcon(masks, combined_mask, m_style)); + + const QColor disabledColor = QColor::fromRgba(0x60a4a6a8); + result.addPixmap(maskToColorAndAlpha(combined_mask, disabledColor), QIcon::Disabled); + } + return result; + } +} + +QPixmap Icon::pixmap() const +{ + if (isEmpty()) { + return QPixmap(); + } else if (m_style == None) { + return combinedPlainPixmaps(*this); + } else { + const MasksAndColors masks = + masksAndColors(*this, qRound(qApp->devicePixelRatio())); + const QPixmap combined_mask = combinedMask(masks, m_style); + return masksToIcon(masks, combined_mask, m_style); + } +} + +QString Icon::imageFileName() const +{ + return first().first; +} + +QIcon Icon::sideBarIcon(const Icon &classic, const Icon &flat) +{ + return flat.icon(); +} + +QIcon Icon::modeIcon(const Icon &classic, const Icon &flat, const Icon &flatActive) +{ + QIcon result = sideBarIcon(classic, flat); + result.addPixmap(flatActive.pixmap(), QIcon::Active); + return result; +} + +QIcon Icon::combinedIcon(const QList &icons) +{ + QIcon result; + QWindow *window = QApplication::allWidgets().first()->windowHandle(); + for (const QIcon &icon: icons) + for (const QIcon::Mode mode: {QIcon::Disabled, QIcon::Normal}) + for (const QSize &size: icon.availableSizes(mode)) + result.addPixmap(icon.pixmap(window, size, mode), mode); + return result; +} + +} } diff --git a/visualization/motion_planning_tasks/utils/icon.h b/visualization/motion_planning_tasks/utils/icon.h new file mode 100644 index 00000000..547a72be --- /dev/null +++ b/visualization/motion_planning_tasks/utils/icon.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is adapted from Qt Creator (replacing theme stuff by QColor) +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +class QColor; +class QIcon; +class QPixmap; +class QString; + +namespace moveit_rviz_plugin { namespace utils { + +typedef QPair IconMaskAndColor; + +// Returns a recolored icon with shadow and custom disabled state for a +// series of grayscalemask|Theme::Color mask pairs +class Icon : public QVector +{ +public: + enum IconStyleOption { + None = 0, + Tint = 1, + DropShadow = 2, + PunchEdges = 4, + + ToolBarStyle = Tint | DropShadow | PunchEdges, + MenuTintedStyle = Tint | PunchEdges + }; + + Q_DECLARE_FLAGS(IconStyleOptions, IconStyleOption) + + Icon(); + Icon(std::initializer_list args, IconStyleOptions style = ToolBarStyle); + Icon(const QString &imageFileName); + Icon(const Icon &other) = default; + + QIcon icon() const; + // Same as icon() but without disabled state. + QPixmap pixmap() const; + + // Try to avoid it. it is just there for special API cases in Qt Creator + // where icons are still defined as filename. + QString imageFileName() const; + + // Returns either the classic or a themed icon depending on + // the current Theme::FlatModeIcons flag. + static QIcon sideBarIcon(const Icon &classic, const Icon &flat); + // Like sideBarIcon plus added action mode for the flat icon + static QIcon modeIcon(const Icon &classic, const Icon &flat, const Icon &flatActive); + + // Combined icon pixmaps in Normal and Disabled states from several QIcons + static QIcon combinedIcon(const QList &icons); + +private: + IconStyleOptions m_style = None; +}; + +} } + +Q_DECLARE_OPERATORS_FOR_FLAGS(moveit_rviz_plugin::utils::Icon::IconStyleOptions)