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:
parent
d88233a0fb
commit
e381e6b45f
|
@ -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
|
|
@ -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/
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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)),¶ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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) {}
|
||||||
|
};
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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__
|
|
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue