Initial commit of Qt5 backend

Known issues:
- Mouse and Key event translations are incomplete
- Margin chaning is not implemented
- uiBox controls can not be re-parented (see comment in qt5/tab.cpp)
- It is bit ugly since margins have not been folded..
  ..or any styling to make it look decent for that matter
- Most signals are not supressed on programmatic changes

TODO:
- Finish unused drawmatrix function
- Fix the uiBox so every uiControl is backed by a QWidget to simplify
  reparenting/margins
This commit is contained in:
Kristjan B Birgisson 2016-05-29 17:43:21 +00:00
parent d88233a0fb
commit e381e6b45f
38 changed files with 2429 additions and 0 deletions

74
qt5/GNUfiles.mk Normal file
View File

@ -0,0 +1,74 @@
# 28 may 2016
CXXFILES += \
qt5/alloc.cpp \
qt5/area.cpp \
qt5/box.cpp \
qt5/button.cpp \
qt5/checkbox.cpp \
# qt5/child.cpp \
CXXFILES += \
qt5/colorbutton.cpp \
qt5/combobox.cpp \
qt5/control.cpp \
qt5/datetimepicker.cpp \
qt5/debug.cpp \
qt5/draw.cpp \
qt5/drawmatrix.cpp \
qt5/drawpath.cpp \
qt5/drawtext.cpp \
qt5/editablecombo.cpp \
qt5/entry.cpp \
qt5/fontbutton.cpp \
qt5/group.cpp \
qt5/label.cpp \
qt5/main.cpp \
qt5/menu.cpp \
qt5/multilineentry.cpp \
qt5/progressbar.cpp \
qt5/radiobuttons.cpp \
qt5/separator.cpp \
qt5/slider.cpp \
qt5/spinbox.cpp \
qt5/stddialogs.cpp \
qt5/tab.cpp \
qt5/text.cpp \
# qt5/util.cpp \
CXXFILES += \
qt5/window.cpp
HFILES += \
qt5/draw.hpp \
qt5/uipriv_qt5.hpp
# TODO split into a separate file or put in GNUmakefile.libui somehow?
# flags for Qt5
CFLAGS += $(NATIVE_UI_CFLAGS)
CXXFLAGS += $(NATIVE_UI_CXXFLAGS)
LDFLAGS += $(NATIVE_UI_LDFLAGS)
ifneq ($(RELEASE),1)
CXXFLAGS += -DQT_DEBUG
else
# disable Q_ASSERT checks
CXXFLAGS += -DQT_NO_DEBUG
endif
# flags for building a shared library
# OS X does support -shared but it has a preferred name for this so let's use that there instead; hence this is not gcc-global
LDFLAGS += \
-shared
# flags for warning on undefined symbols
# this is not gcc-global because OS X doesn't support these flags
# TODO figure out why FreeBSD follows linked libraries here
ifneq ($(shell uname -s),FreeBSD)
LDFLAGS += \
-Wl,--no-undefined -Wl,--no-allow-shlib-undefined
endif

8
qt5/GNUinstall.mk Normal file
View File

@ -0,0 +1,8 @@
ifndef PREFIX
PREFIX=/usr
endif
install: $(OUT)
cp $(OUT) $(DESTDIR)$(PREFIX)/lib/libui.so.0
ln -fs libui.so.0 $(DESTDIR)$(PREFIX)/lib/libui.so
cp ui.h ui_$(OS).h $(DESTDIR)$(PREFIX)/include/

21
qt5/GNUosspecific.mk Normal file
View File

@ -0,0 +1,21 @@
# 16 october 2015
EXESUFFIX =
LIBSUFFIX = .so
OSHSUFFIX = .h
STATICLIBSUFFIX = .a
TOOLCHAIN = gcc
# LONGTERM clean up all the NAMEs and SUFFIXs and NOSOSUFFIXs or whatever it was
USESSONAME = 1
SOVERSION = $(SOVERSION0)
SONAMEEXT = $(LIBSUFFIX).$(SOVERSION)
# this is not gcc-global because OS X uses a different filename format
SONAMEFLAG = -Wl,-soname,
NATIVE_UI_CFLAGS += \
`pkg-config --cflags Qt5Widgets`
NATIVE_UI_CXXFLAGS += \
`pkg-config --cflags Qt5Widgets`
NATIVE_UI_LDFLAGS += \
`pkg-config --libs Qt5Widgets` -lm

15
qt5/GNUosspecificlink.mk Normal file
View File

@ -0,0 +1,15 @@
# 28 may 2016
$(OUT): $(OFILES) | $(OUTDIR)
ifeq (,$(STATICLIB))
@$(reallinker) -o $(OUT) $(OFILES) $(LDFLAGS)
ifeq ($(USESSONAME),1)
@ln -sf $(NAME)$(SUFFIX) $(OUTNOSONAME)
endif
else
$(LD) -r $(OFILES) -o $(OUT:%.a=%.o)
objcopy --localize-hidden $(OUT:%.a=%.o)
$(AR) rcs $(OUT) $(OUT:%.a=%.o)
endif
@echo ====== Linked $(OUT)

76
qt5/alloc.cpp Normal file
View File

@ -0,0 +1,76 @@
#include "uipriv_qt5.hpp"
#include <QHash>
#include <QThread>
#include <QDebug>
#include "stdlib.h"
static QHash<void*,const char *> allocations;
#ifdef QT_DEBUG
// not going to put a lock on this, just simple sanity check since the rest of the code doesn't do much about thread safety
static QThread *uiThread = nullptr;
#endif
void initAlloc(void)
{
#ifdef QT_DEBUG
uiThread = QThread::currentThread();
#endif
}
static void checkThread(const char *func)
{
#ifdef QT_DEBUG
if (QThread::currentThread() != uiThread) {
qWarning("Invoking %s on different thread from the one used for initialization, corruption will occur!", func);
}
#endif
}
void uninitAlloc(void)
{
checkThread(QT_MESSAGELOG_FUNC);
if (!allocations.isEmpty()) {
qWarning("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:");
for (auto it = allocations.constBegin(), e = allocations.constEnd(); it != e; ++it) {
auto widget = uiValidateAndCastObjTo<QObject>((uiControl*)it.key());
qWarning() << it.key() << ':' << it.value() << ':' << widget;
}
}
}
void *uiAlloc(size_t size, const char *type)
{
checkThread(QT_MESSAGELOG_FUNC);
auto p = malloc(size);
allocations[p] = type;
return p;
}
void *uiRealloc(void *p, size_t new_size, const char *type)
{
checkThread(QT_MESSAGELOG_FUNC);
if (!allocations.remove(p)) {
qFatal("Reallocing of invalid/non-existent allocation: %p type: %s", p, type);
return nullptr;
}
p = realloc(p, new_size);
allocations[p] = type;
return p;
}
void uiFree(void *p)
{
checkThread(QT_MESSAGELOG_FUNC);
if (!allocations.remove(p)) {
qWarning("Ignoring freeing of invalid/non-existent allocation: %p", p);
return;
}
free(p);
}

