ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvFontPropertyWidget.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <QActionGroup>
11 #include <QColorDialog>
12 #include <QIcon>
13 #include <QMenu>
14 #include <QPainter>
15 #include <QPixmap>
16 #include <QResizeEvent>
17 #include <QTimer>
18 #include <QToolButton>
19 #include <algorithm>
20 
21 #include "ui_ecvFontPropertyWidget.h"
22 
23 //-----------------------------------------------------------------------------
24 // FontProperties helper methods
25 //-----------------------------------------------------------------------------
27  if (family == "Arial") return 0;
28  if (family == "Courier") return 1;
29  if (family == "Times") return 2;
30  return 0; // Default to Arial
31 }
32 
34  switch (index) {
35  case 0:
36  family = "Arial";
37  break;
38  case 1:
39  family = "Courier";
40  break;
41  case 2:
42  family = "Times";
43  break;
44  default:
45  family = "Arial";
46  break;
47  }
48 }
49 
50 //-----------------------------------------------------------------------------
51 // ecvFontPropertyWidget implementation
52 //-----------------------------------------------------------------------------
54  : QWidget(parent),
55  ui(new Ui::ecvFontPropertyWidget),
56  m_fontColor(255, 255, 255) {
57  ui->setupUi(this);
58 
59  // Ensure icons are loaded (fallback if UI file doesn't load them)
60  // Icons are from ParaView: pqBold24.png, pqItalics24.png, pqShadow24.png
61  if (ui->boldButton) {
62  QIcon boldIcon(":/Resources/images/font/pqBold24.png");
63  if (!boldIcon.isNull()) {
64  ui->boldButton->setIcon(boldIcon);
65  }
66  }
67  if (ui->italicButton) {
68  QIcon italicIcon(":/Resources/images/font/pqItalics24.png");
69  if (!italicIcon.isNull()) {
70  ui->italicButton->setIcon(italicIcon);
71  }
72  }
73  if (ui->shadowButton) {
74  QIcon shadowIcon(":/Resources/images/font/pqShadow24.png");
75  if (!shadowIcon.isNull()) {
76  ui->shadowButton->setIcon(shadowIcon);
77  }
78  }
79 
80  setupConnections();
81 
82  // Update color button icon after widget is shown and has proper size
83  // This ensures button height is available for icon size calculation
84  QTimer::singleShot(0, this, [this]() { updateColorButtonAppearance(); });
85 
86  // Apply ParaView-style toggle button styling for better visual feedback
87  const QString toggleButtonStyle =
88  "QToolButton {"
89  " border: 1px solid #999;"
90  " border-radius: 3px;"
91  " padding: 2px;"
92  " background-color: #f0f0f0;"
93  "}"
94  "QToolButton:hover {"
95  " background-color: #e0e0e0;"
96  " border-color: #666;"
97  "}"
98  "QToolButton:checked {"
99  " background-color: #c0d0e8;"
100  " border-color: #4080c0;"
101  "}"
102  "QToolButton:checked:hover {"
103  " background-color: #a0c0e0;"
104  "}";
105 
106  if (ui->boldButton) ui->boldButton->setStyleSheet(toggleButtonStyle);
107  if (ui->italicButton) ui->italicButton->setStyleSheet(toggleButtonStyle);
108  if (ui->shadowButton) ui->shadowButton->setStyleSheet(toggleButtonStyle);
109 
110  // Setup justification buttons
111  setupHorizontalJustificationButton();
112  setupVerticalJustificationButton();
113 }
114 
116 
117 void ecvFontPropertyWidget::setupConnections() {
118  connect(ui->fontFamilyComboBox,
119  QOverload<int>::of(&QComboBox::currentIndexChanged), this,
120  &ecvFontPropertyWidget::onFontFamilyChanged);
121  connect(ui->fontSizeSpinBox, QOverload<int>::of(&QSpinBox::valueChanged),
122  this, &ecvFontPropertyWidget::onFontSizeChanged);
123  connect(ui->fontColorButton, &QToolButton::clicked, this,
124  &ecvFontPropertyWidget::onFontColorClicked);
125  connect(ui->fontOpacitySpinBox,
126  QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
127  &ecvFontPropertyWidget::onFontOpacityChanged);
128  connect(ui->boldButton, &QToolButton::toggled, this,
129  &ecvFontPropertyWidget::onBoldToggled);
130  connect(ui->italicButton, &QToolButton::toggled, this,
131  &ecvFontPropertyWidget::onItalicToggled);
132  connect(ui->shadowButton, &QToolButton::toggled, this,
133  &ecvFontPropertyWidget::onShadowToggled);
134 }
135 
137  const {
138  FontProperties props;
139  props.family = fontFamily();
140  props.size = fontSize();
141  props.color = fontColor();
142  props.opacity = fontOpacity();
143  props.bold = isBold();
144  props.italic = isItalic();
145  props.shadow = hasShadow();
148  return props;
149 }
150 
152  m_blockSignals = true;
153  setFontFamily(props.family, true);
154  setFontSize(props.size, true);
155  setFontColor(props.color, true);
156  setFontOpacity(props.opacity, true);
157  setBold(props.bold, true);
158  setItalic(props.italic, true);
159  setShadow(props.shadow, true);
162  m_blockSignals = false;
163 
164  // Emit single change signal
165  Q_EMIT fontPropertiesChanged();
166 }
167 
169  return ui->fontFamilyComboBox ? ui->fontFamilyComboBox->currentText()
170  : "Arial";
171 }
172 
174  return ui->fontFamilyComboBox ? ui->fontFamilyComboBox->currentIndex() : 0;
175 }
176 
178  return ui->fontSizeSpinBox ? ui->fontSizeSpinBox->value() : 6;
179 }
180 
181 QColor ecvFontPropertyWidget::fontColor() const { return m_fontColor; }
182 
184  return ui->fontOpacitySpinBox ? ui->fontOpacitySpinBox->value() : 1.0;
185 }
186 
188  return ui->boldButton ? ui->boldButton->isChecked() : false;
189 }
190 
192  return ui->italicButton ? ui->italicButton->isChecked() : false;
193 }
194 
196  return ui->shadowButton ? ui->shadowButton->isChecked() : true;
197 }
198 
200  return m_horizontalJustification;
201 }
202 
204  return m_verticalJustification;
205 }
206 
207 void ecvFontPropertyWidget::setFontFamily(const QString& family,
208  bool blockSignal) {
209  if (!ui->fontFamilyComboBox) return;
210 
211  int index = ui->fontFamilyComboBox->findText(family);
212  if (index >= 0) {
213  if (blockSignal) {
214  ui->fontFamilyComboBox->blockSignals(true);
215  }
216  ui->fontFamilyComboBox->setCurrentIndex(index);
217  if (blockSignal) {
218  ui->fontFamilyComboBox->blockSignals(false);
219  }
220  }
221 }
222 
223 void ecvFontPropertyWidget::setFontFamilyIndex(int index, bool blockSignal) {
224  if (!ui->fontFamilyComboBox) return;
225 
226  if (index >= 0 && index < ui->fontFamilyComboBox->count()) {
227  if (blockSignal) {
228  ui->fontFamilyComboBox->blockSignals(true);
229  }
230  ui->fontFamilyComboBox->setCurrentIndex(index);
231  if (blockSignal) {
232  ui->fontFamilyComboBox->blockSignals(false);
233  }
234  }
235 }
236 
237 void ecvFontPropertyWidget::setFontSize(int size, bool blockSignal) {
238  if (!ui->fontSizeSpinBox) return;
239 
240  if (blockSignal) {
241  ui->fontSizeSpinBox->blockSignals(true);
242  }
243  ui->fontSizeSpinBox->setValue(size);
244  if (blockSignal) {
245  ui->fontSizeSpinBox->blockSignals(false);
246  }
247 }
248 
250  bool blockSignal) {
251  if (m_fontColor != color) {
252  m_fontColor = color;
253  // Delay update to ensure button has correct size after dialog closes
254  // This prevents the icon from being rendered at incorrect size (1/4
255  // circle issue)
256  QTimer::singleShot(0, this,
257  [this]() { updateColorButtonAppearance(); });
258  if (!blockSignal && !m_blockSignals) {
259  Q_EMIT fontColorChanged(color);
260  Q_EMIT fontPropertiesChanged();
261  }
262  }
263 }
264 
265 void ecvFontPropertyWidget::setFontOpacity(double opacity, bool blockSignal) {
266  if (!ui->fontOpacitySpinBox) return;
267 
268  if (blockSignal) {
269  ui->fontOpacitySpinBox->blockSignals(true);
270  }
271  ui->fontOpacitySpinBox->setValue(opacity);
272  if (blockSignal) {
273  ui->fontOpacitySpinBox->blockSignals(false);
274  }
275 }
276 
277 void ecvFontPropertyWidget::setBold(bool bold, bool blockSignal) {
278  if (!ui->boldButton) return;
279 
280  if (blockSignal) {
281  ui->boldButton->blockSignals(true);
282  }
283  ui->boldButton->setChecked(bold);
284  if (blockSignal) {
285  ui->boldButton->blockSignals(false);
286  }
287 }
288 
289 void ecvFontPropertyWidget::setItalic(bool italic, bool blockSignal) {
290  if (!ui->italicButton) return;
291 
292  if (blockSignal) {
293  ui->italicButton->blockSignals(true);
294  }
295  ui->italicButton->setChecked(italic);
296  if (blockSignal) {
297  ui->italicButton->blockSignals(false);
298  }
299 }
300 
301 void ecvFontPropertyWidget::setShadow(bool shadow, bool blockSignal) {
302  if (!ui->shadowButton) return;
303 
304  if (blockSignal) {
305  ui->shadowButton->blockSignals(true);
306  }
307  ui->shadowButton->setChecked(shadow);
308  if (blockSignal) {
309  ui->shadowButton->blockSignals(false);
310  }
311 }
312 
314  const QString& justification, bool blockSignal) {
315  if (m_horizontalJustification != justification) {
316  m_horizontalJustification = justification;
317  updateJustificationButtonIcon(justification,
318  ui->horizontalJustificationButton);
319  if (!blockSignal && !m_blockSignals) {
320  Q_EMIT horizontalJustificationChanged(justification);
321  Q_EMIT fontPropertiesChanged();
322  }
323  }
324 }
325 
327  const QString& justification, bool blockSignal) {
328  if (m_verticalJustification != justification) {
329  m_verticalJustification = justification;
330  updateJustificationButtonIcon(justification,
331  ui->verticalJustificationButton);
332  if (!blockSignal && !m_blockSignals) {
333  Q_EMIT verticalJustificationChanged(justification);
334  Q_EMIT fontPropertiesChanged();
335  }
336  }
337 }
338 
340  if (ui->fontColorButton) {
341  ui->fontColorButton->setVisible(visible);
342  }
343 }
344 
346  if (ui->fontFamilyComboBox) ui->fontFamilyComboBox->setEnabled(enabled);
347  if (ui->fontSizeSpinBox) ui->fontSizeSpinBox->setEnabled(enabled);
348  if (ui->fontColorButton) ui->fontColorButton->setEnabled(enabled);
349  if (ui->fontOpacitySpinBox) ui->fontOpacitySpinBox->setEnabled(enabled);
350  if (ui->boldButton) ui->boldButton->setEnabled(enabled);
351  if (ui->italicButton) ui->italicButton->setEnabled(enabled);
352  if (ui->shadowButton) ui->shadowButton->setEnabled(enabled);
353 }
354 
355 void ecvFontPropertyWidget::onFontFamilyChanged(int index) {
356  if (!m_blockSignals) {
357  Q_EMIT fontFamilyChanged(fontFamily());
358  Q_EMIT fontFamilyIndexChanged(index);
359  Q_EMIT fontPropertiesChanged();
360  }
361 }
362 
363 void ecvFontPropertyWidget::onFontSizeChanged(int size) {
364  if (!m_blockSignals) {
365  Q_EMIT fontSizeChanged(size);
366  Q_EMIT fontPropertiesChanged();
367  }
368 }
369 
370 void ecvFontPropertyWidget::onFontColorClicked() {
371  QColor newColor =
372  QColorDialog::getColor(m_fontColor, this, tr("Select Font Color"));
373  if (newColor.isValid() && newColor != m_fontColor) {
374  setFontColor(newColor, false);
375  }
376 }
377 
378 void ecvFontPropertyWidget::onFontOpacityChanged(double opacity) {
379  if (!m_blockSignals) {
380  Q_EMIT fontOpacityChanged(opacity);
381  Q_EMIT fontPropertiesChanged();
382  }
383 }
384 
385 void ecvFontPropertyWidget::onBoldToggled(bool checked) {
386  if (!m_blockSignals) {
387  Q_EMIT boldChanged(checked);
388  Q_EMIT fontPropertiesChanged();
389  }
390 }
391 
392 void ecvFontPropertyWidget::onItalicToggled(bool checked) {
393  if (!m_blockSignals) {
394  Q_EMIT italicChanged(checked);
395  Q_EMIT fontPropertiesChanged();
396  }
397 }
398 
399 void ecvFontPropertyWidget::onShadowToggled(bool checked) {
400  if (!m_blockSignals) {
401  Q_EMIT shadowChanged(checked);
402  Q_EMIT fontPropertiesChanged();
403  }
404 }
405 
406 //-----------------------------------------------------------------------------
408  QWidget::resizeEvent(event);
409  // Update color button icon when widget is resized (ParaView behavior)
410  // This ensures icon size matches button height
411  updateColorButtonAppearance();
412 }
413 
414 void ecvFontPropertyWidget::updateColorButtonAppearance() {
415  if (!ui->fontColorButton) return;
416 
417  // Force button to update its geometry first to ensure correct size
418  ui->fontColorButton->updateGeometry();
419 
420  // ParaView style: use button height * 0.75 for icon radius (same as
421  // pqColorChooserButton) Wait for button to have proper size
422  int buttonHeight = ui->fontColorButton->height();
423  if (buttonHeight <= 0) {
424  // Force a layout update to get correct size
425  ui->fontColorButton->adjustSize();
426  buttonHeight = ui->fontColorButton->height();
427  }
428  if (buttonHeight <= 0) {
429  buttonHeight = ui->fontColorButton->sizeHint().height();
430  }
431  if (buttonHeight <= 0) {
432  // Fallback if height not available yet - use a reasonable default
433  buttonHeight = 25;
434  }
435 
436  // Calculate radius based on height (ParaView style: IconRadiusHeightRatio =
437  // 0.75)
438  int radius = qRound(buttonHeight * 0.75);
439  radius = std::max(radius, 10); // Minimum 10px (ParaView default)
440 
441  // Create circular color swatch icon (ParaView-style)
442  // Use exact same approach as pqColorChooserButton::renderColorSwatch
443  QPixmap pix(radius, radius);
444  pix.fill(QColor(0, 0, 0, 0)); // Transparent background
445 
446  QPainter painter(&pix);
447  painter.setRenderHint(QPainter::Antialiasing, true);
448  painter.setBrush(QBrush(m_fontColor));
449  painter.drawEllipse(1, 1, radius - 2, radius - 2);
450  painter.end();
451 
452  QIcon icon(pix);
453 
454  // Add high-dpi version for retina displays (ParaView exact style)
455  QPixmap pix2x(radius * 2, radius * 2);
456  pix2x.setDevicePixelRatio(2.0);
457  pix2x.fill(QColor(0, 0, 0, 0));
458 
459  QPainter painter2x(&pix2x);
460  painter2x.setRenderHint(QPainter::Antialiasing, true);
461  painter2x.setBrush(QBrush(m_fontColor));
462  // ParaView uses: drawEllipse(2, 2, radius - 4, radius - 4) for 2x version
463  painter2x.drawEllipse(2, 2, radius - 4, radius - 4);
464  painter2x.end();
465 
466  icon.addPixmap(pix2x);
467 
468  ui->fontColorButton->setIcon(icon);
469 
470  // Set icon size explicitly to match the pixmap size (ParaView style)
471  // This ensures the button's internal icon area matches the rendered pixmap
472  ui->fontColorButton->setIconSize(QSize(radius, radius));
473 
474  // Ensure button has minimum width to display icon properly
475  // For ToolButtonTextBesideIcon, we need width for icon + text spacing
476  // Icon diameter = radius * 2, add some padding
477  int minWidth = radius * 2 + 6; // Icon diameter + small padding
478  if (ui->fontColorButton->minimumWidth() < minWidth) {
479  ui->fontColorButton->setMinimumWidth(minWidth);
480  }
481 }
482 
483 void ecvFontPropertyWidget::setupHorizontalJustificationButton() {
484  if (!ui->horizontalJustificationButton) return;
485 
486  QActionGroup* actionGroup = new QActionGroup(this);
487  actionGroup->setExclusive(true);
488 
489  // Create actions with icons, matching ParaView's implementation
490  QAction* leftAlign =
491  new QAction(QIcon(":/Resources/images/font/pqTextAlignLeft.svg"),
492  tr("Left"), actionGroup);
493  leftAlign->setIconVisibleInMenu(true);
494 
495  QAction* centerAlign =
496  new QAction(QIcon(":/Resources/images/font/pqTextAlignCenter.svg"),
497  tr("Center"), actionGroup);
498  centerAlign->setIconVisibleInMenu(true);
499 
500  QAction* rightAlign =
501  new QAction(QIcon(":/Resources/images/font/pqTextAlignRight.svg"),
502  tr("Right"), actionGroup);
503  rightAlign->setIconVisibleInMenu(true);
504 
505  QMenu* popup = new QMenu(this);
506  popup->addAction(leftAlign);
507  popup->addAction(centerAlign);
508  popup->addAction(rightAlign);
509  ui->horizontalJustificationButton->setMenu(popup);
510 
511  connect(actionGroup, &QActionGroup::triggered, this,
512  &ecvFontPropertyWidget::onHorizontalJustificationTriggered);
513 
514  // Set initial icon
515  updateJustificationButtonIcon("Left", ui->horizontalJustificationButton);
516 }
517 
518 void ecvFontPropertyWidget::setupVerticalJustificationButton() {
519  if (!ui->verticalJustificationButton) return;
520 
521  QActionGroup* actionGroup = new QActionGroup(this);
522  actionGroup->setExclusive(true);
523 
524  // Create actions with icons, matching ParaView's implementation
525  QAction* topAlign = new QAction(
526  QIcon(":/Resources/images/font/pqTextVerticalAlignTop.svg"),
527  tr("Top"), actionGroup);
528  topAlign->setIconVisibleInMenu(true);
529 
530  QAction* centerAlign = new QAction(
531  QIcon(":/Resources/images/font/pqTextVerticalAlignCenter.svg"),
532  tr("Center"), actionGroup);
533  centerAlign->setIconVisibleInMenu(true);
534 
535  QAction* bottomAlign = new QAction(
536  QIcon(":/Resources/images/font/pqTextVerticalAlignBottom.svg"),
537  tr("Bottom"), actionGroup);
538  bottomAlign->setIconVisibleInMenu(true);
539 
540  QMenu* popup = new QMenu(this);
541  popup->addAction(topAlign);
542  popup->addAction(centerAlign);
543  popup->addAction(bottomAlign);
544  ui->verticalJustificationButton->setMenu(popup);
545 
546  connect(actionGroup, &QActionGroup::triggered, this,
547  &ecvFontPropertyWidget::onVerticalJustificationTriggered);
548 
549  // Set initial icon
550  updateJustificationButtonIcon("Bottom", ui->verticalJustificationButton);
551 }
552 
553 void ecvFontPropertyWidget::updateJustificationButtonIcon(
554  const QString& justification, QToolButton* button) {
555  if (!button || !button->menu()) return;
556 
557  QList<QAction*> actions = button->menu()->actions();
558  for (QAction* action : actions) {
559  if (action->text() == justification) {
560  QIcon icon = action->icon();
561  if (!icon.isNull()) {
562  button->setIcon(icon);
563  } else {
564  // Fallback: use text as icon
565  button->setText(justification.left(1));
566  }
567  break;
568  }
569  }
570 }
571 
572 void ecvFontPropertyWidget::onHorizontalJustificationTriggered(
573  QAction* action) {
574  QString justification = action->text();
575  setHorizontalJustification(justification, false);
576 }
577 
578 void ecvFontPropertyWidget::onVerticalJustificationTriggered(QAction* action) {
579  QString justification = action->text();
580  setVerticalJustification(justification, false);
581 }
MouseEvent event
int size
math::float4 color
A reusable font property widget matching ParaView's font editor style.
void setColorPickerVisible(bool visible)
Show/hide the color picker button.
void resizeEvent(QResizeEvent *event) override
FontProperties fontProperties() const
void horizontalJustificationChanged(const QString &justification)
void setFontProperties(const FontProperties &props)
void setFontFamilyIndex(int index, bool blockSignal=false)
void setShadow(bool shadow, bool blockSignal=false)
QString horizontalJustification() const
void fontFamilyChanged(const QString &family)
Individual property change signals.
void italicChanged(bool italic)
void setControlsEnabled(bool enabled)
Enable/disable all controls.
ecvFontPropertyWidget(QWidget *parent=nullptr)
void setHorizontalJustification(const QString &justification, bool blockSignal=false)
void setFontOpacity(double opacity, bool blockSignal=false)
void setFontFamily(const QString &family, bool blockSignal=false)
void fontFamilyIndexChanged(int index)
void setFontColor(const QColor &color, bool blockSignal=false)
void fontColorChanged(const QColor &color)
void setFontSize(int size, bool blockSignal=false)
void setItalic(bool italic, bool blockSignal=false)
QString verticalJustification() const
void verticalJustificationChanged(const QString &justification)
void fontPropertiesChanged()
Emitted when any font property changes.
void fontSizeChanged(int size)
void setVerticalJustification(const QString &justification, bool blockSignal=false)
void boldChanged(bool bold)
void shadowChanged(bool shadow)
void setBold(bool bold, bool blockSignal=false)
void fontOpacityChanged(double opacity)
Definition: sfEditDlg.h:16
Font property structure for convenience.
int fontFamilyIndex() const
Returns VTK font family index (0=Arial, 1=Courier, 2=Times)
void setFontFamilyFromIndex(int index)
Sets family from VTK font family index.