How to resize the icon of specific QTabBar icon? - c++

I'm trying to create an effect similar to this gif:
While you are hovering over the tab button it shows a text and also resizes the icon.
I was reading the docs about the property icon-size it says it supports QTabBar, but adding the property into the tab stylesheet, has no effect when the button is being hovered.
I'm doing something wrong or it's a 'bug'?
QTabBar::tab:hover {
font-size: 12pt;
icon-size: 64px 64px;
color: rgb(255, 255, 255);
background: transparent;
}
As I couldn't get it working with a stylesheet, I tried using an event filter:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->tabWidget->tabBar()->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::HoverEnter)
{
QTabBar *tabbar = static_cast<QTabBar*>(obj);
tabbar->setIconSize(QSize(40, 40));
}
else if (event->type() == QEvent::HoverLeave)
{
QTabBar *tabbar = static_cast<QTabBar*>(obj);
tabbar->setIconSize(QSize(32,32));
}
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
Result:
Whenever any tab button has hovered, it resizes all icons.
How I could resize just the tab button under the mouse instead of the entire "QTabBar"?
Tab stylesheet options from the gif above:
QTabWidget::pane {
background: transparent;
}
QTabBar::tab {
font-size: 12pt;
color: rgba(255, 255, 255, 0);
background: transparent;
}
QTabBar::tab:hover {
font-size: 12pt;
color: rgb(255, 255, 255);
background: transparent;
}
QTabBar::tab:selected {
background: transparent;
}
I don't understand what's going on with the text, the tab text is "Tab" but it's cropping the "T".
Why the tab pane background is not getting transparent?

The recommendation in my comment of yesterday:
The last resort might be to make a custom widget for the tab bar, and to use a QStackedWidget as container for the tab pages.
There are probably samples around. Instead of googling I didn't hesitate to add one on my own:
qIconBar.h:
#ifndef Q_ICON_BAR
#define Q_ICON_BAR
#include <vector>
#include <QIcon>
#include <QWidget>
class QIconBar: public QWidget {
Q_OBJECT
private:
Qt::Orientation _ori;
QSize _qSizeIcon = QSize(64, 64); // icon size
int _spc = 4; // size between
double _scl = 0.6; // scale factor for icons not under mouse
struct Tab {
QIcon qIcon;
QString qLabel;
};
std::vector<Tab> _tabs;
int _iTab = 0; // current tab
signals:
void currentChanged(int i);
void tabClicked(int i);
public:
/// constructor.
explicit QIconBar(
Qt::Orientation ori = Qt::Horizontal, QWidget* pQParent = nullptr);
/// destructor.
virtual ~QIconBar() = default;
// disabled:
QIconBar(const QIconBar&) = delete;
QIconBar& operator=(const QIconBar&) = delete;
public:
virtual QSize sizeHint() const;
QSize iconSize() const { return _qSizeIcon; }
void setIconSize(const QSize& qSizeIcon);
int addTab(const QIcon& qIcon, const QString& qText)
{
return insertTab(-1, qIcon, qText);
}
int insertTab(int i, const QIcon& qIcon, const QString& qText);
void removeTab(int i);
int tabAt(const QPoint& qPos) const;
QIcon tabIcon(int i) const
{
return (size_t)i < _tabs.size() ? _tabs[i].qIcon : QIcon();
}
void setTabIcon(int i, const QIcon& qIcon);
QString tabText(int i) const
{
return (size_t)i < _tabs.size() ? _tabs[i].qLabel : QString();
}
void setTabText(int i, const QString& qLabel);
int count() const { return (int)_tabs.size(); }
int currentIndex() const { return _iTab; }
void setCurrentIndex(int i);
protected:
virtual void paintEvent(QPaintEvent* pQEvent) override;
virtual void mouseMoveEvent(QMouseEvent* pQEvent) override;
virtual void mousePressEvent(QMouseEvent* pQEvent) override;
virtual void leaveEvent(QEvent* pQEvent) override;
};
#endif // Q_ICON_BAR
Concerning the API, I (shamelessly) borrowed from QTabBar.
qIconBar.cc:
#include <QMouseEvent>
#include <QPainter>
#include "qIconBar.h"
QIconBar::QIconBar(Qt::Orientation ori, QWidget* pQParent):
QWidget(pQParent),
_ori(ori)
{
setMouseTracking(true);
}
QSize QIconBar::sizeHint() const
{
const int n = (int)_tabs.size();
const int spc = std::max(n - 1, 0) * _spc;
return _ori == Qt::Vertical
? QSize(_qSizeIcon.width(), _qSizeIcon.height() * n + spc)
: QSize(_qSizeIcon.width() * n + spc, _qSizeIcon.height());
}
int QIconBar::insertTab(int i, const QIcon& qIcon, const QString& qText)
{
if ((size_t)i > _tabs.size()) i = (int)_tabs.size();
_tabs.insert(_tabs.begin() + i, { qIcon, qText });
update();
if (i <= _iTab && _iTab + 1 < (int)_tabs.size()) {
setCurrentIndex(_iTab + 1);
}
return i;
}
void QIconBar::removeTab(int i)
{
if ((size_t)i >= _tabs.size()) return;
_tabs.erase(_tabs.begin() + i);
update();
if ((size_t)_iTab >= _tabs.size()) {
setCurrentIndex(0);
}
}
int QIconBar::tabAt(const QPoint& qPos) const
{
if (!QRect(QPoint(), size()).contains(qPos)) return -1;
return _ori == Qt::Vertical
? qPos.y() % (_qSizeIcon.height() + _spc) < _qSizeIcon.height()
? qPos.y() / (_qSizeIcon.height() + _spc)
: -1
: qPos.x() % (_qSizeIcon.width() + _spc) < _qSizeIcon.width()
? qPos.x() / (_qSizeIcon.width() + _spc)
: -1;
}
void QIconBar::setCurrentIndex(int i)
{
_iTab = i;
emit currentChanged(_iTab);
update();
}
void QIconBar::paintEvent(QPaintEvent* pQEvent)
{
const QPoint qPosMouse = mapFromGlobal(QCursor::pos());
const int iTabMouse = tabAt(qPosMouse);
QPainter qPainter(this);
for (int i = 0, n = (int)_tabs.size(); i < n; ++i) {
const Tab& tab = _tabs[i];
const QPoint qPos(
(_ori == Qt::Horizontal) * i * (_qSizeIcon.width() + _spc)
+ _qSizeIcon.width() / 2,
(_ori == Qt::Vertical) * i *(_qSizeIcon.height() + _spc)
+ _qSizeIcon.height() / 2);
if (i == _iTab) {
QRect qRect(QPoint(), _qSizeIcon);
qRect.moveCenter(qPos);
qPainter.fillRect(qRect, palette().color(QPalette::Base));
}
if (!tab.qIcon.isNull()) {
const double scale = iTabMouse == i ? 1.0 : _scl;
const QSize qSizeIcon = scale * _qSizeIcon;
const QPixmap qPixmap = tab.qIcon.pixmap(qSizeIcon);
QRect qRect(QPoint(), qSizeIcon);
qRect.moveCenter(qPos);
qPainter.drawPixmap(qRect, qPixmap);
}
if (i == iTabMouse) {
QFont qFont = font();
qFont.setBold(true);
qPainter.setFont(qFont);
QRect qRect(QPoint(), _qSizeIcon);
qRect.moveCenter(qPos);
qPainter.drawText(qRect, Qt::AlignHCenter | Qt::AlignBottom,
tab.qLabel);
}
}
}
void QIconBar::mouseMoveEvent(QMouseEvent* pQEvent)
{
update();
QWidget::mouseMoveEvent(pQEvent);
}
void QIconBar::mousePressEvent(QMouseEvent* pQEvent)
{
if (pQEvent->button() == Qt::LeftButton) {
const int iTab = tabAt(pQEvent->pos());
if ((size_t)iTab < _tabs.size()) {
setCurrentIndex(iTab);
emit tabClicked(iTab);
return;
}
}
QWidget::mousePressEvent(pQEvent);
}
void QIconBar::leaveEvent(QEvent* pQEvent)
{
update();
QWidget::leaveEvent(pQEvent);
}
A sample application testQIconBar.cc:
#include <QtWidgets>
#include "qIconBar.h"
const struct Item {
const char* filePathIcon;
const char* label;
} items[] = {
{ "icons/document-open.svg", "File" },
{ "icons/document-properties.svg", "Edit" },
{ "icons/edit-find.svg", "Find" },
{ "icons/help-help.svg", "Help" }
};
const size_t nItems = std::size(items);
int main(int argc, char** argv)
{
QApplication app(argc, argv);
// setup GUI
QWidget qWinMain;
qWinMain.setWindowTitle("QIconBar Test");
qWinMain.resize(640, 480);
QHBoxLayout qHBox;
QIconBar qIconBar(Qt::Vertical);
qHBox.addWidget(&qIconBar);
QStackedLayout qStack;
qHBox.addLayout(&qStack, 1);
qWinMain.setLayout(&qHBox);
qWinMain.show();
// populate icon bar and stack
QFont qFont = qWinMain.font();
qFont.setPixelSize(30);
for (const Item& item : items) {
qIconBar.addTab(
QIcon(item.filePathIcon), QString::fromUtf8(item.label));
const QString qText
= QString("This is the page for item %1.").arg(item.label);
QLabel* pQLbl = new QLabel(qText);
pQLbl->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
pQLbl->setFont(qFont);
pQLbl->setAlignment(Qt::AlignCenter);
qStack.addWidget(pQLbl);
}
qStack.setCurrentIndex(qIconBar.currentIndex());
// install signal handlers
QObject::connect(&qIconBar, &QIconBar::currentChanged,
&qStack, &QStackedLayout::setCurrentIndex);
// runtime loop
return app.exec();
}
Additionally, I used the following CMakeLists.txt:
project(QIconBar)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt5Widgets CONFIG REQUIRED)
include_directories("${CMAKE_SOURCE_DIR}")
qt5_wrap_cpp(moc_sources QIconBar.h
TARGET testQIconBar)
add_executable(testQIconBar
testQIconBar.cc qIconBar.cc qIconBar.h
${moc_sources})
target_link_libraries(testQIconBar Qt5::Widgets)
…and compiled this in Visual Studio 2019.
Output:
According to feedback, I tested my MCVE concerning transparency of the widgets — i.e. with a background image in the main window:
The QIconBar doesn't clear it's background — the contents is just drawn over the background of the parent window. (It's the same with the QLabels I used in the QStackedLayout.)

Related

Mouse resizable, draggable widgets

I am trying to allow users to add new "widgets" (images, text, perhaps other custom data too. Image is good enough for now) to a kind of design area. And then I would like them to be able to resize/move those conveniently. The best way for the moving part seems to be to use QGraphicsView. Can be done nicely with 4 lines of code:
auto const scene = new QGraphicsScene{this};
auto const item = scene->addPixmap(QPixmap{":/img/example.png"});
item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
ui->graphicsView->setScene(scene);
This results in something like this:
It's nice, but cannot be resized with the mouse. I've seen (on this site) multiple ways to make this, sort of, resizable with a mouse, but they are all kind of hacky.
I've seen this example on Qt website which takes a different approach of creating a custom container for a moveable-and-resizeable container. It seems like it can be made work with a bit more tweaking, but it's again, not a nice solution. The widget doesn't really look like it's resizable. When selected, the borders don't have the nice the clue that it's a dynamically placed/sized thing.
Having ms-paint like moveable widgets must be a common use case, so I reckon there has got to be a nice way to get this happen. Is this the case? QGraphicsScene seems like a good candidate honestly. Perhaps I am missing something?
Ok, so I had this problem and my solution was to link the creation of the handlers with the selection of the item:
mainwindow.h
#pragma once
#include <QMainWindow>
#include <QGraphicsItem>
#include <QPainter>
class Handler: public QGraphicsItem
{
public:
enum Mode
{
Top = 0x1,
Bottom = 0x2,
Left = 0x4,
TopLeft = Top | Left,
BottomLeft = Bottom | Left,
Right = 0x8,
TopRight = Top | Right,
BottomRight = Bottom | Right,
Rotate = 0x10
};
Handler(QGraphicsItem *parent, Mode mode);
~Handler(){}
void updatePosition();
QRectF boundingRect() const override;
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
QPointF iniPos;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
private:
Mode mode;
bool isMoving = false;
};
class ObjectResizerGrip: public QGraphicsItem
{
public:
ObjectResizerGrip(QGraphicsItem *parent): QGraphicsItem(parent)
{
setFlag(QGraphicsItem::ItemHasNoContents, true);
setFlag(QGraphicsItem::ItemIsSelectable, false);
setFlag(QGraphicsItem::ItemIsFocusable, false);
}
void updateHandlerPositions();
virtual QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override{Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget)}
protected:
QList<Handler*> handlers;
};
class Object4SidesResizerGrip: public ObjectResizerGrip
{
public:
Object4SidesResizerGrip(QGraphicsItem *parent);
};
class Item:public QGraphicsItem
{
public:
Item(QGraphicsItem *parent=nullptr): QGraphicsItem(parent)
{
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
setAcceptHoverEvents(true);
}
QRectF boundingRect() const override
{
return boundingBox;
}
void setWidth(qreal value)
{
auto width = boundingBox.width();
if(width == value) return;
width = qMax(value, 100.0);
setDimensions(width, boundingBox.height());
}
void setHeight(qreal value)
{
auto height = boundingBox.height();
if(height == value) return;
height = qMax(value, 100.0);
setDimensions(boundingBox.width(), height);
}
void setDimensions(qreal w, qreal h)
{
prepareGeometryChange();
boundingBox = QRectF(-w/2.0, -h/2.0, w, h);
if(resizerGrip) resizerGrip->updateHandlerPositions();
update();
}
private:
ObjectResizerGrip* resizerGrip = nullptr;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
{
if(change == ItemSelectedHasChanged && scene())
{
if(value.toBool())
{
if(!resizerGrip)
resizerGrip = newSelectionGrip();
}
else
{
if(resizerGrip)
{
delete resizerGrip;
resizerGrip = nullptr;
}
}
}
return QGraphicsItem::itemChange(change, value);
}
QRectF boundingBox;
virtual ObjectResizerGrip *newSelectionGrip() =0;
};
class CrossItem:public Item
{
public:
CrossItem(QGraphicsItem *parent=nullptr): Item(parent){};
private:
virtual ObjectResizerGrip *newSelectionGrip() override
{
return new Object4SidesResizerGrip(this);
}
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->drawLine(boundingRect().topLeft(), boundingRect().bottomRight());
painter->drawLine(boundingRect().topRight(), boundingRect().bottomLeft());
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
};
mainwindow.cpp
#include "mainwindow.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QHBoxLayout>
// Return nearest point along the line to a given point
// http://stackoverflow.com/questions/1459368/snap-point-to-a-line
QPointF getClosestPoint(const QPointF &vertexA, const QPointF &vertexB, const QPointF &point, const bool segmentClamp)
{
QPointF AP = point - vertexA;
QPointF AB = vertexB - vertexA;
qreal ab2 = AB.x()*AB.x() + AB.y()*AB.y();
if(ab2 == 0) // Line lenth == 0
return vertexA;
qreal ap_ab = AP.x()*AB.x() + AP.y()*AB.y();
qreal t = ap_ab / ab2;
if (segmentClamp)
{
if (t < 0.0f) t = 0.0f;
else if (t > 1.0f) t = 1.0f;
}
return vertexA + AB * t;
}
Object4SidesResizerGrip::Object4SidesResizerGrip(QGraphicsItem* parent) : ObjectResizerGrip(parent)
{
handlers.append(new Handler(this, Handler::Left));
handlers.append(new Handler(this, Handler::BottomLeft));
handlers.append(new Handler(this, Handler::Bottom));
handlers.append(new Handler(this, Handler::BottomRight));
handlers.append(new Handler(this, Handler::Right));
handlers.append(new Handler(this, Handler::TopRight));
handlers.append(new Handler(this, Handler::Top));
handlers.append(new Handler(this, Handler::TopLeft));
handlers.append(new Handler(this, Handler::Rotate));
updateHandlerPositions();
}
QRectF ObjectResizerGrip::boundingRect() const
{
return QRectF();
}
void ObjectResizerGrip::updateHandlerPositions()
{
foreach (Handler* item, handlers)
item->updatePosition();
}
Handler::Handler(QGraphicsItem *parent, Mode mode): QGraphicsItem(parent), mode(mode)
{
QPen pen(Qt::white);
pen.setWidth(0);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, false);
setAcceptHoverEvents(true);
setZValue(100);
setCursor(Qt::UpArrowCursor);
updatePosition();
}
void Handler::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen(isMoving ? QColor(250,214,36) : QColor(100,100,100));
pen.setWidth(0);
pen.setBrush(pen.color());
painter->setPen(pen);
painter->setBrush(QColor(100,100,100,150));
if(mode & Rotate)
{
auto rect_ = ((Item*) parentItem()->parentItem())->boundingRect();
auto topPos = QPointF(rect_.left() + rect_.width() / 2 - 1, rect_.top());
painter->drawLine(mapFromParent(topPos), mapFromParent(topPos - QPointF(0, 175)));
painter->drawEllipse(boundingRect());
}
else
painter->drawRect(boundingRect());
}
QRectF Handler::boundingRect() const
{
return QRectF(-25, -25, 50, 50);
}
void Handler::updatePosition()
{
auto rect_ = ((Item*) parentItem()->parentItem())->boundingRect();
switch (mode)
{
case TopLeft:
setPos(rect_.topLeft());
break;
case Top:
setPos(rect_.left() + rect_.width() / 2 - 1,rect_.top());
break;
case TopRight:
setPos(rect_.topRight());
break;
case Right:
setPos(rect_.right(),rect_.top() + rect_.height() / 2 - 1);
break;
case BottomRight:
setPos(rect_.bottomRight());
break;
case Bottom:
setPos(rect_.left() + rect_.width() / 2 - 1,rect_.bottom());
break;
case BottomLeft:
setPos(rect_.bottomLeft());
break;
case Left:
setPos(rect_.left(), rect_.top() + rect_.height() / 2 - 1);
break;
case Rotate:
setPos(0, rect_.top() - 200);
break;
}
}
void Handler::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(mode & Rotate)
{
Item* item = (Item*) parentItem()->parentItem();
auto angle = QLineF(item->mapToScene(QPoint()), event->scenePos()).angle();
if(!(QApplication::keyboardModifiers() & Qt::AltModifier)) // snap to 45deg
{
auto modAngle = fmod(angle+180, 45);
if(modAngle < 10 || modAngle > 35)
angle = round(angle/45)*45;
}
item->setRotation(0);
angle = QLineF(item->mapFromScene(QPoint()), item->mapFromScene(QLineF::fromPolar(10, angle).p2())).angle();
item->setRotation(90 - angle);
item->update();
}
else
{
Item* item = (Item*) parentItem()->parentItem();
auto diff = mapToItem(item, event->pos()) - mapToItem(item, event->lastPos());
auto bRect = item->boundingRect();
if(mode == TopLeft || mode == BottomRight)
diff = getClosestPoint(bRect.topLeft(), QPoint(0,0), diff, false);
else if(mode == TopRight || mode == BottomLeft)
diff = getClosestPoint(bRect.bottomLeft(), QPoint(0,0), diff, false);
if(mode & Left || mode & Right)
{
item->setPos(item->mapToScene(QPointF(diff.x()/2.0, 0)));
if(mode & Left)
item->setWidth(item->boundingRect().width() - diff.x());
else
item->setWidth(item->boundingRect().width() + diff.x());
}
if(mode & Top || mode & Bottom)
{
item->setPos(item->mapToScene(QPointF(0, diff.y()/2.0)));
if(mode & Top)
item->setHeight(item->boundingRect().height() - diff.y());
else
item->setHeight(item->boundingRect().height() + diff.y());
}
item->update();
}
((ObjectResizerGrip*) parentItem())->updateHandlerPositions();
}
void Handler::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
isMoving = true;
}
void Handler::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
isMoving = false;
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto const graphicsView = new QGraphicsView(this);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
auto const scene = new QGraphicsScene(this);
auto const item = new CrossItem();
item->setWidth(100);
item->setHeight(100);
scene->addItem(item);
item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
graphicsView->setScene(scene);
graphicsView-> fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
setCentralWidget(graphicsView);
}
MainWindow::~MainWindow()
{
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
This solution is far from perfect but it works and can be a good start for improvements.
Known issues:
rotation grip requires FullViewportUpdate because I was too lazy to implement it in a separate child item and it is drawing outside the bounding box.
there are probably better architectures like using proxies or signals/event.
When it comes to using mouse, keyboard and in general capturing operating system events, you have to rely on the event system. The base class is QEvent, which in your specific case allows you to "QResizeEvent, QMouseEvent, QScrollEvent, ..." and many more fun things.

When customizing a Qt slider with a Style in code, the handle goes off the groove

In most examples, customizing the Qt slider is done like this (with a stylesheet):
mySlider = new QSlider(centralWidget);
mySlider->setObjectName(QStringLiteral("mySlider"));
mySlider->setGeometry(QRect(645, 678, 110, 21));
mySlider->setOrientation(Qt::Horizontal);
mySlider->setStyleSheet("QSlider::groove:horizontal {background-image:url(:/main/graphics/mySliderBackround.png);}"
"QSlider::handle:horizontal {background-image:url(:/main/graphics/mySliderHandle.png); height:21px; width: 21px;}");
This works fine for me as well.
I have a situation where I need to programmatically set the background using a dyamically created pixmap. Using the code below, this is how I accomplished it. The problem is that when I am on Fedora Linux, this slider works fine. When I'm on OSX or Windows, the slider handle goes off the goove.
Here's what it looks like on OSX. Notice how the handle is off the groove. The left side is customized with a stylesheet, the right is customized with the Style object below.
Create the slider and assign the style:
mySlider = new QSlider(centralWidget);
mySlider->setObjectName(QStringLiteral("mySlider"));
mySlider->setGeometry(QRect(645, 678, 110, 21));
mySlider->setOrientation(Qt::Horizontal);
mySlider->setStyle(new MySliderStyle(mySlider->style()));
The custom slider style code:
Header
#ifndef MYSTYLE_H
#define MYSTYLE_H
#include <QObject>
#include <QWidget>
#include <QProxyStyle>
#include <QPainter>
#include <QStyleOption>
#include <QtWidgets/QCommonStyle>
class MySliderStyle : public QProxyStyle
{
private:
QPixmap groovePixmap;
QPixmap handlePixmap;
public:
LightingSliderStyle(QStyle *style)
: QProxyStyle(style)
{
setColor(QColor::fromRgba(0));
this->handlePixmap = <snip initialize the pixmap>;
this->grooveMaskPixmap = <snip initialize the pixmap>;
}
void drawComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;
QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const;
void setColor(QColor color);
};
#endif // MYSTYLE_H
Implementation*
#include "MySliderStyle.h"
QRect MySliderStyle::subControlRect(ComplexControl control,
const QStyleOptionComplex *option,
SubControl subControl,
const QWidget *widget) const
{
QRect rect;
rect = QCommonStyle::subControlRect(control, option, subControl, widget);
if (control == CC_Slider && subControl == SC_SliderHandle)
{
// this is the exact pixel dimensions of the handle png files
rect.setWidth(21);
rect.setHeight(21);
}
else if (control == CC_Slider && subControl == SC_SliderGroove)
{
// this is the exact pixel dimensions of the slider png files
rect.setWidth(widget->width());
rect.setHeight(widget->height());
}
return rect;
}
void MySliderStyle::drawComplexControl(QStyle::ComplexControl control,
const QStyleOptionComplex *option,
QPainter *painter,
const QWidget *widget) const
{
if (control == CC_Slider)
{
if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option))
{
QRect groove = subControlRect(CC_Slider, slider, SC_SliderGroove, widget);
QRect handle = subControlRect(CC_Slider, slider, SC_SliderHandle, widget);
if ((slider->subControls & SC_SliderGroove) && groove.isValid())
{
Qt::BGMode oldMode = painter->backgroundMode();
painter->setBackgroundMode(Qt::TransparentMode);
painter->drawPixmap(groove, groovePixmap);
painter->setBackgroundMode(oldMode);
}
if ((slider->subControls & SC_SliderHandle) && handle.isValid())
{
Qt::BGMode oldMode = painter->backgroundMode();
painter->setBackgroundMode(Qt::TransparentMode);
painter->drawPixmap(handle, handlePixmap);
painter->setBackgroundMode(oldMode);
}
}
}
else
{
QProxyStyle::drawComplexControl(control, option, painter, widget);
}
}
void MySliderStyle::setColor(QColor color)
{
QImage myGrooveImage;
// <snip>
// Code to create the custom pixmap
// <snip>
groovePixmap = QPixmap::fromImage(myGrooveImage);
}
UPDATE
The code for this project is open source and available here
A call to QCommonStyle::subControlRect and adjusting the width/height is not enough. You must also recalculate the x/y position.
You can therfore use the QCommonStyle::subControlRect function as reference to calculate the proper rectangle:
QRect LightingSliderStyle::subControlRect(ComplexControl control,
const QStyleOptionComplex *option,
SubControl subControl,
const QWidget *widget) const
{
if (control == CC_Slider)
{
if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
QRect ret;
int tickOffset = 0;
int thickness = 21; // height
int len = 21; // width
switch (subControl) {
case SC_SliderHandle: {
int sliderPos = 0;
bool horizontal = slider->orientation == Qt::Horizontal;
sliderPos = sliderPositionFromValue(slider->minimum, slider->maximum+1,
slider->sliderPosition,
(horizontal ? slider->rect.width()
: slider->rect.height()) - len,
slider->upsideDown);
if (horizontal)
ret.setRect(slider->rect.x() + sliderPos, slider->rect.y() + tickOffset, len, thickness);
else
ret.setRect(slider->rect.x() + tickOffset, slider->rect.y() + sliderPos, thickness, len);
break; }
case SC_SliderGroove:
if (slider->orientation == Qt::Horizontal)
ret.setRect(slider->rect.x(), slider->rect.y() + tickOffset,
slider->rect.width(), thickness);
else
ret.setRect(slider->rect.x() + tickOffset, slider->rect.y(),
thickness, slider->rect.height());
break;
default:
break;
}
return visualRect(slider->direction, slider->rect, ret);
}
}
return QCommonStyle::subControlRect(control, option, subControl, widget);
}