176
qt5/area.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "uipriv_qt5.hpp"
#include "draw.hpp"
#include <QScrollArea>
#include <QPaintEvent>
#include <QDebug> // TODO: remove
struct uiArea : public uiQt5Control {};
class Area : public QWidget
{
Q_DISABLE_COPY(Area)
public:
Area(uiAreaHandler *ah)
: ah_(ah)
{
setAttribute(Qt::WA_Hover);
}
private:
void paintEvent(QPaintEvent *event)
{
uiDrawContext painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
const auto clip = event->rect();
// avoid using C99 initializer for portability *cough*MSVC*cough*
uiAreaDrawParams params = {
&painter, // Context
(double)width(), // AreaWidth
(double)height(), // AreaHeight
(double)clip.x(), // ClipX;
(double)clip.y(), // ClipY;
(double)clip.width(), // ClipWidth;
(double)clip.height(), // ClipHeight;
};
ah_->Draw(ah_,static_cast<uiArea*>(uiFindQt5ControlForQObject(this)),&params);
}
bool event(QEvent *event)
{
switch(event->type()) {
//case QEvent::HoverMove: // don't care
case QEvent::HoverEnter:
case QEvent::HoverLeave:
translateHoverEvent(static_cast<QHoverEvent*>(event));
break;
}
return QWidget::event(event);
}
void translateHoverEvent(QHoverEvent *hoverEvent)
{
auto area = static_cast<uiArea*>(uiFindQt5ControlForQObject(this));
switch(hoverEvent->type()) {
case QEvent::HoverEnter:
ah_->MouseCrossed(ah_,area,0);
setFocus(Qt::MouseFocusReason); // so we get key events, hacky?
setMouseTracking(true);
break;
case QEvent::HoverLeave:
ah_->MouseCrossed(ah_,area,1);
clearFocus();
setMouseTracking(false);
break;
}
}
void mousePressEvent(QMouseEvent *mouseEvent) { translateMouseEvent(mouseEvent); }
void mouseReleaseEvent(QMouseEvent *mouseEvent) { translateMouseEvent(mouseEvent); }
void mouseDoubleClickEvent(QMouseEvent *mouseEvent) { translateMouseEvent(mouseEvent); }
void mouseMoveEvent(QMouseEvent *mouseEvent) { translateMouseEvent(mouseEvent); }
void translateMouseEvent(QMouseEvent *mouseEvent)
{
static bool warned = false;
if (warned) {
qWarning("TODO: mouseEvent translation is incomplete!");
warned = true;
}
uiAreaMouseEvent me;
auto pos = mouseEvent->pos();
me.X = pos.x();
me.Y = pos.y();
me.AreaWidth = width();
me.AreaHeight = height();
// TODO: at a glance these are not tested nor well defined..?
me.Down = 0; // TODO
me.Up = 0; // TODO
me.Count = 0; // TODO
me.Modifiers = 0; // TODO
me.Held1To64 = 0; // TODO
ah_->MouseEvent(ah_,static_cast<uiArea*>(uiFindQt5ControlForQObject(this)),&me);
}
void keyPressEvent(QKeyEvent *keyEvent) { translateKeyEvent(keyEvent); }
void keyReleaseEvent(QKeyEvent *keyEvent) { translateKeyEvent(keyEvent); }
void translateKeyEvent(QKeyEvent *keyEvent)
{
uiAreaKeyEvent ke;
qWarning() << "TODO: keyEvent translation is incomplete!" << keyEvent;
ke.Key = keyEvent->key();
ke.ExtKey = 0;
ke.Modifier = 0;
ke.Modifiers = 0;
ke.Up = (keyEvent->type() == QEvent::KeyRelease);
ah_->KeyEvent(ah_,static_cast<uiArea*>(uiFindQt5ControlForQObject(this)),&ke);
}
private:
uiAreaHandler *ah_ = nullptr;
};
static Area *findArea(uiArea *a)
{
if (auto widget = uiValidateAndCastObjTo<QWidget>(a)) {
if (auto area = dynamic_cast<Area*>(widget)) {
return area;
}
return widget->findChild<Area*>();
}
return nullptr;
}
void uiAreaSetSize(uiArea *a, intmax_t width, intmax_t height)
{
qWarning("TODO: %p %d, %d", (void *)a, (int)width, (int)height);
}
void uiAreaQueueRedrawAll(uiArea *a)
{
if (auto area = findArea(a)) {
area->update();;
}
}
void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)
{
qWarning("TODO: %p %f, %f, %f, %f", (void *)a, x, y, width, height);
}
uiArea *uiNewArea(uiAreaHandler *ah)
{
auto area = new Area(ah);
// The widget should get as much space as possible.
area->setSizePolicy({QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding});
return uiAllocQt5ControlType(uiArea,area,uiQt5Control::DeleteControlOnQObjectFree);
}
uiArea *uiNewScrollingArea(uiAreaHandler *ah, intmax_t width, intmax_t height)
{
auto area = new Area(ah);
area->setMinimumSize({(int)width,(int)height});
auto scrollArea = new QScrollArea;
scrollArea->setBackgroundRole(QPalette::Dark);
scrollArea->setWidget(area);
return uiAllocQt5ControlType(uiArea,scrollArea,uiQt5Control::DeleteControlOnQObjectFree);
}

62
qt5/box.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "uipriv_qt5.hpp"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLayoutItem>
#include <QWidget>
struct uiBox : public uiQt5Control {};
void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
{
if (auto layoutBox = uiValidateAndCastObjTo<QBoxLayout>(b)) {
auto obj = uiValidateAndCastObjTo<QObject>(c);
if (auto layout = qobject_cast<QLayout*>(obj)) {
layoutBox->addLayout(layout, stretchy);
} else if (auto widget = qobject_cast<QWidget*>(obj)) {
layoutBox->addWidget(widget);
} else {
qWarning("object is neither layout nor widget");
}
}
}
void uiBoxDelete(uiBox *b, uintmax_t index)
{
if (auto layoutBox = uiValidateAndCastObjTo<QBoxLayout>(b)) {
if (index < (uint)layoutBox->count()) {
delete layoutBox->takeAt(index);
}
}
}
int uiBoxPadded(uiBox *b)
{
if (auto layoutBox = uiValidateAndCastObjTo<QBoxLayout>(b)) {
layoutBox->spacing();
}
return 0;
}
void uiBoxSetPadded(uiBox *b, int padded)
{
if (auto layoutBox = uiValidateAndCastObjTo<QBoxLayout>(b)) {
layoutBox->setSpacing(padded);
}
}
uiBox *uiNewHorizontalBox(void)
{
return uiAllocQt5ControlType(uiBox, new QHBoxLayout, uiQt5Control::DeleteControlOnQObjectFree);
}
uiBox *uiNewVerticalBox(void)
{
auto layout = new QVBoxLayout;
layout->setAlignment(Qt::AlignTop);
return uiAllocQt5ControlType(uiBox, layout, uiQt5Control::DeleteControlOnQObjectFree);
}

39
qt5/button.cpp Normal file
View File

@ -0,0 +1,39 @@
#include "uipriv_qt5.hpp"
#include <QPushButton>
struct uiButton : public uiQt5Control {};
char *uiButtonText(uiButton *b)
{
if (auto button = uiValidateAndCastObjTo<QPushButton>(b)) {
return uiQt5StrdupQString(button->text());
}
return nullptr;
}
void uiButtonSetText(uiButton *b, const char *text)
{
if (auto button = uiValidateAndCastObjTo<QPushButton>(b)) {
button->setText(QString::fromUtf8(text));
}
}
void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)
{
if (auto button = uiValidateAndCastObjTo<QPushButton>(b)) {
QObject::connect(button, &QPushButton::clicked, button, [f,b,data]{
f(b,data);
}, Qt::UniqueConnection);
}
}
uiButton *uiNewButton(const char *text)
{
auto button = new QPushButton(QString::fromUtf8(text));
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiButton,button,uiQt5Control::DeleteControlOnQObjectFree);
}

54
qt5/checkbox.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "uipriv_qt5.hpp"
#include <QCheckBox>
struct uiCheckbox : public uiQt5Control {};
char *uiCheckboxText(uiCheckbox *c)
{
if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
return uiQt5StrdupQString(checkBox->text());
}
return nullptr;
}
void uiCheckboxSetText(uiCheckbox *c, const char *text)
{
if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
checkBox->setText(QString::fromUtf8(text));
}
}
void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)
{
if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
QObject::connect(checkBox, &QCheckBox::toggled, checkBox, [f,c,data]{
f(c,data);
}, Qt::UniqueConnection);
}
}
int uiCheckboxChecked(uiCheckbox *c)
{
if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
return checkBox->isChecked();
}
return 0;
}
void uiCheckboxSetChecked(uiCheckbox *c, int checked)
{
if (auto checkBox = uiValidateAndCastObjTo<QCheckBox>(c)) {
checkBox->setChecked(checked);
}
}
uiCheckbox *uiNewCheckbox(const char *text)
{
auto checkBox = new QCheckBox(QString::fromUtf8(text));
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiCheckbox,checkBox,uiQt5Control::DeleteControlOnQObjectFree);
}

69
qt5/colorbutton.cpp Normal file
View File

