gqtbuttonswidget
Loading...
Searching...
No Matches
gQtButtonsWidget.cc
Go to the documentation of this file.
1#include "gQtButtonsWidget.h"
2#include <QBoxLayout>
3#include <QFile>
4#include <QListView>
5#include <QEvent>
6#include <QShowEvent>
7
8/* --- ButtonInfo Implementation ---
9 *
10 * This implementation provides:
11 * - construction of a QListWidgetItem configured as selectable/enabled
12 * - icon lookup using the "<base>_<state>.svg" filename convention
13 */
14
15ButtonInfo::ButtonInfo(const std::string& icon)
16 : buttonName(icon) {
17 thisButton = new QListWidgetItem();
18
19 // The list item must be enabled/selectable to behave as a clickable icon entry.
20 thisButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
21}
22
23QIcon ButtonInfo::iconForState(int state, const QSize& iconSize, const QPalette& palette) const {
24 // Build the expected resource/file name for the requested state.
25 const std::string filename = buttonName + "_" + std::to_string(state) + ".svg";
26
27 QFile file(QString::fromStdString(filename));
28 if (!file.open(QIODevice::ReadOnly)) {
29 return QIcon();
30 }
31
32 QString svg = QString::fromUtf8(file.readAll());
33 const QColor foreground = state == 2
34 ? palette.color(QPalette::HighlightedText)
35 : palette.color(QPalette::WindowText);
36 const QColor selectedBackground = palette.color(QPalette::Highlight);
37
38 svg.replace("width=\"48\" height=\"48\"",
39 QString("width=\"%1\" height=\"%2\"").arg(iconSize.width()).arg(iconSize.height()));
40 svg.replace("currentColor", foreground.name(QColor::HexRgb));
41 svg.replace("#aaddff", selectedBackground.name(QColor::HexRgb), Qt::CaseInsensitive);
42
43 QPixmap pixmap;
44 if (!pixmap.loadFromData(svg.toUtf8(), "SVG")) {
45 return QIcon();
46 }
47 return QIcon(pixmap);
48}
49
50/* --- GQTButtonsWidget Implementation ---
51 *
52 * This widget:
53 * - builds one ButtonInfo per base icon name
54 * - displays them in a QListWidget configured for icon mode
55 * - updates icons on press to reflect "normal" vs "pressed" state
56 */
57
59 const std::vector<std::string>& bicons,
60 bool vertical, QWidget* parent)
61 : QWidget(parent) {
62 constexpr int distanceToMargin = 12;
63
64 // Convert icon base names into internal ButtonInfo entries.
65 for (const auto& b : bicons) {
66 buttons.push_back(new ButtonInfo(b));
67 }
68
69 // Create and configure the QListWidget backend (icon mode, fixed icon size).
70 buttonsWidget = new QListWidget(this);
71 buttonsWidget->setViewMode(QListView::IconMode);
72 buttonsWidget->setIconSize(QSize(static_cast<int>(h), static_cast<int>(v)));
73
74 // SVG colors are resolved from the Qt palette in ButtonInfo::iconForState().
75 buttonsWidget->setStyleSheet(
76 "background-color: transparent; "
77 "QListWidget::item:selected { background: transparent; border: none; }");
78
79 // Insert each QListWidgetItem into the list widget.
80 for (auto& b : buttons) {
81 b->thisButton->setIcon(b->iconForState(1, buttonsWidget->iconSize(), buttonsWidget->palette()));
82 buttonsWidget->addItem(b->thisButton);
83 }
84
85 // When an item is pressed, update all icons so only the pressed one shows state 2.
86 connect(buttonsWidget, &QListWidget::itemPressed,
87 this, &GQTButtonsWidget::buttonWasPressed);
88
89 // Choose layout based on the requested orientation.
90 QBoxLayout* layout = vertical
91 ? static_cast<QBoxLayout*>(new QVBoxLayout(this))
92 : static_cast<QBoxLayout*>(new QHBoxLayout(this));
93 layout->setContentsMargins(0, 0, 0, 0);
94 layout->addWidget(buttonsWidget);
95 setLayout(layout);
96
97 // Size the widget so the icon strip fits exactly (plus a margin factor).
98 double hsize = (h + distanceToMargin) * (buttons.size());
99 double vsize = v + distanceToMargin;
100 if (vertical) {
101 hsize = h + distanceToMargin;
102 vsize = (v + distanceToMargin) * (buttons.size());
103 }
104 buttonsWidget->setFixedSize(static_cast<int>(hsize), static_cast<int>(vsize));
105
106 // Final style: remove focus rectangles and keep a transparent background.
107 buttonsWidget->setFocusPolicy(Qt::NoFocus);
108 buttonsWidget->setStyleSheet(
109 "QListWidget { background-color: transparent; }"
110 "QListWidget::item { background: transparent; border: none; }"
111 "QListWidget::item:selected { background: transparent; border: none; outline: none; }"
112 );
113
114 refresh_icons();
115}
116
118 // Select the requested row and switch its icon to the "pressed" state.
119 buttonsWidget->setCurrentRow(i);
120 refresh_icons();
121}
122
123void GQTButtonsWidget::buttonWasPressed(QListWidgetItem* item) {
124 buttonsWidget->setCurrentItem(item);
125 refresh_icons();
126}
127
129 // Restore every button icon to the "normal" state (state 1).
130 buttonsWidget->setCurrentRow(-1);
131 refresh_icons();
132}
133
134void GQTButtonsWidget::refresh_icons() {
135 if (!buttonsWidget) { return; }
136
137 const int currentRow = buttonsWidget->currentRow();
138 const QPalette palette = buttonsWidget->palette();
139 const QSize iconSize = buttonsWidget->iconSize();
140
141 for (int row = 0; row < buttonsWidget->count(); row++) {
142 const int state = row == currentRow ? 2 : 1;
143 buttonsWidget->item(row)->setIcon(buttons[row]->iconForState(state, iconSize, palette));
144 }
145}
146
147void GQTButtonsWidget::changeEvent(QEvent* event) {
148 QWidget::changeEvent(event);
149
150 if (event->type() == QEvent::PaletteChange ||
151 event->type() == QEvent::ApplicationPaletteChange ||
152 event->type() == QEvent::StyleChange) {
153 refresh_icons();
154 }
155}
156
157void GQTButtonsWidget::showEvent(QShowEvent* event) {
158 QWidget::showEvent(event);
159 refresh_icons();
160}
161
162/* --- GQTToggleButtonWidget Implementation ---
163 *
164 * This widget:
165 * - creates one checkable QPushButton per title
166 * - arranges them vertically/horizontally
167 * - tracks the last clicked button index and emits a signal on changes
168 */
169
170GQTToggleButtonWidget::GQTToggleButtonWidget(int buttonWidth, int buttonHeight, int borderRadius,
171 const std::vector<std::string>& titles,
172 bool vertical, QWidget* parent)
173 : QWidget(parent),
174 buttonPressedIndex(-1) {
175 QBoxLayout* layout = vertical
176 ? static_cast<QBoxLayout*>(new QVBoxLayout(this))
177 : static_cast<QBoxLayout*>(new QHBoxLayout(this));
178
179 // Convert std::string titles to QString for QPushButton labels.
180 QStringList buttonStrings;
181 for (const auto& title : titles) {
182 buttonStrings.append(QString::fromStdString(title));
183 }
184
185 // Create a toggleable button for each title.
186 for (int i = 0; i < buttonStrings.size(); ++i) {
187 QPushButton* button = new QPushButton(buttonStrings[i], this);
188 button->setCheckable(true);
189 button->setFixedSize(buttonWidth, buttonHeight);
190
191 // SVG handles all visual states (frame, highlight, text color); no CSS decoration needed.
192 button->setStyleSheet(
193 "QPushButton { border: none; background-color: transparent; padding: 0; margin: 0; }"
194 "QPushButton:checked { background-color: transparent; }"
195 );
196
197 layout->addWidget(button);
198 buttons.push_back(button);
199
200 // Use a lambda to bind each button click to its index.
201 connect(button, &QPushButton::clicked, this, [this, i]() {
202 this->setButtonPressed(i);
203 });
204 connect(button, &QPushButton::toggled, this, [this]() {
205 refresh_svg_icons();
206 });
207 }
208 setLayout(layout);
209}
210
212 // A missing or invalid index is treated as "not checked".
213 if (buttonPressedIndex >= 0 && buttonPressedIndex < buttons.size())
214 return buttons[buttonPressedIndex]->isChecked();
215 return false;
216}
217
219 // Toggle changes checked->unchecked or unchecked->checked.
220 if (index >= 0 && index < buttons.size())
221 buttons[index]->toggle();
222}
223
225 // Query individual button state; invalid index returns false.
226 if (index >= 0 && index < buttons.size())
227 return buttons[index]->isChecked();
228 return false;
229}
230
231void GQTToggleButtonWidget::setButtonPressed(int index) {
232 // Track the last pressed index and notify listeners.
233 buttonPressedIndex = index;
234 emit buttonPressedIndexChanged(buttonPressedIndex);
235}
236
238 // Uncheck all buttons; does not change the stored pressed index.
239 for (auto& b : buttons) {
240 b->setChecked(false);
241 }
242}
243
244void GQTToggleButtonWidget::setSvgButtonIcon(int index, const QString& svgResourcePath, const QSize& iconSize) {
245 if (index < 0 || index >= static_cast<int>(buttons.size())) { return; }
246
247 // Grow the svgIcons table to cover this index.
248 while (svgIcons.size() <= index) { svgIcons.append(SvgIcon{}); }
249
250 const QSize sz = iconSize.isValid() ? iconSize
251 : QSize(buttons[index]->width() - 6, buttons[index]->height() - 6);
252 svgIcons[index] = SvgIcon{ svgResourcePath, sz };
253 refresh_svg_icons();
254}
255
256void GQTToggleButtonWidget::refresh_svg_icons() {
257 const QColor windowText = palette().color(QPalette::WindowText);
258 const QColor hlText = palette().color(QPalette::HighlightedText);
259 const QColor hlBg = palette().color(QPalette::Highlight);
260
261 for (int i = 0; i < svgIcons.size(); ++i) {
262 const SvgIcon& entry = svgIcons[i];
263 if (entry.path.isEmpty() || i >= static_cast<int>(buttons.size())) { continue; }
264
265 QFile f(entry.path);
266 if (!f.open(QIODevice::ReadOnly)) { continue; }
267
268 const bool checked = buttons[i]->isChecked();
269 const QColor fg = checked ? hlText : windowText;
270 const QString bg = checked ? hlBg.name(QColor::HexRgb) : "none";
271
272 QString svg = QString::fromUtf8(f.readAll());
273 svg.replace("width=\"48\" height=\"48\"",
274 QString("width=\"%1\" height=\"%2\"").arg(entry.size.width()).arg(entry.size.height()));
275 svg.replace("currentColor", fg.name(QColor::HexRgb));
276 svg.replace("#aaddff", bg, Qt::CaseInsensitive);
277
278 QPixmap pix;
279 if (pix.loadFromData(svg.toUtf8(), "SVG")) {
280 buttons[i]->setIcon(QIcon(pix));
281 buttons[i]->setIconSize(entry.size);
282 }
283 }
284}
285
287 QWidget::changeEvent(event);
288 if (event->type() == QEvent::PaletteChange ||
289 event->type() == QEvent::ApplicationPaletteChange ||
290 event->type() == QEvent::StyleChange) {
291 refresh_svg_icons();
292 }
293}
294
295void GQTToggleButtonWidget::showEvent(QShowEvent* event) {
296 QWidget::showEvent(event);
297 refresh_svg_icons();
298}
void changeEvent(QEvent *event) override
void reset_buttons()
Reset all buttons to the "normal" icon state.
void showEvent(QShowEvent *event) override
QListWidget * buttonsWidget
Underlying QListWidget used to render the icons.
void press_button(int i)
Programmatically press a button.
GQTButtonsWidget(double h, double v, const std::vector< std::string > &bicons, bool vertical=true, QWidget *parent=nullptr)
Constructs a GQTButtonsWidget.
void toggleButton(int index)
Toggle the checked state of a button.
void setSvgButtonIcon(int index, const QString &svgResourcePath, const QSize &iconSize=QSize())
Assign an SVG resource to a button so it re-renders on every palette/theme change.
void showEvent(QShowEvent *event) override
GQTToggleButtonWidget(int buttonWidth, int buttonHeight, int borderRadius, const std::vector< std::string > &titles, bool vertical=true, QWidget *parent=nullptr)
Constructs a GQTToggleButtonWidget.
void changeEvent(QEvent *event) override
void reset_buttons()
Uncheck all buttons.
void buttonPressedIndexChanged(int index)
Emitted whenever the last pressed button index changes.
bool buttonStatus(int index) const
Returns the checked state for a specific button.
bool lastButtonState() const
Returns the checked state of the last pressed button.
event
Logical description of one icon-button entry used by GQTButtonsWidget.
QIcon iconForState(int state, const QSize &iconSize, const QPalette &palette) const
Returns the icon for a given interaction state.
QListWidgetItem * thisButton
The UI item representing this logical button.
ButtonInfo(const std::string &icon)
Constructs a ButtonInfo for a given base icon name.