Qt: How to create a clearly visible glow effect for a QLabel? (e.g. using QGraphicsDropShadowEffect)

I am trying to add a glow effect to a QLabel so that it looks like the time display in the following picture:
I found out that you can "misuse" a QGraphicsDropShadowEffect for this:
QGraphicsDropShadowEffect * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(10);
dse->setOffset(0);
dse->setColor(QColor(255, 255, 255));
ui.label->setGraphicsEffect(dse);
However, the resulting effect is too weak, you can barely see it:
Unfortunately, you can not modify the strength of the effect, only color and blur radius.
One idea would be to apply multiple QGraphicsDropShadowEffect to the label, so that it gets more visible due to overlapping. But calling ui.label->setGraphicsEffect(dse); will always delete any previous effects, i.e. I was not able to set multiple QGraphicsEffect to the same object.
Any ideas how you can create a clearly visible glow effect with Qt?
Meanwhile, I tinkered my own graphics effect based on QGraphicsBlurEffect and using parts of this answer. If you know any better solutions, let me know.
qgraphicsgloweffect.h:
#pragma once
#include <QGraphicsEffect>
#include <QGraphicsBlurEffect>
#include <QGraphicsColorizeEffect>
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QPainter>
class QGraphicsGlowEffect :
public QGraphicsEffect
{
public:
explicit QGraphicsGlowEffect(QObject *parent = 0);
QRectF boundingRectFor(const QRectF &rect) const;
void setColor(QColor value);
void setStrength(int value);
void setBlurRadius(qreal value);
QColor color() const;
int strength() const;
qreal blurRadius() const;
protected:
void draw(QPainter* painter);
private:
static QPixmap applyEffectToPixmap(QPixmap src, QGraphicsEffect *effect, int extent);
int _extent = 5;
QColor _color = QColor(255, 255, 255);
int _strength = 3;
qreal _blurRadius = 5.0;
};
qgraphicsgloweffect.cpp:
#include "QGraphicsGlowEffect.h"
#include <QtCore\qmath.h>
QGraphicsGlowEffect::QGraphicsGlowEffect(QObject *parent) : QGraphicsEffect(parent)
{
}
void QGraphicsGlowEffect::setColor(QColor value) {
_color = value;
}
void QGraphicsGlowEffect::setStrength(int value) {
_strength = value;
}
void QGraphicsGlowEffect::setBlurRadius(qreal value) {
_blurRadius = value;
_extent = qCeil(value);
updateBoundingRect();
}
QColor QGraphicsGlowEffect::color() const {
return _color;
}
int QGraphicsGlowEffect::strength() const {
return _strength;
}
qreal QGraphicsGlowEffect::blurRadius() const {
return _blurRadius;
}
QRectF QGraphicsGlowEffect::boundingRectFor(const QRectF &rect) const {
return QRect(
rect.left() - _extent,
rect.top() - _extent,
rect.width() + 2 * _extent,
rect.height() + 2 * _extent);
}
void QGraphicsGlowEffect::draw(QPainter* painter) {
QPoint offset;
QPixmap source = sourcePixmap(Qt::LogicalCoordinates, &offset);
QPixmap glow;
QGraphicsColorizeEffect *colorize = new QGraphicsColorizeEffect;
colorize->setColor(_color);
colorize->setStrength(1);
glow = applyEffectToPixmap(source, colorize, 0);
QGraphicsBlurEffect *blur = new QGraphicsBlurEffect;
blur->setBlurRadius(_blurRadius);
glow = applyEffectToPixmap(glow, blur, _extent);
for (int i = 0; i < _strength; i++)
painter->drawPixmap(offset - QPoint(_extent, _extent), glow);
drawSource(painter);
}
QPixmap QGraphicsGlowEffect::applyEffectToPixmap(
QPixmap src, QGraphicsEffect *effect, int extent)
{
if (src.isNull()) return QPixmap();
if (!effect) return src;
QGraphicsScene scene;
QGraphicsPixmapItem item;
item.setPixmap(src);
item.setGraphicsEffect(effect);
scene.addItem(&item);
QSize size = src.size() + QSize(extent * 2, extent * 2);
QPixmap res(size.width(), size.height());
res.fill(Qt::transparent);
QPainter ptr(&res);
scene.render(&ptr, QRectF(), QRectF(-extent, -extent, size.width(), size.height()));
return res;
}
Then you can use it like:
QGraphicsGlowEffect * glow = new QGraphicsGlowEffect();
glow->setStrength(4);
glow->setBlurRadius(7);
ui.label->setGraphicsEffect(glow);
This results in a nice glow effect:

How to change text alignment in QTabWidget in C++?

This is the same question as in:
How to change text alignment in QTabWidget?
I tried to port that python code into C++ but it doesn't seem to work.
Here is header file:
#include <QTabBar>
class HorizontalTabWidget : public QTabBar
{
Q_OBJECT
public:
explicit HorizontalTabWidget(QWidget *parent = 0);
protected:
void paintEvent(QPaintEvent *);
QSize sizeHint() const;
};
Here is source file:
void HorizontalTabWidget::paintEvent(QPaintEvent *)
{
for(int index = 0; index < count(); index++)
{
QPainter * painter = new QPainter(this);
painter->begin(this);
painter->setPen(Qt::blue);
painter->setFont(QFont("Arial", 10));
QRect tabrect = tabRect(index);
painter->drawText(tabrect, Qt::AlignVCenter | Qt::TextDontClip, tabText(index));
painter->end();
}
}
QSize HorizontalTabWidget::sizeHint() const
{
return QSize(130, 130);
}
I use it by creating NewTabWidget class that inherits QTabWidget. In the constructor of NewTabWidget I use:
setTabBar(new HorizontalTabWidget);
This is done just to be able to use that tabWidget because setTabBar is protected. Here is what I get:
What am I missing?
Edit:
What I want to create is this but with icons on the top and the labels under the icons (as in Qt Creator):
the problem should be in the paint method; check if an example below would work for you, it should draw tabs the same way QTCreator does. I've reused the original tab style QStyleOptionTabV3 to do majority of paint work and then just rendered icon and tab's text on top of the image it produced:
class TestTabBar : public QTabBar
{
public:
explicit TestTabBar(QWidget* parent=0) : QTabBar(parent)
{
setIconSize(QSize(80, 80));
}
protected:
QSize tabSizeHint(int) const
{
return QSize(80, 80);
}
void paintEvent(QPaintEvent *)
{
QStylePainter p(this);
for (int index = 0; index < count(); index++)
{
QStyleOptionTabV3 tab;
initStyleOption(&tab, index);
QIcon tempIcon = tab.icon;
QString tempText = tab.text;
tab.icon = QIcon();
tab.text = QString();
p.drawControl(QStyle::CE_TabBarTab, tab);
QPainter painter;
painter.begin(this);
QRect tabrect = tabRect(index);
tabrect.adjust(0, 8, 0, -8);
painter.drawText(tabrect, Qt::AlignBottom | Qt::AlignHCenter, tempText);
tempIcon.paint(&painter, 0, tabrect.top(), tab.iconSize.width(), tab.iconSize.height(), Qt::AlignTop | Qt::AlignHCenter);
painter.end();
}
}
};
class TestTabWidget : public QTabWidget
{
public:
explicit TestTabWidget(QWidget *parent = 0) : QTabWidget(parent)
{
setTabBar(new TestTabBar());
}
};
tabwidget init:
TestTabWidget* test = new TestTabWidget(this);
test->setGeometry(20, 20, 300, 200);
test->addTab(new QWidget(), QIcon("icon0.png"), "test0");
test->addTab(new QWidget(), QIcon("icon1.png"), "test1");
test->setTabPosition(QTabWidget::West);
this worked fine on my ubuntu, hope it gonna work for you,
regards
Vasiliy, thanks for fixing the double QPainter bug.
However, calling setTabIcon() and setTabText() from within paintEvent() leads to an infinite recursion. Remember that tab is a local object, so
tab.text = QString();
does not affect tabText().
So, the example can also be written without making temporary copies and do
p.drawText(tabrect, Qt::AlignBottom | Qt::AlignHCenter, tabText(index));
tabIcon(index).paint(&p, tabrect, Qt::AlignTop | Qt::AlignHCenter);
This example does not work. leads to a fall program.
bring your own example with minor edits
- my system qt 4.6.3 for Windows and VS2008
class TestTabBar : public QTabBar
{
public:
explicit TestTabBar(QWidget* parent=0) : QTabBar(parent)
{
setIconSize(QSize(58, 68));
}
protected:
QSize tabSizeHint(int) const
{
return QSize(58, 68);
}
void paintEvent(QPaintEvent *)
{
QStylePainter p(this);
for (int index = 0; index < count(); index++)
{
QStyleOptionTabV3 tab;
initStyleOption(&tab, index);
QIcon tempIcon = tabIcon(index);
QString tempText = this->tabText(index);
QRect tabrect = tabRect(index);
tab.icon = QIcon();
tab.text = QString();
p.drawControl(QStyle::CE_TabBarTab, tab);
tabrect.adjust(0, 3, 0, -3);
p.setPen(Qt::black);
p.setFont(QFont("Arial", 7));
p.drawText(tabrect, Qt::AlignBottom | Qt::AlignHCenter, tempText );
tempIcon.paint(&p, tabrect, Qt::AlignTop | Qt::AlignHCenter);
this->setTabIcon(index, tempIcon );
this->setTabText( index, tempText);
}
}
};
class TestTabWidget : public QTabWidget
{
public:
explicit TestTabWidget(QWidget *parent = 0) : QTabWidget(parent)
{
setTabBar(new TestTabBar());
}
};