@ -0,0 +1,69 @@
#include "uipriv_qt5.hpp"
#include <QColorDialog>
#include <QPushButton>
struct uiColorButton : public uiQt5Control {};
static QColorDialog *findColorDialog(uiColorButton *b)
{
if (auto pushButton = uiValidateAndCastObjTo<QPushButton>(b)) {
if (auto colorDialog = pushButton->findChild<QColorDialog*>()) {
return colorDialog;
}
}
qWarning("colorDialog not found?!");
return nullptr;
}
void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)
{
if (auto colorDialog = findColorDialog(b)) {
auto color = colorDialog->currentColor();
*r = color.redF();
*g = color.greenF();
*bl = color.blueF();
*a = color.alphaF();
}
}
void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)
{
if (auto colorDialog = findColorDialog(b)) {
auto color = QColor::fromRgbF(r,g,bl,a);
colorDialog->setCurrentColor(color);
emit colorDialog->colorSelected(color); // hacky?
}
}
void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)
{
if (auto colorDialog = findColorDialog(b)) {
QObject::connect(colorDialog, &QColorDialog::colorSelected, colorDialog, [f,b,data]{
f(b,data);
}, Qt::UniqueConnection);
}
}
uiColorButton *uiNewColorButton(void)
{
auto pushButton = new QPushButton;
pushButton->setObjectName("colorButton"); // so we can target just this button with stylesheet
// persisting this dialog in the background simplies things, but not a good partice
auto colorDialog = new QColorDialog(pushButton);
auto updateColor = [pushButton](const QColor &color) {
// quick and dirty, probably not ideal, doesn't show transperency in anyway
auto qssColorString = QStringLiteral("rgb(%1,%2,%3)").arg(color.red()).arg(color.green()).arg(color.blue());
pushButton->setStyleSheet(QStringLiteral("#colorButton {background-color: %1;}").arg(qssColorString));
};
QObject::connect(colorDialog, &QColorDialog::colorSelected, pushButton, updateColor);
updateColor({}); // set initial color (black)
QObject::connect(pushButton, &QPushButton::clicked, colorDialog, &QColorDialog::show);
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiColorButton,pushButton,uiQt5Control::DeleteControlOnQObjectFree);
}

48
qt5/combobox.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "uipriv_qt5.hpp"
#include <QComboBox>
struct uiCombobox : public uiQt5Control {};
void uiComboboxAppend(uiCombobox *c, const char *text)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
comboBox->addItem(QString::fromUtf8(text));
}
}
intmax_t uiComboboxSelected(uiCombobox *c)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
return comboBox->currentIndex();
}
return -1;
}
void uiComboboxSetSelected(uiCombobox *c, intmax_t n)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
comboBox->setCurrentIndex(n);
}
}
void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
// disambiguation of overloaded function
void (QComboBox:: *currentIndexChanged)(int) = &QComboBox::currentIndexChanged;
QObject::connect(comboBox, currentIndexChanged, comboBox, [f,c,data]{
f(c,data);
}, Qt::UniqueConnection);
}
}
uiCombobox *uiNewCombobox(void)
{
auto comboBox = new QComboBox;
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiCombobox,comboBox,uiQt5Control::DeleteControlOnQObjectFree);
}

135
qt5/control.cpp Normal file
View File

@ -0,0 +1,135 @@
#include "uipriv_qt5.hpp"
#include <QWidget>
#include <QVariant>
#define uiQt5ControlSignature 0x51743578 // 'Qt5x';
Q_DECLARE_METATYPE(uiQt5Control *)
uiQt5Control *uiValidateQt5Control(uiControl *control)
{
if (control == nullptr) {
qCritical("Called with uiControl == nullptr");
return nullptr;
}
if (control->Signature == uiQt5ControlSignature) {
qCritical("Called with uiControl (%p) that does not contain correct uiQt5ControlSignature? "
"Corruption or incorrect object passed in!", (void*)control);
return nullptr;
}
auto qt5Control = static_cast<uiQt5Control *>(control);;
if (qt5Control->qobject == nullptr) {
if ((qt5Control->flags & uiQt5Control::SuppressValidatationNag) == 0) {
qCritical("Called with uiControl (%p) that doesn't have QObject (missing or already destroyed)", (void *)control);
}
return nullptr;
}
return qt5Control;
}
uiQt5Control *uiFindQt5ControlForQObject(const QObject *qobject)
{
if (qobject) {
return qobject->property("uiQt5Control").value<uiQt5Control *>();
}
return nullptr;
}
uiQt5Control *uiAllocQt5Control(uint32_t typesig, const char *typenamestr, QObject *qobject, uint32_t flags)
{
auto control = (uiQt5Control *)uiAllocControl(sizeof(uiQt5Control), uiQt5ControlSignature, typesig, typenamestr);
control->qobject = qobject;
control->flags = flags;
qobject->setProperty("uiQt5Control", QVariant::fromValue(control));
QObject::connect(qobject, &QWidget::destroyed, qobject, [control]{
control->qobject = nullptr;
if ((control->flags & uiQt5Control::DeleteControlOnQObjectFree) == 1) {
// supress warning about the QObject being already deleted
control->flags |= uiQt5Control::SuppressValidatationNag;
uiFreeControl(control);
}
});
control->Destroy = [](uiControl *control) {
if (auto qt5Control = uiValidateQt5Control(control)) {
// consider synchronous destruction for simplicity? this is safer however depending on the caller state
qt5Control->qobject->deleteLater();
qt5Control->qobject->setProperty("uiQt5Control", {});
// disconnect the destroyed signal and explicity set widget to null since the control
// will be gone when the actual widget destruction happens
QObject::disconnect(qt5Control->qobject, &QWidget::destroyed, qt5Control->qobject, nullptr);
qt5Control->qobject = nullptr;
uiFreeControl(control);
}
};
control->Handle = [](uiControl *control) -> uintptr_t {
if (auto qt5Control = uiValidateQt5Control(control)) {
qWarning("VERIFY Handle usage");
return (uintptr_t)qt5Control->qobject;
}
return 0;
};
control->Parent = [](uiControl *control) -> uiControl * {
if (auto qt5Control = uiValidateQt5Control(control)) {
qWarning("TODO Parent");
Q_UNUSED(qt5Control);
return nullptr;
}
return nullptr;
};
control->SetParent = [](uiControl *control, uiControl *parent) {
if (auto qt5Control = uiValidateQt5Control(control)) {
Q_UNUSED(parent);
qWarning("TODO SetParent");
Q_UNUSED(qt5Control);
}
};
control->Toplevel = [](uiControl *control) -> int {
if (auto qt5Control = uiValidateQt5Control(control)) {
qWarning("TODO Toplevel");
Q_UNUSED(qt5Control);
return 0;
}
return 0;
};
control->Visible = [](uiControl *control) -> int {
if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
return widget->isVisible();
}
return 0;
};
control->Show = [](uiControl *control) {
if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
return widget->show();
}
};
control->Hide = [](uiControl *control) {
if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
return widget->hide();
}
};
control->Enabled = [](uiControl *control) -> int {
if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
return widget->isEnabled();
}
return 0;
};
control->Enable = [](uiControl *control) {
if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
return widget->setEnabled(true);
}
};
control->Disable = [](uiControl *control) {
if (auto widget = uiValidateAndCastObjTo<QWidget>(control)) {
return widget->setEnabled(false);
}
};
return control;
}

37
qt5/datetimepicker.cpp Normal file
View File

@ -0,0 +1,37 @@
#include "uipriv_qt5.hpp"
//#include <QCalendarWidget> // TODO: for dropdown
#include <QDateEdit>
#include <QTimeEdit>
#include <QDateTimeEdit>
struct uiDateTimePicker : public uiQt5Control {};
uiDateTimePicker *uiNewDateTimePicker(void)
{
auto dateEdit = new QDateEdit(QDate::currentDate());
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiDateTimePicker,dateEdit,uiQt5Control::DeleteControlOnQObjectFree);
}
uiDateTimePicker *uiNewDatePicker(void)
{
auto timeEdit = new QTimeEdit(QTime::currentTime());
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiDateTimePicker,timeEdit,uiQt5Control::DeleteControlOnQObjectFree);
}
uiDateTimePicker *uiNewTimePicker(void)
{
auto dateTimeEdit = new QDateTimeEdit(QDateTime::currentDateTime());
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiDateTimePicker,dateTimeEdit,uiQt5Control::DeleteControlOnQObjectFree);
}

9
qt5/debug.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "uipriv_qt5.hpp"
#include <QDebug>
void realbug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)
{
qFatal("[libui] %s:%s:%s() %s%s", file, line, func, prefix, qPrintable(QString::vasprintf(format,ap)));
}

113
qt5/draw.cpp Normal file
View File

