/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #endif #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { QString convertAccelerator(const OUString& rText) { // preserve literal '&'s and use '&' instead of '_' for the accelerator return toQString(rText.replaceAll("&", "&&").replace('_', '&')); } } QtBuilder::QtBuilder(QWidget* pParent, std::u16string_view sUIRoot, const OUString& rUIFile) : WidgetBuilder(sUIRoot, rUIFile, false) { SolarMutexGuard g; GetQtInstance().RunInMainThread([&] { processUIFile(pParent); // tweak widget hierarchy (remove unnecessary parent widgets) for (const std::pair& rPair : m_aWidgetReplacements) replaceWidget(rPair.first, rPair.second); }); } QtBuilder::~QtBuilder() {} QWidget* QtBuilder::get_by_name(const OUString& rId) { auto aIt = m_aWidgets.find(rId); if (aIt != m_aWidgets.end()) return aIt->second; return nullptr; } OUString QtBuilder::getDialogId() { for (const std::pair& rEntry : m_aWidgets) if (qobject_cast(rEntry.second)) return rEntry.first; return OUString(); } void QtBuilder::insertComboBoxOrListBoxItems(QObject* pObject, stringmap& rMap, const std::vector& rItems) { if (QComboBox* pComboBox = qobject_cast(pObject)) { for (const ComboBoxTextItem& rItem : rItems) { QVariant aUserData; if (!rItem.m_sId.isEmpty()) aUserData = QVariant::fromValue(toQString(rItem.m_sId)); pComboBox->addItem(toQString(rItem.m_sItem), aUserData); } const int nActiveId = BuilderBase::extractActive(rMap); pComboBox->setCurrentIndex(nActiveId); return; } assert(false && "list boxes are not supported yet"); } QObject* QtBuilder::insertObject(QObject* pParent, const OUString& rClass, std::string_view sType, const OUString& rId, stringmap& rProps, stringmap&, stringmap&) { QObject* pCurrentChild = makeObject(pParent, rClass, sType, rId, rProps); rProps.clear(); return pCurrentChild; } QObject* QtBuilder::makeObject(QObject* pParent, std::u16string_view sName, std::string_view sType, const OUString& rId, stringmap& rMap) { // ignore placeholders if (sName.empty()) return nullptr; // nothing to do for this one if (sName == u"GtkTreeSelection") return nullptr; QWidget* pParentWidget = qobject_cast(pParent); QLayout* pParentLayout = qobject_cast(pParent); QObject* pObject = nullptr; // in case a QLayout is created, an additional QWidget parent // will also be created because that is needed for QtInstanceContainer QWidget* pLayoutParentWidget = nullptr; if (sName == u"GtkMessageDialog") { QMessageBox* pMessageBox = new QMessageBox(pParentWidget); setMessageDialogProperties(*pMessageBox, rMap); pObject = pMessageBox; } else if (sName == u"GtkAssistant") { pObject = new QWizard(pParentWidget); } else if (sName == u"GtkBox") { // for a QMessageBox, return the existing layout instead of creating a new one if (QMessageBox* pMessageBox = qobject_cast(pParent)) { pObject = pMessageBox->layout(); assert(pObject && "QMessageBox has no layout"); } else { QWidget* pBoxParentWidget = pParentWidget; // Unless this is the direct GtkBox child of a GtkDialog, create a parent widget // that can be used to create a QtInstanceContainer for this box if (!qobject_cast(pParentWidget)) { pLayoutParentWidget = new QWidget(pParentWidget); pBoxParentWidget = pLayoutParentWidget; } const bool bVertical = hasOrientationVertical(rMap); if (bVertical) pObject = new QVBoxLayout(pBoxParentWidget); else pObject = new QHBoxLayout(pBoxParentWidget); } } else if (sName == u"GtkButtonBox") { QWidget* pTopLevel = windowForObject(pParent); if (QMessageBox* pMessageBox = qobject_cast(pTopLevel)) { // for a QMessageBox, return the existing button box instead of creating a new one QDialogButtonBox* pButtonBox = QtInstanceDialog::findButtonBox(pMessageBox); assert(pButtonBox && "Could not find QMessageBox's button box"); pObject = pButtonBox; // skip adding to layout below, button box is already contained in dialog pParentLayout = nullptr; } else { QDialogButtonBox* pButtonBox = new QDialogButtonBox(pParentWidget); if (hasOrientationVertical(rMap)) pButtonBox->setOrientation(Qt::Vertical); pObject = pButtonBox; } } else if (sName == u"GtkButton") { QPushButton* pButton = new QPushButton(pParentWidget); setButtonProperties(*pButton, rMap, pParentWidget); pObject = pButton; } else if (sName == u"GtkCalendar") { pObject = new QCalendarWidget(pParentWidget); } else if (sName == u"GtkCellRendererPixbuf") { return enableTreeViewColumnDataRole(pParentWidget, Qt::DecorationRole); } else if (sName == u"GtkCellRendererText") { return enableTreeViewColumnDataRole(pParentWidget, Qt::DisplayRole); } else if (sName == u"GtkCellRendererToggle") { return enableTreeViewColumnDataRole(pParentWidget, Qt::CheckStateRole); } else if (sName == u"GtkCheckButton") { QCheckBox* pCheckBox = new QCheckBox(pParentWidget); setCheckButtonProperties(*pCheckBox, rMap, pParentWidget); pObject = pCheckBox; } else if (sName == u"GtkComboBox" || sName == u"GtkComboBoxText") { QComboBox* pComboBox = new QComboBox(pParentWidget); pComboBox->setEditable(extractEntry(rMap)); pObject = pComboBox; } else if (sName == u"GtkDialog") { QDialog* pDialog = new QDialog(pParentWidget); setDialogProperties(*pDialog, rMap); pObject = pDialog; } else if (sName == u"GtkDrawingArea") { pObject = new QLabel(pParentWidget); } else if (sName == u"GtkEntry") { QLineEdit* pLineEdit = new QLineEdit(pParentWidget); setEntryProperties(*pLineEdit, rMap); pObject = pLineEdit; } else if (sName == u"GtkExpander") { pObject = new QtExpander(pParentWidget); } else if (sName == u"GtkFrame") { pObject = new QGroupBox(pParentWidget); } else if (sName == u"GtkGrid") { pLayoutParentWidget = new QWidget(pParentWidget); pObject = new QGridLayout(pLayoutParentWidget); } else if (sName == u"GtkIconView") { QListView* pListView = new QListView(pParentWidget); pListView->setModel(new QStandardItemModel(pListView)); pListView->setViewMode(QListView::IconMode); pListView->setMovement(QListView::Static); pObject = pListView; } else if (sName == u"GtkImage") { QLabel* pLabel = new QLabel(pParentWidget); const OUString sIconName = extractIconName(rMap); if (!sIconName.isEmpty()) { const Image aImage = loadThemeImage(sIconName); if (!aImage.GetBitmap().IsEmpty()) { pLabel->setPixmap(toQPixmap(aImage)); } else { const QIcon aIcon = QIcon::fromTheme(toQString(sIconName)); assert(!aIcon.isNull() && "No icon found for that icon name"); const int nIconSize = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); pLabel->setPixmap(aIcon.pixmap(nIconSize)); } } pObject = pLabel; } else if (sName == u"GtkLabel") { QLabel* pLabel = new QLabel(pParentWidget); setLabelProperties(*pLabel, rMap); extractMnemonicWidget(rId, rMap); pObject = pLabel; } else if (sName == u"GtkLevelBar" || sName == u"GtkProgressBar") { QProgressBar* pProgressBar = new QProgressBar(pParentWidget); // don't show text (progress in percent) by default pProgressBar->setTextVisible(false); pObject = pProgressBar; } else if (sName == u"GtkLinkButton") { QtHyperlinkLabel* pLabel = new QtHyperlinkLabel(pParentWidget); if (rMap.contains(u"label"_ustr)) pLabel->setDisplayText(toQString(rMap[u"label"_ustr])); if (rMap.contains(u"uri"_ustr)) pLabel->setUri(toQString(rMap[u"uri"_ustr])); pObject = pLabel; } else if (sName == u"GtkMenuButton") { QToolButton* pMenuButton = new QToolButton(pParentWidget); setMenuButtonProperties(*pMenuButton, rMap, pParentWidget); pObject = pMenuButton; } else if (sName == u"GtkNotebook") { pObject = new QTabWidget(pParentWidget); } else if (sName == u"GtkPaned") { pObject = new QSplitter(pParentWidget); } else if (sName == u"GtkPopover") { QWidget* pWidget = new QWidget(pParentWidget, Qt::Popup); pWidget->setLayout(new QVBoxLayout); pObject = pWidget; } else if (sName == u"GtkRadioButton") { QRadioButton* pRadioButton = new QRadioButton(pParentWidget); // apply GtkCheckButton properties because GtkRadioButton subclasses GtkCheckButton in GTK 3 setCheckButtonProperties(*pRadioButton, rMap, pParentWidget); extractRadioButtonGroup(rId, rMap); pObject = pRadioButton; } else if (sName == u"GtkScale") { QSlider* pSlider = new QSlider(pParentWidget); setScaleProperties(*pSlider, rMap); pObject = pSlider; } else if (sName == u"GtkScrollbar") { pObject = new QScrollBar(pParentWidget); } else if (sName == u"GtkScrolledWindow") { QScrollArea* pScrollArea = new QScrollArea(pParentWidget); pScrollArea->setWidgetResizable(true); pObject = pScrollArea; } else if (sName == u"GtkSeparator") { const bool bVertical = hasOrientationVertical(rMap); QFrame* pFrame = new QFrame(pParentWidget); pFrame->setFrameShape(bVertical ? QFrame::VLine : QFrame::HLine); pObject = pFrame; } else if (sName == u"GtkSeparatorToolItem") { QToolBar* pToolBar = qobject_cast(pParentWidget); assert(pToolBar && "GtkSeparatorToolItem doesn't have a toolbar parent"); pToolBar->addSeparator(); } else if (sName == u"GtkSpinButton") { QtDoubleSpinBox* pSpinBox = new QtDoubleSpinBox(pParentWidget); setSpinButtonProperties(*pSpinBox, rMap); pObject = pSpinBox; } else if (sName == u"GtkSpinner") { // use a QProgressBar in undetermined state (i.e. with range [0, 0]) QProgressBar* pProgressBar = new QProgressBar(pParentWidget); pProgressBar->setRange(0, 0); pObject = pProgressBar; } else if (sName == u"GtkTextView") { QPlainTextEdit* pTextEdit = new QPlainTextEdit(pParentWidget); setTextViewProperties(*pTextEdit, rMap); pObject = pTextEdit; } else if (sName == u"GtkToggleButton") { QToolButton* pButton = new QToolButton(pParentWidget); setButtonProperties(*pButton, rMap, pParentWidget); pObject = pButton; } else if (sName == u"GtkToolbar") { pObject = new QToolBar(pParentWidget); } else if (sName == u"GtkToggleToolButton" || sName == u"GtkToolButton") { QToolButton* pToolButton = new QToolButton(pParentWidget); const OUString sIconName = extractIconName(rMap); if (!sIconName.isEmpty()) { const Image aImage = loadThemeImage(sIconName); pToolButton->setIcon(toQPixmap(aImage)); } pToolButton->setText(toQString(extractLabel(rMap))); pToolButton->setCheckable(sName == u"GtkToggleToolButton"); pObject = pToolButton; } else if (sName == u"GtkTreeView") { QTreeView* pTreeView = new QTreeView(pParentWidget); QStandardItemModel* pItemModel = new QStandardItemModel(pTreeView); QSortFilterProxyModel* pProxyModel = new QSortFilterProxyModel(pTreeView); pProxyModel->setSourceModel(pItemModel); pTreeView->setModel(pProxyModel); pTreeView->setHeaderHidden(!extractHeadersVisible(rMap)); pTreeView->setRootIsDecorated(extractShowExpanders(rMap)); pObject = pTreeView; } else if (sName == u"GtkTreeViewColumn") { QTreeView* pTreeView = qobject_cast(pParentWidget); assert(pTreeView && "Tree view column doesn't have a tree view parent"); QAbstractItemModel* pModel = pTreeView->model(); assert(pModel && "Tree view doesn't have model set"); const int nCol = pModel->columnCount(); pModel->insertColumn(nCol); pModel->setHeaderData(nCol, Qt::Horizontal, toQString(extractTitle(rMap))); // add initially empty list of supported roles for the new column that will // be extended based on the GtkCellRenderer children of the column QList> aColumnRoles = QtInstanceTreeView::columnRoles(*pTreeView); aColumnRoles.push_back({}); QtInstanceTreeView::setColumnRoles(*pTreeView, aColumnRoles); // nothing else to do, return tree view parent for the widget return pTreeView; } else if (sName == u"GtkViewport") { // GtkViewport is an adaptor to make GtkWidgets scrollable // inside a GtkScrolledWindow; no equivalent needed for widgets // inside QScrollArea - just create a simple QWidget pObject = new QWidget(pParentWidget); } else { SAL_WARN("vcl.qt", "Widget type not supported yet: " << OUStringToOString(sName, RTL_TEXTENCODING_UTF8)); assert(false && "Widget type not supported yet"); } QWidget* pWidget = qobject_cast(pObject); if (pWidget) setWidgetProperties(*pWidget, rMap); else pWidget = pLayoutParentWidget; if (QSplitter* pSplitterParent = qobject_cast(pParentWidget)) { assert(pWidget); pSplitterParent->addWidget(pWidget); // unset to not create a layout below pParentWidget = nullptr; } else if (QTabWidget* pParentTabWidget = qobject_cast(pParentWidget)) { // remove QTabWidget child widget, set via QTabWidget::addTab instead assert(pWidget); pWidget->setParent(nullptr); // initially, add tab with empty label, QtBuilder::applyTabChildProperties will evaluate actual one pParentTabWidget->addTab(pWidget, QStringLiteral()); // unset pParentWidget to not create a layout below pParentWidget = nullptr; } else if (QToolBar* pParentToolBar = qobject_cast(pParentWidget)) { pParentToolBar->addWidget(pWidget); // unset pParentWidget to not create a layout below pParentWidget = nullptr; } else if (QtExpander* pExpander = qobject_cast(pParentWidget)) { // set the content (not the label) child as the expander's widget if (sType != "label") { pExpander->setContentWidget(pWidget); // erase "visible" property, QtExpander shows/hides the widget as needed rMap.erase("visible"); } } if (pWidget) { if (!pParentLayout && pParentWidget) { // if the parent is a widget, use the widget's layout, and ensure it has one set pParentLayout = pParentWidget->layout(); if (!pParentLayout) pParentLayout = new QVBoxLayout(pParentWidget); } // add widget to parent layout if (pParentLayout) pParentLayout->addWidget(pWidget); QtInstanceWidget::setHelpId(*pWidget, getHelpRoot() + rId); pWidget->setToolTip(toRichTextTooltip(extractTooltipText(rMap))); pWidget->setVisible(extractVisible(rMap)); #if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) // Set GtkBuilder ID as accessible ID pWidget->setAccessibleIdentifier(toQString(rId)); #endif } else if (QLayout* pLayout = qobject_cast(pObject)) { // add layout to parent layout if (QBoxLayout* pParentBoxLayout = qobject_cast(pParentLayout)) pParentBoxLayout->addLayout(pLayout); else if (QGridLayout* pParentGridLayout = qobject_cast(pParentLayout)) pParentGridLayout->addLayout(pLayout, pParentGridLayout->rowCount(), 0); } if (pObject) pObject->setObjectName(toQString(rId)); if (pWidget) m_aWidgets[rId] = pWidget; return pObject; } void QtBuilder::tweakInsertedChild(QObject* pParent, QObject* pCurrentChild, std::string_view sType, std::string_view sInternalChild) { if (sInternalChild == "entry" && qobject_cast(pParent)) { // an editable GtkComboBox has an internal GtkEntry child, // but QComboBox doesn't need a separate widget for it, so // delete it deleteObject(pCurrentChild); } if (sType == "label") { if (QLabel* pLabel = qobject_cast(pCurrentChild)) { if (QGroupBox* pGroupBox = qobject_cast(pParent)) { // GtkFrame has a `child-type="label"` child for the GtkFrame label // in the GtkBuilder .ui file, s. https://docs.gtk.org/gtk3/class.Frame.html // For QGroupBox, the title can be set directly. Therefore, take over the // title from the label and delete the separate label widget again pGroupBox->setTitle(pLabel->text()); deleteObject(pLabel); } else if (QtExpander* pExpander = qobject_cast(pParent)) { // GtkExpander has a `child-type="label"` child for the expander label // in the GtkBuilder .ui file, s. https://docs.gtk.org/gtk3/class.Expander.html // For QtExpander, the (button) text can be set directly. Therefore, take over // text from the label and delete the separate label widget again pExpander->setText(pLabel->text()); deleteObject(pLabel); } } } if (QScrollArea* pScrollAreaParent = qobject_cast(pParent)) { if (QAbstractScrollArea* pScrollArea = qobject_cast(pCurrentChild)) { // if the child provides scrolling capabilities itself, it doesn't need // another scroll area parent -> mark parent scroll area for removal m_aWidgetReplacements.emplace_back(pScrollAreaParent, pScrollArea); } else { // set as the scroll area's widget QWidget* pCurrentWidget = nullptr; if (pCurrentChild->isWidgetType()) pCurrentWidget = static_cast(pCurrentChild); else pCurrentWidget = static_cast(pCurrentChild)->parentWidget(); assert(pCurrentWidget); pScrollAreaParent->setWidget(pCurrentWidget); } } if (QDialog* pDialog = qobject_cast(pCurrentChild)) { // no action needed for QMessageBox, where the default button box is used // and button click is handled in QtInstanceMessageDialog if (!qobject_cast(pDialog)) { if (QDialogButtonBox* pButtonBox = QtInstanceDialog::findButtonBox(pDialog)) { // ensure that button box is the last item in QDialog's layout // (that seems to be implicitly the case for GtkDialog in GTK) QLayout* pLayout = pDialog->layout(); assert(pLayout && "dialog has no layout"); pLayout->removeWidget(pButtonBox); pLayout->addWidget(pButtonBox); // connect button click handler const QList aButtons = pButtonBox->buttons(); for (QAbstractButton* pButton : aButtons) { assert(pButton); QObject::connect(pButton, &QAbstractButton::clicked, pDialog, [pDialog, pButton] { QtInstanceDialog::handleButtonClick(*pDialog, *pButton); }); } } } } } void QtBuilder::setMnemonicWidget(const OUString& rLabelId, const OUString& rMnemonicWidgetId) { QLabel* pLabel = get(rLabelId); QWidget* pBuddy = get_by_name(rMnemonicWidgetId); if (!pLabel || !pBuddy) return; pLabel->setBuddy(pBuddy); } void QtBuilder::setRadioButtonGroup(const OUString& rRadioButtonId, const OUString& rRadioGroupId) { // insert all buttons into a button group owned by button whose ID matches the group's QRadioButton* pGroupOwner = get(rRadioGroupId); assert(pGroupOwner && "No radio button with the given group name"); QButtonGroup* pButtonGroup = nullptr; static const char* const pPropertyKey = "PROPERTY_BUTTONGROUP"; QVariant aVariant = pGroupOwner->property(pPropertyKey); if (aVariant.canConvert()) { pButtonGroup = aVariant.value(); } else { pButtonGroup = new QButtonGroup(pGroupOwner); pButtonGroup->addButton(pGroupOwner); } QRadioButton* pRadioButton = get(rRadioButtonId); assert(pRadioButton && "No radio button with given ID"); pButtonGroup->addButton(pRadioButton); pGroupOwner->setProperty(pPropertyKey, QVariant::fromValue(pButtonGroup)); } void QtBuilder::setPriority(QObject*, int) { SAL_WARN("vcl.qt", "Ignoring priority"); } void QtBuilder::setContext(QObject*, std::vector&&) { SAL_WARN("vcl.qt", "Ignoring context"); } bool QtBuilder::isHorizontalTabControl(QObject* pObject) { QTabWidget* pTabWidget = qobject_cast(pObject); if (!pTabWidget) return false; const QTabWidget::TabPosition ePosition = pTabWidget->tabPosition(); return ePosition == QTabWidget::TabPosition::North || ePosition == QTabWidget::TabPosition::South; } QMenu* QtBuilder::createMenu(const OUString& rId) { QMenu* pMenu = new QMenu; pMenu->setObjectName(toQString(rId)); return pMenu; } void QtBuilder::setMenuActionGroup(QMenu* pMenu, QAction* pAction, const OUString& rRadioGroupId) { // use QActionGroup owned by and set as property for the QMenu QActionGroup* pActionGroup = nullptr; const OString sPropertyKey = OUString(u"ACTIONGROUP::"_ustr + rRadioGroupId).toUtf8(); QVariant aVariant = pMenu->property(sPropertyKey.getStr()); if (aVariant.isValid()) { assert(aVariant.canConvert()); pActionGroup = aVariant.value(); } else { pActionGroup = new QActionGroup(pMenu); pMenu->setProperty(sPropertyKey.getStr(), QVariant::fromValue(pActionGroup)); // insert the menu item which defines the group (whose ID matches the group's ID) QAction* pIdAction = pMenu->findChild(toQString(rRadioGroupId)); assert(pIdAction && "No action with the ID that the group refers to"); pActionGroup->addAction(pIdAction); } pActionGroup->addAction(pAction); } void QtBuilder::insertMenuObject(QMenu* pParent, QMenu* pSubMenu, const OUString& rClass, const OUString& rId, stringmap& rProps, stringmap&, accelmap&) { const QString sLabel = convertAccelerator(extractLabel(rProps)); QAction* pAction = nullptr; if (pSubMenu) { pAction = pParent->addMenu(pSubMenu); pAction->setText(sLabel); } else { pAction = pParent->addAction(sLabel); } pAction->setObjectName(toQString(rId)); const OUString sActionName(extractActionName(rProps)); QtInstanceMenu::setActionName(*pAction, sActionName); if (rClass == u"GtkCheckMenuItem") { pAction->setCheckable(true); } else if (rClass == u"GtkMenuItem") { // nothing else to do } else if (rClass == u"GtkRadioMenuItem") { pAction->setCheckable(true); const OUString sGroup = extractGroup(rProps); if (!sGroup.isEmpty()) setMenuActionGroup(pParent, pAction, sGroup); } else if (rClass == u"GtkSeparatorMenuItem") { pAction->setSeparator(true); } else { assert(false && "Not implemented yet"); } } void QtBuilder::applyAtkProperties(QObject* pObject, const stringmap& rProperties, bool) { if (!pObject || !pObject->isWidgetType()) return; QWidget* pWidget = static_cast(pObject); for (auto const & [ rKey, rValue ] : rProperties) { if (rKey == "AtkObject::accessible-description") pWidget->setAccessibleDescription(toQString(rValue)); else if (rKey == "AtkObject::accessible-name") pWidget->setAccessibleName(toQString(rValue)); } } void QtBuilder::applyGridPackingProperties(QWidget* pCurrentChild, QGridLayout& rGrid, const stringmap& rPackingProperties) { assert(pCurrentChild); // properties not set when there's no explicit GtkGrid in the .ui file, // like for the QGridLayout that's the (implicit) layout of a QMessageBox if (!rPackingProperties.contains(u"left-attach"_ustr) || !rPackingProperties.contains(u"top-attach"_ustr)) return; const sal_Int32 nColumn = rPackingProperties.at(u"left-attach"_ustr).toInt32(); const sal_Int32 nRow = rPackingProperties.at(u"top-attach"_ustr).toInt32(); auto aWidthIt = rPackingProperties.find(u"width"_ustr); sal_Int32 nColumnSpan = (aWidthIt == rPackingProperties.end()) ? 1 : aWidthIt->second.toInt32(); auto aHeightIt = rPackingProperties.find(u"height"_ustr); sal_Int32 nRowSpan = (aHeightIt == rPackingProperties.end()) ? 1 : aHeightIt->second.toInt32(); rGrid.removeWidget(pCurrentChild); rGrid.addWidget(pCurrentChild, nRow, nColumn, nRowSpan, nColumnSpan); } void QtBuilder::applyPackingProperties(QObject* pCurrentChild, QObject* pParent, const stringmap& rPackingProperties) { if (!pCurrentChild) return; QWidget* pWidget = nullptr; if (pCurrentChild->isWidgetType()) pWidget = static_cast(pCurrentChild); else { QObject* pParentObject = pCurrentChild->parent(); assert(pParent && "Non-widget (i.e. layout) has no parent"); if (pParentObject->isWidgetType()) pWidget = static_cast(pParentObject); } if (!pWidget) return; // check parent's parent, due to extra QWidget parents for layouts if (QGridLayout* pGrid = qobject_cast(pParent)) applyGridPackingProperties(pWidget, *pGrid, rPackingProperties); else SAL_WARN("vcl.qt", "QtBuilder::applyPackingProperties not yet implemented for this case"); } void QtBuilder::applyTabChildProperties(QObject* pParent, const std::vector& rIds, std::vector&, stringmap& rProperties, stringmap&) { QTabWidget* pTabWidget = qobject_cast(pParent); assert(pTabWidget && "parent must be a QTabWidget"); // set ID and label for the last inserted tab assert(rProperties.contains(u"label"_ustr) && "Tab has no label"); QtInstanceNotebook::setTabIdAndLabel(*pTabWidget, pTabWidget->count() - 1, rIds.front(), rProperties.at(u"label"_ustr)); } void QtBuilder::set_response(const OUString& rId, int nResponse) { QPushButton* pPushButton = get(rId); assert(pPushButton); pPushButton->setProperty(QtInstanceMessageDialog::PROPERTY_VCL_RESPONSE_CODE, nResponse); } void QtBuilder::deleteObject(QObject* pObject) { if (pObject->isWidgetType()) static_cast(pObject)->hide(); pObject->deleteLater(); } QTreeView* QtBuilder::enableTreeViewColumnDataRole(QWidget* pParentWidget, Qt::ItemDataRole eDataRole) { QTreeView* pTreeView = qobject_cast(pParentWidget); if (!pTreeView) return nullptr; // Mark support for the new role for the tree view's last inserted column // (the GtkCellRenderer is a child object of the GtkTreeViewColumn) QList> aColumnRoles = QtInstanceTreeView::columnRoles(*pTreeView); assert(!aColumnRoles.empty() && "Missing list of column data roles"); assert(!aColumnRoles.back().contains(eDataRole) && "Using the same role multiple times in one column is not supported"); aColumnRoles.back().push_back(eDataRole); QtInstanceTreeView::setColumnRoles(*pTreeView, aColumnRoles); return pTreeView; } void QtBuilder::replaceWidget(QWidget* pOldWidget, QWidget* pNewWidget) { QWidget* pParent = pOldWidget->parentWidget(); assert(pParent); // replace old with new widget and mark old widget for deletion if (QLayout* pParentLayout = pParent->layout()) { std::unique_ptr pOldItem(pParentLayout->replaceWidget(pOldWidget, pNewWidget)); } else if (QSplitter* pSplitter = qobject_cast(pParent)) { const int nIndex = pSplitter->indexOf(pOldWidget); assert(nIndex >= 0 && "old widget not a child of the splitter"); pSplitter->replaceWidget(nIndex, pNewWidget); } else { assert(false && "Unhandled case in replaceWidget - not implemented (yet)"); } deleteObject(pOldWidget); } void QtBuilder::setButtonProperties(QAbstractButton& rButton, stringmap& rProps, QWidget* pParentWidget) { for (auto const & [ rKey, rValue ] : rProps) { if (rKey == u"image") { QLabel* pImageLabel = get(rValue); assert(pImageLabel && "Button has non-existent image set"); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) rButton.setIcon(QIcon(pImageLabel->pixmap())); #else rButton.setIcon(QIcon(pImageLabel->pixmap(Qt::ReturnByValue))); #endif // mark original label object for deletion deleteObject(pImageLabel); } else if (rKey == u"label") { rButton.setText(convertAccelerator(rValue)); } } if (QDialogButtonBox* pButtonBox = qobject_cast(pParentWidget)) { pButtonBox->addButton(&rButton, QDialogButtonBox::NoRole); // for message boxes, avoid implicit standard buttons in addition to those explicitly added if (QMessageBox* pMessageBox = qobject_cast(pParentWidget->window())) pMessageBox->setStandardButtons(QMessageBox::NoButton); } } void QtBuilder::setCheckButtonProperties(QAbstractButton& rButton, stringmap& rProps, QWidget* pParentWidget) { for (auto const & [ rKey, rValue ] : rProps) { if (rKey == u"active") rButton.setChecked(toBool(rValue)); else if (rKey == u"inconsistent" && toBool(rValue)) { if (QCheckBox* pCheckBox = qobject_cast(&rButton)) { pCheckBox->setTristate(true); pCheckBox->setCheckState(Qt::PartiallyChecked); } } } setButtonProperties(rButton, rProps, pParentWidget); } void QtBuilder::setDialogProperties(QDialog& rDialog, stringmap& rProps) { for (auto const & [ rKey, rValue ] : rProps) { if (rKey == u"modal") rDialog.setModal(toBool(rValue)); else if (rKey == u"title") rDialog.setWindowTitle(toQString(rValue)); } } void QtBuilder::setEntryProperties(QLineEdit& rLineEdit, stringmap& rProps) { auto aIt = rProps.find(u"placeholder-text"_ustr); if (aIt != rProps.end()) rLineEdit.setPlaceholderText(toQString(aIt->second)); aIt = rProps.find(u"text"_ustr); if (aIt != rProps.end()) rLineEdit.setText(toQString(aIt->second)); aIt = rProps.find(u"visibility"_ustr); if (aIt != rProps.end() && !toBool(aIt->second)) rLineEdit.setEchoMode(QLineEdit::Password); } void QtBuilder::setLabelProperties(QLabel& rLabel, stringmap& rProps) { for (auto const & [ rKey, rValue ] : rProps) { if (rKey == u"label") rLabel.setText(convertAccelerator(rValue)); else if (rKey == u"wrap") rLabel.setWordWrap(toBool(rValue)); } } void QtBuilder::setMenuButtonProperties(QToolButton& rButton, stringmap& rProps, QWidget* pParentWidget) { const OUString sMenu = extractPopupMenu(rProps); if (!sMenu.isEmpty()) { QMenu* pMenu = get_menu(sMenu); assert(pMenu && "menu button references non-existing menu"); rButton.setMenu(pMenu); } setButtonProperties(rButton, rProps, pParentWidget); } void QtBuilder::setMessageDialogProperties(QMessageBox& rMessageBox, stringmap& rProps) { for (auto const & [ rKey, rValue ] : rProps) { if (rKey == u"buttons") { const VclButtonsType eButtons = BuilderBase::mapGtkToVclButtonsType(rValue); QtInstanceMessageDialog::addStandardButtons(rMessageBox, eButtons); } else if (rKey == u"text") { rMessageBox.setText(toQString(rValue)); } else if (rKey == u"title") { rMessageBox.setWindowTitle(toQString(rValue)); } else if (rKey == u"secondary-text") { rMessageBox.setInformativeText(toQString(rValue)); } else if (rKey == u"message-type") { if (rValue == u"error") rMessageBox.setIcon(QMessageBox::Critical); else if (rValue == u"info") rMessageBox.setIcon(QMessageBox::Information); else if (rValue == u"question") rMessageBox.setIcon(QMessageBox::Question); else if (rValue == u"warning") rMessageBox.setIcon(QMessageBox::Warning); else assert(false && "Unhandled message-type"); } } } void QtBuilder::setScaleProperties(QSlider& rSlider, stringmap& rProps) { if (!hasOrientationVertical(rProps)) rSlider.setOrientation(Qt::Horizontal); auto aAdjustmentIt = rProps.find("adjustment"); if (aAdjustmentIt != rProps.end()) { const Adjustment* pAdjustment = get_adjustment_by_name(aAdjustmentIt->second); assert(pAdjustment && "referenced adjustment doesn't exist"); for (auto const & [ rKey, rValue ] : *pAdjustment) { if (rKey == u"upper") rSlider.setMaximum(rValue.toInt32()); else if (rKey == u"lower") rSlider.setMinimum(rValue.toInt32()); else if (rKey == "value") rSlider.setValue(rValue.toInt32()); else if (rKey == "page-increment") rSlider.setPageStep(rValue.toInt32()); else if (rKey == "step-increment") rSlider.setSingleStep(rValue.toInt32()); } } } void QtBuilder::setSpinButtonProperties(QDoubleSpinBox& rSpinBox, stringmap& rProps) { auto aDigitsIt = rProps.find(u"digits"_ustr); sal_Int32 nDigits = (aDigitsIt != rProps.end()) ? aDigitsIt->second.toInt32() : 0; rSpinBox.setDecimals(nDigits); auto aAdjustmentIt = rProps.find("adjustment"); if (aAdjustmentIt != rProps.end()) { const Adjustment* pAdjustment = get_adjustment_by_name(aAdjustmentIt->second); assert(pAdjustment && "referenced adjustment doesn't exist"); for (auto const & [ rKey, rValue ] : *pAdjustment) { if (rKey == u"upper") rSpinBox.setMaximum(rValue.toDouble()); else if (rKey == u"lower") rSpinBox.setMinimum(rValue.toDouble()); else if (rKey == "value") rSpinBox.setValue(rValue.toDouble()); else if (rKey == "step-increment") rSpinBox.setSingleStep(rValue.toDouble()); } } } void QtBuilder::setTextViewProperties(QPlainTextEdit& rTextEdit, stringmap& rProps) { for (auto const & [ rKey, rValue ] : rProps) { if (rKey == u"accepts-tab") { rTextEdit.setTabChangesFocus(!toBool(rValue)); } else if (rKey == u"buffer") { const TextBuffer* pTextBuffer = get_buffer_by_name(rValue); assert(pTextBuffer && "No TextBuffer with the given ID"); auto aIt = pTextBuffer->find(u"text"_ustr); if (aIt != pTextBuffer->end()) rTextEdit.setPlainText(toQString(aIt->second)); } else if (rKey == u"editable") { rTextEdit.setReadOnly(!toBool(rValue)); } } } void QtBuilder::setWidgetProperties(QWidget& rWidget, stringmap& rProps) { auto aHeightRequest = rProps.find(u"height-request"_ustr); if (aHeightRequest != rProps.end()) rWidget.setMinimumHeight(aHeightRequest->second.toInt32()); auto aSensitiveIt = rProps.find(u"sensitive"_ustr); if (aSensitiveIt != rProps.end()) rWidget.setEnabled(toBool(aSensitiveIt->second)); auto aWidthRequestIt = rProps.find(u"width-request"_ustr); if (aWidthRequestIt != rProps.end()) rWidget.setMinimumWidth(aWidthRequestIt->second.toInt32()); } QWidget* QtBuilder::windowForObject(QObject* pObject) { if (QWidget* pWidget = qobject_cast(pObject)) return pWidget->window(); if (QLayout* pLayout = qobject_cast(pObject)) { if (QWidget* pParentWidget = pLayout->parentWidget()) return pParentWidget->window(); } return nullptr; } /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */