2024-09-25 09:48:09 +02:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
/*
|
|
|
|
* 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 <QtBuilder.hxx>
|
|
|
|
|
2024-09-25 12:10:12 +02:00
|
|
|
#include <QtInstanceMessageDialog.hxx>
|
2024-09-25 09:48:09 +02:00
|
|
|
#include <QtTools.hxx>
|
|
|
|
|
|
|
|
#include <rtl/ustrbuf.hxx>
|
|
|
|
|
2024-10-02 17:36:21 +02:00
|
|
|
#include <QtWidgets/QCheckBox>
|
2024-09-25 09:48:09 +02:00
|
|
|
#include <QtWidgets/QDialog>
|
|
|
|
#include <QtWidgets/QDialogButtonBox>
|
2024-10-04 10:38:32 +02:00
|
|
|
#include <QtWidgets/QGroupBox>
|
2024-09-28 00:07:28 +02:00
|
|
|
#include <QtWidgets/QLabel>
|
2024-10-04 16:36:16 +02:00
|
|
|
#include <QtWidgets/QLineEdit>
|
2024-09-25 09:48:09 +02:00
|
|
|
#include <QtWidgets/QLayout>
|
2024-10-04 16:54:31 +02:00
|
|
|
#include <QtWidgets/QPlainTextEdit>
|
2024-09-25 09:48:09 +02:00
|
|
|
#include <QtWidgets/QPushButton>
|
2024-10-04 16:47:55 +02:00
|
|
|
#include <QtWidgets/QScrollArea>
|
2024-09-25 09:48:09 +02:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
QString convertAccelerator(const OUString& rText)
|
|
|
|
{
|
|
|
|
// preserve literal '&'s and use '&' instead of '_' for the accelerator
|
|
|
|
return toQString(rText.replaceAll("&", "&&").replace('_', '&'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QtBuilder::QtBuilder(QObject* pParent, std::u16string_view sUIRoot, const OUString& rUIFile)
|
|
|
|
: WidgetBuilder(sUIRoot, rUIFile, false)
|
|
|
|
{
|
|
|
|
processUIFile(pParent);
|
|
|
|
}
|
|
|
|
|
|
|
|
QtBuilder::~QtBuilder() {}
|
|
|
|
|
|
|
|
QObject* QtBuilder::get_by_name(std::u16string_view sID)
|
|
|
|
{
|
|
|
|
for (auto const& child : m_aChildren)
|
|
|
|
{
|
|
|
|
if (child.m_sID == sID)
|
|
|
|
return child.m_pWindow;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QtBuilder::insertComboBoxOrListBoxItems(QObject*, stringmap&,
|
|
|
|
const std::vector<ComboBoxTextItem>&)
|
|
|
|
{
|
|
|
|
assert(false && "comboboxes and list boxes are not supported yet");
|
|
|
|
}
|
|
|
|
|
|
|
|
QObject* QtBuilder::insertObject(QObject* pParent, const OUString& rClass, const OUString& rID,
|
|
|
|
stringmap& rProps, stringmap&, stringmap&)
|
|
|
|
{
|
|
|
|
QObject* pCurrentChild = nullptr;
|
|
|
|
|
|
|
|
pCurrentChild = makeObject(pParent, rClass, rID, rProps);
|
|
|
|
|
|
|
|
setProperties(pCurrentChild, rProps);
|
|
|
|
|
|
|
|
rProps.clear();
|
|
|
|
|
|
|
|
return pCurrentChild;
|
|
|
|
}
|
|
|
|
|
|
|
|
QObject* QtBuilder::makeObject(QObject* pParent, std::u16string_view sName, const OUString& sID,
|
|
|
|
stringmap& rMap)
|
|
|
|
{
|
2024-09-27 20:15:11 +02:00
|
|
|
// ignore placeholders
|
|
|
|
if (sName.empty())
|
|
|
|
return nullptr;
|
|
|
|
|
2024-09-25 09:48:09 +02:00
|
|
|
QWidget* pParentWidget = qobject_cast<QWidget*>(pParent);
|
2024-09-27 21:41:43 +02:00
|
|
|
QLayout* pParentLayout = qobject_cast<QLayout*>(pParent);
|
2024-09-25 09:48:09 +02:00
|
|
|
|
|
|
|
QObject* pObject = nullptr;
|
|
|
|
|
|
|
|
if (sName == u"GtkMessageDialog")
|
|
|
|
{
|
|
|
|
pObject = new QMessageBox(pParentWidget);
|
|
|
|
}
|
|
|
|
else if (sName == u"GtkBox")
|
|
|
|
{
|
|
|
|
// for a QMessageBox, return the existing layout instead of creating a new one
|
|
|
|
if (QMessageBox* pMessageBox = qobject_cast<QMessageBox*>(pParent))
|
|
|
|
{
|
|
|
|
pObject = pMessageBox->layout();
|
|
|
|
assert(pObject && "QMessageBox has no layout");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const bool bVertical = hasOrientationVertical(rMap);
|
|
|
|
if (bVertical)
|
2024-09-27 21:41:43 +02:00
|
|
|
pObject = new QVBoxLayout(pParentWidget);
|
2024-09-25 09:48:09 +02:00
|
|
|
else
|
2024-09-27 21:41:43 +02:00
|
|
|
pObject = new QHBoxLayout(pParentWidget);
|
2024-09-25 09:48:09 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (sName == u"GtkButtonBox")
|
|
|
|
{
|
|
|
|
QWidget* pTopLevel = windowForObject(pParent);
|
|
|
|
if (QMessageBox* pMessageBox = qobject_cast<QMessageBox*>(pTopLevel))
|
|
|
|
{
|
|
|
|
// for a QMessageBox, return the existing button box instead of creating a new one
|
|
|
|
QDialogButtonBox* pButtonBox = findButtonBox(pMessageBox);
|
|
|
|
assert(pButtonBox && "Could not find QMessageBox's button box");
|
|
|
|
pObject = pButtonBox;
|
2024-09-27 21:41:43 +02:00
|
|
|
|
|
|
|
// skip adding to layout below, button box is already contained in dialog
|
|
|
|
pParentLayout = nullptr;
|
2024-09-25 09:48:09 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pObject = new QDialogButtonBox(pParentWidget);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (sName == u"GtkButton")
|
|
|
|
{
|
|
|
|
if (QDialogButtonBox* pButtonBox = qobject_cast<QDialogButtonBox*>(pParentWidget))
|
|
|
|
{
|
|
|
|
pObject = pButtonBox->addButton("", QDialogButtonBox::NoRole);
|
|
|
|
|
|
|
|
// for message boxes, avoid implicit standard buttons in addition to those explicitly added
|
|
|
|
if (QMessageBox* pMessageBox = qobject_cast<QMessageBox*>(pParentWidget->window()))
|
|
|
|
pMessageBox->setStandardButtons(QMessageBox::NoButton);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pObject = new QPushButton(pParentWidget);
|
|
|
|
}
|
|
|
|
}
|
2024-10-02 17:36:21 +02:00
|
|
|
else if (sName == u"GtkCheckButton")
|
|
|
|
{
|
|
|
|
pObject = new QCheckBox(pParentWidget);
|
|
|
|
}
|
2024-09-28 00:07:28 +02:00
|
|
|
else if (sName == u"GtkDialog")
|
|
|
|
{
|
|
|
|
pObject = new QDialog(pParentWidget);
|
|
|
|
}
|
2024-10-04 16:36:16 +02:00
|
|
|
else if (sName == u"GtkEntry")
|
|
|
|
{
|
|
|
|
pObject = new QLineEdit(pParentWidget);
|
|
|
|
}
|
2024-10-04 10:38:32 +02:00
|
|
|
else if (sName == u"GtkFrame")
|
|
|
|
{
|
|
|
|
pObject = new QGroupBox(pParentWidget);
|
|
|
|
}
|
2024-09-28 00:07:28 +02:00
|
|
|
else if (sName == u"GtkLabel")
|
|
|
|
{
|
|
|
|
pObject = new QLabel(pParentWidget);
|
|
|
|
}
|
2024-10-04 16:47:55 +02:00
|
|
|
else if (sName == u"GtkScrolledWindow")
|
|
|
|
{
|
|
|
|
pObject = new QScrollArea(pParentWidget);
|
|
|
|
}
|
2024-10-04 16:54:31 +02:00
|
|
|
else if (sName == u"GtkTextView")
|
|
|
|
{
|
|
|
|
pObject = new QPlainTextEdit(pParentWidget);
|
|
|
|
}
|
2024-09-27 19:22:53 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
SAL_WARN("vcl.qt", "Widget type not supported yet: "
|
|
|
|
<< OUStringToOString(sName, RTL_TEXTENCODING_UTF8));
|
|
|
|
assert(false && "Widget type not supported yet");
|
|
|
|
}
|
2024-09-25 09:48:09 +02:00
|
|
|
|
2024-09-28 22:19:53 +02:00
|
|
|
if (QWidget* pWidget = qobject_cast<QWidget*>(pObject))
|
2024-09-27 21:41:43 +02:00
|
|
|
{
|
2024-09-28 22:19:53 +02:00
|
|
|
// add widget to parent layout
|
|
|
|
if (pParentLayout)
|
2024-09-27 21:41:43 +02:00
|
|
|
pParentLayout->addWidget(pWidget);
|
2024-09-28 22:19:53 +02:00
|
|
|
|
2024-10-04 14:41:29 +02:00
|
|
|
QtInstanceWidget::setHelpId(*pWidget, getHelpRoot() + sID);
|
|
|
|
|
2024-09-28 22:19:53 +02:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
|
|
|
|
// Set GtkBuilder ID as accessible ID
|
|
|
|
pWidget->setAccessibleIdentifier(toQString(sID));
|
|
|
|
#endif
|
2024-09-27 21:41:43 +02:00
|
|
|
}
|
2024-10-02 17:10:22 +02:00
|
|
|
else if (QLayout* pLayout = qobject_cast<QLayout*>(pObject))
|
|
|
|
{
|
|
|
|
// add layout to parent layout
|
|
|
|
if (QBoxLayout* pParentBoxLayout = qobject_cast<QBoxLayout*>(pParentLayout))
|
|
|
|
pParentBoxLayout->addLayout(pLayout);
|
|
|
|
}
|
2024-09-27 21:41:43 +02:00
|
|
|
|
2024-09-25 09:48:09 +02:00
|
|
|
m_aChildren.emplace_back(sID, pObject);
|
|
|
|
|
|
|
|
return pObject;
|
|
|
|
}
|
|
|
|
|
2024-10-04 10:38:32 +02:00
|
|
|
void QtBuilder::tweakInsertedChild(QObject* pParent, QObject* pCurrentChild, std::string_view sType,
|
2024-09-28 00:26:52 +02:00
|
|
|
std::string_view)
|
|
|
|
{
|
2024-10-04 10:38:32 +02:00
|
|
|
if (sType == "label")
|
|
|
|
{
|
|
|
|
if (QLabel* pLabel = qobject_cast<QLabel*>(pCurrentChild))
|
|
|
|
{
|
|
|
|
if (QGroupBox* pGroupBox = qobject_cast<QGroupBox*>(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());
|
|
|
|
pLabel->setParent(nullptr);
|
|
|
|
pLabel->deleteLater();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-28 00:26:52 +02:00
|
|
|
if (QDialog* pDialog = qobject_cast<QDialog*>(pCurrentChild))
|
|
|
|
{
|
2024-09-28 00:32:15 +02:00
|
|
|
// no action needed for QMessageBox, where the default button box is used
|
|
|
|
// and button click is handled in QtInstanceMessageDialog
|
2024-09-28 00:26:52 +02:00
|
|
|
if (!qobject_cast<QMessageBox*>(pDialog))
|
|
|
|
{
|
|
|
|
if (QDialogButtonBox* pButtonBox = findButtonBox(pDialog))
|
|
|
|
{
|
2024-09-28 00:32:15 +02:00
|
|
|
// ensure that button box is the last item in QDialog's layout
|
|
|
|
// (that seems to be implicitly the case for GtkDialog in GTK)
|
2024-09-28 00:26:52 +02:00
|
|
|
QLayout* pLayout = pDialog->layout();
|
|
|
|
assert(pLayout && "dialog has no layout");
|
|
|
|
pLayout->removeWidget(pButtonBox);
|
|
|
|
pLayout->addWidget(pButtonBox);
|
2024-09-28 00:32:15 +02:00
|
|
|
|
2024-10-04 12:50:10 +02:00
|
|
|
// connect button click handler
|
2024-09-28 00:32:15 +02:00
|
|
|
const QList<QAbstractButton*> aButtons = pButtonBox->buttons();
|
2024-10-04 14:49:12 +02:00
|
|
|
for (QAbstractButton* pButton : aButtons)
|
2024-09-28 00:32:15 +02:00
|
|
|
{
|
2024-10-04 12:50:10 +02:00
|
|
|
assert(pButton);
|
|
|
|
QObject::connect(pButton, &QAbstractButton::clicked, pDialog,
|
|
|
|
[pDialog, pButton] {
|
|
|
|
QtInstanceDialog::handleButtonClick(*pDialog, *pButton);
|
|
|
|
});
|
2024-09-28 00:32:15 +02:00
|
|
|
}
|
2024-09-28 00:26:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-09-25 09:48:09 +02:00
|
|
|
|
2024-10-04 22:18:17 +02:00
|
|
|
void QtBuilder::setMnemonicWidget(const OUString&, const OUString&)
|
|
|
|
{
|
|
|
|
SAL_WARN("vcl.qt", "QtBuilder::setMnemonicWidget not implemented yet");
|
|
|
|
}
|
|
|
|
|
2024-09-25 09:48:09 +02:00
|
|
|
void QtBuilder::setPriority(QObject*, int) { SAL_WARN("vcl.qt", "Ignoring priority"); }
|
|
|
|
|
|
|
|
void QtBuilder::setContext(QObject*, std::vector<vcl::EnumContext::Context>&&)
|
|
|
|
{
|
|
|
|
SAL_WARN("vcl.qt", "Ignoring context");
|
|
|
|
}
|
|
|
|
|
2024-10-04 11:14:24 +02:00
|
|
|
void QtBuilder::applyAtkProperties(QObject* pObject, const stringmap& rProperties, bool)
|
2024-09-25 09:48:09 +02:00
|
|
|
{
|
2024-10-04 11:14:24 +02:00
|
|
|
if (!pObject || !pObject->isWidgetType())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QWidget* pWidget = static_cast<QWidget*>(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));
|
|
|
|
}
|
2024-09-25 09:48:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void QtBuilder::applyPackingProperties(QObject*, QObject*, const stringmap&)
|
|
|
|
{
|
|
|
|
SAL_WARN("vcl.qt", "QtBuilder::applyPackingProperties not implemented yet");
|
|
|
|
}
|
|
|
|
|
2024-09-25 12:10:12 +02:00
|
|
|
void QtBuilder::set_response(std::u16string_view sID, short nResponse)
|
2024-09-25 09:48:09 +02:00
|
|
|
{
|
2024-09-25 12:10:12 +02:00
|
|
|
QPushButton* pPushButton = get<QPushButton>(sID);
|
|
|
|
assert(pPushButton);
|
|
|
|
pPushButton->setProperty(QtInstanceMessageDialog::PROPERTY_VCL_RESPONSE_CODE, int(nResponse));
|
2024-09-25 09:48:09 +02:00
|
|
|
}
|
|
|
|
|
2024-09-27 21:52:18 +02:00
|
|
|
void QtBuilder::setProperties(QObject* pObject, stringmap& rProps)
|
2024-09-25 09:48:09 +02:00
|
|
|
{
|
2024-09-27 21:52:18 +02:00
|
|
|
if (QMessageBox* pMessageBox = qobject_cast<QMessageBox*>(pObject))
|
2024-09-25 09:48:09 +02:00
|
|
|
{
|
2024-09-27 21:56:34 +02:00
|
|
|
for (auto const & [ rKey, rValue ] : rProps)
|
2024-09-25 09:48:09 +02:00
|
|
|
{
|
|
|
|
if (rKey == u"text")
|
|
|
|
{
|
|
|
|
pMessageBox->setText(toQString(rValue));
|
|
|
|
}
|
|
|
|
else if (rKey == u"title")
|
|
|
|
{
|
|
|
|
pMessageBox->setWindowTitle(toQString(rValue));
|
|
|
|
}
|
|
|
|
else if (rKey == u"secondary-text")
|
|
|
|
{
|
|
|
|
pMessageBox->setInformativeText(toQString(rValue));
|
|
|
|
}
|
|
|
|
else if (rKey == u"message-type")
|
|
|
|
{
|
|
|
|
if (rValue == u"error")
|
|
|
|
pMessageBox->setIcon(QMessageBox::Critical);
|
|
|
|
else if (rValue == u"info")
|
|
|
|
pMessageBox->setIcon(QMessageBox::Information);
|
|
|
|
else if (rValue == u"question")
|
|
|
|
pMessageBox->setIcon(QMessageBox::Question);
|
|
|
|
else if (rValue == u"warning")
|
|
|
|
pMessageBox->setIcon(QMessageBox::Warning);
|
|
|
|
else
|
|
|
|
assert(false && "Unhandled message-type");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-10-02 17:36:21 +02:00
|
|
|
else if (QCheckBox* pCheckBox = qobject_cast<QCheckBox*>(pObject))
|
|
|
|
{
|
|
|
|
for (auto const & [ rKey, rValue ] : rProps)
|
|
|
|
{
|
|
|
|
if (rKey == u"active")
|
|
|
|
pCheckBox->setChecked(toBool(rValue));
|
|
|
|
else if (rKey == u"label")
|
2024-10-04 10:59:54 +02:00
|
|
|
pCheckBox->setText(convertAccelerator(rValue));
|
2024-10-02 17:36:21 +02:00
|
|
|
}
|
|
|
|
}
|
2024-09-28 20:35:42 +02:00
|
|
|
else if (QDialog* pDialog = qobject_cast<QDialog*>(pObject))
|
|
|
|
{
|
|
|
|
for (auto const & [ rKey, rValue ] : rProps)
|
|
|
|
{
|
|
|
|
if (rKey == u"modal")
|
|
|
|
pDialog->setModal(toBool(rValue));
|
|
|
|
else if (rKey == u"title")
|
|
|
|
pDialog->setWindowTitle(toQString(rValue));
|
|
|
|
}
|
|
|
|
}
|
2024-09-28 00:07:28 +02:00
|
|
|
else if (QLabel* pLabel = qobject_cast<QLabel*>(pObject))
|
|
|
|
{
|
|
|
|
for (auto const & [ rKey, rValue ] : rProps)
|
|
|
|
{
|
|
|
|
if (rKey == u"label")
|
|
|
|
pLabel->setText(toQString(rValue));
|
|
|
|
else if (rKey == u"wrap")
|
|
|
|
pLabel->setWordWrap(toBool(rValue));
|
|
|
|
}
|
|
|
|
}
|
2024-09-27 21:52:18 +02:00
|
|
|
else if (QPushButton* pButton = qobject_cast<QPushButton*>(pObject))
|
2024-09-25 09:48:09 +02:00
|
|
|
{
|
|
|
|
for (auto const & [ rKey, rValue ] : rProps)
|
|
|
|
{
|
|
|
|
if (rKey == u"label")
|
|
|
|
pButton->setText(convertAccelerator(rValue));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QWidget* QtBuilder::windowForObject(QObject* pObject)
|
|
|
|
{
|
|
|
|
if (QWidget* pWidget = qobject_cast<QWidget*>(pObject))
|
|
|
|
return pWidget->window();
|
|
|
|
|
|
|
|
if (QLayout* pLayout = qobject_cast<QLayout*>(pObject))
|
|
|
|
{
|
|
|
|
if (QWidget* pParentWidget = pLayout->parentWidget())
|
|
|
|
return pParentWidget->window();
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2024-09-27 23:00:46 +02:00
|
|
|
QDialogButtonBox* QtBuilder::findButtonBox(QDialog* pDialog)
|
2024-09-25 09:48:09 +02:00
|
|
|
{
|
2024-09-27 23:00:46 +02:00
|
|
|
assert(pDialog);
|
|
|
|
QLayout* pLayout = pDialog->layout();
|
|
|
|
if (!pLayout)
|
|
|
|
return nullptr;
|
|
|
|
|
2024-09-25 09:48:09 +02:00
|
|
|
for (int i = 0; i < pLayout->count(); i++)
|
|
|
|
{
|
|
|
|
QLayoutItem* pItem = pLayout->itemAt(i);
|
|
|
|
if (QWidget* pItemWidget = pItem->widget())
|
|
|
|
{
|
|
|
|
if (QDialogButtonBox* pButtonBox = qobject_cast<QDialogButtonBox*>(pItemWidget))
|
|
|
|
return pButtonBox;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|