@ -0,0 +1,113 @@
#include "uipriv_qt5.hpp"
#include "draw.hpp"
static QGradient uiDrawBrushToQGradient(const uiDrawBrush *b, QGradient gradient)
{
for (size_t i=0; i<b->NumStops; ++i) {
const auto &stop = b->Stops[i];
const auto color = QColor::fromRgbF(stop.R, stop.G, stop.B, stop.A);
gradient.setColorAt(stop.Pos, color);
}
return gradient;
}
static QBrush uiDrawBrushToQBrush(const uiDrawBrush *b)
{
switch (b->Type)
{
case uiDrawBrushTypeSolid:
return {QColor::fromRgbF(b->R, b->G, b->B, b->A), Qt::SolidPattern};
case uiDrawBrushTypeLinearGradient:
return uiDrawBrushToQGradient(b, QLinearGradient(b->X0, b->Y0, b->X1, b->Y1));
case uiDrawBrushTypeRadialGradient:
return uiDrawBrushToQGradient(b, QRadialGradient(b->X0, b->Y0, b->OuterRadius, b->X1, b->Y1, 0));
case uiDrawBrushTypeImage:
// not implemented in uiDrawBrush at time of writing
qWarning("TODO: uiDrawBrushTypeImage");
break;
default:
qWarning("Unknown uiDrawBrushType: %d", b->Type);
}
// something noticable
return {Qt::magenta, Qt::DiagCrossPattern};
}
static QPen uiDrawStrokeParamsToQPen(const uiDrawStrokeParams *p, const QBrush &brush)
{
QPen pen(brush, p->Thickness);
switch (p->Cap)
{
case uiDrawLineCapFlat:
pen.setCapStyle(Qt::FlatCap);
break;
case uiDrawLineCapRound:
pen.setCapStyle(Qt::RoundCap);
break;
case uiDrawLineCapSquare:
pen.setCapStyle(Qt::SquareCap);
break;
default:
qWarning("Unknown uiDrawLineCap: %d", p->Cap);
}
switch (p->Join)
{
case uiDrawLineJoinMiter:
pen.setJoinStyle(Qt::MiterJoin);
break;
case uiDrawLineJoinRound:
pen.setJoinStyle(Qt::RoundJoin);
break;
case uiDrawLineJoinBevel:
pen.setJoinStyle(Qt::BevelJoin);
break;
default:
qWarning("Unknown uiDrawLineJoin: %d", p->Join);
}
pen.setMiterLimit(p->MiterLimit);
if (p->NumDashes) {
pen.setDashOffset(p->DashPhase/10.0);
QVector<qreal> pattern;
for (size_t i=0; i<p->NumDashes; ++i) {
pattern.append(p->Dashes[i]/10.0);
}
pen.setDashPattern(pattern);
}
return pen;
}
void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
{
c->strokePath(*path, uiDrawStrokeParamsToQPen(p, uiDrawBrushToQBrush(b)));
}
void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
{
c->fillPath(*path, uiDrawBrushToQBrush(b));
}
void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
{
c->setTransform(m2t(m), true /* combine */);
}
void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
{
c->setClipPath(*path);
}
void uiDrawSave(uiDrawContext *c)
{
c->save();
}
void uiDrawRestore(uiDrawContext *c)
{
c->restore();
}

41
qt5/draw.hpp Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include <QPainter>
typedef struct uiDrawContext uiDrawContext;
struct uiDrawContext : public QPainter
{
uiDrawContext(QPaintDevice *device)
: QPainter(device)
{
}
};
#include <QPainterPath>
struct uiDrawPath : QPainterPath
{
uiDrawPath(uiDrawFillMode mode)
{
switch (mode)
{
case uiDrawFillModeWinding:
setFillRule(Qt::WindingFill);
break;
case uiDrawFillModeAlternate:
setFillRule(Qt::OddEvenFill);
break;
default:
qWarning("Unknown uiDrawFillMode: %d", mode);
}
}
};
extern QTransform m2t(const uiDrawMatrix *m);
struct uiDrawTextFont : public QFont
{
uiDrawTextFont(const QFont &font)
: QFont(font) {}
};

80
qt5/drawmatrix.cpp Normal file
View File

@ -0,0 +1,80 @@
#include "uipriv_qt5.hpp"
#include "draw.hpp"
QTransform m2t(const uiDrawMatrix *m)
{
return QTransform(m->M11, m->M12, m->M21, m->M22, m->M31, m->M32);
}
static void t2m(const QTransform &t, uiDrawMatrix *m)
{
m->M11 = t.m11();
m->M12 = t.m12();
m->M21 = t.m21();
m->M22 = t.m22();
m->M31 = t.m31();
m->M32 = t.m32();
}
void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
{
auto t = m2t(m);
t.translate(x,y);
t2m(t,m);
}
void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
{
auto t = m2t(m);
t.translate(x,y);
t.scale(x,y);
t.translate(-x,-y);
t2m(t,m);
}
void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
{
auto t = m2t(m);
t.translate(x,y);
t.rotateRadians(amount);
t.translate(-x,-y);
t2m(t,m);
}
void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
{
auto t = m2t(m);
t.translate(x,y);
t.shear(xamount, yamount);
t.translate(-x,-y);
t2m(t,m);
}
void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
{
qWarning("TODO: %p, %p", (void *)dest, (void *)src);
}
int uiDrawMatrixInvertible(uiDrawMatrix *m)
{
qWarning("TODO: %p", (void *)m);
return 0;
}
int uiDrawMatrixInvert(uiDrawMatrix *m)
{
qWarning("TODO: %p", (void *)m);
return 0;
}
void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
{
qWarning("TODO: %p, %f %f", (void *)m, *x, *y);
}
void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
{
qWarning("TODO: %p, %f %f", (void *)m, *x, *y);
}

76
qt5/drawpath.cpp Normal file
View File

@ -0,0 +1,76 @@
#include "uipriv_qt5.hpp"
#include "draw.hpp"
#include <QtMath>
uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
{
return new uiDrawPath(mode);
}
void uiDrawFreePath(uiDrawPath *p)
{
delete p;
}
void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
{
p->moveTo(x,y);
}
void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
{
qreal xStart = xCenter + radius*qCos(startAngle);
qreal yStart = yCenter + radius*qSin(startAngle);
p->moveTo(xStart, yStart);
if (negative) {
sweep = -(2*M_PI-sweep);
}
p->arcTo(xCenter-radius, yCenter-radius, radius*2, radius*2, -qRadiansToDegrees(startAngle), -qRadiansToDegrees(sweep));
}
void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
{
p->lineTo(x,y);
}
void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
{
if (negative) {
sweep = -(2*M_PI-sweep);
}
p->arcTo(xCenter-radius, yCenter-radius, radius*2, radius*2, -qRadiansToDegrees(startAngle), -qRadiansToDegrees(sweep));
}
void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
{
p->cubicTo(c1x, c1y, c2x, c2y, endX, endY);
}
void uiDrawPathCloseFigure(uiDrawPath *p)
{
p->closeSubpath();
}
void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
{
p->addRect(x,y,width,height);
}
void uiDrawPathEnd(uiDrawPath *p)
{
Q_UNUSED(p);
}
uiDrawFillMode pathFillMode(uiDrawPath *path)
{
switch (path->fillRule())
{
case Qt::WindingFill: return uiDrawFillModeWinding;
case Qt::OddEvenFill: return uiDrawFillModeAlternate;
}
Q_UNREACHABLE();
return 0;
}

190
qt5/drawtext.cpp Normal file
View File