Event does not work after drag and drop operation

I developing some kind of builder for our project. I want to use both drag and drop support and context menu in my application. Currently I use drag and drop support but no luck with context menu.
Left side my gui is toolbox. I am draging and droping widgets to the right side(QGraphicsScene) and I also want to use context menu inside QGraphicsScene. I use context menu inside graphics scene before. But before I did not use drap & drop operations. I write proper code but it does not work. What is missing?(Is it related drag & drop). Below is my code files.
//#dragwidget.cpp - Toolbox widgets
#include "dragwidget.h"
DragWidget::DragWidget(void)
{
}
DragWidget::~DragWidget(void)
{
}
void DragWidget::mousePressEvent(QMouseEvent *ev)
{
if(ev->button() == Qt::LeftButton)
{
QMimeData *data = new QMimeData;
data->setProperty("type",property("type"));
QDrag *drag = new QDrag(this);
drag->setMimeData(data);
drag->start();
}
}
//#view.cpp - Wrapper class for QGraphicsView
#include "view.h"
View::View(Scene *scene,QWidget *parent)
:QGraphicsView(scene,parent)
{
}
View::~View()
{
}
//#scene.cpp - Wrapper class for QGraphicsScene which catch drop events
#include "scene.h"
Scene::Scene(QObject *parent)
:QGraphicsScene(parent)
{
}
Scene::~Scene(void)
{
}
void Scene::setSceneRect(qreal x, qreal y, qreal w, qreal h)
{
QGraphicsScene::setSceneRect(x,y,w,h);
}
void Scene::dragEnterEvent(QGraphicsSceneDragDropEvent *e)
{
if(e->mimeData()->hasFormat("text/plain"));
e->acceptProposedAction();
}
void Scene::dragMoveEvent(QGraphicsSceneDragDropEvent *e)
{
}
void Scene::dropEvent(QGraphicsSceneDragDropEvent *e)
{
e->acceptProposedAction();
int itemType = e->mimeData()->property("type").toInt();
switch(itemType)
{
case BlockSegment:
drawStandartBlockSegment(e->scenePos());
break;
case Switch:
drawStandartSwitch(e->scenePos());
break;
case Signal:
drawStandartSignal(e->scenePos());
break;
}
}
void Scene::drawStandartBlockSegment(QPointF p)
{
BlockSegmentItem *blockSegment = new BlockSegmentItem(p.x(),p.y(),p.x()+100,p.y());
addItem(blockSegment);
}
void Scene::drawStandartSwitch(QPointF p)
{
SwitchItem *switchItem = new SwitchItem(":app/SS.svg");
switchItem->setPos(p);
addItem(switchItem);
}
void Scene::drawStandartSignal(QPointF p)
{
SignalItem *signalItem = new SignalItem(":app/sgs3lr.svg");
signalItem->setPos(p);
addItem(signalItem);
}
//#item.cpp - Base class for my custom graphics scene items
#include "item.h"
Item::Item(ItemType itemType, QGraphicsItem *parent)
:QGraphicsWidget(parent),m_type(itemType)
{
}
Item::~Item()
{
}
int Item::type()
{
return m_type;
}
QRectF Item::boundingRect() const
{
return QRectF(0,0,0,0);
}
QPainterPath Item::shape() const
{
QPainterPath path;
return path;
}
void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(painter);
Q_UNUSED(option);
Q_UNUSED(widget);
}
void Item::deleteItem()
{
}
void Item::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
Q_UNUSED(event);
}
//#switchitem.cpp - my custom switch item.
#include "switchitem.h"
SwitchItem::SwitchItem(const QString& fileName,QGraphicsItem *parent /* = 0 */)
:Item(Switch,parent)
{
svg = new QGraphicsSvgItem(fileName,this);
createActions();
createContextMenu();
}
SwitchItem::~SwitchItem(void)
{
}
QRectF SwitchItem::boundingRect() const
{
return QRectF(pos().x(),pos().y(),svg->boundingRect().width(),
svg->boundingRect().height());
}
QPainterPath SwitchItem::shape() const
{
QPainterPath path;
path.addRect(boundingRect());
return path;
}
void SwitchItem::createActions()
{
deleteAct = new QAction("Sil",this);
deleteAct->setIcon(QIcon(":app/delete.png"));
connect(deleteAct,SIGNAL(triggered()),this,SLOT(deleteItem()));
}
void SwitchItem::createContextMenu()
{
contextMenu = new QMenu("SwitchContextMenu");
contextMenu->addAction(deleteAct);
}
void SwitchItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *e)
{
contextMenu->exec(e->screenPos());
}
//#app.cpp - Application class
#include "app.h"
#include "dragwidget.h"
App::App(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
Q_INIT_RESOURCE(app);
ui.setupUi(this);
canvasScene = new Scene(this);
canvasScene->setSceneRect(0,0,5000,5000);
canvasView = new View(canvasScene);
canvasView->setBackgroundBrush(Qt::black);
canvasView->ensureVisible(0,0,canvasView->geometry().width(),canvasView->geometry().height());
QHBoxLayout *layout = new QHBoxLayout;
toolBox = new QFrame(this);
layout->addWidget(toolBox,1);
layout->addWidget(canvasView,7);
ui.centralWidget->setLayout(layout);
createToolBox();
}
App::~App()
{
}
void App::createToolBox()
{
QVBoxLayout *layout = new QVBoxLayout;
QLabel *blockSegmentLabel = new QLabel("Block Segment");
DragWidget *blockSegmentWidget = new DragWidget;
blockSegmentWidget->setProperty("type",BlockSegment);
blockSegmentWidget->setPixmap(QPixmap(":app/blocksegment.png"));
QLabel *switchLabel = new QLabel("Switch");
DragWidget *switchWidget = new DragWidget;
switchWidget->setProperty("type",Switch);
switchWidget->setPixmap(QPixmap(":app/SS.png"));
QLabel *signalLabel = new QLabel("Signal");
DragWidget *signalWidget = new DragWidget;
signalWidget->setProperty("type",Signal);
signalWidget->setPixmap(QPixmap(":app/sgs3lr.png"));
layout->addWidget(blockSegmentLabel);
layout->addWidget(blockSegmentWidget);
layout->addWidget(switchLabel);
layout->addWidget(switchWidget);
layout->addWidget(signalLabel);
layout->addWidget(signalWidget);
toolBox->setLayout(layout);
}
void DragWidget::mousePressEvent(QMouseEvent *ev)
{
if(ev->button() == Qt::LeftButton)
{
QMimeData *data = new QMimeData;
data->setProperty("type",property("type"));
QDrag *drag = new QDrag(this);
drag->setMimeData(data);
drag->start();
}
}
Problem is here. You've "eaten" right button :) Try fall back into base implementation of mousePressEvent