qt 6.5.1 original
29
examples/widgets/painting/shared/CMakeLists.txt
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
add_library(painting_shared OBJECT)
|
||||
add_library(painting_shared::painting_shared ALIAS painting_shared)
|
||||
qt6_wrap_cpp(moc_files arthurwidgets.h hoverpoints.h) # no automoc for OBJECT libs:-/
|
||||
target_sources(painting_shared PRIVATE
|
||||
arthurstyle.cpp arthurstyle.h
|
||||
arthurwidgets.cpp arthurwidgets.h
|
||||
hoverpoints.cpp hoverpoints.h
|
||||
${moc_files}
|
||||
)
|
||||
|
||||
set_target_properties(painting_shared PROPERTIES UNITY_BUILD OFF)
|
||||
|
||||
target_link_libraries(painting_shared PUBLIC Qt6::Widgets)
|
||||
target_include_directories(painting_shared PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
## Scopes:
|
||||
#####################################################################
|
||||
|
||||
if (TARGET Qt6::OpenGL OR QT_FEATURE_opengles2)
|
||||
target_compile_definitions(painting_shared PRIVATE QT_OPENGL_SUPPORT)
|
||||
target_link_libraries(painting_shared PUBLIC
|
||||
Qt6::OpenGL
|
||||
)
|
||||
qt6_wrap_cpp(moc_files_gl fbopaintdevice.h) # no automoc for OBJECT libs
|
||||
target_sources(painting_shared PRIVATE fbopaintdevice.cpp fbopaintdevice.h ${moc_files_gl})
|
||||
endif()
|
452
examples/widgets/painting/shared/arthurstyle.cpp
Normal file
@ -0,0 +1,452 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "arthurstyle.h"
|
||||
#include "arthurwidgets.h"
|
||||
#include <QLayout>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPixmapCache>
|
||||
#include <QRadioButton>
|
||||
#include <QString>
|
||||
#include <QStyleOption>
|
||||
#include <QtDebug>
|
||||
|
||||
QPixmap cached(const QString &img)
|
||||
{
|
||||
QPixmap pm;
|
||||
if (QPixmapCache::find(img, &pm))
|
||||
return pm;
|
||||
|
||||
pm = QPixmap::fromImage(QImage(img), Qt::OrderedDither | Qt::OrderedAlphaDither);
|
||||
if (pm.isNull())
|
||||
return QPixmap();
|
||||
|
||||
QPixmapCache::insert(img, pm);
|
||||
return pm;
|
||||
}
|
||||
|
||||
|
||||
ArthurStyle::ArthurStyle()
|
||||
: QCommonStyle()
|
||||
{
|
||||
Q_INIT_RESOURCE(shared);
|
||||
}
|
||||
|
||||
|
||||
void ArthurStyle::drawHoverRect(QPainter *painter, const QRect &r) const
|
||||
{
|
||||
qreal h = r.height();
|
||||
qreal h2 = r.height() / qreal(2);
|
||||
QPainterPath path;
|
||||
path.addRect(r.x() + h2, r.y() + 0, r.width() - h2 * 2, r.height());
|
||||
path.addEllipse(r.x(), r.y(), h, h);
|
||||
path.addEllipse(r.x() + r.width() - h, r.y(), h, h);
|
||||
path.setFillRule(Qt::WindingFill);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(QColor(191, 215, 191));
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
painter->drawPath(path);
|
||||
}
|
||||
|
||||
|
||||
void ArthurStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
|
||||
QPainter *painter, const QWidget *widget) const
|
||||
{
|
||||
|
||||
Q_ASSERT(option);
|
||||
switch (element) {
|
||||
case PE_FrameFocusRect:
|
||||
break;
|
||||
|
||||
case PE_IndicatorRadioButton:
|
||||
if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) {
|
||||
bool hover = (button->state & State_Enabled) && (button->state & State_MouseOver);
|
||||
painter->save();
|
||||
QPixmap radio;
|
||||
if (hover)
|
||||
drawHoverRect(painter, widget->rect());
|
||||
|
||||
if (button->state & State_Sunken)
|
||||
radio = cached(":res/images/radiobutton-on.png");
|
||||
else if (button->state & State_On)
|
||||
radio = cached(":res/images/radiobutton_on.png");
|
||||
else
|
||||
radio = cached(":res/images/radiobutton_off.png");
|
||||
painter->drawPixmap(button->rect.topLeft(), radio);
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
break;
|
||||
|
||||
case PE_PanelButtonCommand:
|
||||
if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) {
|
||||
bool hover = (button->state & State_Enabled) && (button->state & State_MouseOver);
|
||||
|
||||
painter->save();
|
||||
const QPushButton *pushButton = qobject_cast<const QPushButton *>(widget);
|
||||
Q_ASSERT(pushButton);
|
||||
QWidget *parent = pushButton->parentWidget();
|
||||
if (parent && qobject_cast<QGroupBox *>(parent)) {
|
||||
QLinearGradient lg(0, 0, 0, parent->height());
|
||||
lg.setColorAt(0, QColor(224,224,224));
|
||||
lg.setColorAt(1, QColor(255,255,255));
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(lg);
|
||||
painter->setBrushOrigin(-widget->mapToParent(QPoint(0,0)));
|
||||
painter->drawRect(button->rect);
|
||||
painter->setBrushOrigin(0,0);
|
||||
}
|
||||
|
||||
bool down = (button->state & State_Sunken) || (button->state & State_On);
|
||||
|
||||
QPixmap left, right, mid;
|
||||
if (down) {
|
||||
left = cached(":res/images/button_pressed_cap_left.png");
|
||||
right = cached(":res/images/button_pressed_cap_right.png");
|
||||
mid = cached(":res/images/button_pressed_stretch.png");
|
||||
} else {
|
||||
left = cached(":res/images/button_normal_cap_left.png");
|
||||
right = cached(":res/images/button_normal_cap_right.png");
|
||||
mid = cached(":res/images/button_normal_stretch.png");
|
||||
}
|
||||
painter->drawPixmap(button->rect.topLeft(), left);
|
||||
painter->drawTiledPixmap(QRect(button->rect.x() + left.width(),
|
||||
button->rect.y(),
|
||||
button->rect.width() - left.width() - right.width(),
|
||||
left.height()),
|
||||
mid);
|
||||
painter->drawPixmap(button->rect.x() + button->rect.width() - right.width(),
|
||||
button->rect.y(),
|
||||
right);
|
||||
if (hover)
|
||||
painter->fillRect(widget->rect().adjusted(3,5,-3,-5), QColor(31,127,31,63));
|
||||
painter->restore();
|
||||
}
|
||||
break;
|
||||
|
||||
case PE_FrameGroupBox:
|
||||
if (const QStyleOptionFrame *group
|
||||
= qstyleoption_cast<const QStyleOptionFrame *>(option)) {
|
||||
const QRect &r = group->rect;
|
||||
|
||||
painter->save();
|
||||
int radius = 14;
|
||||
int radius2 = radius*2;
|
||||
QPainterPath clipPath;
|
||||
clipPath.moveTo(radius, 0);
|
||||
clipPath.arcTo(r.right() - radius2, 0, radius2, radius2, 90, -90);
|
||||
clipPath.arcTo(r.right() - radius2, r.bottom() - radius2, radius2, radius2, 0, -90);
|
||||
clipPath.arcTo(r.left(), r.bottom() - radius2, radius2, radius2, 270, -90);
|
||||
clipPath.arcTo(r.left(), r.top(), radius2, radius2, 180, -90);
|
||||
painter->setClipPath(clipPath);
|
||||
QPixmap titleStretch = cached(":res/images/title_stretch.png");
|
||||
QPixmap topLeft = cached(":res/images/groupframe_topleft.png");
|
||||
QPixmap topRight = cached(":res/images/groupframe_topright.png");
|
||||
QPixmap bottomLeft = cached(":res/images/groupframe_bottom_left.png");
|
||||
QPixmap bottomRight = cached(":res/images/groupframe_bottom_right.png");
|
||||
QPixmap leftStretch = cached(":res/images/groupframe_left_stretch.png");
|
||||
QPixmap topStretch = cached(":res/images/groupframe_top_stretch.png");
|
||||
QPixmap rightStretch = cached(":res/images/groupframe_right_stretch.png");
|
||||
QPixmap bottomStretch = cached(":res/images/groupframe_bottom_stretch.png");
|
||||
QLinearGradient lg(0, 0, 0, r.height());
|
||||
lg.setColorAt(0, QColor(224,224,224));
|
||||
lg.setColorAt(1, QColor(255,255,255));
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(lg);
|
||||
painter->drawRect(r.adjusted(0, titleStretch.height()/2, 0, 0));
|
||||
painter->setClipping(false);
|
||||
|
||||
int topFrameOffset = titleStretch.height()/2 - 2;
|
||||
painter->drawPixmap(r.topLeft() + QPoint(0, topFrameOffset), topLeft);
|
||||
painter->drawPixmap(r.topRight() - QPoint(topRight.width()-1, 0)
|
||||
+ QPoint(0, topFrameOffset), topRight);
|
||||
painter->drawPixmap(r.bottomLeft() - QPoint(0, bottomLeft.height()-1), bottomLeft);
|
||||
painter->drawPixmap(r.bottomRight() - QPoint(bottomRight.width()-1,
|
||||
bottomRight.height()-1), bottomRight);
|
||||
|
||||
QRect left = r;
|
||||
left.setY(r.y() + topLeft.height() + topFrameOffset);
|
||||
left.setWidth(leftStretch.width());
|
||||
left.setHeight(r.height() - topLeft.height() - bottomLeft.height() - topFrameOffset);
|
||||
painter->drawTiledPixmap(left, leftStretch);
|
||||
|
||||
QRect top = r;
|
||||
top.setX(r.x() + topLeft.width());
|
||||
top.setY(r.y() + topFrameOffset);
|
||||
top.setWidth(r.width() - topLeft.width() - topRight.width());
|
||||
top.setHeight(topLeft.height());
|
||||
painter->drawTiledPixmap(top, topStretch);
|
||||
|
||||
QRect right = r;
|
||||
right.setX(r.right() - rightStretch.width()+1);
|
||||
right.setY(r.y() + topRight.height() + topFrameOffset);
|
||||
right.setWidth(rightStretch.width());
|
||||
right.setHeight(r.height() - topRight.height()
|
||||
- bottomRight.height() - topFrameOffset);
|
||||
painter->drawTiledPixmap(right, rightStretch);
|
||||
|
||||
QRect bottom = r;
|
||||
bottom.setX(r.x() + bottomLeft.width());
|
||||
bottom.setY(r.bottom() - bottomStretch.height()+1);
|
||||
bottom.setWidth(r.width() - bottomLeft.width() - bottomRight.width());
|
||||
bottom.setHeight(bottomLeft.height());
|
||||
painter->drawTiledPixmap(bottom, bottomStretch);
|
||||
painter->restore();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
QCommonStyle::drawPrimitive(element, option, painter, widget);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void ArthurStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option,
|
||||
QPainter *painter, const QWidget *widget) const
|
||||
{
|
||||
switch (control) {
|
||||
case CC_Slider:
|
||||
if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
|
||||
QRect groove = subControlRect(CC_Slider, option, SC_SliderGroove, widget);
|
||||
QRect handle = subControlRect(CC_Slider, option, SC_SliderHandle, widget);
|
||||
|
||||
painter->save();
|
||||
|
||||
bool hover = (slider->state & State_Enabled) && (slider->state & State_MouseOver);
|
||||
if (hover) {
|
||||
QRect moderated = widget->rect().adjusted(0, 4, 0, -4);
|
||||
drawHoverRect(painter, moderated);
|
||||
}
|
||||
|
||||
if ((option->subControls & SC_SliderGroove) && groove.isValid()) {
|
||||
QPixmap grv = cached(":res/images/slider_bar.png");
|
||||
painter->drawPixmap(QRect(groove.x() + 5, groove.y(),
|
||||
groove.width() - 10, grv.height()),
|
||||
grv);
|
||||
}
|
||||
if ((option->subControls & SC_SliderHandle) && handle.isValid()) {
|
||||
QPixmap hndl = cached(":res/images/slider_thumb_on.png");
|
||||
painter->drawPixmap(handle.topLeft(), hndl);
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
break;
|
||||
case CC_GroupBox:
|
||||
if (const QStyleOptionGroupBox *groupBox
|
||||
= qstyleoption_cast<const QStyleOptionGroupBox *>(option)) {
|
||||
QStyleOptionGroupBox groupBoxCopy(*groupBox);
|
||||
groupBoxCopy.subControls &= ~SC_GroupBoxLabel;
|
||||
QCommonStyle::drawComplexControl(control, &groupBoxCopy, painter, widget);
|
||||
|
||||
if (groupBox->subControls & SC_GroupBoxLabel) {
|
||||
const QRect &r = groupBox->rect;
|
||||
QPixmap titleLeft = cached(":res/images/title_cap_left.png");
|
||||
QPixmap titleRight = cached(":res/images/title_cap_right.png");
|
||||
QPixmap titleStretch = cached(":res/images/title_stretch.png");
|
||||
int txt_width = groupBox->fontMetrics.horizontalAdvance(groupBox->text) + 20;
|
||||
painter->drawPixmap(r.center().x() - txt_width/2, 0, titleLeft);
|
||||
QRect tileRect = subControlRect(control, groupBox, SC_GroupBoxLabel, widget);
|
||||
painter->drawTiledPixmap(tileRect, titleStretch);
|
||||
painter->drawPixmap(tileRect.x() + tileRect.width(), 0, titleRight);
|
||||
int opacity = 31;
|
||||
painter->setPen(QColor(0, 0, 0, opacity));
|
||||
painter->drawText(tileRect.translated(0, 1),
|
||||
Qt::AlignVCenter | Qt::AlignHCenter, groupBox->text);
|
||||
painter->drawText(tileRect.translated(2, 1),
|
||||
Qt::AlignVCenter | Qt::AlignHCenter, groupBox->text);
|
||||
painter->setPen(QColor(0, 0, 0, opacity * 2));
|
||||
painter->drawText(tileRect.translated(1, 1),
|
||||
Qt::AlignVCenter | Qt::AlignHCenter, groupBox->text);
|
||||
painter->setPen(Qt::white);
|
||||
painter->drawText(tileRect, Qt::AlignVCenter | Qt::AlignHCenter, groupBox->text);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
QCommonStyle::drawComplexControl(control, option, painter, widget);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void ArthurStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option,
|
||||
QPainter *painter, const QWidget *widget) const
|
||||
{
|
||||
switch (element) {
|
||||
case CE_RadioButtonLabel:
|
||||
if (const QStyleOptionButton *button
|
||||
= qstyleoption_cast<const QStyleOptionButton *>(option)) {
|
||||
|
||||
if (button->text.isEmpty()) {
|
||||
QCommonStyle::drawControl(element, option, painter, widget);
|
||||
} else {
|
||||
painter->save();
|
||||
painter->setPen(Qt::black);
|
||||
painter->drawText(button->rect, Qt::AlignVCenter, button->text);
|
||||
painter->restore();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CE_PushButtonLabel:
|
||||
if (const QStyleOptionButton *button
|
||||
= qstyleoption_cast<const QStyleOptionButton *>(option)) {
|
||||
|
||||
if (button->text.isEmpty()) {
|
||||
QCommonStyle::drawControl(element, option, painter, widget);
|
||||
} else {
|
||||
painter->save();
|
||||
painter->setPen(Qt::black);
|
||||
painter->drawText(button->rect, Qt::AlignVCenter | Qt::AlignHCenter, button->text);
|
||||
painter->restore();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
QCommonStyle::drawControl(element, option, painter, widget);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QRect ArthurStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option,
|
||||
SubControl subControl, const QWidget *widget) const
|
||||
{
|
||||
QRect rect;
|
||||
|
||||
switch (control) {
|
||||
default:
|
||||
rect = QCommonStyle::subControlRect(control, option, subControl, widget);
|
||||
break;
|
||||
case CC_GroupBox:
|
||||
if (const QStyleOptionGroupBox *group
|
||||
= qstyleoption_cast<const QStyleOptionGroupBox *>(option)) {
|
||||
switch (subControl) {
|
||||
default:
|
||||
rect = QCommonStyle::subControlRect(control, option, subControl, widget);
|
||||
break;
|
||||
case SC_GroupBoxContents:
|
||||
rect = QCommonStyle::subControlRect(control, option, subControl, widget);
|
||||
rect.adjust(0, -8, 0, 0);
|
||||
break;
|
||||
case SC_GroupBoxFrame:
|
||||
rect = group->rect;
|
||||
break;
|
||||
case SC_GroupBoxLabel:
|
||||
QPixmap titleLeft = cached(":res/images/title_cap_left.png");
|
||||
QPixmap titleRight = cached(":res/images/title_cap_right.png");
|
||||
QPixmap titleStretch = cached(":res/images/title_stretch.png");
|
||||
int txt_width = group->fontMetrics.horizontalAdvance(group->text) + 20;
|
||||
rect = QRect(group->rect.center().x() - txt_width/2 + titleLeft.width(), 0,
|
||||
txt_width - titleLeft.width() - titleRight.width(),
|
||||
titleStretch.height());
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (control == CC_Slider && subControl == SC_SliderHandle) {
|
||||
rect.setWidth(13);
|
||||
rect.setHeight(27);
|
||||
} else if (control == CC_Slider && subControl == SC_SliderGroove) {
|
||||
rect.setHeight(9);
|
||||
rect.moveTop(27/2 - 9/2);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
QSize ArthurStyle::sizeFromContents(ContentsType type, const QStyleOption *option,
|
||||
const QSize &size, const QWidget *widget) const
|
||||
{
|
||||
QSize newSize = QCommonStyle::sizeFromContents(type, option, size, widget);
|
||||
|
||||
|
||||
switch (type) {
|
||||
case CT_RadioButton:
|
||||
newSize += QSize(20, 0);
|
||||
break;
|
||||
|
||||
case CT_PushButton:
|
||||
newSize.setHeight(26);
|
||||
break;
|
||||
|
||||
case CT_Slider:
|
||||
newSize.setHeight(27);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return newSize;
|
||||
}
|
||||
|
||||
int ArthurStyle::pixelMetric(PixelMetric pm, const QStyleOption *opt, const QWidget *widget) const
|
||||
{
|
||||
if (pm == PM_SliderLength)
|
||||
return 13;
|
||||
return QCommonStyle::pixelMetric(pm, opt, widget);
|
||||
}
|
||||
|
||||
void ArthurStyle::polish(QWidget *widget)
|
||||
{
|
||||
if (widget->layout() && qobject_cast<QGroupBox *>(widget)) {
|
||||
if (!widget->findChild<QGroupBox *>()) {
|
||||
widget->layout()->setSpacing(0);
|
||||
widget->layout()->setContentsMargins(12, 12, 12, 12);
|
||||
} else {
|
||||
widget->layout()->setContentsMargins(13, 13, 13, 13);
|
||||
}
|
||||
}
|
||||
|
||||
if (qobject_cast<QPushButton *>(widget)
|
||||
|| qobject_cast<QRadioButton *>(widget)
|
||||
|| qobject_cast<QSlider *>(widget)) {
|
||||
widget->setAttribute(Qt::WA_Hover);
|
||||
}
|
||||
|
||||
QPalette pal = widget->palette();
|
||||
if (widget->isWindow()) {
|
||||
pal.setColor(QPalette::Window, QColor(241, 241, 241));
|
||||
widget->setPalette(pal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ArthurStyle::unpolish(QWidget *widget)
|
||||
{
|
||||
if (qobject_cast<QPushButton *>(widget)
|
||||
|| qobject_cast<QRadioButton *>(widget)
|
||||
|| qobject_cast<QSlider *>(widget)) {
|
||||
widget->setAttribute(Qt::WA_Hover, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ArthurStyle::polish(QPalette &palette)
|
||||
{
|
||||
palette.setColor(QPalette::Window, QColor(241, 241, 241));
|
||||
}
|
||||
|
||||
QRect ArthurStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const
|
||||
{
|
||||
QRect r;
|
||||
switch(element) {
|
||||
case SE_RadioButtonClickRect:
|
||||
r = widget->rect();
|
||||
break;
|
||||
case SE_RadioButtonContents:
|
||||
r = widget->rect().adjusted(20, 0, 0, 0);
|
||||
break;
|
||||
default:
|
||||
r = QCommonStyle::subElementRect(element, option, widget);
|
||||
break;
|
||||
}
|
||||
|
||||
if (qobject_cast<const QRadioButton*>(widget))
|
||||
r = r.adjusted(5, 0, -5, 0);
|
||||
|
||||
return r;
|
||||
}
|
38
examples/widgets/painting/shared/arthurstyle.h
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef ARTHURSTYLE_H
|
||||
#define ARTHURSTYLE_H
|
||||
|
||||
#include <QCommonStyle>
|
||||
|
||||
QT_USE_NAMESPACE
|
||||
|
||||
class ArthurStyle : public QCommonStyle
|
||||
{
|
||||
public:
|
||||
ArthurStyle();
|
||||
|
||||
void drawHoverRect(QPainter *painter, const QRect &rect) const;
|
||||
|
||||
void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
|
||||
QPainter *painter, const QWidget *widget = nullptr) const override;
|
||||
void drawControl(ControlElement element, const QStyleOption *option,
|
||||
QPainter *painter, const QWidget *widget) const override;
|
||||
void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option,
|
||||
QPainter *painter, const QWidget *widget) const override;
|
||||
QSize sizeFromContents(ContentsType type, const QStyleOption *option,
|
||||
const QSize &size, const QWidget *widget) const override;
|
||||
|
||||
QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const override;
|
||||
QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt,
|
||||
SubControl sc, const QWidget *widget) const override;
|
||||
|
||||
int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const override;
|
||||
|
||||
void polish(QPalette &palette) override;
|
||||
void polish(QWidget *widget) override;
|
||||
void unpolish(QWidget *widget) override;
|
||||
};
|
||||
|
||||
#endif
|
330
examples/widgets/painting/shared/arthurwidgets.cpp
Normal file
@ -0,0 +1,330 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "arthurwidgets.h"
|
||||
#include <QApplication>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPixmapCache>
|
||||
#include <QtEvents>
|
||||
#include <QTextDocument>
|
||||
#include <QAbstractTextDocumentLayout>
|
||||
#include <QFile>
|
||||
#include <QTextBrowser>
|
||||
#include <QBoxLayout>
|
||||
#include <QRegularExpression>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#if QT_CONFIG(opengl)
|
||||
#include <QtOpenGL/QOpenGLPaintDevice>
|
||||
#include <QtOpenGL/QOpenGLWindow>
|
||||
#endif
|
||||
|
||||
extern QPixmap cached(const QString &img);
|
||||
|
||||
ArthurFrame::ArthurFrame(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
m_tile(QPixmap(128, 128))
|
||||
{
|
||||
m_tile.fill(Qt::white);
|
||||
QPainter pt(&m_tile);
|
||||
QColor color(230, 230, 230);
|
||||
pt.fillRect(0, 0, 64, 64, color);
|
||||
pt.fillRect(64, 64, 64, 64, color);
|
||||
pt.end();
|
||||
}
|
||||
|
||||
|
||||
#if QT_CONFIG(opengl)
|
||||
void ArthurFrame::enableOpenGL(bool use_opengl)
|
||||
{
|
||||
if (m_use_opengl == use_opengl)
|
||||
return;
|
||||
|
||||
m_use_opengl = use_opengl;
|
||||
|
||||
if (!m_glWindow && use_opengl) {
|
||||
createGlWindow();
|
||||
QApplication::postEvent(this, new QResizeEvent(size(), size()));
|
||||
}
|
||||
|
||||
if (use_opengl) {
|
||||
m_glWidget->show();
|
||||
} else {
|
||||
if (m_glWidget)
|
||||
m_glWidget->hide();
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void ArthurFrame::createGlWindow()
|
||||
{
|
||||
Q_ASSERT(m_use_opengl);
|
||||
|
||||
m_glWindow = new QOpenGLWindow();
|
||||
QSurfaceFormat f = QSurfaceFormat::defaultFormat();
|
||||
f.setSamples(4);
|
||||
f.setAlphaBufferSize(8);
|
||||
f.setStencilBufferSize(8);
|
||||
m_glWindow->setFormat(f);
|
||||
m_glWindow->setFlags(Qt::WindowTransparentForInput);
|
||||
m_glWindow->resize(width(), height());
|
||||
m_glWidget = QWidget::createWindowContainer(m_glWindow, this);
|
||||
// create() must be called after createWindowContainer() otherwise
|
||||
// an incorrect offsetting of the position will occur.
|
||||
m_glWindow->create();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void ArthurFrame::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
static QImage *static_image = nullptr;
|
||||
|
||||
QPainter painter;
|
||||
|
||||
if (preferImage()
|
||||
#if QT_CONFIG(opengl)
|
||||
&& !m_use_opengl
|
||||
#endif
|
||||
) {
|
||||
if (!static_image || static_image->size() != size()) {
|
||||
delete static_image;
|
||||
static_image = new QImage(size(), QImage::Format_RGB32);
|
||||
}
|
||||
painter.begin(static_image);
|
||||
|
||||
int o = 10;
|
||||
|
||||
QBrush bg = palette().brush(QPalette::Window);
|
||||
painter.fillRect(0, 0, o, o, bg);
|
||||
painter.fillRect(width() - o, 0, o, o, bg);
|
||||
painter.fillRect(0, height() - o, o, o, bg);
|
||||
painter.fillRect(width() - o, height() - o, o, o, bg);
|
||||
} else {
|
||||
#if QT_CONFIG(opengl)
|
||||
if (m_use_opengl && m_glWindow->isValid()) {
|
||||
m_glWindow->makeCurrent();
|
||||
|
||||
painter.begin(m_glWindow);
|
||||
painter.fillRect(QRectF(0, 0, m_glWindow->width(), m_glWindow->height()), palette().color(backgroundRole()));
|
||||
} else {
|
||||
painter.begin(this);
|
||||
}
|
||||
#else
|
||||
painter.begin(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
painter.setClipRect(e->rect());
|
||||
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
QPainterPath clipPath;
|
||||
|
||||
QRect r = rect();
|
||||
qreal left = r.x() + 1;
|
||||
qreal top = r.y() + 1;
|
||||
qreal right = r.right();
|
||||
qreal bottom = r.bottom();
|
||||
qreal radius2 = 8 * 2;
|
||||
|
||||
clipPath.moveTo(right - radius2, top);
|
||||
clipPath.arcTo(right - radius2, top, radius2, radius2, 90, -90);
|
||||
clipPath.arcTo(right - radius2, bottom - radius2, radius2, radius2, 0, -90);
|
||||
clipPath.arcTo(left, bottom - radius2, radius2, radius2, 270, -90);
|
||||
clipPath.arcTo(left, top, radius2, radius2, 180, -90);
|
||||
clipPath.closeSubpath();
|
||||
|
||||
painter.save();
|
||||
painter.setClipPath(clipPath, Qt::IntersectClip);
|
||||
|
||||
painter.drawTiledPixmap(rect(), m_tile);
|
||||
|
||||
// client painting
|
||||
|
||||
paint(&painter);
|
||||
|
||||
painter.restore();
|
||||
|
||||
painter.save();
|
||||
if (m_showDoc)
|
||||
paintDescription(&painter);
|
||||
painter.restore();
|
||||
|
||||
int level = 180;
|
||||
painter.setPen(QPen(QColor(level, level, level), 2));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawPath(clipPath);
|
||||
|
||||
if (preferImage()
|
||||
#if QT_CONFIG(opengl)
|
||||
&& !m_use_opengl
|
||||
#endif
|
||||
) {
|
||||
painter.end();
|
||||
painter.begin(this);
|
||||
painter.drawImage(e->rect(), *static_image, e->rect());
|
||||
}
|
||||
#if QT_CONFIG(opengl)
|
||||
if (m_use_opengl)
|
||||
m_glWindow->update();
|
||||
#endif
|
||||
}
|
||||
|
||||
void ArthurFrame::resizeEvent(QResizeEvent *e)
|
||||
{
|
||||
#if QT_CONFIG(opengl)
|
||||
if (m_glWidget)
|
||||
m_glWidget->setGeometry(0, 0, e->size().width(), e->size().height());
|
||||
#endif
|
||||
QWidget::resizeEvent(e);
|
||||
}
|
||||
|
||||
void ArthurFrame::setDescriptionEnabled(bool enabled)
|
||||
{
|
||||
if (m_showDoc != enabled) {
|
||||
m_showDoc = enabled;
|
||||
emit descriptionEnabledChanged(m_showDoc);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ArthurFrame::loadDescription(const QString &fileName)
|
||||
{
|
||||
QFile textFile(fileName);
|
||||
QString text;
|
||||
if (!textFile.open(QFile::ReadOnly))
|
||||
text = QString("Unable to load resource file: '%1'").arg(fileName);
|
||||
else
|
||||
text = textFile.readAll();
|
||||
setDescription(text);
|
||||
}
|
||||
|
||||
|
||||
void ArthurFrame::setDescription(const QString &text)
|
||||
{
|
||||
m_document = new QTextDocument(this);
|
||||
m_document->setHtml(text);
|
||||
}
|
||||
|
||||
void ArthurFrame::paintDescription(QPainter *painter)
|
||||
{
|
||||
if (!m_document)
|
||||
return;
|
||||
|
||||
int pageWidth = qMax(width() - 100, 100);
|
||||
int pageHeight = qMax(height() - 100, 100);
|
||||
if (pageWidth != m_document->pageSize().width())
|
||||
m_document->setPageSize(QSize(pageWidth, pageHeight));
|
||||
|
||||
QRect textRect(width() / 2 - pageWidth / 2,
|
||||
height() / 2 - pageHeight / 2,
|
||||
pageWidth,
|
||||
pageHeight);
|
||||
int pad = 10;
|
||||
QRect clearRect = textRect.adjusted(-pad, -pad, pad, pad);
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(QColor(0, 0, 0, 63));
|
||||
int shade = 10;
|
||||
painter->drawRect(clearRect.x() + clearRect.width() + 1,
|
||||
clearRect.y() + shade,
|
||||
shade,
|
||||
clearRect.height() + 1);
|
||||
painter->drawRect(clearRect.x() + shade,
|
||||
clearRect.y() + clearRect.height() + 1,
|
||||
clearRect.width() - shade + 1,
|
||||
shade);
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing, false);
|
||||
painter->setBrush(QColor(255, 255, 255, 220));
|
||||
painter->setPen(Qt::black);
|
||||
painter->drawRect(clearRect);
|
||||
|
||||
painter->setClipRegion(textRect, Qt::IntersectClip);
|
||||
painter->translate(textRect.topLeft());
|
||||
|
||||
QAbstractTextDocumentLayout::PaintContext ctx;
|
||||
|
||||
QLinearGradient g(0, 0, 0, textRect.height());
|
||||
g.setColorAt(0, Qt::black);
|
||||
g.setColorAt(0.9, Qt::black);
|
||||
g.setColorAt(1, Qt::transparent);
|
||||
|
||||
QPalette pal = palette();
|
||||
pal.setBrush(QPalette::Text, g);
|
||||
|
||||
ctx.palette = pal;
|
||||
ctx.clip = QRect(0, 0, textRect.width(), textRect.height());
|
||||
m_document->documentLayout()->draw(painter, ctx);
|
||||
}
|
||||
|
||||
void ArthurFrame::loadSourceFile(const QString &sourceFile)
|
||||
{
|
||||
m_sourceFileName = sourceFile;
|
||||
}
|
||||
|
||||
void ArthurFrame::showSource()
|
||||
{
|
||||
// Check for existing source
|
||||
if (findChild<QTextBrowser *>())
|
||||
return;
|
||||
|
||||
QString contents;
|
||||
if (m_sourceFileName.isEmpty()) {
|
||||
contents = tr("No source for widget: '%1'").arg(objectName());
|
||||
} else {
|
||||
QFile f(m_sourceFileName);
|
||||
if (!f.open(QFile::ReadOnly))
|
||||
contents = tr("Could not open file: '%1'").arg(m_sourceFileName);
|
||||
else
|
||||
contents = QString::fromUtf8(f.readAll());
|
||||
}
|
||||
|
||||
contents.replace(QLatin1Char('&'), QStringLiteral("&"));
|
||||
contents.replace(QLatin1Char('<'), QStringLiteral("<"));
|
||||
contents.replace(QLatin1Char('>'), QStringLiteral(">"));
|
||||
|
||||
static const QString keywords[] = {
|
||||
QStringLiteral("for "), QStringLiteral("if "),
|
||||
QStringLiteral("switch "), QStringLiteral(" int "),
|
||||
QStringLiteral("#include "), QStringLiteral("const"),
|
||||
QStringLiteral("void "), QStringLiteral("uint "),
|
||||
QStringLiteral("case "), QStringLiteral("double "),
|
||||
QStringLiteral("#define "), QStringLiteral("static"),
|
||||
QStringLiteral("new"), QStringLiteral("this")
|
||||
};
|
||||
|
||||
for (const QString &keyword : keywords)
|
||||
contents.replace(keyword, QLatin1String("<font color=olive>") + keyword + QLatin1String("</font>"));
|
||||
contents.replace(QStringLiteral("(int "), QStringLiteral("(<font color=olive><b>int </b></font>"));
|
||||
|
||||
static const QString ppKeywords[] = {
|
||||
QStringLiteral("#ifdef"), QStringLiteral("#ifndef"),
|
||||
QStringLiteral("#if"), QStringLiteral("#endif"),
|
||||
QStringLiteral("#else")
|
||||
};
|
||||
|
||||
for (const QString &keyword : ppKeywords)
|
||||
contents.replace(keyword, QLatin1String("<font color=navy>") + keyword + QLatin1String("</font>"));
|
||||
|
||||
contents.replace(QRegularExpression("(\\d\\d?)"), QLatin1String("<font color=navy>\\1</font>"));
|
||||
|
||||
QRegularExpression commentRe("(//.+?)\\n");
|
||||
contents.replace(commentRe, QLatin1String("<font color=red>\\1</font>\n"));
|
||||
|
||||
QRegularExpression stringLiteralRe("(\".+?\")");
|
||||
contents.replace(stringLiteralRe, QLatin1String("<font color=green>\\1</font>"));
|
||||
|
||||
const QString html = QStringLiteral("<html><pre>") + contents + QStringLiteral("</pre></html>");
|
||||
|
||||
QTextBrowser *sourceViewer = new QTextBrowser;
|
||||
sourceViewer->setWindowTitle(tr("Source: %1").arg(QStringView{ m_sourceFileName }.mid(5)));
|
||||
sourceViewer->setParent(this, Qt::Dialog);
|
||||
sourceViewer->setAttribute(Qt::WA_DeleteOnClose);
|
||||
sourceViewer->setLineWrapMode(QTextEdit::NoWrap);
|
||||
sourceViewer->setHtml(html);
|
||||
sourceViewer->resize(600, 600);
|
||||
sourceViewer->show();
|
||||
}
|
69
examples/widgets/painting/shared/arthurwidgets.h
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef ARTHURWIDGETS_H
|
||||
#define ARTHURWIDGETS_H
|
||||
|
||||
#include "arthurstyle.h"
|
||||
#include <QBitmap>
|
||||
#include <QPushButton>
|
||||
#include <QGroupBox>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QOpenGLWindow)
|
||||
QT_FORWARD_DECLARE_CLASS(QTextDocument)
|
||||
QT_FORWARD_DECLARE_CLASS(QTextEdit)
|
||||
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
|
||||
class ArthurFrame : public QWidget
|
||||
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ArthurFrame(QWidget *parent);
|
||||
virtual void paint(QPainter *) {}
|
||||
|
||||
void paintDescription(QPainter *p);
|
||||
|
||||
void loadDescription(const QString &filename);
|
||||
void setDescription(const QString &htmlDesc);
|
||||
|
||||
void loadSourceFile(const QString &fileName);
|
||||
|
||||
bool preferImage() const { return m_preferImage; }
|
||||
#if QT_CONFIG(opengl)
|
||||
QOpenGLWindow *glWindow() const { return m_glWindow; }
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
void setPreferImage(bool pi) { m_preferImage = pi; }
|
||||
void setDescriptionEnabled(bool enabled);
|
||||
void showSource();
|
||||
|
||||
#if QT_CONFIG(opengl)
|
||||
void enableOpenGL(bool use_opengl);
|
||||
bool usesOpenGL() { return m_use_opengl; }
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void descriptionEnabledChanged(bool);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
void resizeEvent(QResizeEvent *) override;
|
||||
|
||||
#if QT_CONFIG(opengl)
|
||||
virtual void createGlWindow();
|
||||
QOpenGLWindow *m_glWindow = nullptr;
|
||||
QWidget *m_glWidget = nullptr;
|
||||
bool m_use_opengl = false;
|
||||
#endif
|
||||
QPixmap m_tile;
|
||||
|
||||
bool m_showDoc = false;
|
||||
bool m_preferImage = false;
|
||||
QTextDocument *m_document = nullptr;;
|
||||
|
||||
QString m_sourceFileName;
|
||||
};
|
||||
|
||||
#endif
|
75
examples/widgets/painting/shared/fbopaintdevice.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright (C) 2018 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "fbopaintdevice.h"
|
||||
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLFunctions>
|
||||
|
||||
QFboPaintDevice::QFboPaintDevice(const QSize &size, bool flipped, bool clearOnInit,
|
||||
QOpenGLFramebufferObject::Attachment attachment)
|
||||
: QOpenGLPaintDevice(size)
|
||||
{
|
||||
QOpenGLFramebufferObjectFormat format;
|
||||
format.setAttachment(attachment);
|
||||
format.setSamples(4);
|
||||
m_framebufferObject = new QOpenGLFramebufferObject(size, format);
|
||||
QOffscreenSurface *surface = new QOffscreenSurface();
|
||||
surface->create();
|
||||
m_surface = surface;
|
||||
setPaintFlipped(flipped);
|
||||
if (clearOnInit) {
|
||||
m_framebufferObject->bind();
|
||||
|
||||
context()->functions()->glClearColor(0, 0, 0, 0);
|
||||
context()->functions()->glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
m_resolvedFbo = new QOpenGLFramebufferObject(m_framebufferObject->size(), m_framebufferObject->attachment());
|
||||
}
|
||||
|
||||
QFboPaintDevice::~QFboPaintDevice()
|
||||
{
|
||||
delete m_framebufferObject;
|
||||
delete m_resolvedFbo;
|
||||
delete m_surface;
|
||||
}
|
||||
|
||||
void QFboPaintDevice::ensureActiveTarget()
|
||||
{
|
||||
if (QOpenGLContext::currentContext() != context())
|
||||
context()->makeCurrent(m_surface);
|
||||
|
||||
m_framebufferObject->bind();
|
||||
}
|
||||
|
||||
GLuint QFboPaintDevice::texture()
|
||||
{
|
||||
m_resolvedFbo->bind(); // to get the backing texture recreated if it was taken (in takeTexture) previously
|
||||
QOpenGLFramebufferObject::blitFramebuffer(m_resolvedFbo, m_framebufferObject);
|
||||
return m_resolvedFbo->texture();
|
||||
}
|
||||
|
||||
GLuint QFboPaintDevice::takeTexture()
|
||||
{
|
||||
m_resolvedFbo->bind(); // to get the backing texture recreated if it was taken (in takeTexture) previously
|
||||
// We have multisamples so we can't just forward takeTexture(), have to resolve first.
|
||||
QOpenGLFramebufferObject::blitFramebuffer(m_resolvedFbo, m_framebufferObject);
|
||||
return m_resolvedFbo->takeTexture();
|
||||
}
|
||||
|
||||
QImage QFboPaintDevice::toImage() const
|
||||
{
|
||||
QOpenGLContext *currentContext = QOpenGLContext::currentContext();
|
||||
QSurface *currentSurface = currentContext ? currentContext->surface() : nullptr;
|
||||
|
||||
context()->makeCurrent(m_surface);
|
||||
|
||||
QImage image = m_framebufferObject->toImage(!paintFlipped());
|
||||
|
||||
if (currentContext)
|
||||
currentContext->makeCurrent(currentSurface);
|
||||
else
|
||||
context()->doneCurrent();
|
||||
|
||||
return image;
|
||||
}
|
46
examples/widgets/painting/shared/fbopaintdevice.h
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2018 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef QFBOPAINTDEVICE_H
|
||||
#define QFBOPAINTDEVICE_H
|
||||
|
||||
#ifndef QT_NO_OPENGL
|
||||
|
||||
#include <QImage>
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QOpenGLPaintDevice>
|
||||
#include <QSurface>
|
||||
|
||||
class QFboPaintDevice : public QOpenGLPaintDevice {
|
||||
public:
|
||||
QFboPaintDevice(const QSize &size, bool flipped = false, bool clearOnInit = true,
|
||||
QOpenGLFramebufferObject::Attachment = QOpenGLFramebufferObject::CombinedDepthStencil);
|
||||
~QFboPaintDevice();
|
||||
|
||||
// QOpenGLPaintDevice:
|
||||
void ensureActiveTarget() override;
|
||||
|
||||
bool isValid() const { return m_framebufferObject->isValid(); }
|
||||
GLuint handle() const { return m_framebufferObject->handle(); }
|
||||
GLuint texture();
|
||||
GLuint takeTexture();
|
||||
QImage toImage() const;
|
||||
|
||||
bool bind() { return m_framebufferObject->bind(); }
|
||||
bool release() { return m_framebufferObject->release(); }
|
||||
QSize size() const { return m_framebufferObject->size(); }
|
||||
|
||||
QOpenGLFramebufferObject* framebufferObject() { return m_framebufferObject; }
|
||||
const QOpenGLFramebufferObject* framebufferObject() const { return m_framebufferObject; }
|
||||
|
||||
static bool isSupported() { return QOpenGLFramebufferObject::hasOpenGLFramebufferObjects(); }
|
||||
|
||||
private:
|
||||
QOpenGLFramebufferObject *m_framebufferObject;
|
||||
QOpenGLFramebufferObject *m_resolvedFbo;
|
||||
QSurface *m_surface;
|
||||
};
|
||||
|
||||
#endif // QT_NO_OPENGL
|
||||
|
||||
#endif // QFBOPAINTDEVICE_H
|
355
examples/widgets/painting/shared/hoverpoints.cpp
Normal file
@ -0,0 +1,355 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include "arthurwidgets.h"
|
||||
#include "hoverpoints.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#if QT_CONFIG(opengl)
|
||||
#include <QtOpenGL/QOpenGLWindow>
|
||||
#endif
|
||||
|
||||
HoverPoints::HoverPoints(QWidget *widget, PointShape shape)
|
||||
: QObject(widget),
|
||||
m_widget(widget),
|
||||
m_shape(shape)
|
||||
{
|
||||
widget->installEventFilter(this);
|
||||
widget->setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
|
||||
connect(this, &HoverPoints::pointsChanged,
|
||||
m_widget, QOverload<>::of(&QWidget::update));
|
||||
}
|
||||
|
||||
void HoverPoints::setEnabled(bool enabled)
|
||||
{
|
||||
if (m_enabled != enabled) {
|
||||
m_enabled = enabled;
|
||||
m_widget->update();
|
||||
}
|
||||
}
|
||||
|
||||
bool HoverPoints::eventFilter(QObject *object, QEvent *event)
|
||||
{
|
||||
if (object != m_widget || !m_enabled)
|
||||
return false;
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::MouseButtonPress:
|
||||
{
|
||||
if (!m_fingerPointMapping.isEmpty())
|
||||
return true;
|
||||
auto *me = static_cast<const QMouseEvent *>(event);
|
||||
QPointF clickPos = me->position().toPoint();
|
||||
qsizetype index = -1;
|
||||
for (qsizetype i = 0; i < m_points.size(); ++i) {
|
||||
QPainterPath path;
|
||||
const QRectF rect = pointBoundingRect(m_points.at(i));
|
||||
if (m_shape == CircleShape)
|
||||
path.addEllipse(rect);
|
||||
else
|
||||
path.addRect(rect);
|
||||
|
||||
if (path.contains(clickPos)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (me->button() == Qt::LeftButton) {
|
||||
if (index == -1) {
|
||||
if (!m_editable)
|
||||
return false;
|
||||
qsizetype pos = 0;
|
||||
// Insert sort for x or y
|
||||
switch (m_sortType) {
|
||||
case XSort:
|
||||
for (qsizetype i = 0; i < m_points.size(); ++i) {
|
||||
if (m_points.at(i).x() > clickPos.x()) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case YSort:
|
||||
for (qsizetype i = 0; i < m_points.size(); ++i) {
|
||||
if (m_points.at(i).y() > clickPos.y()) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_points.insert(pos, clickPos);
|
||||
m_locks.insert(pos, 0);
|
||||
m_currentIndex = pos;
|
||||
firePointChange();
|
||||
} else {
|
||||
m_currentIndex = index;
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if (me->button() == Qt::RightButton) {
|
||||
if (index >= 0 && m_editable) {
|
||||
if (m_locks[index] == 0) {
|
||||
m_locks.remove(index);
|
||||
m_points.remove(index);
|
||||
}
|
||||
firePointChange();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::MouseButtonRelease:
|
||||
if (!m_fingerPointMapping.isEmpty())
|
||||
return true;
|
||||
m_currentIndex = -1;
|
||||
break;
|
||||
|
||||
case QEvent::MouseMove:
|
||||
if (!m_fingerPointMapping.isEmpty())
|
||||
return true;
|
||||
if (m_currentIndex >= 0) {
|
||||
auto *me = static_cast<const QMouseEvent *>(event);
|
||||
movePoint(m_currentIndex, me->position().toPoint());
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::TouchBegin:
|
||||
case QEvent::TouchUpdate:
|
||||
{
|
||||
auto *touchEvent = static_cast<const QTouchEvent*>(event);
|
||||
const auto points = touchEvent->points();
|
||||
const qreal pointSize = qMax(m_pointSize.width(), m_pointSize.height());
|
||||
for (const auto &point : points) {
|
||||
const int id = point.id();
|
||||
switch (point.state()) {
|
||||
case QEventPoint::Pressed:
|
||||
{
|
||||
// find the point, move it
|
||||
const auto mappedPoints = m_fingerPointMapping.values();
|
||||
QSet<qsizetype> activePoints(mappedPoints.begin(), mappedPoints.end());
|
||||
qsizetype activePoint = -1;
|
||||
qreal distance = -1;
|
||||
const qsizetype pointsCount = m_points.size();
|
||||
const qsizetype activePointCount = activePoints.size();
|
||||
if (pointsCount == 2 && activePointCount == 1) { // only two points
|
||||
activePoint = activePoints.contains(0) ? 1 : 0;
|
||||
} else {
|
||||
for (qsizetype i = 0; i < pointsCount; ++i) {
|
||||
if (activePoints.contains(i))
|
||||
continue;
|
||||
|
||||
qreal d = QLineF(point.position(), m_points.at(i)).length();
|
||||
if ((distance < 0 && d < 12 * pointSize) || d < distance) {
|
||||
distance = d;
|
||||
activePoint = i;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (activePoint != -1) {
|
||||
m_fingerPointMapping.insert(point.id(), activePoint);
|
||||
movePoint(activePoint, point.position());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case QEventPoint::Released:
|
||||
{
|
||||
// move the point and release
|
||||
const auto it = m_fingerPointMapping.constFind(id);
|
||||
movePoint(it.value(), point.position());
|
||||
m_fingerPointMapping.erase(it);
|
||||
}
|
||||
break;
|
||||
case QEventPoint::Updated:
|
||||
{
|
||||
// move the point
|
||||
const qsizetype pointIdx = m_fingerPointMapping.value(id, -1);
|
||||
if (pointIdx >= 0) // do we track this point?
|
||||
movePoint(pointIdx, point.position());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_fingerPointMapping.isEmpty()) {
|
||||
event->ignore();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case QEvent::TouchEnd:
|
||||
if (m_fingerPointMapping.isEmpty()) {
|
||||
event->ignore();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
case QEvent::Resize:
|
||||
{
|
||||
auto *e = static_cast<const QResizeEvent *>(event);
|
||||
if (e->oldSize().width() <= 0 || e->oldSize().height() <= 0)
|
||||
break;
|
||||
qreal stretch_x = e->size().width() / qreal(e->oldSize().width());
|
||||
qreal stretch_y = e->size().height() / qreal(e->oldSize().height());
|
||||
for (qsizetype i = 0; i < m_points.size(); ++i) {
|
||||
QPointF p = m_points.at(i);
|
||||
movePoint(i, QPointF(p.x() * stretch_x, p.y() * stretch_y), false);
|
||||
}
|
||||
|
||||
firePointChange();
|
||||
break;
|
||||
}
|
||||
|
||||
case QEvent::Paint:
|
||||
{
|
||||
QWidget *that_widget = m_widget;
|
||||
m_widget = nullptr;
|
||||
QCoreApplication::sendEvent(object, event);
|
||||
m_widget = that_widget;
|
||||
paintPoints();
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void HoverPoints::paintPoints()
|
||||
{
|
||||
QPainter p;
|
||||
#if QT_CONFIG(opengl)
|
||||
ArthurFrame *af = qobject_cast<ArthurFrame *>(m_widget);
|
||||
if (af && af->usesOpenGL() && af->glWindow()->isValid()) {
|
||||
af->glWindow()->makeCurrent();
|
||||
p.begin(af->glWindow());
|
||||
} else {
|
||||
p.begin(m_widget);
|
||||
}
|
||||
#else
|
||||
p.begin(m_widget);
|
||||
#endif
|
||||
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
if (m_connectionPen.style() != Qt::NoPen && m_connectionType != NoConnection) {
|
||||
p.setPen(m_connectionPen);
|
||||
|
||||
if (m_connectionType == CurveConnection) {
|
||||
QPainterPath path;
|
||||
path.moveTo(m_points.at(0));
|
||||
for (qsizetype i = 1; i < m_points.size(); ++i) {
|
||||
QPointF p1 = m_points.at(i - 1);
|
||||
QPointF p2 = m_points.at(i);
|
||||
qreal distance = p2.x() - p1.x();
|
||||
|
||||
path.cubicTo(p1.x() + distance / 2, p1.y(),
|
||||
p1.x() + distance / 2, p2.y(),
|
||||
p2.x(), p2.y());
|
||||
}
|
||||
p.drawPath(path);
|
||||
} else {
|
||||
p.drawPolyline(m_points);
|
||||
}
|
||||
}
|
||||
|
||||
p.setPen(m_pointPen);
|
||||
p.setBrush(m_pointBrush);
|
||||
|
||||
for (const auto &point : std::as_const(m_points)) {
|
||||
QRectF bounds = pointBoundingRect(point);
|
||||
if (m_shape == CircleShape)
|
||||
p.drawEllipse(bounds);
|
||||
else
|
||||
p.drawRect(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
static QPointF bound_point(const QPointF &point, const QRectF &bounds, int lock)
|
||||
{
|
||||
QPointF p = point;
|
||||
|
||||
qreal left = bounds.left();
|
||||
qreal right = bounds.right();
|
||||
qreal top = bounds.top();
|
||||
qreal bottom = bounds.bottom();
|
||||
|
||||
if (p.x() < left || (lock & HoverPoints::LockToLeft)) p.setX(left);
|
||||
else if (p.x() > right || (lock & HoverPoints::LockToRight)) p.setX(right);
|
||||
|
||||
if (p.y() < top || (lock & HoverPoints::LockToTop)) p.setY(top);
|
||||
else if (p.y() > bottom || (lock & HoverPoints::LockToBottom)) p.setY(bottom);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void HoverPoints::setPoints(const QPolygonF &points)
|
||||
{
|
||||
if (points.size() != m_points.size())
|
||||
m_fingerPointMapping.clear();
|
||||
m_points.clear();
|
||||
for (qsizetype i = 0; i < points.size(); ++i)
|
||||
m_points << bound_point(points.at(i), boundingRect(), 0);
|
||||
|
||||
m_locks.clear();
|
||||
if (m_points.size() > 0) {
|
||||
m_locks.resize(m_points.size());
|
||||
m_locks.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
void HoverPoints::movePoint(qsizetype index, const QPointF &point, bool emitUpdate)
|
||||
{
|
||||
m_points[index] = bound_point(point, boundingRect(), m_locks.at(index));
|
||||
if (emitUpdate)
|
||||
firePointChange();
|
||||
}
|
||||
|
||||
inline static bool x_less_than(const QPointF &p1, const QPointF &p2)
|
||||
{
|
||||
return p1.x() < p2.x();
|
||||
}
|
||||
|
||||
inline static bool y_less_than(const QPointF &p1, const QPointF &p2)
|
||||
{
|
||||
return p1.y() < p2.y();
|
||||
}
|
||||
|
||||
void HoverPoints::firePointChange()
|
||||
{
|
||||
if (m_sortType != NoSort) {
|
||||
|
||||
QPointF oldCurrent;
|
||||
if (m_currentIndex != -1)
|
||||
oldCurrent = m_points[m_currentIndex];
|
||||
|
||||
if (m_sortType == XSort)
|
||||
std::sort(m_points.begin(), m_points.end(), x_less_than);
|
||||
else if (m_sortType == YSort)
|
||||
std::sort(m_points.begin(), m_points.end(), y_less_than);
|
||||
|
||||
// Compensate for changed order...
|
||||
if (m_currentIndex != -1) {
|
||||
for (qsizetype i = 0; i < m_points.size(); ++i) {
|
||||
if (m_points[i] == oldCurrent) {
|
||||
m_currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit pointsChanged(m_points);
|
||||
}
|
122
examples/widgets/painting/shared/hoverpoints.h
Normal file
@ -0,0 +1,122 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#ifndef HOVERPOINTS_H
|
||||
#define HOVERPOINTS_H
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QBypassWidget)
|
||||
|
||||
class HoverPoints : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum PointShape {
|
||||
CircleShape,
|
||||
RectangleShape
|
||||
};
|
||||
|
||||
enum LockType {
|
||||
LockToLeft = 0x01,
|
||||
LockToRight = 0x02,
|
||||
LockToTop = 0x04,
|
||||
LockToBottom = 0x08
|
||||
};
|
||||
|
||||
enum SortType {
|
||||
NoSort,
|
||||
XSort,
|
||||
YSort
|
||||
};
|
||||
|
||||
enum ConnectionType {
|
||||
NoConnection,
|
||||
LineConnection,
|
||||
CurveConnection
|
||||
};
|
||||
|
||||
HoverPoints(QWidget *widget, PointShape shape);
|
||||
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
void paintPoints();
|
||||
|
||||
inline QRectF boundingRect() const;
|
||||
void setBoundingRect(const QRectF &boundingRect) { m_bounds = boundingRect; }
|
||||
|
||||
QPolygonF points() const { return m_points; }
|
||||
void setPoints(const QPolygonF &points);
|
||||
|
||||
QSizeF pointSize() const { return m_pointSize; }
|
||||
void setPointSize(const QSizeF &size) { m_pointSize = size; }
|
||||
|
||||
SortType sortType() const { return m_sortType; }
|
||||
void setSortType(SortType sortType) { m_sortType = sortType; }
|
||||
|
||||
ConnectionType connectionType() const { return m_connectionType; }
|
||||
void setConnectionType(ConnectionType connectionType) { m_connectionType = connectionType; }
|
||||
|
||||
void setConnectionPen(const QPen &pen) { m_connectionPen = pen; }
|
||||
void setShapePen(const QPen &pen) { m_pointPen = pen; }
|
||||
void setShapeBrush(const QBrush &brush) { m_pointBrush = brush; }
|
||||
|
||||
void setPointLock(int pos, LockType lock) { m_locks[pos] = lock; }
|
||||
|
||||
void setEditable(bool editable) { m_editable = editable; }
|
||||
bool editable() const { return m_editable; }
|
||||
|
||||
public slots:
|
||||
void setEnabled(bool enabled);
|
||||
void setDisabled(bool disabled) { setEnabled(!disabled); }
|
||||
|
||||
signals:
|
||||
void pointsChanged(const QPolygonF &points);
|
||||
|
||||
public:
|
||||
void firePointChange();
|
||||
|
||||
private:
|
||||
inline QRectF pointBoundingRect(const QPointF &p) const;
|
||||
void movePoint(qsizetype i, const QPointF &newPos, bool emitChange = true);
|
||||
|
||||
QWidget *m_widget;
|
||||
|
||||
QPolygonF m_points;
|
||||
QRectF m_bounds;
|
||||
PointShape m_shape;
|
||||
SortType m_sortType = NoSort;
|
||||
ConnectionType m_connectionType = CurveConnection;
|
||||
|
||||
QList<uint> m_locks;
|
||||
|
||||
QSizeF m_pointSize{11, 11};
|
||||
qsizetype m_currentIndex= -1;
|
||||
bool m_editable = true;
|
||||
bool m_enabled = true;
|
||||
|
||||
QHash<int, qsizetype> m_fingerPointMapping;
|
||||
|
||||
QPen m_pointPen{QColor(255, 255, 255, 191), 1};
|
||||
QBrush m_pointBrush{QColor(191, 191, 191, 127)};
|
||||
QPen m_connectionPen{QColor(255, 255, 255, 127), 2};
|
||||
};
|
||||
|
||||
inline QRectF HoverPoints::pointBoundingRect(const QPointF &p) const
|
||||
{
|
||||
qreal w = m_pointSize.width();
|
||||
qreal h = m_pointSize.height();
|
||||
qreal x = p.x() - w / 2;
|
||||
qreal y = p.y() - h / 2;
|
||||
return QRectF(x, y, w, h);
|
||||
}
|
||||
|
||||
inline QRectF HoverPoints::boundingRect() const
|
||||
{
|
||||
if (m_bounds.isEmpty())
|
||||
return m_widget->rect();
|
||||
else
|
||||
return m_bounds;
|
||||
}
|
||||
|
||||
#endif // HOVERPOINTS_H
|
BIN
examples/widgets/painting/shared/images/bg_pattern.png
Normal file
After Width: | Height: | Size: 104 B |
After Width: | Height: | Size: 654 B |
After Width: | Height: | Size: 674 B |
After Width: | Height: | Size: 185 B |
After Width: | Height: | Size: 710 B |
After Width: | Height: | Size: 785 B |
After Width: | Height: | Size: 217 B |
BIN
examples/widgets/painting/shared/images/curve_thing_edit-6.png
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
examples/widgets/painting/shared/images/frame_bottom.png
Normal file
After Width: | Height: | Size: 166 B |
BIN
examples/widgets/painting/shared/images/frame_bottomleft.png
Normal file
After Width: | Height: | Size: 602 B |
BIN
examples/widgets/painting/shared/images/frame_bottomright.png
Normal file
After Width: | Height: | Size: 553 B |
BIN
examples/widgets/painting/shared/images/frame_left.png
Normal file
After Width: | Height: | Size: 182 B |
BIN
examples/widgets/painting/shared/images/frame_right.png
Normal file
After Width: | Height: | Size: 175 B |
BIN
examples/widgets/painting/shared/images/frame_top.png
Normal file
After Width: | Height: | Size: 188 B |
BIN
examples/widgets/painting/shared/images/frame_topleft.png
Normal file
After Width: | Height: | Size: 801 B |
BIN
examples/widgets/painting/shared/images/frame_topright.png
Normal file
After Width: | Height: | Size: 851 B |
After Width: | Height: | Size: 397 B |
After Width: | Height: | Size: 383 B |
After Width: | Height: | Size: 141 B |
After Width: | Height: | Size: 132 B |
After Width: | Height: | Size: 113 B |
After Width: | Height: | Size: 115 B |
BIN
examples/widgets/painting/shared/images/groupframe_topleft.png
Normal file
After Width: | Height: | Size: 412 B |
BIN
examples/widgets/painting/shared/images/groupframe_topright.png
Normal file
After Width: | Height: | Size: 449 B |
BIN
examples/widgets/painting/shared/images/line_dash_dot.png
Normal file
After Width: | Height: | Size: 151 B |
BIN
examples/widgets/painting/shared/images/line_dash_dot_dot.png
Normal file
After Width: | Height: | Size: 155 B |
BIN
examples/widgets/painting/shared/images/line_dashed.png
Normal file
After Width: | Height: | Size: 121 B |
BIN
examples/widgets/painting/shared/images/line_dotted.png
Normal file
After Width: | Height: | Size: 116 B |
BIN
examples/widgets/painting/shared/images/line_solid.png
Normal file
After Width: | Height: | Size: 110 B |
BIN
examples/widgets/painting/shared/images/radiobutton-off.png
Normal file
After Width: | Height: | Size: 442 B |
BIN
examples/widgets/painting/shared/images/radiobutton-on.png
Normal file
After Width: | Height: | Size: 474 B |
BIN
examples/widgets/painting/shared/images/radiobutton_off.png
Normal file
After Width: | Height: | Size: 442 B |
BIN
examples/widgets/painting/shared/images/radiobutton_on.png
Normal file
After Width: | Height: | Size: 499 B |
BIN
examples/widgets/painting/shared/images/slider_bar.png
Normal file
After Width: | Height: | Size: 748 B |
BIN
examples/widgets/painting/shared/images/slider_thumb_off.png
Normal file
After Width: | Height: | Size: 823 B |
BIN
examples/widgets/painting/shared/images/slider_thumb_on.png
Normal file
After Width: | Height: | Size: 798 B |
BIN
examples/widgets/painting/shared/images/title_cap_left.png
Normal file
After Width: | Height: | Size: 179 B |
BIN
examples/widgets/painting/shared/images/title_cap_right.png
Normal file
After Width: | Height: | Size: 184 B |
BIN
examples/widgets/painting/shared/images/title_stretch.png
Normal file
After Width: | Height: | Size: 106 B |
20
examples/widgets/painting/shared/shared.pri
Normal file
@ -0,0 +1,20 @@
|
||||
INCLUDEPATH += $$PWD
|
||||
|
||||
qtConfig(opengl) {
|
||||
QT += opengl
|
||||
SOURCES += $$PWD/fbopaintdevice.cpp
|
||||
HEADERS += $$PWD/fbopaintdevice.h
|
||||
}
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/arthurstyle.cpp\
|
||||
$$PWD/arthurwidgets.cpp \
|
||||
$$PWD/hoverpoints.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/arthurstyle.h \
|
||||
$$PWD/arthurwidgets.h \
|
||||
$$PWD/hoverpoints.h
|
||||
|
||||
RESOURCES += $$PWD/shared.qrc
|
||||
|
39
examples/widgets/painting/shared/shared.qrc
Normal file
@ -0,0 +1,39 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource prefix="/res">
|
||||
<file>images/button_normal_cap_left.png</file>
|
||||
<file>images/button_normal_cap_right.png</file>
|
||||
<file>images/button_normal_stretch.png</file>
|
||||
<file>images/button_pressed_cap_left.png</file>
|
||||
<file>images/button_pressed_cap_right.png</file>
|
||||
<file>images/button_pressed_stretch.png</file>
|
||||
<file>images/radiobutton-on.png</file>
|
||||
<file>images/radiobutton_on.png</file>
|
||||
<file>images/radiobutton_off.png</file>
|
||||
<file>images/slider_bar.png</file>
|
||||
<file>images/slider_thumb_on.png</file>
|
||||
<file>images/groupframe_topleft.png</file>
|
||||
<file>images/groupframe_topright.png</file>
|
||||
<file>images/groupframe_bottom_left.png</file>
|
||||
<file>images/groupframe_bottom_right.png</file>
|
||||
<file>images/groupframe_top_stretch.png</file>
|
||||
<file>images/groupframe_bottom_stretch.png</file>
|
||||
<file>images/groupframe_left_stretch.png</file>
|
||||
<file>images/groupframe_right_stretch.png</file>
|
||||
<file>images/frame_topleft.png</file>
|
||||
<file>images/frame_topright.png</file>
|
||||
<file>images/frame_bottomleft.png</file>
|
||||
<file>images/frame_bottomright.png</file>
|
||||
<file>images/frame_left.png</file>
|
||||
<file>images/frame_top.png</file>
|
||||
<file>images/frame_right.png</file>
|
||||
<file>images/frame_bottom.png</file>
|
||||
<file>images/title_cap_left.png</file>
|
||||
<file>images/title_cap_right.png</file>
|
||||
<file>images/title_stretch.png</file>
|
||||
<file>images/line_dash_dot.png</file>
|
||||
<file>images/line_dotted.png</file>
|
||||
<file>images/line_dashed.png</file>
|
||||
<file>images/line_solid.png</file>
|
||||
<file>images/line_dash_dot_dot.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
16
examples/widgets/painting/shared/use_lib.cmake
Normal file
@ -0,0 +1,16 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Include this file in your example project to use the library defined in this directory.
|
||||
# This avoids find_package calls in a directory scope different from the directory scope of the
|
||||
# consuming target.
|
||||
|
||||
if(NOT TARGET Qt::Widgets)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET Qt::OpenGL)
|
||||
find_package(Qt6 OPTIONAL_COMPONENTS OpenGL)
|
||||
endif()
|
||||
|
||||
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}" painting_shared)
|