@ -0,0 +1,190 @@
#include "uipriv_qt5.hpp"
#include "draw.hpp" // draw context
#include <QFontDatabase>
#include <QFontMetricsF>
#include <QTextLayout>
struct uiDrawFontFamilies : public QStringList
{
uiDrawFontFamilies(const QStringList &stringList)
: QStringList(stringList) {}
};
struct uiDrawTextLayout : public QTextLayout
{
uiDrawTextLayout(const QString& text, const QFont &font, double width)
: QTextLayout(text, font)
, width_(width)
{
// qWarning("TODO: figure out wtf width is for: %f", width_);
}
double width_ = 0.0;
};
uiDrawFontFamilies *uiDrawListFontFamilies(void)
{
return new uiDrawFontFamilies(QFontDatabase().families());
}
uintmax_t uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
{
return ff->count();
}
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, uintmax_t n)
{
return uiQt5StrdupQString(ff->value(n));
}
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
{
delete ff;
}
static int uiDrawTextWeightToFontWeight(uiDrawTextWeight weight)
{
switch (weight)
{
case uiDrawTextWeightThin: return QFont::Thin;
case uiDrawTextWeightUltraLight: return QFont::ExtraLight;
case uiDrawTextWeightLight: return QFont::Light;
case uiDrawTextWeightBook: return (QFont::Light+QFont::Normal)/2;
case uiDrawTextWeightNormal: return QFont::Normal;
case uiDrawTextWeightMedium: return QFont::Medium;
case uiDrawTextWeightSemiBold: return QFont::DemiBold;
case uiDrawTextWeightBold: return QFont::Bold;
case uiDrawTextWeightUtraBold: return QFont::ExtraBold;
case uiDrawTextWeightHeavy: return (QFont::ExtraBold+QFont::Black)/2;
case uiDrawTextWeightUltraHeavy: return QFont::Black;
default:
qWarning("Unknown uiDrawTextWeight: %d", weight);
}
return QFont::Normal;
}
static int uiDrawTextItalicToFontStyle(uiDrawTextItalic italic)
{
switch(italic)
{
case uiDrawTextItalicNormal: return QFont::StyleNormal;
case uiDrawTextItalicOblique: return QFont::StyleOblique;
case uiDrawTextItalicItalic: return QFont::StyleItalic;
default:
qWarning("Unknown uiDrawTextItalic: %d", italic);
}
return QFont::StyleNormal;
}
static int uiDrawTextStretchToFont(uiDrawTextStretch stretch)
{
switch(stretch)
{
case uiDrawTextStretchUltraCondensed: return QFont::UltraCondensed;
case uiDrawTextStretchExtraCondensed: return QFont::ExtraCondensed;
case uiDrawTextStretchCondensed: return QFont::Condensed;
case uiDrawTextStretchSemiCondensed: return QFont::SemiCondensed;
case uiDrawTextStretchNormal: return QFont::Unstretched;
case uiDrawTextStretchSemiExpanded: return QFont::SemiExpanded;
case uiDrawTextStretchExpanded: return QFont::Expanded;
case uiDrawTextStretchExtraExpanded: return QFont::ExtraExpanded;
case uiDrawTextStretchUltraExpanded: return QFont::UltraExpanded;
default:
qWarning("Unknown uiDrawTextStretch: %d", stretch);
}
return QFont::Unstretched;
}
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
{
QFont font(QString::fromUtf8(desc->Family));
font.setPointSizeF(desc->Size);
font.setWeight(uiDrawTextWeightToFontWeight(desc->Weight));
font.setItalic(uiDrawTextItalicToFontStyle(desc->Italic));
font.setStretch(uiDrawTextStretchToFont(desc->Stretch));
return new uiDrawTextFont(font);
}
void uiDrawFreeTextFont(uiDrawTextFont *font)
{
delete font;
}
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
{
qWarning("TODO: %p", (void *)font);
return 0;
}
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
{
qWarning("TODO: %p %p", (void *)font, (void *)desc);
}
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
{
if (font) {
QFontMetricsF fontMetrics(*font);
metrics->Ascent = fontMetrics.ascent();
metrics->Descent = fontMetrics.descent();
metrics->Leading = fontMetrics.leading();
metrics->UnderlinePos = fontMetrics.underlinePos();
metrics->UnderlineThickness = 0; // TODO?
}
}
uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)
{
return new uiDrawTextLayout(QString::fromUtf8(text), *defaultFont, width);
}
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
{
delete layout;
}
void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
{
layout->width_ = width;
}
static void runLayouting(uiDrawTextLayout *layout)
{
layout->setCacheEnabled(true);
layout->beginLayout();
forever {
QTextLine line = layout->createLine();
if (!line.isValid()) {
break;
}
// TODO: shift by line height?
}
layout->endLayout();
}
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
{
runLayouting(layout);
auto boundingRect = layout->boundingRect();
*width = boundingRect.width();
*height = boundingRect.height();
}
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
{
runLayouting(layout);
layout->draw(c, {x,y});
}
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, intmax_t startChar, intmax_t endChar, double r, double g, double b, double a)
{
// ineffective..? but works
auto additionalFormats = layout->additionalFormats();
QTextCharFormat format;
format.setForeground(QColor::fromRgbF(r,g,b,a));
additionalFormats.append(QTextLayout::FormatRange{(int)startChar,(int)(1+endChar-startChar),format});
layout->setAdditionalFormats(additionalFormats);
}

65
qt5/editablecombo.cpp Normal file
View File

@ -0,0 +1,65 @@
#include "uipriv_qt5.hpp"
#include <QComboBox>
struct uiEditableCombobox : public uiQt5Control {};
void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
comboBox->addItem(QString::fromUtf8(text));
}
}
intmax_t uiEditableComboboxSelected(uiEditableCombobox *c)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
return comboBox->currentIndex();
}
return -1;
}
void uiEditableComboboxSetSelected(uiEditableCombobox *c, intmax_t n)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
return comboBox->setCurrentIndex(n);
}
}
char *uiEditableComboboxText(uiEditableCombobox *c)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
return uiQt5StrdupQString(comboBox->currentText());
}
return nullptr;
}
void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
comboBox->setCurrentText(QString::fromUtf8(text));
}
}
void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)
{
if (auto comboBox = uiValidateAndCastObjTo<QComboBox>(c)) {
// disambiguation of overloaded function
void (QComboBox:: *currentIndexChanged)(const QString &) = &QComboBox::currentIndexChanged;
QObject::connect(comboBox, currentIndexChanged, comboBox, [f,c,data]{
f(c,data);
}, Qt::UniqueConnection);
}
}
uiEditableCombobox *uiNewEditableCombobox(void)
{
auto comboBox = new QComboBox;
comboBox->setEditable(true);
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiEditableCombobox,comboBox,uiQt5Control::DeleteControlOnQObjectFree);
}

55
qt5/entry.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "uipriv_qt5.hpp"
#include <QLineEdit>
struct uiEntry : uiQt5Control {};
char *uiEntryText(uiEntry *e)
{
if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
return uiQt5StrdupQString(lineEdit->text());
}
return nullptr;
}
void uiEntrySetText(uiEntry *e, const char *text)
{
if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
lineEdit->setText(QString::fromUtf8(text));
}
}
void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data)
{
if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
// Unlike textChanged(), this signal is not emitted when the text is changed programmatically
QObject::connect(lineEdit, &QLineEdit::textChanged, lineEdit, [f,e,data]{
f(e,data);
}, Qt::UniqueConnection);
}
}
int uiEntryReadOnly(uiEntry *e)
{
if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
return lineEdit->isReadOnly();
}
return false;
}
void uiEntrySetReadOnly(uiEntry *e, int readonly)
{
if (auto lineEdit = uiValidateAndCastObjTo<QLineEdit>(e)) {
lineEdit->setReadOnly(readonly);
}
}
uiEntry *uiNewEntry(void)
{
auto lineEdit = new QLineEdit;
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiEntry,lineEdit,uiQt5Control::DeleteControlOnQObjectFree);
}

56
qt5/fontbutton.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "uipriv_qt5.hpp"
#include "draw.hpp"
// another way would be to return a QFontComboBox + QSpinBox (for font size),
// but this is more true the control name
#include <QFontDialog>
#include <QPushButton>
struct uiFontButton : public uiQt5Control {};
uiDrawTextFont *uiFontButtonFont(uiFontButton *b)
{
if (auto pushButton = uiValidateAndCastObjTo<QPushButton>(b)) {
if (auto fontDialog = pushButton->findChild<QFontDialog*>()) {
return new uiDrawTextFont(fontDialog->currentFont());
}
}
return nullptr;
}
void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)
{
if (auto pushButton = uiValidateAndCastObjTo<QPushButton>(b)) {
if (auto fontDialog = pushButton->findChild<QFontDialog*>()) {
QObject::connect(fontDialog, &QFontDialog::fontSelected, fontDialog, [f,b,data]{
f(b,data);
}, Qt::UniqueConnection);
}
}
}
uiFontButton *uiNewFontButton(void)
{
auto pushButton = new QPushButton;
// persisting this dialog in the background simplies things, but not a good partice
auto fontDialog = new QFontDialog(pushButton);
auto updateFont = [pushButton](const QFont &font) {
auto family = font.family();
auto pointSize = font.pointSize(); // or pixelSize..?
pushButton->setText(QStringLiteral("%1 %2").arg(family).arg(pointSize));
};
QObject::connect(fontDialog, &QFontDialog::fontSelected, pushButton, updateFont);
QObject::connect(pushButton, &QPushButton::clicked, fontDialog, &QFontDialog::show);
QFont font; font.setPointSize(12);
updateFont(font);
fontDialog->setCurrentFont(font); // set initial font (system font)
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiFontButton,pushButton,uiQt5Control::DeleteControlOnQObjectFree);
}

63
qt5/group.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "uipriv_qt5.hpp"
#include <QGroupBox>
#include <QVBoxLayout>
struct uiGroup : public uiQt5Control {};
char *uiGroupTitle(uiGroup *g)
{
if (auto groupBox = uiValidateAndCastObjTo<QGroupBox>(g)) {
return uiQt5StrdupQString(groupBox->title());
}
return nullptr;
}
void uiGroupSetTitle(uiGroup *g, const char *text)
{
if (auto groupBox = uiValidateAndCastObjTo<QGroupBox>(g)) {
groupBox->setTitle(QString::fromUtf8(text));
}
}
void uiGroupSetChild(uiGroup *g, uiControl *child)
{
if (auto groupBox = uiValidateAndCastObjTo<QGroupBox>(g)) {
auto obj = uiValidateAndCastObjTo<QObject>(child);
if (groupBox->layout()) {
groupBox->layout()->deleteLater();
}
if (auto layout = qobject_cast<QLayout*>(obj)) {
groupBox->setLayout(layout);
} else if (auto widget = qobject_cast<QWidget*>(obj)) {
auto layout = new QVBoxLayout;
layout->setMargin(0); // ?
layout->addWidget(widget);
groupBox->setLayout(layout);
} else {
qWarning("object is neither layout nor widget");
}
}
}
int uiGroupMargined(uiGroup *g)
{
qWarning("TODO: %p", (void*)g);
return 0;
}
void uiGroupSetMargined(uiGroup *g, int margined)
{
qWarning("TODO: %p, %d", (void*)g, margined);
}
uiGroup *uiNewGroup(const char *text)
{
auto groupBox = new QGroupBox(QString::fromUtf8(text));
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiGroup,groupBox,uiQt5Control::DeleteControlOnQObjectFree);
}

30
qt5/label.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "uipriv_qt5.hpp"
#include <QLabel>
struct uiLabel : public uiQt5Control {};
char *uiLabelText(uiLabel *l)
{
if (auto label = uiValidateAndCastObjTo<QLabel>(l)) {
return uiQt5StrdupQString(label->text());
}
return nullptr;
}
void uiLabelSetText(uiLabel *l, const char *text)
{
if (auto label = uiValidateAndCastObjTo<QLabel>(l)) {
label->setText(QString::fromUtf8(text));
}
}
uiLabel *uiNewLabel(const char *text)
{
auto label = new QLabel(QString::fromUtf8(text));
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiLabel,label,uiQt5Control::DeleteControlOnQObjectFree);
}

138
qt5/main.cpp Normal file
View File

@ -0,0 +1,138 @@
#include "uipriv_qt5.hpp"
#include <QApplication>
#include <QTimer>
#include <QWidget>
#include <QThread>
#include <functional>
const char *styleSheet = R"qcss(
QGroupBox {
font: bold;
color: #333;
/*border: none;*/
}
)qcss";
const char *uiInit(uiInitOptions *o)
{
Q_UNUSED(o); // don't care about something that isn't used
if (QCoreApplication::instance() != nullptr) {
return "another QApplication instance already exists";
}
static int argc = 0;
static const char *argv[] = {"libui"};
auto app = new QApplication(argc,(char **)argv);
if (app == nullptr) {
return "failed to allocate new QApplication";
}
qSetMessagePattern("%{file}:%{line} %{function}(): %{message}");
// few consistency things
app->setQuitOnLastWindowClosed(false);
app->setStyleSheet(styleSheet);
initAlloc();
return NULL;
}
void uiUninit(void)
{
auto app = static_cast<QApplication*>(QCoreApplication::instance());
// not strictly necessary, just so we don't get needless nag from uninitAlloc()
// becasue of cascading it's not possible to iterate over the list (copy)
while (app->topLevelWidgets().count()) {
delete app->topLevelWidgets().first();
}
uninitAlloc();
delete app;
}
void uiFreeInitError(const char *err)
{
Q_UNUSED(err); // we only return static constants for errors
}
void uiMain(void)
{
if (QApplication::instance()) {
QApplication::instance()->exec();
}
}
int uiMainStep(int wait)
{
if (QApplication::instance()) {
if (wait) {
// processEvents will always return immediately if there are no
// immediate events to process
// we would have to install a event listener and run in while loop
// that seems very questionable.. don't understand the use case
qWarning("TODO: wait - no equivalent?");
}
QApplication::instance()->processEvents();
}
return 0;
}
void uiQuit(void)
{
QTimer::singleShot(0,QApplication::instance(), &QApplication::quit);
}
void uiQueueMain(void (*f)(void *data), void *data)
{
if (!QCoreApplication::instance()) {
// no instance...?
return;
}
if (QThread::currentThread()->eventDispatcher()) {
// the simple way to queue something to run on main thread
QTimer::singleShot(0,QCoreApplication::instance(), [f,data]{
f(data);
});
} else {
// very dirty workaround, spawn a new thread.. with event dispatcher to deliver this
// this could be done with custom signal, but since I decided to do
// this without using moc this is the simplest solution I could think of.
// sort of works, but not very light weight and will block to ensure resource cleanup
class Thread : public QThread
{
std::function<void ()> taskForMain_;
void run()
{
QTimer::singleShot(0,QCoreApplication::instance(), taskForMain_);
exec(); // start event loop
}
public:
Thread(std::function<void ()> taskForMain)
: taskForMain_(taskForMain)
{
start();
}
~Thread()
{
quit();
wait();
}
};
Thread([f,data]{
f(data);
});
}
}

162
qt5/menu.cpp Normal file
View File

@ -0,0 +1,162 @@
#include "uipriv_qt5.hpp"
#include <QMenu>
#include <QApplication>
#include <QMenuBar>
struct uiMenu : public uiQt5Control {};
#ifndef uiMenuSignature
#define uiMenuSignature 0x4d656e75 // 'Menu'
#endif
struct uiMenuItem : public uiQt5Control {};
#ifndef uiMenuItemSignature
#define uiMenuItemSignature 0x4d654974 // 'MeIt'
#endif
void uiMenuItemEnable(uiMenuItem *item)
{
if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
action->setEnabled(true);
}
}
void uiMenuItemDisable(uiMenuItem *item)
{
if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
action->setEnabled(false);
}
}
static uiWindow *uiQt5FindWindow(QObject *qobject)
{
if (qobject) {
if (auto widget = qobject_cast<QWidget*>(qobject)) {
if (auto topLevel = widget->topLevelWidget()) {
return static_cast<uiWindow *>(uiFindQt5ControlForQObject(topLevel));
}
}
return uiQt5FindWindow(qobject->parent());
}
return nullptr;
}
static uiWindow *uiQt5FindWindow(uiControl *leaf)
{
if (auto qobject = uiValidateAndCastObjTo<QObject>(leaf)) {
return uiQt5FindWindow(qobject);
}
return nullptr;
}
void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
{
if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
QObject::connect(action,&QAction::triggered, action, [f,item,data]{
auto window = uiQt5FindWindow(item);
f(item,window,data);
}, Qt::UniqueConnection);
}
}
int uiMenuItemChecked(uiMenuItem *item)
{
if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
return action->isChecked();
}
return 0;
}
void uiMenuItemSetChecked(uiMenuItem *item, int checked)
{
if (auto action = uiValidateAndCastObjTo<QAction>(item)) {
action->setChecked(checked);
}
}
uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
{
if (auto menu = uiValidateAndCastObjTo<QMenu>(m)) {
auto action = menu->addAction(name);
action->setObjectName(name); // for debugging only
return uiAllocQt5ControlType(uiMenuItem, action, uiQt5Control::DeleteControlOnQObjectFree);
}
return nullptr;
}
uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)
{
auto menuItem = uiMenuAppendItem(m, name);
if (auto action = uiValidateAndCastObjTo<QAction>(menuItem)) {
action->setCheckable(true);
}
return menuItem;
}
uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)
{
uiMenuAppendSeparator(m);
auto menuItem = uiMenuAppendItem(m, "&Quit");
if (auto action = uiValidateAndCastObjTo<QAction>(menuItem)) {
action->setShortcut(QKeySequence::Quit);
}
uiMenuItemOnClicked(menuItem, [](uiMenuItem *, uiWindow *, void *) { uiQuit(); }, nullptr);
return menuItem;
}
uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
{
uiMenuAppendSeparator(m);
auto menuItem = uiMenuAppendItem(m, "&Preferences...");
if (auto action = uiValidateAndCastObjTo<QAction>(menuItem)) {
action->setShortcut(QKeySequence::Preferences);
}
return menuItem;
}
uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
{
uiMenuAppendSeparator(m);
return uiMenuAppendItem(m, "&About");
}
void uiMenuAppendSeparator(uiMenu *m)
{
if (auto menu = uiValidateAndCastObjTo<QMenu>(m)) {
menu->addSeparator();
}
}
QMenuBar *uiQt5FindMainMenuBar()
{
auto app = static_cast<QApplication*>(QCoreApplication::instance());
for (auto widget : app->topLevelWidgets()) {
// see if the widget is the menubar
if (auto menuBar = qobject_cast<QMenuBar*>(widget)) {
return menuBar;
}
// see if the widget owns the menubar
if (auto menuBar = widget->findChild<QMenuBar*>()) {
return menuBar;
}
}
// not found, create
return new QMenuBar;
}
uiMenu *uiNewMenu(const char *name)
{
auto menuBar = uiQt5FindMainMenuBar();
auto menu = new QMenu(QString::fromUtf8(name), menuBar);
menuBar->addMenu(menu);
menu->setObjectName(name); // for debugging only
return uiAllocQt5ControlType(uiMenu, menu, uiQt5Control::DeleteControlOnQObjectFree);
}

79
qt5/multilineentry.cpp Normal file
View File

@ -0,0 +1,79 @@
#include "uipriv_qt5.hpp"
#include <QTextEdit>
struct uiMultilineEntry : public uiQt5Control {};
char *uiMultilineEntryText(uiMultilineEntry *e)
{
if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
return uiQt5StrdupQString(textEdit->toPlainText());
}
return nullptr;
}
void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)
{
if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
textEdit->setPlainText(QString::fromUtf8(text));
}
}
void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)
{
if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
// append is least interferring (keeps selection and cursor),
// but will add newline by it self hence not exactly compatible
// since the newline is implicitly inserted we need to remove
// the explicitly inserted one if we want the examples to look
// same, but this may cause other issues..
auto oldBlockSignals = textEdit->blockSignals(true);
textEdit->append(QString::fromUtf8(text).trimmed());
textEdit->blockSignals(oldBlockSignals);
}
}
void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data)
{
if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
QObject::connect(textEdit, &QTextEdit::textChanged, textEdit, [f,e,data]{
f(e,data);
}, Qt::UniqueConnection);
}
}
int uiMultilineEntryReadOnly(uiMultilineEntry *e)
{
if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
textEdit->isReadOnly();
}
return 0;
}
void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)
{
if (auto textEdit = uiValidateAndCastObjTo<QTextEdit>(e)) {
textEdit->setReadOnly(readonly);
}
}
uiMultilineEntry *uiNewMultilineEntry(void)
{
auto textEdit = new QTextEdit;
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiMultilineEntry,textEdit,uiQt5Control::DeleteControlOnQObjectFree);
}
uiMultilineEntry *uiNewNonWrappingMultilineEntry(void)
{
auto textEdit = new QTextEdit;
textEdit->setLineWrapMode(QTextEdit::NoWrap);
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiMultilineEntry,textEdit,uiQt5Control::DeleteControlOnQObjectFree);
}

22
qt5/progressbar.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "uipriv_qt5.hpp"
#include <QProgressBar>
struct uiProgressBar : public uiQt5Control {};
void uiProgressBarSetValue(uiProgressBar *p, int value)
{
if (auto progressBar = uiValidateAndCastObjTo<QProgressBar>(p)) {
return progressBar->setValue(value);
}
}
uiProgressBar *uiNewProgressBar(void)
{
auto progressBar = new QProgressBar;
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiProgressBar,progressBar,uiQt5Control::DeleteControlOnQObjectFree);
}

28
qt5/radiobuttons.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "uipriv_qt5.hpp"
#include <QRadioButton>
#include <QVBoxLayout>
struct uiRadioButtons : public uiQt5Control {};
void uiRadioButtonsAppend(uiRadioButtons *r, const char *text)
{
if (auto layout = uiValidateAndCastObjTo<QLayout>(r)) {
auto radioButton = new QRadioButton(QString::fromUtf8(text));
layout->addWidget(radioButton);
if (layout->count() == 1) {
radioButton->setChecked(true);
}
}
}
uiRadioButtons *uiNewRadioButtons(void)
{
// TODO: check does this need a QButtonGroup or is the layout sufficent?
auto layout = new QVBoxLayout;
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiRadioButtons,layout,uiQt5Control::DeleteControlOnQObjectFree);
}

16
qt5/separator.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "uipriv_qt5.hpp"
#include <QFrame>
struct uiSeparator : public uiQt5Control {};
uiSeparator *uiNewHorizontalSeparator(void)
{
auto frame = new QFrame;
frame->setFrameStyle(QFrame::HLine | QFrame::Sunken);
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiSeparator,frame,uiQt5Control::DeleteControlOnQObjectFree);
}

40
qt5/slider.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "uipriv_qt5.hpp"
#include <QSlider>
struct uiSlider : public uiQt5Control {};
intmax_t uiSliderValue(uiSlider *s)
{
if (auto slider = uiValidateAndCastObjTo<QSlider>(s)) {
return slider->value();
}
return 0;
}
void uiSliderSetValue(uiSlider *s, intmax_t value)
{
if (auto slider = uiValidateAndCastObjTo<QSlider>(s)) {
return slider->setValue((int)value);
}
}
void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)
{
if (auto slider = uiValidateAndCastObjTo<QSlider>(s)) {
QObject::connect(slider, &QSlider::valueChanged, slider, [f,s,data]{
f(s,data);
}, Qt::UniqueConnection);
}
}
uiSlider *uiNewSlider(intmax_t min, intmax_t max)
{
auto slider = new QSlider(Qt::Horizontal);
slider->setRange(qMin<int>(min,max), qMax<int>(min,max));
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiSlider,slider,uiQt5Control::DeleteControlOnQObjectFree);
}

40
qt5/spinbox.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "uipriv_qt5.hpp"
#include <QSpinBox>
struct uiSpinbox : public uiQt5Control {};
intmax_t uiSpinboxValue(uiSpinbox *s)
{
if (auto spinBox = uiValidateAndCastObjTo<QSpinBox>(s)) {
return spinBox->value();
}
return 0;
}
void uiSpinboxSetValue(uiSpinbox *s, intmax_t value)
{
if (auto spinBox = uiValidateAndCastObjTo<QSpinBox>(s)) {
spinBox->setValue(value);
}
}
void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data)
{
if (auto spinBox = uiValidateAndCastObjTo<QSpinBox>(s)) {
QObject::connect(spinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), spinBox, [f,s,data]{
f(s,data);
}, Qt::UniqueConnection);
}
}
uiSpinbox *uiNewSpinbox(intmax_t min, intmax_t max)
{
auto spinBox = new QSpinBox;
spinBox->setRange(qMin(min,max),qMax(min,max));
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiSpinbox,spinBox,uiQt5Control::DeleteControlOnQObjectFree);
}

29
qt5/stddialogs.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "uipriv_qt5.hpp"
#include <QFileDialog>
#include <QMessageBox>
char *uiOpenFile(uiWindow *parent)
{
return uiQt5StrdupQString(QFileDialog::getOpenFileName(uiValidateAndCastObjTo<QWidget>(parent)));
}
char *uiSaveFile(uiWindow *parent)
{
return uiQt5StrdupQString(QFileDialog::getSaveFileName(uiValidateAndCastObjTo<QWidget>(parent)));
}
void uiMsgBox(uiWindow *parent, const char *title, const char *description)
{
QMessageBox::information(uiValidateAndCastObjTo<QWidget>(parent),
QString::fromUtf8(title),
QString::fromUtf8(description));
}
void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)
{
QMessageBox::warning(uiValidateAndCastObjTo<QWidget>(parent),
QString::fromUtf8(title),
QString::fromUtf8(description));
}

88
qt5/tab.cpp Normal file
View File

@ -0,0 +1,88 @@
#include "uipriv_qt5.hpp"
#include <QTabWidget>
#include <QLayout>
struct uiTab : public uiQt5Control {};
static void tabInsertAt(QTabWidget *tabWidget, const char *name, int n, uiControl *child)
{
auto obj = uiValidateAndCastObjTo<QObject>(child);
if (auto layout = qobject_cast<QLayout*>(obj)) {
auto widget = new QWidget;
widget->setLayout(layout);
tabWidget->insertTab(n, widget, QString::fromUtf8(name));
} else if (auto widget = qobject_cast<QWidget*>(obj)) {
tabWidget->insertTab(n, widget, QString::fromUtf8(name));
} else {
qWarning("object is neither layout nor widget");
}
}
void uiTabAppend(uiTab *t, const char *name, uiControl *child)
{
if (auto tabWidget = uiValidateAndCastObjTo<QTabWidget>(t)) {
tabInsertAt(tabWidget,name,tabWidget->count(),child);
}
}
void uiTabInsertAt(uiTab *t, const char *name, uintmax_t n, uiControl *child)
{
if (auto tabWidget = uiValidateAndCastObjTo<QTabWidget>(t)) {
tabInsertAt(tabWidget,name,qBound<int>(0, n, tabWidget->count()),child);
}
}
// reminder: badly named function, is remove not really a delete
void uiTabDelete(uiTab *t, uintmax_t n)
{
if (auto tabWidget = uiValidateAndCastObjTo<QTabWidget>(t)) {
int i = qBound<int>(0, n, tabWidget->count());
if ((uintmax_t)i != n) {
qWarning("Bad index: %llu", (unsigned long long)n);
return;
}
auto widget = tabWidget->widget(i);
tabWidget->removeTab(i);
// check if it's a internal temp container widget we created
if (uiFindQt5ControlForQObject(widget) == nullptr) {
// widget is temp we created used as a container, separate the layout and then delete it
// problem once a layout assigned to widget, it's not so easy to get it back..?
// one solution is to recreate the layout, but this needs some thought..
// another solution is to never expose "layout" (uiBox), but always wrap them in containers..
qWarning("FIXME: can't separate layout from temp widget");
// delete widget;
}
}
}
uintmax_t uiTabNumPages(uiTab *t)
{
if (auto tabWidget = uiValidateAndCastObjTo<QTabWidget>(t)) {
return tabWidget->count();
}
return 0;
}
int uiTabMargined(uiTab *t, uintmax_t n)
{
qWarning("TODO %p, %d", (void*)t, (int)n);
return 0;
}
void uiTabSetMargined(uiTab *t, uintmax_t n, int margined)
{
qWarning("TODO %p, %d, %d", (void*)t, (int)n, margined);
}
uiTab *uiNewTab(void)
{
auto tabWidget = new QTabWidget;
// note styling is being set in main.cpp -> styleSheet
return uiAllocQt5ControlType(uiTab,tabWidget,uiQt5Control::DeleteControlOnQObjectFree);
}

33
qt5/text.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "uipriv_qt5.hpp"
#include <QString>
static char *uiStrdupText(const char *s, int len)
{
// Reminder: could use strndup, but it has poor portability
auto copy = (char *)malloc(len+1);
if (copy) {
memcpy(copy, s, len);
copy[len] = 0;
} else {
qCritical("Failed to allocate %d+1 bytes", len);
}
return copy;
}
char *uiQt5StrdupQString(const QString &string)
{
if (string.isNull()) {
return nullptr;
}
const auto utf8 = string.toUtf8();
return uiStrdupText(utf8.constData(), utf8.length());
}
void uiFreeText(char *t)
{
free(t);
}

52
qt5/uipriv_qt5.hpp Normal file
View File

@ -0,0 +1,52 @@
#ifndef __LIBUI_UIPRIV_QT5_HPP__
#define __LIBUI_UIPRIV_QT5_HPP__
#include "../ui.h"
#include "../ui_qt5.h"
#include "../common/uipriv.h"
extern void initAlloc(void);
extern void uninitAlloc(void);
// text
class QString;
char *uiQt5StrdupQString(const QString &string);
// control
class QObject;
struct uiQt5Control : public uiControl
{
QObject *qobject;
enum {
DefaultFlags=0,
DeleteControlOnQObjectFree=0x1,
SuppressValidatationNag=0x2
};
uint32_t flags;
};
extern uiQt5Control *uiValidateQt5Control(uiControl *control);
// control to qobject
template <typename T=QObject>
T *uiValidateAndCastObjTo(uiControl *control)
{
if (auto qt5Control = uiValidateQt5Control(control)) {
return dynamic_cast<T*>(qt5Control->qobject);
}
return nullptr;
}
struct uiWindow : public uiQt5Control {};
// qobject to control
extern uiQt5Control *uiFindQt5ControlForQObject(const QObject *qobject);
// alloc control
extern uiQt5Control *uiAllocQt5Control(uint32_t typesig, const char *typenamestr, QObject *qobject, uint32_t flags = uiQt5Control::DefaultFlags);
#define uiAllocQt5ControlType(type, widget, flags) static_cast<type*>(uiAllocQt5Control(type ## Signature, #type, widget, flags))
// menu
class QMenuBar;
extern QMenuBar *uiQt5FindMainMenuBar();
#endif // __LIBUI_UIPRIV_QT5_HPP__

103
qt5/window.cpp Normal file
View File

@ -0,0 +1,103 @@
#include "uipriv_qt5.hpp"
#include <QMainWindow>
#include <QCloseEvent>
#include <QMenuBar>
#include <QLayout>
#include <functional>
class WindowWidget : public QMainWindow
{
public:
std::function<void (QCloseEvent *event)> onClosing;
void closeEvent(QCloseEvent *event)
{
if (onClosing) {
onClosing(event);
}
}
};
char *uiWindowTitle(uiWindow *w)
{
if (auto window = uiValidateAndCastObjTo<WindowWidget>(w)) {
return uiQt5StrdupQString(window->windowTitle());
}
return nullptr;
}
void uiWindowSetTitle(uiWindow *w, const char *title)
{
if (auto window = uiValidateAndCastObjTo<WindowWidget>(w)) {
window->setWindowTitle(QString::fromUtf8(title));
}
}
void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)
{
if (auto window = uiValidateAndCastObjTo<WindowWidget>(w)) {
if (f) {
window->onClosing = [f,w,data](QCloseEvent *event){
if (f(w,data)) {
// eat the event and destroy the control
// normally such behavior would be achived with
// setAttribute(Qt::WA_DeleteOnClose, true)
// but we need to behave consistently
event->accept();
w->Destroy(w);
}
};
} else {
// clear callback
window->onClosing = nullptr;
}
}
}
void uiWindowSetChild(uiWindow *w, uiControl *child)
{
if (auto window = uiValidateAndCastObjTo<WindowWidget>(w)) {
auto obj = uiValidateAndCastObjTo<QObject>(child);
if (window->centralWidget()) {
window->centralWidget()->deleteLater();
}
if (auto layout = qobject_cast<QLayout*>(obj)) {
auto widget = new QWidget;
widget->setLayout(layout);
window->setCentralWidget(widget);
} else if (auto widget = qobject_cast<QWidget*>(obj)) {
window->setCentralWidget(widget);
} else {
qWarning("object is neither layout nor widget");
}
}
}
int uiWindowMargined(uiWindow *w)
{
qWarning("TODO %p", (void*)w);
return 0;
}
void uiWindowSetMargined(uiWindow *w, int margined)
{
qWarning("TODO %p, %d", (void*)w, margined);
}
uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)
{
auto window = new WindowWidget;
window->setWindowTitle(title);
window->resize(width,height);
if (hasMenubar) {
window->setMenuBar(uiQt5FindMainMenuBar());
}
return uiAllocQt5ControlType(uiWindow, window, uiQt5Control::DeleteControlOnQObjectFree);
}

7
ui_qt5.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef __LIBUI_UI_QT5_H__
#define __LIBUI_UI_QT5_H__
#endif // __LIBUI_UI_QT5_H__