mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-07-29 09:10:08 -06:00
another UI attempt, I guess.
sorry.
This commit is contained in:
85
src/libui_sdl/libui/unix/CMakeLists.txt
Normal file
85
src/libui_sdl/libui/unix/CMakeLists.txt
Normal file
@ -0,0 +1,85 @@
|
||||
# 3 june 2016
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK REQUIRED gtk+-3.0)
|
||||
|
||||
list(APPEND _LIBUI_SOURCES
|
||||
unix/alloc.c
|
||||
unix/area.c
|
||||
unix/box.c
|
||||
unix/button.c
|
||||
unix/cellrendererbutton.c
|
||||
unix/checkbox.c
|
||||
unix/child.c
|
||||
unix/colorbutton.c
|
||||
unix/combobox.c
|
||||
unix/control.c
|
||||
unix/datetimepicker.c
|
||||
unix/debug.c
|
||||
unix/draw.c
|
||||
unix/drawmatrix.c
|
||||
unix/drawpath.c
|
||||
unix/drawtext.c
|
||||
unix/editablecombo.c
|
||||
unix/entry.c
|
||||
unix/fontbutton.c
|
||||
unix/form.c
|
||||
unix/future.c
|
||||
unix/graphemes.c
|
||||
unix/grid.c
|
||||
unix/group.c
|
||||
unix/image.c
|
||||
unix/label.c
|
||||
unix/main.c
|
||||
unix/menu.c
|
||||
unix/multilineentry.c
|
||||
unix/progressbar.c
|
||||
unix/radiobuttons.c
|
||||
unix/separator.c
|
||||
unix/slider.c
|
||||
unix/spinbox.c
|
||||
unix/stddialogs.c
|
||||
unix/tab.c
|
||||
unix/text.c
|
||||
unix/util.c
|
||||
unix/window.c
|
||||
)
|
||||
set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE)
|
||||
|
||||
list(APPEND _LIBUI_INCLUDEDIRS
|
||||
unix
|
||||
)
|
||||
set(_LIBUI_INCLUDEDIRS _LIBUI_INCLUDEDIRS PARENT_SCOPE)
|
||||
|
||||
set(_LIBUINAME libui PARENT_SCOPE)
|
||||
if(NOT BUILD_SHARED_LIBS)
|
||||
set(_LIBUINAME libui-temporary PARENT_SCOPE)
|
||||
endif()
|
||||
macro(_handle_static)
|
||||
set_target_properties(${_LIBUINAME} PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
|
||||
set(_aname $<TARGET_FILE:${_LIBUINAME}>)
|
||||
set(_oname libui-combined.o)
|
||||
add_custom_command(
|
||||
OUTPUT ${_oname}
|
||||
COMMAND
|
||||
ld -r --whole-archive ${_aname} -o ${_oname}
|
||||
COMMAND
|
||||
objcopy --localize-hidden ${_oname}
|
||||
COMMENT "Removing hidden symbols")
|
||||
add_library(libui STATIC ${_oname})
|
||||
# otherwise cmake won't know which linker to use
|
||||
set_target_properties(libui PROPERTIES
|
||||
LINKER_LANGUAGE C)
|
||||
set(_aname)
|
||||
set(_oname)
|
||||
endmacro()
|
||||
|
||||
# TODO the other variables don't work?
|
||||
set(_LIBUI_CFLAGS
|
||||
${GTK_CFLAGS}
|
||||
PARENT_SCOPE)
|
||||
|
||||
set(_LIBUI_LIBS
|
||||
${GTK_LDFLAGS} m ${CMAKE_DL_LIBS}
|
||||
PARENT_SCOPE)
|
84
src/libui_sdl/libui/unix/alloc.c
Normal file
84
src/libui_sdl/libui/unix/alloc.c
Normal file
@ -0,0 +1,84 @@
|
||||
// 7 april 2015
|
||||
#include <string.h>
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
static GPtrArray *allocations;
|
||||
|
||||
#define UINT8(p) ((uint8_t *) (p))
|
||||
#define PVOID(p) ((void *) (p))
|
||||
#define EXTRA (sizeof (size_t) + sizeof (const char **))
|
||||
#define DATA(p) PVOID(UINT8(p) + EXTRA)
|
||||
#define BASE(p) PVOID(UINT8(p) - EXTRA)
|
||||
#define SIZE(p) ((size_t *) (p))
|
||||
#define CCHAR(p) ((const char **) (p))
|
||||
#define TYPE(p) CCHAR(UINT8(p) + sizeof (size_t))
|
||||
|
||||
void initAlloc(void)
|
||||
{
|
||||
allocations = g_ptr_array_new();
|
||||
}
|
||||
|
||||
static void uninitComplain(gpointer ptr, gpointer data)
|
||||
{
|
||||
char **str = (char **) data;
|
||||
char *str2;
|
||||
|
||||
if (*str == NULL)
|
||||
*str = g_strdup_printf("");
|
||||
str2 = g_strdup_printf("%s%p %s\n", *str, ptr, *TYPE(ptr));
|
||||
g_free(*str);
|
||||
*str = str2;
|
||||
}
|
||||
|
||||
void uninitAlloc(void)
|
||||
{
|
||||
char *str = NULL;
|
||||
|
||||
if (allocations->len == 0) {
|
||||
g_ptr_array_free(allocations, TRUE);
|
||||
return;
|
||||
}
|
||||
g_ptr_array_foreach(allocations, uninitComplain, &str);
|
||||
userbug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", str);
|
||||
g_free(str);
|
||||
}
|
||||
|
||||
void *uiAlloc(size_t size, const char *type)
|
||||
{
|
||||
void *out;
|
||||
|
||||
out = g_malloc0(EXTRA + size);
|
||||
*SIZE(out) = size;
|
||||
*TYPE(out) = type;
|
||||
g_ptr_array_add(allocations, out);
|
||||
return DATA(out);
|
||||
}
|
||||
|
||||
void *uiRealloc(void *p, size_t new, const char *type)
|
||||
{
|
||||
void *out;
|
||||
size_t *s;
|
||||
|
||||
if (p == NULL)
|
||||
return uiAlloc(new, type);
|
||||
p = BASE(p);
|
||||
out = g_realloc(p, EXTRA + new);
|
||||
s = SIZE(out);
|
||||
if (new <= *s)
|
||||
memset(((uint8_t *) DATA(out)) + *s, 0, new - *s);
|
||||
*s = new;
|
||||
if (g_ptr_array_remove(allocations, p) == FALSE)
|
||||
implbug("%p not found in allocations array in uiRealloc()", p);
|
||||
g_ptr_array_add(allocations, out);
|
||||
return DATA(out);
|
||||
}
|
||||
|
||||
void uiFree(void *p)
|
||||
{
|
||||
if (p == NULL)
|
||||
implbug("attempt to uiFree(NULL)");
|
||||
p = BASE(p);
|
||||
g_free(p);
|
||||
if (g_ptr_array_remove(allocations, p) == FALSE)
|
||||
implbug("%p not found in allocations array in uiFree()", p);
|
||||
}
|
633
src/libui_sdl/libui/unix/area.c
Normal file
633
src/libui_sdl/libui/unix/area.c
Normal file
@ -0,0 +1,633 @@
|
||||
// 4 september 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// notes:
|
||||
// - G_DECLARE_DERIVABLE/FINAL_INTERFACE() requires glib 2.44 and that's starting with debian stretch (testing) (GTK+ 3.18) and ubuntu 15.04 (GTK+ 3.14) - debian jessie has 2.42 (GTK+ 3.14)
|
||||
#define areaWidgetType (areaWidget_get_type())
|
||||
#define areaWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), areaWidgetType, areaWidget))
|
||||
#define isAreaWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), areaWidgetType))
|
||||
#define areaWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), areaWidgetType, areaWidgetClass))
|
||||
#define isAreaWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), areaWidget))
|
||||
#define getAreaWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), areaWidgetType, areaWidgetClass))
|
||||
|
||||
typedef struct areaWidget areaWidget;
|
||||
typedef struct areaWidgetClass areaWidgetClass;
|
||||
|
||||
struct areaWidget {
|
||||
GtkDrawingArea parent_instance;
|
||||
uiArea *a;
|
||||
// construct-only parameters aare not set until after the init() function has returned
|
||||
// we need this particular object available during init(), so put it here instead of in uiArea
|
||||
// keep a pointer in uiArea for convenience, though
|
||||
clickCounter cc;
|
||||
};
|
||||
|
||||
struct areaWidgetClass {
|
||||
GtkDrawingAreaClass parent_class;
|
||||
};
|
||||
|
||||
struct uiArea {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget; // either swidget or areaWidget depending on whether it is scrolling
|
||||
|
||||
GtkWidget *swidget;
|
||||
GtkContainer *scontainer;
|
||||
GtkScrolledWindow *sw;
|
||||
|
||||
GtkWidget *areaWidget;
|
||||
GtkDrawingArea *drawingArea;
|
||||
areaWidget *area;
|
||||
|
||||
uiAreaHandler *ah;
|
||||
|
||||
gboolean scrolling;
|
||||
int scrollWidth;
|
||||
int scrollHeight;
|
||||
|
||||
// note that this is a pointer; see above
|
||||
clickCounter *cc;
|
||||
|
||||
// for user window drags
|
||||
GdkEventButton *dragevent;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(areaWidget, areaWidget, GTK_TYPE_DRAWING_AREA)
|
||||
|
||||
static void areaWidget_init(areaWidget *aw)
|
||||
{
|
||||
// for events
|
||||
gtk_widget_add_events(GTK_WIDGET(aw),
|
||||
GDK_POINTER_MOTION_MASK |
|
||||
GDK_BUTTON_MOTION_MASK |
|
||||
GDK_BUTTON_PRESS_MASK |
|
||||
GDK_BUTTON_RELEASE_MASK |
|
||||
GDK_KEY_PRESS_MASK |
|
||||
GDK_KEY_RELEASE_MASK |
|
||||
GDK_ENTER_NOTIFY_MASK |
|
||||
GDK_LEAVE_NOTIFY_MASK);
|
||||
|
||||
gtk_widget_set_can_focus(GTK_WIDGET(aw), TRUE);
|
||||
|
||||
clickCounterReset(&(aw->cc));
|
||||
}
|
||||
|
||||
static void areaWidget_dispose(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(areaWidget_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void areaWidget_finalize(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(areaWidget_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
static void areaWidget_size_allocate(GtkWidget *w, GtkAllocation *allocation)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
// GtkDrawingArea has a size_allocate() implementation; we need to call it
|
||||
// this will call gtk_widget_set_allocation() for us
|
||||
GTK_WIDGET_CLASS(areaWidget_parent_class)->size_allocate(w, allocation);
|
||||
|
||||
if (!a->scrolling)
|
||||
// we must redraw everything on resize because Windows requires it
|
||||
gtk_widget_queue_resize(w);
|
||||
}
|
||||
|
||||
static void loadAreaSize(uiArea *a, double *width, double *height)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
|
||||
*width = 0;
|
||||
*height = 0;
|
||||
// don't provide size information for scrolling areas
|
||||
if (!a->scrolling) {
|
||||
gtk_widget_get_allocation(a->areaWidget, &allocation);
|
||||
// these are already in drawing space coordinates
|
||||
// for drawing, the size of drawing space has the same value as the widget allocation
|
||||
// thanks to tristan in irc.gimp.net/#gtk+
|
||||
*width = allocation.width;
|
||||
*height = allocation.height;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean areaWidget_draw(GtkWidget *w, cairo_t *cr)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
uiAreaDrawParams dp;
|
||||
double clipX0, clipY0, clipX1, clipY1;
|
||||
|
||||
dp.Context = newContext(cr);
|
||||
|
||||
loadAreaSize(a, &(dp.AreaWidth), &(dp.AreaHeight));
|
||||
|
||||
cairo_clip_extents(cr, &clipX0, &clipY0, &clipX1, &clipY1);
|
||||
dp.ClipX = clipX0;
|
||||
dp.ClipY = clipY0;
|
||||
dp.ClipWidth = clipX1 - clipX0;
|
||||
dp.ClipHeight = clipY1 - clipY0;
|
||||
|
||||
// no need to save or restore the graphics state to reset transformations; GTK+ does that for us
|
||||
(*(a->ah->Draw))(a->ah, a, &dp);
|
||||
|
||||
freeContext(dp.Context);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// to do this properly for scrolling areas, we need to
|
||||
// - return the same value for min and nat
|
||||
// - call gtk_widget_queue_resize() when the size changes
|
||||
// thanks to Company in irc.gimp.net/#gtk+
|
||||
static void areaWidget_get_preferred_height(GtkWidget *w, gint *min, gint *nat)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
// always chain up just in case
|
||||
GTK_WIDGET_CLASS(areaWidget_parent_class)->get_preferred_height(w, min, nat);
|
||||
if (a->scrolling) {
|
||||
*min = a->scrollHeight;
|
||||
*nat = a->scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
static void areaWidget_get_preferred_width(GtkWidget *w, gint *min, gint *nat)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
// always chain up just in case
|
||||
GTK_WIDGET_CLASS(areaWidget_parent_class)->get_preferred_width(w, min, nat);
|
||||
if (a->scrolling) {
|
||||
*min = a->scrollWidth;
|
||||
*nat = a->scrollWidth;
|
||||
}
|
||||
}
|
||||
|
||||
static guint translateModifiers(guint state, GdkWindow *window)
|
||||
{
|
||||
GdkModifierType statetype;
|
||||
|
||||
// GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+)
|
||||
statetype = state;
|
||||
gdk_keymap_add_virtual_modifiers(
|
||||
gdk_keymap_get_for_display(gdk_window_get_display(window)),
|
||||
&statetype);
|
||||
return statetype;
|
||||
}
|
||||
|
||||
static uiModifiers toModifiers(guint state)
|
||||
{
|
||||
uiModifiers m;
|
||||
|
||||
m = 0;
|
||||
if ((state & GDK_CONTROL_MASK) != 0)
|
||||
m |= uiModifierCtrl;
|
||||
if ((state & GDK_META_MASK) != 0)
|
||||
m |= uiModifierAlt;
|
||||
if ((state & GDK_MOD1_MASK) != 0) // GTK+ itself requires this to be Alt (just read through gtkaccelgroup.c)
|
||||
m |= uiModifierAlt;
|
||||
if ((state & GDK_SHIFT_MASK) != 0)
|
||||
m |= uiModifierShift;
|
||||
if ((state & GDK_SUPER_MASK) != 0)
|
||||
m |= uiModifierSuper;
|
||||
return m;
|
||||
}
|
||||
|
||||
// capture on drag is done automatically on GTK+
|
||||
static void finishMouseEvent(uiArea *a, uiAreaMouseEvent *me, guint mb, gdouble x, gdouble y, guint state, GdkWindow *window)
|
||||
{
|
||||
// on GTK+, mouse buttons 4-7 are for scrolling; if we got here, that's a mistake
|
||||
if (mb >= 4 && mb <= 7)
|
||||
return;
|
||||
// if the button ID >= 8, continue counting from 4, as in the MouseEvent spec
|
||||
if (me->Down >= 8)
|
||||
me->Down -= 4;
|
||||
if (me->Up >= 8)
|
||||
me->Up -= 4;
|
||||
|
||||
state = translateModifiers(state, window);
|
||||
me->Modifiers = toModifiers(state);
|
||||
|
||||
// the mb != # checks exclude the Up/Down button from Held
|
||||
me->Held1To64 = 0;
|
||||
if (mb != 1 && (state & GDK_BUTTON1_MASK) != 0)
|
||||
me->Held1To64 |= 1 << 0;
|
||||
if (mb != 2 && (state & GDK_BUTTON2_MASK) != 0)
|
||||
me->Held1To64 |= 1 << 1;
|
||||
if (mb != 3 && (state & GDK_BUTTON3_MASK) != 0)
|
||||
me->Held1To64 |= 1 << 2;
|
||||
// don't check GDK_BUTTON4_MASK or GDK_BUTTON5_MASK because those are for the scrolling buttons mentioned above
|
||||
// GDK expressly does not support any more buttons in the GdkModifierType; see https://git.gnome.org/browse/gtk+/tree/gdk/x11/gdkdevice-xi2.c#n763 (thanks mclasen in irc.gimp.net/#gtk+)
|
||||
|
||||
// these are already in drawing space coordinates
|
||||
// the size of drawing space has the same value as the widget allocation
|
||||
// thanks to tristan in irc.gimp.net/#gtk+
|
||||
me->X = x;
|
||||
me->Y = y;
|
||||
|
||||
loadAreaSize(a, &(me->AreaWidth), &(me->AreaHeight));
|
||||
|
||||
(*(a->ah->MouseEvent))(a->ah, a, me);
|
||||
}
|
||||
|
||||
static gboolean areaWidget_button_press_event(GtkWidget *w, GdkEventButton *e)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
gint maxTime, maxDistance;
|
||||
GtkSettings *settings;
|
||||
uiAreaMouseEvent me;
|
||||
|
||||
// clicking doesn't automatically transfer keyboard focus; we must do so manually (thanks tristan in irc.gimp.net/#gtk+)
|
||||
gtk_widget_grab_focus(w);
|
||||
|
||||
// we handle multiple clicks ourselves here, in the same way as we do on Windows
|
||||
if (e->type != GDK_BUTTON_PRESS)
|
||||
// ignore GDK's generated double-clicks and beyond
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
settings = gtk_widget_get_settings(w);
|
||||
g_object_get(settings,
|
||||
"gtk-double-click-time", &maxTime,
|
||||
"gtk-double-click-distance", &maxDistance,
|
||||
NULL);
|
||||
// don't unref settings; it's transfer-none (thanks gregier in irc.gimp.net/#gtk+)
|
||||
// e->time is guint32
|
||||
// e->x and e->y are floating-point; just make them 32-bit integers
|
||||
// maxTime and maxDistance... are gint, which *should* fit, hopefully...
|
||||
me.Count = clickCounterClick(a->cc, me.Down,
|
||||
e->x, e->y,
|
||||
e->time, maxTime,
|
||||
maxDistance, maxDistance);
|
||||
|
||||
me.Down = e->button;
|
||||
me.Up = 0;
|
||||
|
||||
// and set things up for window drags
|
||||
a->dragevent = e;
|
||||
finishMouseEvent(a, &me, e->button, e->x, e->y, e->state, e->window);
|
||||
a->dragevent = NULL;
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static gboolean areaWidget_button_release_event(GtkWidget *w, GdkEventButton *e)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
uiAreaMouseEvent me;
|
||||
|
||||
me.Down = 0;
|
||||
me.Up = e->button;
|
||||
me.Count = 0;
|
||||
finishMouseEvent(a, &me, e->button, e->x, e->y, e->state, e->window);
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static gboolean areaWidget_motion_notify_event(GtkWidget *w, GdkEventMotion *e)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
uiAreaMouseEvent me;
|
||||
|
||||
me.Down = 0;
|
||||
me.Up = 0;
|
||||
me.Count = 0;
|
||||
finishMouseEvent(a, &me, 0, e->x, e->y, e->state, e->window);
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
// we want switching away from the control to reset the double-click counter, like with WM_ACTIVATE on Windows
|
||||
// according to tristan in irc.gimp.net/#gtk+, doing this on both enter-notify-event and leave-notify-event is correct (and it seems to be true in my own tests; plus the events DO get sent when switching programs with the keyboard (just pointing that out))
|
||||
static gboolean onCrossing(areaWidget *aw, int left)
|
||||
{
|
||||
uiArea *a = aw->a;
|
||||
|
||||
(*(a->ah->MouseCrossed))(a->ah, a, left);
|
||||
clickCounterReset(a->cc);
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static gboolean areaWidget_enter_notify_event(GtkWidget *w, GdkEventCrossing *e)
|
||||
{
|
||||
return onCrossing(areaWidget(w), 0);
|
||||
}
|
||||
|
||||
static gboolean areaWidget_leave_notify_event(GtkWidget *w, GdkEventCrossing *e)
|
||||
{
|
||||
return onCrossing(areaWidget(w), 1);
|
||||
}
|
||||
|
||||
// note: there is no equivalent to WM_CAPTURECHANGED on GTK+; there literally is no way to break a grab like that (at least not on X11 and Wayland)
|
||||
// even if I invoke the task switcher and switch processes, the mouse grab will still be held until I let go of all buttons
|
||||
// therefore, no DragBroken()
|
||||
|
||||
// we use GDK_KEY_Print as a sentinel because libui will never support the print screen key; that key belongs to the user
|
||||
|
||||
static const struct {
|
||||
guint keyval;
|
||||
uiExtKey extkey;
|
||||
} extKeys[] = {
|
||||
{ GDK_KEY_Escape, uiExtKeyEscape },
|
||||
{ GDK_KEY_Insert, uiExtKeyInsert },
|
||||
{ GDK_KEY_Delete, uiExtKeyDelete },
|
||||
{ GDK_KEY_Home, uiExtKeyHome },
|
||||
{ GDK_KEY_End, uiExtKeyEnd },
|
||||
{ GDK_KEY_Page_Up, uiExtKeyPageUp },
|
||||
{ GDK_KEY_Page_Down, uiExtKeyPageDown },
|
||||
{ GDK_KEY_Up, uiExtKeyUp },
|
||||
{ GDK_KEY_Down, uiExtKeyDown },
|
||||
{ GDK_KEY_Left, uiExtKeyLeft },
|
||||
{ GDK_KEY_Right, uiExtKeyRight },
|
||||
{ GDK_KEY_F1, uiExtKeyF1 },
|
||||
{ GDK_KEY_F2, uiExtKeyF2 },
|
||||
{ GDK_KEY_F3, uiExtKeyF3 },
|
||||
{ GDK_KEY_F4, uiExtKeyF4 },
|
||||
{ GDK_KEY_F5, uiExtKeyF5 },
|
||||
{ GDK_KEY_F6, uiExtKeyF6 },
|
||||
{ GDK_KEY_F7, uiExtKeyF7 },
|
||||
{ GDK_KEY_F8, uiExtKeyF8 },
|
||||
{ GDK_KEY_F9, uiExtKeyF9 },
|
||||
{ GDK_KEY_F10, uiExtKeyF10 },
|
||||
{ GDK_KEY_F11, uiExtKeyF11 },
|
||||
{ GDK_KEY_F12, uiExtKeyF12 },
|
||||
// numpad numeric keys and . are handled in events.c
|
||||
{ GDK_KEY_KP_Enter, uiExtKeyNEnter },
|
||||
{ GDK_KEY_KP_Add, uiExtKeyNAdd },
|
||||
{ GDK_KEY_KP_Subtract, uiExtKeyNSubtract },
|
||||
{ GDK_KEY_KP_Multiply, uiExtKeyNMultiply },
|
||||
{ GDK_KEY_KP_Divide, uiExtKeyNDivide },
|
||||
{ GDK_KEY_Print, 0 },
|
||||
};
|
||||
|
||||
static const struct {
|
||||
guint keyval;
|
||||
uiModifiers mod;
|
||||
} modKeys[] = {
|
||||
{ GDK_KEY_Control_L, uiModifierCtrl },
|
||||
{ GDK_KEY_Control_R, uiModifierCtrl },
|
||||
{ GDK_KEY_Alt_L, uiModifierAlt },
|
||||
{ GDK_KEY_Alt_R, uiModifierAlt },
|
||||
{ GDK_KEY_Meta_L, uiModifierAlt },
|
||||
{ GDK_KEY_Meta_R, uiModifierAlt },
|
||||
{ GDK_KEY_Shift_L, uiModifierShift },
|
||||
{ GDK_KEY_Shift_R, uiModifierShift },
|
||||
{ GDK_KEY_Super_L, uiModifierSuper },
|
||||
{ GDK_KEY_Super_R, uiModifierSuper },
|
||||
{ GDK_KEY_Print, 0 },
|
||||
};
|
||||
|
||||
static int areaKeyEvent(uiArea *a, int up, GdkEventKey *e)
|
||||
{
|
||||
uiAreaKeyEvent ke;
|
||||
guint state;
|
||||
int i;
|
||||
|
||||
ke.Key = 0;
|
||||
ke.ExtKey = 0;
|
||||
ke.Modifier = 0;
|
||||
|
||||
state = translateModifiers(e->state, e->window);
|
||||
ke.Modifiers = toModifiers(state);
|
||||
|
||||
ke.Up = up;
|
||||
|
||||
for (i = 0; extKeys[i].keyval != GDK_KEY_Print; i++)
|
||||
if (extKeys[i].keyval == e->keyval) {
|
||||
ke.ExtKey = extKeys[i].extkey;
|
||||
goto keyFound;
|
||||
}
|
||||
|
||||
for (i = 0; modKeys[i].keyval != GDK_KEY_Print; i++)
|
||||
if (modKeys[i].keyval == e->keyval) {
|
||||
ke.Modifier = modKeys[i].mod;
|
||||
// don't include the modifier in ke.Modifiers
|
||||
ke.Modifiers &= ~ke.Modifier;
|
||||
goto keyFound;
|
||||
}
|
||||
|
||||
if (fromScancode(e->hardware_keycode - 8, &ke))
|
||||
goto keyFound;
|
||||
|
||||
// no supported key found; treat as unhandled
|
||||
return 0;
|
||||
|
||||
keyFound:
|
||||
return (*(a->ah->KeyEvent))(a->ah, a, &ke);
|
||||
}
|
||||
|
||||
static gboolean areaWidget_key_press_event(GtkWidget *w, GdkEventKey *e)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
if (areaKeyEvent(a, 0, e))
|
||||
return GDK_EVENT_STOP;
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static gboolean areaWidget_key_release_event(GtkWidget *w, GdkEventKey *e)
|
||||
{
|
||||
areaWidget *aw = areaWidget(w);
|
||||
uiArea *a = aw->a;
|
||||
|
||||
if (areaKeyEvent(a, 1, e))
|
||||
return GDK_EVENT_STOP;
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
enum {
|
||||
pArea = 1,
|
||||
nProps,
|
||||
};
|
||||
|
||||
static GParamSpec *pspecArea;
|
||||
|
||||
static void areaWidget_set_property(GObject *obj, guint prop, const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
areaWidget *aw = areaWidget(obj);
|
||||
|
||||
switch (prop) {
|
||||
case pArea:
|
||||
aw->a = (uiArea *) g_value_get_pointer(value);
|
||||
aw->a->cc = &(aw->cc);
|
||||
return;
|
||||
}
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
|
||||
}
|
||||
|
||||
static void areaWidget_get_property(GObject *obj, guint prop, GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop, pspec);
|
||||
}
|
||||
|
||||
static void areaWidget_class_init(areaWidgetClass *class)
|
||||
{
|
||||
G_OBJECT_CLASS(class)->dispose = areaWidget_dispose;
|
||||
G_OBJECT_CLASS(class)->finalize = areaWidget_finalize;
|
||||
G_OBJECT_CLASS(class)->set_property = areaWidget_set_property;
|
||||
G_OBJECT_CLASS(class)->get_property = areaWidget_get_property;
|
||||
|
||||
GTK_WIDGET_CLASS(class)->size_allocate = areaWidget_size_allocate;
|
||||
GTK_WIDGET_CLASS(class)->draw = areaWidget_draw;
|
||||
GTK_WIDGET_CLASS(class)->get_preferred_height = areaWidget_get_preferred_height;
|
||||
GTK_WIDGET_CLASS(class)->get_preferred_width = areaWidget_get_preferred_width;
|
||||
GTK_WIDGET_CLASS(class)->button_press_event = areaWidget_button_press_event;
|
||||
GTK_WIDGET_CLASS(class)->button_release_event = areaWidget_button_release_event;
|
||||
GTK_WIDGET_CLASS(class)->motion_notify_event = areaWidget_motion_notify_event;
|
||||
GTK_WIDGET_CLASS(class)->enter_notify_event = areaWidget_enter_notify_event;
|
||||
GTK_WIDGET_CLASS(class)->leave_notify_event = areaWidget_leave_notify_event;
|
||||
GTK_WIDGET_CLASS(class)->key_press_event = areaWidget_key_press_event;
|
||||
GTK_WIDGET_CLASS(class)->key_release_event = areaWidget_key_release_event;
|
||||
|
||||
pspecArea = g_param_spec_pointer("libui-area",
|
||||
"libui-area",
|
||||
"uiArea.",
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
g_object_class_install_property(G_OBJECT_CLASS(class), pArea, pspecArea);
|
||||
}
|
||||
|
||||
// control implementation
|
||||
|
||||
uiUnixControlAllDefaults(uiArea)
|
||||
|
||||
void uiAreaSetSize(uiArea *a, int width, int height)
|
||||
{
|
||||
if (!a->scrolling)
|
||||
userbug("You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)", a);
|
||||
a->scrollWidth = width;
|
||||
a->scrollHeight = height;
|
||||
gtk_widget_queue_resize(a->areaWidget);
|
||||
}
|
||||
|
||||
void uiAreaQueueRedrawAll(uiArea *a)
|
||||
{
|
||||
gtk_widget_queue_draw(a->areaWidget);
|
||||
}
|
||||
|
||||
void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)
|
||||
{
|
||||
// TODO
|
||||
// TODO adjust adjustments and find source for that
|
||||
}
|
||||
|
||||
void uiAreaBeginUserWindowMove(uiArea *a)
|
||||
{
|
||||
GtkWidget *toplevel;
|
||||
|
||||
if (a->dragevent == NULL)
|
||||
userbug("cannot call uiAreaBeginUserWindowMove() outside of a Mouse() with Down != 0");
|
||||
// TODO don't we have a libui function for this? did I scrap it?
|
||||
// TODO widget or areaWidget?
|
||||
toplevel = gtk_widget_get_toplevel(a->widget);
|
||||
if (toplevel == NULL) {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
// the docs say to do this
|
||||
if (!gtk_widget_is_toplevel(toplevel)) {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
if (!GTK_IS_WINDOW(toplevel)) {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
gtk_window_begin_move_drag(GTK_WINDOW(toplevel),
|
||||
a->dragevent->button,
|
||||
a->dragevent->x_root, // TODO are these correct?
|
||||
a->dragevent->y_root,
|
||||
a->dragevent->time);
|
||||
}
|
||||
|
||||
static const GdkWindowEdge edges[] = {
|
||||
[uiWindowResizeEdgeLeft] = GDK_WINDOW_EDGE_WEST,
|
||||
[uiWindowResizeEdgeTop] = GDK_WINDOW_EDGE_NORTH,
|
||||
[uiWindowResizeEdgeRight] = GDK_WINDOW_EDGE_EAST,
|
||||
[uiWindowResizeEdgeBottom] = GDK_WINDOW_EDGE_SOUTH,
|
||||
[uiWindowResizeEdgeTopLeft] = GDK_WINDOW_EDGE_NORTH_WEST,
|
||||
[uiWindowResizeEdgeTopRight] = GDK_WINDOW_EDGE_NORTH_EAST,
|
||||
[uiWindowResizeEdgeBottomLeft] = GDK_WINDOW_EDGE_SOUTH_WEST,
|
||||
[uiWindowResizeEdgeBottomRight] = GDK_WINDOW_EDGE_SOUTH_EAST,
|
||||
};
|
||||
|
||||
void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)
|
||||
{
|
||||
GtkWidget *toplevel;
|
||||
|
||||
if (a->dragevent == NULL)
|
||||
userbug("cannot call uiAreaBeginUserWindowResize() outside of a Mouse() with Down != 0");
|
||||
// TODO don't we have a libui function for this? did I scrap it?
|
||||
// TODO widget or areaWidget?
|
||||
toplevel = gtk_widget_get_toplevel(a->widget);
|
||||
if (toplevel == NULL) {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
// the docs say to do this
|
||||
if (!gtk_widget_is_toplevel(toplevel)) {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
if (!GTK_IS_WINDOW(toplevel)) {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
gtk_window_begin_resize_drag(GTK_WINDOW(toplevel),
|
||||
edges[edge],
|
||||
a->dragevent->button,
|
||||
a->dragevent->x_root, // TODO are these correct?
|
||||
a->dragevent->y_root,
|
||||
a->dragevent->time);
|
||||
}
|
||||
|
||||
uiArea *uiNewArea(uiAreaHandler *ah)
|
||||
{
|
||||
uiArea *a;
|
||||
|
||||
uiUnixNewControl(uiArea, a);
|
||||
|
||||
a->ah = ah;
|
||||
a->scrolling = FALSE;
|
||||
|
||||
a->areaWidget = GTK_WIDGET(g_object_new(areaWidgetType,
|
||||
"libui-area", a,
|
||||
NULL));
|
||||
a->drawingArea = GTK_DRAWING_AREA(a->areaWidget);
|
||||
a->area = areaWidget(a->areaWidget);
|
||||
|
||||
a->widget = a->areaWidget;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)
|
||||
{
|
||||
uiArea *a;
|
||||
|
||||
uiUnixNewControl(uiArea, a);
|
||||
|
||||
a->ah = ah;
|
||||
a->scrolling = TRUE;
|
||||
a->scrollWidth = width;
|
||||
a->scrollHeight = height;
|
||||
|
||||
a->swidget = gtk_scrolled_window_new(NULL, NULL);
|
||||
a->scontainer = GTK_CONTAINER(a->swidget);
|
||||
a->sw = GTK_SCROLLED_WINDOW(a->swidget);
|
||||
|
||||
a->areaWidget = GTK_WIDGET(g_object_new(areaWidgetType,
|
||||
"libui-area", a,
|
||||
NULL));
|
||||
a->drawingArea = GTK_DRAWING_AREA(a->areaWidget);
|
||||
a->area = areaWidget(a->areaWidget);
|
||||
|
||||
a->widget = a->swidget;
|
||||
|
||||
gtk_container_add(a->scontainer, a->areaWidget);
|
||||
// and make the area visible; only the scrolled window's visibility is controlled by libui
|
||||
gtk_widget_show(a->areaWidget);
|
||||
|
||||
return a;
|
||||
}
|
159
src/libui_sdl/libui/unix/box.c
Normal file
159
src/libui_sdl/libui/unix/box.c
Normal file
@ -0,0 +1,159 @@
|
||||
// 7 april 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct boxChild {
|
||||
uiControl *c;
|
||||
int stretchy;
|
||||
gboolean oldhexpand;
|
||||
GtkAlign oldhalign;
|
||||
gboolean oldvexpand;
|
||||
GtkAlign oldvalign;
|
||||
};
|
||||
|
||||
struct uiBox {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkContainer *container;
|
||||
GtkBox *box;
|
||||
GArray *controls;
|
||||
int vertical;
|
||||
int padded;
|
||||
GtkSizeGroup *stretchygroup; // ensures all stretchy controls have the same size
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaultsExceptDestroy(uiBox)
|
||||
|
||||
#define ctrl(b, i) &g_array_index(b->controls, struct boxChild, i)
|
||||
|
||||
static void uiBoxDestroy(uiControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
struct boxChild *bc;
|
||||
guint i;
|
||||
|
||||
// kill the size group
|
||||
g_object_unref(b->stretchygroup);
|
||||
// free all controls
|
||||
for (i = 0; i < b->controls->len; i++) {
|
||||
bc = ctrl(b, i);
|
||||
uiControlSetParent(bc->c, NULL);
|
||||
// and make sure the widget itself stays alive
|
||||
uiUnixControlSetContainer(uiUnixControl(bc->c), b->container, TRUE);
|
||||
uiControlDestroy(bc->c);
|
||||
}
|
||||
g_array_free(b->controls, TRUE);
|
||||
// and then ourselves
|
||||
g_object_unref(b->widget);
|
||||
uiFreeControl(uiControl(b));
|
||||
}
|
||||
|
||||
void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
|
||||
{
|
||||
struct boxChild bc;
|
||||
GtkWidget *widget;
|
||||
|
||||
bc.c = c;
|
||||
bc.stretchy = stretchy;
|
||||
widget = GTK_WIDGET(uiControlHandle(bc.c));
|
||||
bc.oldhexpand = gtk_widget_get_hexpand(widget);
|
||||
bc.oldhalign = gtk_widget_get_halign(widget);
|
||||
bc.oldvexpand = gtk_widget_get_vexpand(widget);
|
||||
bc.oldvalign = gtk_widget_get_valign(widget);
|
||||
|
||||
if (bc.stretchy) {
|
||||
if (b->vertical) {
|
||||
gtk_widget_set_vexpand(widget, TRUE);
|
||||
gtk_widget_set_valign(widget, GTK_ALIGN_FILL);
|
||||
} else {
|
||||
gtk_widget_set_hexpand(widget, TRUE);
|
||||
gtk_widget_set_halign(widget, GTK_ALIGN_FILL);
|
||||
}
|
||||
gtk_size_group_add_widget(b->stretchygroup, widget);
|
||||
} else
|
||||
if (b->vertical)
|
||||
gtk_widget_set_vexpand(widget, FALSE);
|
||||
else
|
||||
gtk_widget_set_hexpand(widget, FALSE);
|
||||
// and make them fill the opposite direction
|
||||
if (b->vertical) {
|
||||
gtk_widget_set_hexpand(widget, TRUE);
|
||||
gtk_widget_set_halign(widget, GTK_ALIGN_FILL);
|
||||
} else {
|
||||
gtk_widget_set_vexpand(widget, TRUE);
|
||||
gtk_widget_set_valign(widget, GTK_ALIGN_FILL);
|
||||
}
|
||||
|
||||
uiControlSetParent(bc.c, uiControl(b));
|
||||
uiUnixControlSetContainer(uiUnixControl(bc.c), b->container, FALSE);
|
||||
g_array_append_val(b->controls, bc);
|
||||
}
|
||||
|
||||
void uiBoxDelete(uiBox *b, int index)
|
||||
{
|
||||
struct boxChild *bc;
|
||||
GtkWidget *widget;
|
||||
|
||||
bc = ctrl(b, index);
|
||||
widget = GTK_WIDGET(uiControlHandle(bc->c));
|
||||
|
||||
uiControlSetParent(bc->c, NULL);
|
||||
uiUnixControlSetContainer(uiUnixControl(bc->c), b->container, TRUE);
|
||||
|
||||
if (bc->stretchy)
|
||||
gtk_size_group_remove_widget(b->stretchygroup, widget);
|
||||
gtk_widget_set_hexpand(widget, bc->oldhexpand);
|
||||
gtk_widget_set_halign(widget, bc->oldhalign);
|
||||
gtk_widget_set_vexpand(widget, bc->oldvexpand);
|
||||
gtk_widget_set_valign(widget, bc->oldvalign);
|
||||
|
||||
g_array_remove_index(b->controls, index);
|
||||
}
|
||||
|
||||
int uiBoxPadded(uiBox *b)
|
||||
{
|
||||
return b->padded;
|
||||
}
|
||||
|
||||
void uiBoxSetPadded(uiBox *b, int padded)
|
||||
{
|
||||
b->padded = padded;
|
||||
if (b->padded)
|
||||
if (b->vertical)
|
||||
gtk_box_set_spacing(b->box, gtkYPadding);
|
||||
else
|
||||
gtk_box_set_spacing(b->box, gtkXPadding);
|
||||
else
|
||||
gtk_box_set_spacing(b->box, 0);
|
||||
}
|
||||
|
||||
static uiBox *finishNewBox(GtkOrientation orientation)
|
||||
{
|
||||
uiBox *b;
|
||||
|
||||
uiUnixNewControl(uiBox, b);
|
||||
|
||||
b->widget = gtk_box_new(orientation, 0);
|
||||
b->container = GTK_CONTAINER(b->widget);
|
||||
b->box = GTK_BOX(b->widget);
|
||||
|
||||
b->vertical = orientation == GTK_ORIENTATION_VERTICAL;
|
||||
|
||||
if (b->vertical)
|
||||
b->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);
|
||||
else
|
||||
b->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
|
||||
|
||||
b->controls = g_array_new(FALSE, TRUE, sizeof (struct boxChild));
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
uiBox *uiNewHorizontalBox(void)
|
||||
{
|
||||
return finishNewBox(GTK_ORIENTATION_HORIZONTAL);
|
||||
}
|
||||
|
||||
uiBox *uiNewVerticalBox(void)
|
||||
{
|
||||
return finishNewBox(GTK_ORIENTATION_VERTICAL);
|
||||
}
|
55
src/libui_sdl/libui/unix/button.c
Normal file
55
src/libui_sdl/libui/unix/button.c
Normal file
@ -0,0 +1,55 @@
|
||||
// 10 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiButton {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkButton *button;
|
||||
void (*onClicked)(uiButton *, void *);
|
||||
void *onClickedData;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiButton)
|
||||
|
||||
static void onClicked(GtkButton *button, gpointer data)
|
||||
{
|
||||
uiButton *b = uiButton(data);
|
||||
|
||||
(*(b->onClicked))(b, b->onClickedData);
|
||||
}
|
||||
|
||||
static void defaultOnClicked(uiButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
char *uiButtonText(uiButton *b)
|
||||
{
|
||||
return uiUnixStrdupText(gtk_button_get_label(b->button));
|
||||
}
|
||||
|
||||
void uiButtonSetText(uiButton *b, const char *text)
|
||||
{
|
||||
gtk_button_set_label(b->button, text);
|
||||
}
|
||||
|
||||
void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)
|
||||
{
|
||||
b->onClicked = f;
|
||||
b->onClickedData = data;
|
||||
}
|
||||
|
||||
uiButton *uiNewButton(const char *text)
|
||||
{
|
||||
uiButton *b;
|
||||
|
||||
uiUnixNewControl(uiButton, b);
|
||||
|
||||
b->widget = gtk_button_new_with_label(text);
|
||||
b->button = GTK_BUTTON(b->widget);
|
||||
|
||||
g_signal_connect(b->widget, "clicked", G_CALLBACK(onClicked), b);
|
||||
uiButtonOnClicked(b, defaultOnClicked, NULL);
|
||||
|
||||
return b;
|
||||
}
|
299
src/libui_sdl/libui/unix/cellrendererbutton.c
Normal file
299
src/libui_sdl/libui/unix/cellrendererbutton.c
Normal file
@ -0,0 +1,299 @@
|
||||
// 28 june 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// TODOs
|
||||
// - it's a rather tight fit
|
||||
// - selected row text color is white
|
||||
// - resizing a column with a button in it crashes the program
|
||||
// - accessibility
|
||||
// - right side too big?
|
||||
|
||||
#define cellRendererButtonType (cellRendererButton_get_type())
|
||||
#define cellRendererButton(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), cellRendererButtonType, cellRendererButton))
|
||||
#define isCellRendererButton(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), cellRendererButtonType))
|
||||
#define cellRendererButtonClass(class) (G_TYPE_CHECK_CLASS_CAST((class), cellRendererButtonType, cellRendererButtonClass))
|
||||
#define isCellRendererButtonClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), cellRendererButton))
|
||||
#define getCellRendererButtonClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), cellRendererButtonType, cellRendererButtonClass))
|
||||
|
||||
typedef struct cellRendererButton cellRendererButton;
|
||||
typedef struct cellRendererButtonClass cellRendererButtonClass;
|
||||
|
||||
struct cellRendererButton {
|
||||
GtkCellRenderer parent_instance;
|
||||
char *text;
|
||||
};
|
||||
|
||||
struct cellRendererButtonClass {
|
||||
GtkCellRendererClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(cellRendererButton, cellRendererButton, GTK_TYPE_CELL_RENDERER)
|
||||
|
||||
static void cellRendererButton_init(cellRendererButton *c)
|
||||
{
|
||||
g_object_set(c, "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
|
||||
// the standard cell renderers all do this
|
||||
gtk_cell_renderer_set_padding(GTK_CELL_RENDERER(c), 2, 2);
|
||||
}
|
||||
|
||||
static void cellRendererButton_dispose(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(cellRendererButton_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void cellRendererButton_finalize(GObject *obj)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(obj);
|
||||
|
||||
if (c->text != NULL) {
|
||||
g_free(c->text);
|
||||
c->text = NULL;
|
||||
}
|
||||
G_OBJECT_CLASS(cellRendererButton_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
static GtkSizeRequestMode cellRendererButton_get_request_mode(GtkCellRenderer *r)
|
||||
{
|
||||
return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
|
||||
}
|
||||
|
||||
// this is basically what GtkCellRendererToggle did in 3.10 and does in 3.20, as well as what the Foreign Drawing gtk3-demo demo does
|
||||
static GtkStyleContext *setButtonStyle(GtkWidget *widget)
|
||||
{
|
||||
GtkStyleContext *base, *context;
|
||||
GtkWidgetPath *path;
|
||||
|
||||
base = gtk_widget_get_style_context(widget);
|
||||
context = gtk_style_context_new();
|
||||
|
||||
path = gtk_widget_path_copy(gtk_style_context_get_path(base));
|
||||
gtk_widget_path_append_type(path, G_TYPE_NONE);
|
||||
if (!FUTURE_gtk_widget_path_iter_set_object_name(path, -1, "button"))
|
||||
// not on 3.20; try the type
|
||||
gtk_widget_path_iter_set_object_type(path, -1, GTK_TYPE_BUTTON);
|
||||
|
||||
gtk_style_context_set_path(context, path);
|
||||
gtk_style_context_set_parent(context, base);
|
||||
// the gtk3-demo example (which says we need to do this) uses gtk_widget_path_iter_get_state(path, -1) but that's not available until 3.14
|
||||
// TODO make a future for that too
|
||||
gtk_style_context_set_state(context, gtk_style_context_get_state(base));
|
||||
gtk_widget_path_unref(path);
|
||||
|
||||
// and if the above widget path screwery stil doesn't work, this will
|
||||
gtk_style_context_add_class(context, GTK_STYLE_CLASS_BUTTON);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
void unsetButtonStyle(GtkStyleContext *context)
|
||||
{
|
||||
g_object_unref(context);
|
||||
}
|
||||
|
||||
// this is based on what GtkCellRendererText does
|
||||
static void cellRendererButton_get_preferred_width(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(r);
|
||||
gint xpad;
|
||||
PangoLayout *layout;
|
||||
PangoRectangle rect;
|
||||
gint out;
|
||||
|
||||
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, NULL);
|
||||
|
||||
layout = gtk_widget_create_pango_layout(widget, c->text);
|
||||
pango_layout_set_width(layout, -1);
|
||||
pango_layout_get_extents(layout, NULL, &rect);
|
||||
g_object_unref(layout);
|
||||
|
||||
out = 2 * xpad + PANGO_PIXELS_CEIL(rect.width);
|
||||
if (minimum != NULL)
|
||||
*minimum = out;
|
||||
if (natural != NULL)
|
||||
*natural = out;
|
||||
}
|
||||
|
||||
// this is based on what GtkCellRendererText does
|
||||
static void cellRendererButton_get_preferred_height_for_width(GtkCellRenderer *r, GtkWidget *widget, gint width, gint *minimum, gint *natural)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(r);
|
||||
gint xpad, ypad;
|
||||
PangoLayout *layout;
|
||||
gint height;
|
||||
gint out;
|
||||
|
||||
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);
|
||||
|
||||
layout = gtk_widget_create_pango_layout(widget, c->text);
|
||||
pango_layout_set_width(layout, ((2 * xpad + width) * PANGO_SCALE));
|
||||
pango_layout_get_pixel_size(layout, NULL, &height);
|
||||
g_object_unref(layout);
|
||||
|
||||
out = 2 * ypad + height;
|
||||
if (minimum != NULL)
|
||||
*minimum = out;
|
||||
if (natural != NULL)
|
||||
*natural = out;
|
||||
}
|
||||
|
||||
// this is basically what GtkCellRendererText does
|
||||
static void cellRendererButton_get_preferred_height(GtkCellRenderer *r, GtkWidget *widget, gint *minimum, gint *natural)
|
||||
{
|
||||
gint width;
|
||||
|
||||
gtk_cell_renderer_get_preferred_width(r, widget, &width, NULL);
|
||||
gtk_cell_renderer_get_preferred_height_for_width(r, widget, width, minimum, natural);
|
||||
}
|
||||
|
||||
// this is based on what GtkCellRendererText does
|
||||
static void cellRendererButton_get_aligned_area(GtkCellRenderer *r, GtkWidget *widget, GtkCellRendererState flags, const GdkRectangle *cell_area, GdkRectangle *aligned_area)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(r);
|
||||
gint xpad, ypad;
|
||||
PangoLayout *layout;
|
||||
PangoRectangle rect;
|
||||
gfloat xalign, yalign;
|
||||
gint xoffset, yoffset;
|
||||
|
||||
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);
|
||||
|
||||
layout = gtk_widget_create_pango_layout(widget, c->text);
|
||||
pango_layout_set_width(layout, -1);
|
||||
pango_layout_get_pixel_extents(layout, NULL, &rect);
|
||||
|
||||
xoffset = 0;
|
||||
yoffset = 0;
|
||||
if (cell_area != NULL) {
|
||||
gtk_cell_renderer_get_alignment(GTK_CELL_RENDERER(c), &xalign, &yalign);
|
||||
xoffset = cell_area->width - (2 * xpad + rect.width);
|
||||
// use explicit casts just to be safe
|
||||
if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
|
||||
xoffset = ((gdouble) xoffset) * (1.0 - xalign);
|
||||
else
|
||||
xoffset *= ((gdouble) xoffset) * xalign;
|
||||
yoffset = yalign * (cell_area->height - (2 * ypad + rect.height));
|
||||
yoffset = MAX(yoffset, 0);
|
||||
}
|
||||
|
||||
aligned_area->x = cell_area->x + xoffset;
|
||||
aligned_area->y = cell_area->y + yoffset;
|
||||
aligned_area->width = 2 * xpad + rect.width;
|
||||
aligned_area->height = 2 * ypad + rect.height;
|
||||
|
||||
g_object_unref(layout);
|
||||
}
|
||||
|
||||
// this is based on both what GtkCellRendererText does and what GtkCellRendererToggle does
|
||||
static void cellRendererButton_render(GtkCellRenderer *r, cairo_t *cr, GtkWidget *widget, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(r);
|
||||
gint xpad, ypad;
|
||||
GdkRectangle alignedArea;
|
||||
gint xoffset, yoffset;
|
||||
GtkStyleContext *context;
|
||||
PangoLayout *layout;
|
||||
PangoRectangle rect;
|
||||
|
||||
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);
|
||||
gtk_cell_renderer_get_aligned_area(GTK_CELL_RENDERER(c), widget, flags, cell_area, &alignedArea);
|
||||
xoffset = alignedArea.x - cell_area->x;
|
||||
yoffset = alignedArea.y - cell_area->y;
|
||||
|
||||
context = setButtonStyle(widget);
|
||||
layout = gtk_widget_create_pango_layout(widget, c->text);
|
||||
|
||||
gtk_render_background(context, cr,
|
||||
background_area->x + xoffset + xpad,
|
||||
background_area->y + yoffset + ypad,
|
||||
background_area->width - 2 * xpad,
|
||||
background_area->height - 2 * ypad);
|
||||
gtk_render_frame(context, cr,
|
||||
background_area->x + xoffset + xpad,
|
||||
background_area->y + yoffset + ypad,
|
||||
background_area->width - 2 * xpad,
|
||||
background_area->height - 2 * ypad);
|
||||
|
||||
pango_layout_set_width(layout, -1);
|
||||
pango_layout_get_pixel_extents(layout, NULL, &rect);
|
||||
xoffset -= rect.x;
|
||||
gtk_render_layout(context, cr,
|
||||
cell_area->x + xoffset + xpad,
|
||||
cell_area->y + yoffset + ypad,
|
||||
layout);
|
||||
|
||||
g_object_unref(layout);
|
||||
unsetButtonStyle(context);
|
||||
}
|
||||
|
||||
static guint clickedSignal;
|
||||
|
||||
static gboolean cellRendererButton_activate(GtkCellRenderer *r, GdkEvent *e, GtkWidget *widget, const gchar *path, const GdkRectangle *background_area, const GdkRectangle *cell_area, GtkCellRendererState flags)
|
||||
{
|
||||
g_signal_emit(r, clickedSignal, 0, path);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GParamSpec *props[2] = { NULL, NULL };
|
||||
|
||||
static void cellRendererButton_set_property(GObject *object, guint prop, const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(object);
|
||||
|
||||
if (prop != 1) {
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(c, prop, pspec);
|
||||
return;
|
||||
}
|
||||
if (c->text != NULL)
|
||||
g_free(c->text);
|
||||
c->text = g_value_dup_string(value);
|
||||
// GtkCellRendererText doesn't queue a redraw; we won't either
|
||||
}
|
||||
|
||||
static void cellRendererButton_get_property(GObject *object, guint prop, GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
cellRendererButton *c = cellRendererButton(object);
|
||||
|
||||
if (prop != 1) {
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(c, prop, pspec);
|
||||
return;
|
||||
}
|
||||
g_value_set_string(value, c->text);
|
||||
}
|
||||
|
||||
static void cellRendererButton_class_init(cellRendererButtonClass *class)
|
||||
{
|
||||
G_OBJECT_CLASS(class)->dispose = cellRendererButton_dispose;
|
||||
G_OBJECT_CLASS(class)->finalize = cellRendererButton_finalize;
|
||||
G_OBJECT_CLASS(class)->set_property = cellRendererButton_set_property;
|
||||
G_OBJECT_CLASS(class)->get_property = cellRendererButton_get_property;
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_request_mode = cellRendererButton_get_request_mode;
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_preferred_width = cellRendererButton_get_preferred_width;
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_preferred_height_for_width = cellRendererButton_get_preferred_height_for_width;
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_preferred_height = cellRendererButton_get_preferred_height;
|
||||
// don't provide a get_preferred_width_for_height()
|
||||
GTK_CELL_RENDERER_CLASS(class)->get_aligned_area = cellRendererButton_get_aligned_area;
|
||||
// don't provide a get_size()
|
||||
GTK_CELL_RENDERER_CLASS(class)->render = cellRendererButton_render;
|
||||
GTK_CELL_RENDERER_CLASS(class)->activate = cellRendererButton_activate;
|
||||
// don't provide a start_editing()
|
||||
|
||||
props[1] = g_param_spec_string("text",
|
||||
"Text",
|
||||
"Button text",
|
||||
"",
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
||||
g_object_class_install_properties(G_OBJECT_CLASS(class), 2, props);
|
||||
|
||||
clickedSignal = g_signal_new("clicked",
|
||||
G_TYPE_FROM_CLASS(class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE,
|
||||
1, G_TYPE_STRING);
|
||||
}
|
||||
|
||||
GtkCellRenderer *newCellRendererButton(void)
|
||||
{
|
||||
return GTK_CELL_RENDERER(g_object_new(cellRendererButtonType, NULL));
|
||||
}
|
78
src/libui_sdl/libui/unix/checkbox.c
Normal file
78
src/libui_sdl/libui/unix/checkbox.c
Normal file
@ -0,0 +1,78 @@
|
||||
// 10 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiCheckbox {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkButton *button;
|
||||
GtkToggleButton *toggleButton;
|
||||
GtkCheckButton *checkButton;
|
||||
void (*onToggled)(uiCheckbox *, void *);
|
||||
void *onToggledData;
|
||||
gulong onToggledSignal;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiCheckbox)
|
||||
|
||||
static void onToggled(GtkToggleButton *b, gpointer data)
|
||||
{
|
||||
uiCheckbox *c = uiCheckbox(data);
|
||||
|
||||
(*(c->onToggled))(c, c->onToggledData);
|
||||
}
|
||||
|
||||
static void defaultOnToggled(uiCheckbox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
char *uiCheckboxText(uiCheckbox *c)
|
||||
{
|
||||
return uiUnixStrdupText(gtk_button_get_label(c->button));
|
||||
}
|
||||
|
||||
void uiCheckboxSetText(uiCheckbox *c, const char *text)
|
||||
{
|
||||
gtk_button_set_label(GTK_BUTTON(c->button), text);
|
||||
}
|
||||
|
||||
void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)
|
||||
{
|
||||
c->onToggled = f;
|
||||
c->onToggledData = data;
|
||||
}
|
||||
|
||||
int uiCheckboxChecked(uiCheckbox *c)
|
||||
{
|
||||
return gtk_toggle_button_get_active(c->toggleButton) != FALSE;
|
||||
}
|
||||
|
||||
void uiCheckboxSetChecked(uiCheckbox *c, int checked)
|
||||
{
|
||||
gboolean active;
|
||||
|
||||
active = FALSE;
|
||||
if (checked)
|
||||
active = TRUE;
|
||||
// we need to inhibit sending of ::toggled because this WILL send a ::toggled otherwise
|
||||
g_signal_handler_block(c->toggleButton, c->onToggledSignal);
|
||||
gtk_toggle_button_set_active(c->toggleButton, active);
|
||||
g_signal_handler_unblock(c->toggleButton, c->onToggledSignal);
|
||||
}
|
||||
|
||||
uiCheckbox *uiNewCheckbox(const char *text)
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
uiUnixNewControl(uiCheckbox, c);
|
||||
|
||||
c->widget = gtk_check_button_new_with_label(text);
|
||||
c->button = GTK_BUTTON(c->widget);
|
||||
c->toggleButton = GTK_TOGGLE_BUTTON(c->widget);
|
||||
c->checkButton = GTK_CHECK_BUTTON(c->widget);
|
||||
|
||||
c->onToggledSignal = g_signal_connect(c->widget, "toggled", G_CALLBACK(onToggled), c);
|
||||
uiCheckboxOnToggled(c, defaultOnToggled, NULL);
|
||||
|
||||
return c;
|
||||
}
|
120
src/libui_sdl/libui/unix/child.c
Normal file
120
src/libui_sdl/libui/unix/child.c
Normal file
@ -0,0 +1,120 @@
|
||||
// 28 august 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// This file contains helpers for managing child controls.
|
||||
|
||||
struct child {
|
||||
uiControl *c;
|
||||
GtkWidget *widget;
|
||||
|
||||
gboolean oldhexpand;
|
||||
GtkAlign oldhalign;
|
||||
gboolean oldvexpand;
|
||||
GtkAlign oldvalign;
|
||||
|
||||
// Some children can be boxed; that is, they can have an optionally-margined box around them.
|
||||
// uiGroup, uiTab, and uiWindow all do this.
|
||||
GtkWidget *box;
|
||||
|
||||
// If the child is not boxed, this is its parent.
|
||||
// If the child is boxed, this is the box.
|
||||
GtkContainer *parent;
|
||||
|
||||
// This flag is for users of these functions.
|
||||
// For uiBox, this is "spaced".
|
||||
// For uiTab, this is "margined". (uiGroup and uiWindow have to maintain their margined state themselves, since the margined state is independent of whether there is a child for those two.)
|
||||
int flag;
|
||||
};
|
||||
|
||||
struct child *newChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer)
|
||||
{
|
||||
struct child *c;
|
||||
|
||||
if (child == NULL)
|
||||
return NULL;
|
||||
|
||||
c = uiNew(struct child);
|
||||
c->c = child;
|
||||
c->widget = GTK_WIDGET(uiControlHandle(c->c));
|
||||
|
||||
c->oldhexpand = gtk_widget_get_hexpand(c->widget);
|
||||
c->oldhalign = gtk_widget_get_halign(c->widget);
|
||||
c->oldvexpand = gtk_widget_get_vexpand(c->widget);
|
||||
c->oldvalign = gtk_widget_get_valign(c->widget);
|
||||
|
||||
uiControlSetParent(c->c, parent);
|
||||
uiUnixControlSetContainer(uiUnixControl(c->c), parentContainer, FALSE);
|
||||
c->parent = parentContainer;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
struct child *newChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined)
|
||||
{
|
||||
struct child *c;
|
||||
GtkWidget *box;
|
||||
|
||||
if (child == NULL)
|
||||
return NULL;
|
||||
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_show(box);
|
||||
c = newChild(child, parent, GTK_CONTAINER(box));
|
||||
gtk_widget_set_hexpand(c->widget, TRUE);
|
||||
gtk_widget_set_halign(c->widget, GTK_ALIGN_FILL);
|
||||
gtk_widget_set_vexpand(c->widget, TRUE);
|
||||
gtk_widget_set_valign(c->widget, GTK_ALIGN_FILL);
|
||||
c->box = box;
|
||||
gtk_container_add(parentContainer, c->box);
|
||||
childSetMargined(c, margined);
|
||||
return c;
|
||||
}
|
||||
|
||||
void childRemove(struct child *c)
|
||||
{
|
||||
uiControlSetParent(c->c, NULL);
|
||||
uiUnixControlSetContainer(uiUnixControl(c->c), c->parent, TRUE);
|
||||
|
||||
gtk_widget_set_hexpand(c->widget, c->oldhexpand);
|
||||
gtk_widget_set_halign(c->widget, c->oldhalign);
|
||||
gtk_widget_set_vexpand(c->widget, c->oldvexpand);
|
||||
gtk_widget_set_valign(c->widget, c->oldvalign);
|
||||
|
||||
if (c->box != NULL)
|
||||
gtk_widget_destroy(c->box);
|
||||
|
||||
uiFree(c);
|
||||
}
|
||||
|
||||
void childDestroy(struct child *c)
|
||||
{
|
||||
uiControl *child;
|
||||
|
||||
child = c->c;
|
||||
childRemove(c);
|
||||
uiControlDestroy(child);
|
||||
}
|
||||
|
||||
GtkWidget *childWidget(struct child *c)
|
||||
{
|
||||
return c->widget;
|
||||
}
|
||||
|
||||
int childFlag(struct child *c)
|
||||
{
|
||||
return c->flag;
|
||||
}
|
||||
|
||||
void childSetFlag(struct child *c, int flag)
|
||||
{
|
||||
c->flag = flag;
|
||||
}
|
||||
|
||||
GtkWidget *childBox(struct child *c)
|
||||
{
|
||||
return c->box;
|
||||
}
|
||||
|
||||
void childSetMargined(struct child *c, int margined)
|
||||
{
|
||||
setMargined(GTK_CONTAINER(c->box), margined);
|
||||
}
|
80
src/libui_sdl/libui/unix/colorbutton.c
Normal file
80
src/libui_sdl/libui/unix/colorbutton.c
Normal file
@ -0,0 +1,80 @@
|
||||
// 15 may 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiColorButton {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkButton *button;
|
||||
GtkColorButton *cb;
|
||||
GtkColorChooser *cc;
|
||||
void (*onChanged)(uiColorButton *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiColorButton)
|
||||
|
||||
static void onColorSet(GtkColorButton *button, gpointer data)
|
||||
{
|
||||
uiColorButton *b = uiColorButton(data);
|
||||
|
||||
(*(b->onChanged))(b, b->onChangedData);
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiColorButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)
|
||||
{
|
||||
GdkRGBA rgba;
|
||||
|
||||
gtk_color_chooser_get_rgba(b->cc, &rgba);
|
||||
*r = rgba.red;
|
||||
*g = rgba.green;
|
||||
*bl = rgba.blue;
|
||||
*a = rgba.alpha;
|
||||
}
|
||||
|
||||
void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)
|
||||
{
|
||||
GdkRGBA rgba;
|
||||
|
||||
rgba.red = r;
|
||||
rgba.green = g;
|
||||
rgba.blue = bl;
|
||||
rgba.alpha = a;
|
||||
// no need to inhibit the signal; color-set is documented as only being sent when the user changes the color
|
||||
gtk_color_chooser_set_rgba(b->cc, &rgba);
|
||||
}
|
||||
|
||||
void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)
|
||||
{
|
||||
b->onChanged = f;
|
||||
b->onChangedData = data;
|
||||
}
|
||||
|
||||
uiColorButton *uiNewColorButton(void)
|
||||
{
|
||||
uiColorButton *b;
|
||||
GdkRGBA black;
|
||||
|
||||
uiUnixNewControl(uiColorButton, b);
|
||||
|
||||
// I'm not sure what the initial color is; set up a real one
|
||||
black.red = 0.0;
|
||||
black.green = 0.0;
|
||||
black.blue = 0.0;
|
||||
black.alpha = 1.0;
|
||||
b->widget = gtk_color_button_new_with_rgba(&black);
|
||||
b->button = GTK_BUTTON(b->widget);
|
||||
b->cb = GTK_COLOR_BUTTON(b->widget);
|
||||
b->cc = GTK_COLOR_CHOOSER(b->widget);
|
||||
|
||||
gtk_color_chooser_set_use_alpha(b->cc, TRUE);
|
||||
|
||||
g_signal_connect(b->widget, "color-set", G_CALLBACK(onColorSet), b);
|
||||
uiColorButtonOnChanged(b, defaultOnChanged, NULL);
|
||||
|
||||
return b;
|
||||
}
|
66
src/libui_sdl/libui/unix/combobox.c
Normal file
66
src/libui_sdl/libui/unix/combobox.c
Normal file
@ -0,0 +1,66 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiCombobox {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkComboBox *combobox;
|
||||
GtkComboBoxText *comboboxText;
|
||||
void (*onSelected)(uiCombobox *, void *);
|
||||
void *onSelectedData;
|
||||
gulong onSelectedSignal;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiCombobox)
|
||||
|
||||
static void onChanged(GtkComboBox *cbox, gpointer data)
|
||||
{
|
||||
uiCombobox *c = uiCombobox(data);
|
||||
|
||||
(*(c->onSelected))(c, c->onSelectedData);
|
||||
}
|
||||
|
||||
static void defaultOnSelected(uiCombobox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiComboboxAppend(uiCombobox *c, const char *text)
|
||||
{
|
||||
gtk_combo_box_text_append(c->comboboxText, NULL, text);
|
||||
}
|
||||
|
||||
int uiComboboxSelected(uiCombobox *c)
|
||||
{
|
||||
return gtk_combo_box_get_active(c->combobox);
|
||||
}
|
||||
|
||||
void uiComboboxSetSelected(uiCombobox *c, int n)
|
||||
{
|
||||
// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise
|
||||
g_signal_handler_block(c->combobox, c->onSelectedSignal);
|
||||
gtk_combo_box_set_active(c->combobox, n);
|
||||
g_signal_handler_unblock(c->combobox, c->onSelectedSignal);
|
||||
}
|
||||
|
||||
void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)
|
||||
{
|
||||
c->onSelected = f;
|
||||
c->onSelectedData = data;
|
||||
}
|
||||
|
||||
uiCombobox *uiNewCombobox(void)
|
||||
{
|
||||
uiCombobox *c;
|
||||
|
||||
uiUnixNewControl(uiCombobox, c);
|
||||
|
||||
c->widget = gtk_combo_box_text_new();
|
||||
c->combobox = GTK_COMBO_BOX(c->widget);
|
||||
c->comboboxText = GTK_COMBO_BOX_TEXT(c->widget);
|
||||
|
||||
c->onSelectedSignal = g_signal_connect(c->widget, "changed", G_CALLBACK(onChanged), c);
|
||||
uiComboboxOnSelected(c, defaultOnSelected, NULL);
|
||||
|
||||
return c;
|
||||
}
|
14
src/libui_sdl/libui/unix/control.c
Normal file
14
src/libui_sdl/libui/unix/control.c
Normal file
@ -0,0 +1,14 @@
|
||||
// 16 august 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
void uiUnixControlSetContainer(uiUnixControl *c, GtkContainer *container, gboolean remove)
|
||||
{
|
||||
(*(c->SetContainer))(c, container, remove);
|
||||
}
|
||||
|
||||
#define uiUnixControlSignature 0x556E6978
|
||||
|
||||
uiUnixControl *uiUnixAllocControl(size_t n, uint32_t typesig, const char *typenamestr)
|
||||
{
|
||||
return uiUnixControl(uiAllocControl(n, uiUnixControlSignature, typesig, typenamestr));
|
||||
}
|
599
src/libui_sdl/libui/unix/datetimepicker.c
Normal file
599
src/libui_sdl/libui/unix/datetimepicker.c
Normal file
@ -0,0 +1,599 @@
|
||||
// 4 september 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// LONGTERM imitate gnome-calendar's day/month/year entries above the calendar
|
||||
// LONGTERM allow entering a 24-hour hour in the hour spinbutton and adjust accordingly
|
||||
|
||||
#define dateTimePickerWidgetType (dateTimePickerWidget_get_type())
|
||||
#define dateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), dateTimePickerWidgetType, dateTimePickerWidget))
|
||||
#define isDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), dateTimePickerWidgetType))
|
||||
#define dateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), dateTimePickerWidgetType, dateTimePickerWidgetClass))
|
||||
#define isDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), dateTimePickerWidget))
|
||||
#define getDateTimePickerWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), dateTimePickerWidgetType, dateTimePickerWidgetClass))
|
||||
|
||||
typedef struct dateTimePickerWidget dateTimePickerWidget;
|
||||
typedef struct dateTimePickerWidgetClass dateTimePickerWidgetClass;
|
||||
|
||||
struct dateTimePickerWidget {
|
||||
GtkToggleButton parent_instance;
|
||||
|
||||
gulong toggledSignal;
|
||||
|
||||
gboolean hasTime;
|
||||
gboolean hasDate;
|
||||
|
||||
GtkWidget *window;
|
||||
GtkWidget *box;
|
||||
GtkWidget *calendar;
|
||||
GtkWidget *timebox;
|
||||
GtkWidget *hours;
|
||||
GtkWidget *minutes;
|
||||
GtkWidget *seconds;
|
||||
GtkWidget *ampm;
|
||||
|
||||
gulong hoursBlock;
|
||||
gulong minutesBlock;
|
||||
gulong secondsBlock;
|
||||
gulong ampmBlock;
|
||||
|
||||
GdkDevice *keyboard;
|
||||
GdkDevice *mouse;
|
||||
};
|
||||
|
||||
struct dateTimePickerWidgetClass {
|
||||
GtkToggleButtonClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(dateTimePickerWidget, dateTimePickerWidget, GTK_TYPE_TOGGLE_BUTTON)
|
||||
|
||||
static int realSpinValue(GtkSpinButton *spinButton)
|
||||
{
|
||||
GtkAdjustment *adj;
|
||||
|
||||
adj = gtk_spin_button_get_adjustment(spinButton);
|
||||
return (int) gtk_adjustment_get_value(adj);
|
||||
}
|
||||
|
||||
static void setRealSpinValue(GtkSpinButton *spinButton, int value, gulong block)
|
||||
{
|
||||
GtkAdjustment *adj;
|
||||
|
||||
g_signal_handler_block(spinButton, block);
|
||||
adj = gtk_spin_button_get_adjustment(spinButton);
|
||||
gtk_adjustment_set_value(adj, value);
|
||||
g_signal_handler_unblock(spinButton, block);
|
||||
}
|
||||
|
||||
static GDateTime *selected(dateTimePickerWidget *d)
|
||||
{
|
||||
// choose a day for which all times are likely to be valid for the default date in case we're only dealing with time
|
||||
guint year = 1970, month = 1, day = 1;
|
||||
guint hour = 0, minute = 0, second = 0;
|
||||
|
||||
if (d->hasDate) {
|
||||
gtk_calendar_get_date(GTK_CALENDAR(d->calendar), &year, &month, &day);
|
||||
month++; // GtkCalendar/GDateTime differences
|
||||
}
|
||||
if (d->hasTime) {
|
||||
hour = realSpinValue(GTK_SPIN_BUTTON(d->hours));
|
||||
if (realSpinValue(GTK_SPIN_BUTTON(d->ampm)) != 0)
|
||||
hour += 12;
|
||||
minute = realSpinValue(GTK_SPIN_BUTTON(d->minutes));
|
||||
second = realSpinValue(GTK_SPIN_BUTTON(d->seconds));
|
||||
}
|
||||
return g_date_time_new_local(year, month, day, hour, minute, second);
|
||||
}
|
||||
|
||||
static void setLabel(dateTimePickerWidget *d)
|
||||
{
|
||||
GDateTime *dt;
|
||||
char *fmt;
|
||||
char *msg;
|
||||
gboolean free;
|
||||
|
||||
dt = selected(d);
|
||||
free = FALSE;
|
||||
if (d->hasDate && d->hasTime) {
|
||||
// don't use D_T_FMT; that's too verbose
|
||||
fmt = g_strdup_printf("%s %s", nl_langinfo(D_FMT), nl_langinfo(T_FMT));
|
||||
free = TRUE;
|
||||
} else if (d->hasDate)
|
||||
fmt = nl_langinfo(D_FMT);
|
||||
else
|
||||
fmt = nl_langinfo(T_FMT);
|
||||
msg = g_date_time_format(dt, fmt);
|
||||
gtk_button_set_label(GTK_BUTTON(d), msg);
|
||||
g_free(msg);
|
||||
if (free)
|
||||
g_free(fmt);
|
||||
g_date_time_unref(dt);
|
||||
}
|
||||
|
||||
static void dateTimeChanged(dateTimePickerWidget *d)
|
||||
{
|
||||
setLabel(d);
|
||||
// TODO fire event here
|
||||
}
|
||||
|
||||
// we don't want ::toggled to be sent again
|
||||
static void setActive(dateTimePickerWidget *d, gboolean active)
|
||||
{
|
||||
g_signal_handler_block(d, d->toggledSignal);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d), active);
|
||||
g_signal_handler_unblock(d, d->toggledSignal);
|
||||
}
|
||||
|
||||
// like startGrab() below, a lot of this is in the order that GtkComboBox does it
|
||||
static void endGrab(dateTimePickerWidget *d)
|
||||
{
|
||||
if (d->keyboard != NULL)
|
||||
gdk_device_ungrab(d->keyboard, GDK_CURRENT_TIME);
|
||||
gdk_device_ungrab(d->mouse, GDK_CURRENT_TIME);
|
||||
gtk_device_grab_remove(d->window, d->mouse);
|
||||
d->keyboard = NULL;
|
||||
d->mouse = NULL;
|
||||
}
|
||||
|
||||
static void hidePopup(dateTimePickerWidget *d)
|
||||
{
|
||||
endGrab(d);
|
||||
gtk_widget_hide(d->window);
|
||||
setActive(d, FALSE);
|
||||
}
|
||||
|
||||
// this consolidates a good chunk of what GtkComboBox does
|
||||
static gboolean startGrab(dateTimePickerWidget *d)
|
||||
{
|
||||
GdkDevice *dev;
|
||||
guint32 time;
|
||||
GdkWindow *window;
|
||||
GdkDevice *keyboard, *mouse;
|
||||
|
||||
dev = gtk_get_current_event_device();
|
||||
if (dev == NULL) {
|
||||
// this is what GtkComboBox does
|
||||
// since no device was set, just use the first available "master device"
|
||||
GdkDisplay *disp;
|
||||
GdkDeviceManager *dm;
|
||||
GList *list;
|
||||
|
||||
disp = gtk_widget_get_display(GTK_WIDGET(d));
|
||||
dm = gdk_display_get_device_manager(disp);
|
||||
list = gdk_device_manager_list_devices(dm, GDK_DEVICE_TYPE_MASTER);
|
||||
dev = (GdkDevice *) (list->data);
|
||||
g_list_free(list);
|
||||
}
|
||||
|
||||
time = gtk_get_current_event_time();
|
||||
keyboard = dev;
|
||||
mouse = gdk_device_get_associated_device(dev);
|
||||
if (gdk_device_get_source(dev) != GDK_SOURCE_KEYBOARD) {
|
||||
dev = mouse;
|
||||
mouse = keyboard;
|
||||
keyboard = dev;
|
||||
}
|
||||
|
||||
window = gtk_widget_get_window(d->window);
|
||||
if (keyboard != NULL)
|
||||
if (gdk_device_grab(keyboard, window,
|
||||
GDK_OWNERSHIP_WINDOW, TRUE,
|
||||
GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
|
||||
NULL, time) != GDK_GRAB_SUCCESS)
|
||||
return FALSE;
|
||||
if (mouse != NULL)
|
||||
if (gdk_device_grab(mouse, window,
|
||||
GDK_OWNERSHIP_WINDOW, TRUE,
|
||||
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
|
||||
NULL, time) != GDK_GRAB_SUCCESS) {
|
||||
if (keyboard != NULL)
|
||||
gdk_device_ungrab(keyboard, time);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gtk_device_grab_add(d->window, mouse, TRUE);
|
||||
d->keyboard = keyboard;
|
||||
d->mouse = mouse;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// based on gtk_combo_box_list_position() in the GTK+ source code
|
||||
static void allocationToScreen(dateTimePickerWidget *d, gint *x, gint *y)
|
||||
{
|
||||
GdkWindow *window;
|
||||
GtkAllocation a;
|
||||
GtkRequisition aWin;
|
||||
GdkScreen *screen;
|
||||
GdkRectangle workarea;
|
||||
int otherY;
|
||||
|
||||
gtk_widget_get_allocation(GTK_WIDGET(d), &a);
|
||||
gtk_widget_get_preferred_size(d->window, &aWin, NULL);
|
||||
*x = 0;
|
||||
*y = 0;
|
||||
if (!gtk_widget_get_has_window(GTK_WIDGET(d))) {
|
||||
*x = a.x;
|
||||
*y = a.y;
|
||||
}
|
||||
window = gtk_widget_get_window(GTK_WIDGET(d));
|
||||
gdk_window_get_root_coords(window, *x, *y, x, y);
|
||||
if (gtk_widget_get_direction(GTK_WIDGET(d)) == GTK_TEXT_DIR_RTL)
|
||||
*x += a.width - aWin.width;
|
||||
|
||||
// now adjust to prevent the box from going offscreen
|
||||
screen = gtk_widget_get_screen(GTK_WIDGET(d));
|
||||
gdk_screen_get_monitor_workarea(screen,
|
||||
gdk_screen_get_monitor_at_window(screen, window),
|
||||
&workarea);
|
||||
if (*x < workarea.x) // too far to the left?
|
||||
*x = workarea.x;
|
||||
else if (*x + aWin.width > (workarea.x + workarea.width)) // too far to the right?
|
||||
*x = (workarea.x + workarea.width) - aWin.width;
|
||||
// this isn't the same algorithm used by GtkComboBox
|
||||
// first, get our two choices; *y for down and otherY for up
|
||||
otherY = *y - aWin.height;
|
||||
*y += a.height;
|
||||
// and use otherY if we're too low
|
||||
if (*y + aWin.height >= workarea.y + workarea.height)
|
||||
*y = otherY;
|
||||
}
|
||||
|
||||
static void showPopup(dateTimePickerWidget *d)
|
||||
{
|
||||
GtkWidget *toplevel;
|
||||
gint x, y;
|
||||
|
||||
// GtkComboBox does it
|
||||
toplevel = gtk_widget_get_toplevel(GTK_WIDGET(d));
|
||||
if (GTK_IS_WINDOW(toplevel))
|
||||
gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(toplevel)), GTK_WINDOW(d->window));
|
||||
|
||||
allocationToScreen(d, &x, &y);
|
||||
gtk_window_move(GTK_WINDOW(d->window), x, y);
|
||||
|
||||
gtk_widget_show(d->window);
|
||||
setActive(d, TRUE);
|
||||
|
||||
if (!startGrab(d))
|
||||
hidePopup(d);
|
||||
}
|
||||
|
||||
static void onToggled(GtkToggleButton *b, gpointer data)
|
||||
{
|
||||
dateTimePickerWidget *d = dateTimePickerWidget(b);
|
||||
|
||||
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d)))
|
||||
showPopup(d);
|
||||
else
|
||||
hidePopup(d);
|
||||
}
|
||||
|
||||
static gboolean grabBroken(GtkWidget *w, GdkEventGrabBroken *e, gpointer data)
|
||||
{
|
||||
dateTimePickerWidget *d = dateTimePickerWidget(data);
|
||||
|
||||
hidePopup(d);
|
||||
return TRUE; // this is what GtkComboBox does
|
||||
}
|
||||
|
||||
static gboolean buttonReleased(GtkWidget *w, GdkEventButton *e, gpointer data)
|
||||
{
|
||||
dateTimePickerWidget *d = dateTimePickerWidget(data);
|
||||
int winx, winy;
|
||||
GtkAllocation wina;
|
||||
gboolean in;
|
||||
|
||||
gtk_widget_get_allocation(d->window, &wina);
|
||||
winx = 0;
|
||||
winy = 0;
|
||||
if (!gtk_widget_get_has_window(d->window)) {
|
||||
winx = wina.x;
|
||||
winy = wina.y;
|
||||
}
|
||||
gdk_window_get_root_coords(gtk_widget_get_window(d->window), winx, winy, &winx, &winy);
|
||||
in = TRUE;
|
||||
if (e->x_root < winx)
|
||||
in = FALSE;
|
||||
if (e->x_root >= (winx + wina.width))
|
||||
in = FALSE;
|
||||
if (e->y_root < winy)
|
||||
in = FALSE;
|
||||
if (e->y_root >= (winy + wina.height))
|
||||
in = FALSE;
|
||||
if (!in)
|
||||
hidePopup(d);
|
||||
return TRUE; // this is what GtkComboBox does
|
||||
}
|
||||
|
||||
static gint hoursSpinboxInput(GtkSpinButton *sb, gpointer ptr, gpointer data)
|
||||
{
|
||||
double *out = (double *) ptr;
|
||||
const gchar *text;
|
||||
int value;
|
||||
|
||||
text = gtk_entry_get_text(GTK_ENTRY(sb));
|
||||
value = (int) g_strtod(text, NULL);
|
||||
if (value < 0 || value > 12)
|
||||
return GTK_INPUT_ERROR;
|
||||
if (value == 12) // 12 to the user is 0 internally
|
||||
value = 0;
|
||||
*out = (double) value;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean hoursSpinboxOutput(GtkSpinButton *sb, gpointer data)
|
||||
{
|
||||
gchar *text;
|
||||
int value;
|
||||
|
||||
value = realSpinValue(sb);
|
||||
if (value == 0) // 0 internally is 12 to the user
|
||||
value = 12;
|
||||
text = g_strdup_printf("%d", value);
|
||||
gtk_entry_set_text(GTK_ENTRY(sb), text);
|
||||
g_free(text);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean zeroPadSpinbox(GtkSpinButton *sb, gpointer data)
|
||||
{
|
||||
gchar *text;
|
||||
int value;
|
||||
|
||||
value = realSpinValue(sb);
|
||||
text = g_strdup_printf("%02d", value);
|
||||
gtk_entry_set_text(GTK_ENTRY(sb), text);
|
||||
g_free(text);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// this is really hacky but we can't use GtkCombobox here :(
|
||||
static gint ampmSpinboxInput(GtkSpinButton *sb, gpointer ptr, gpointer data)
|
||||
{
|
||||
double *out = (double *) ptr;
|
||||
const gchar *text;
|
||||
char firstAM, firstPM;
|
||||
|
||||
text = gtk_entry_get_text(GTK_ENTRY(sb));
|
||||
// LONGTERM don't use ASCII here for case insensitivity
|
||||
firstAM = g_ascii_tolower(nl_langinfo(AM_STR)[0]);
|
||||
firstPM = g_ascii_tolower(nl_langinfo(PM_STR)[0]);
|
||||
for (; *text != '\0'; text++)
|
||||
if (g_ascii_tolower(*text) == firstAM) {
|
||||
*out = 0;
|
||||
return TRUE;
|
||||
} else if (g_ascii_tolower(*text) == firstPM) {
|
||||
*out = 1;
|
||||
return TRUE;
|
||||
}
|
||||
return GTK_INPUT_ERROR;
|
||||
}
|
||||
|
||||
static gboolean ampmSpinboxOutput(GtkSpinButton *sb, gpointer data)
|
||||
{
|
||||
int value;
|
||||
|
||||
value = gtk_spin_button_get_value_as_int(sb);
|
||||
if (value == 0)
|
||||
gtk_entry_set_text(GTK_ENTRY(sb), nl_langinfo(AM_STR));
|
||||
else
|
||||
gtk_entry_set_text(GTK_ENTRY(sb), nl_langinfo(PM_STR));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void spinboxChanged(GtkSpinButton *sb, gpointer data)
|
||||
{
|
||||
dateTimePickerWidget *d = dateTimePickerWidget(data);
|
||||
|
||||
dateTimeChanged(d);
|
||||
}
|
||||
|
||||
static GtkWidget *newSpinbox(dateTimePickerWidget *d, int min, int max, gint (*input)(GtkSpinButton *, gpointer, gpointer), gboolean (*output)(GtkSpinButton *, gpointer), gulong *block)
|
||||
{
|
||||
GtkWidget *sb;
|
||||
|
||||
sb = gtk_spin_button_new_with_range(min, max, 1);
|
||||
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(sb), 0);
|
||||
gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(sb), TRUE);
|
||||
gtk_orientable_set_orientation(GTK_ORIENTABLE(sb), GTK_ORIENTATION_VERTICAL);
|
||||
*block = g_signal_connect(sb, "value-changed", G_CALLBACK(spinboxChanged), d);
|
||||
if (input != NULL)
|
||||
g_signal_connect(sb, "input", G_CALLBACK(input), NULL);
|
||||
if (output != NULL)
|
||||
g_signal_connect(sb, "output", G_CALLBACK(output), NULL);
|
||||
return sb;
|
||||
}
|
||||
|
||||
static void dateChanged(GtkCalendar *c, gpointer data)
|
||||
{
|
||||
dateTimePickerWidget *d = dateTimePickerWidget(data);
|
||||
|
||||
dateTimeChanged(d);
|
||||
}
|
||||
|
||||
static void setDateOnly(dateTimePickerWidget *d)
|
||||
{
|
||||
d->hasTime = FALSE;
|
||||
gtk_container_remove(GTK_CONTAINER(d->box), d->timebox);
|
||||
}
|
||||
|
||||
static void setTimeOnly(dateTimePickerWidget *d)
|
||||
{
|
||||
d->hasDate = FALSE;
|
||||
gtk_container_remove(GTK_CONTAINER(d->box), d->calendar);
|
||||
}
|
||||
|
||||
static void dateTimePickerWidget_init(dateTimePickerWidget *d)
|
||||
{
|
||||
GDateTime *dt;
|
||||
gint year, month, day;
|
||||
gint hour;
|
||||
gulong calendarBlock;
|
||||
|
||||
d->window = gtk_window_new(GTK_WINDOW_POPUP);
|
||||
gtk_window_set_resizable(GTK_WINDOW(d->window), FALSE);
|
||||
gtk_window_set_attached_to(GTK_WINDOW(d->window), GTK_WIDGET(d));
|
||||
gtk_window_set_decorated(GTK_WINDOW(d->window), FALSE);
|
||||
gtk_window_set_deletable(GTK_WINDOW(d->window), FALSE);
|
||||
gtk_window_set_type_hint(GTK_WINDOW(d->window), GDK_WINDOW_TYPE_HINT_COMBO);
|
||||
gtk_window_set_skip_taskbar_hint(GTK_WINDOW(d->window), TRUE);
|
||||
gtk_window_set_skip_pager_hint(GTK_WINDOW(d->window), TRUE);
|
||||
gtk_window_set_has_resize_grip(GTK_WINDOW(d->window), FALSE);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(d->window), 12);
|
||||
// and make it stand out a bit
|
||||
gtk_style_context_add_class(gtk_widget_get_style_context(d->window), "frame");
|
||||
|
||||
d->box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||
gtk_container_add(GTK_CONTAINER(d->window), d->box);
|
||||
|
||||
d->calendar = gtk_calendar_new();
|
||||
calendarBlock = g_signal_connect(d->calendar, "day-selected", G_CALLBACK(dateChanged), d);
|
||||
gtk_container_add(GTK_CONTAINER(d->box), d->calendar);
|
||||
|
||||
d->timebox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
||||
gtk_widget_set_valign(d->timebox, GTK_ALIGN_CENTER);
|
||||
gtk_container_add(GTK_CONTAINER(d->box), d->timebox);
|
||||
|
||||
d->hours = newSpinbox(d, 0, 11, hoursSpinboxInput, hoursSpinboxOutput, &(d->hoursBlock));
|
||||
gtk_container_add(GTK_CONTAINER(d->timebox), d->hours);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(d->timebox),
|
||||
gtk_label_new(":"));
|
||||
|
||||
d->minutes = newSpinbox(d, 0, 59, NULL, zeroPadSpinbox, &(d->minutesBlock));
|
||||
gtk_container_add(GTK_CONTAINER(d->timebox), d->minutes);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(d->timebox),
|
||||
gtk_label_new(":"));
|
||||
|
||||
d->seconds = newSpinbox(d, 0, 59, NULL, zeroPadSpinbox, &(d->secondsBlock));
|
||||
gtk_container_add(GTK_CONTAINER(d->timebox), d->seconds);
|
||||
|
||||
// LONGTERM this should be the case, but that interferes with grabs
|
||||
// switch to it when we can drop GTK+ 3.10 and use popovers
|
||||
#if 0
|
||||
d->ampm = gtk_combo_box_text_new();
|
||||
gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(d->ampm), NULL, "AM");
|
||||
gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(d->ampm), NULL, "PM");
|
||||
#endif
|
||||
d->ampm = newSpinbox(d, 0, 1, ampmSpinboxInput, ampmSpinboxOutput, &(d->ampmBlock));
|
||||
gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(d->ampm), FALSE);
|
||||
gtk_widget_set_valign(d->ampm, GTK_ALIGN_CENTER);
|
||||
gtk_container_add(GTK_CONTAINER(d->timebox), d->ampm);
|
||||
|
||||
gtk_widget_show_all(d->box);
|
||||
|
||||
g_signal_connect(d->window, "grab-broken-event", G_CALLBACK(grabBroken), d);
|
||||
g_signal_connect(d->window, "button-release-event", G_CALLBACK(buttonReleased), d);
|
||||
|
||||
d->toggledSignal = g_signal_connect(d, "toggled", G_CALLBACK(onToggled), NULL);
|
||||
d->keyboard = NULL;
|
||||
d->mouse = NULL;
|
||||
|
||||
d->hasTime = TRUE;
|
||||
d->hasDate = TRUE;
|
||||
|
||||
// set the current date/time
|
||||
// notice how we block signals from firing
|
||||
dt = g_date_time_new_now_local();
|
||||
g_date_time_get_ymd(dt, &year, &month, &day);
|
||||
month--; // GDateTime/GtkCalendar differences
|
||||
g_signal_handler_block(d->calendar, calendarBlock);
|
||||
gtk_calendar_select_month(GTK_CALENDAR(d->calendar), month, year);
|
||||
gtk_calendar_select_day(GTK_CALENDAR(d->calendar), day);
|
||||
g_signal_handler_unblock(d->calendar, calendarBlock);
|
||||
hour = g_date_time_get_hour(dt);
|
||||
if (hour >= 12) {
|
||||
hour -= 12;
|
||||
setRealSpinValue(GTK_SPIN_BUTTON(d->ampm), 1, d->ampmBlock);
|
||||
}
|
||||
setRealSpinValue(GTK_SPIN_BUTTON(d->hours), hour, d->hoursBlock);
|
||||
setRealSpinValue(GTK_SPIN_BUTTON(d->minutes), g_date_time_get_minute(dt), d->minutesBlock);
|
||||
setRealSpinValue(GTK_SPIN_BUTTON(d->seconds), g_date_time_get_seconds(dt), d->secondsBlock);
|
||||
g_date_time_unref(dt);
|
||||
}
|
||||
|
||||
static void dateTimePickerWidget_dispose(GObject *obj)
|
||||
{
|
||||
dateTimePickerWidget *d = dateTimePickerWidget(obj);
|
||||
|
||||
if (d->window != NULL) {
|
||||
gtk_widget_destroy(d->window);
|
||||
d->window = NULL;
|
||||
}
|
||||
G_OBJECT_CLASS(dateTimePickerWidget_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void dateTimePickerWidget_finalize(GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS(dateTimePickerWidget_parent_class)->finalize(obj);
|
||||
}
|
||||
|
||||
static void dateTimePickerWidget_class_init(dateTimePickerWidgetClass *class)
|
||||
{
|
||||
G_OBJECT_CLASS(class)->dispose = dateTimePickerWidget_dispose;
|
||||
G_OBJECT_CLASS(class)->finalize = dateTimePickerWidget_finalize;
|
||||
}
|
||||
|
||||
static GtkWidget *newDTP(void)
|
||||
{
|
||||
GtkWidget *w;
|
||||
|
||||
w = GTK_WIDGET(g_object_new(dateTimePickerWidgetType, "label", "", NULL));
|
||||
setLabel(dateTimePickerWidget(w));
|
||||
return w;
|
||||
}
|
||||
|
||||
static GtkWidget *newDP(void)
|
||||
{
|
||||
GtkWidget *w;
|
||||
|
||||
w = GTK_WIDGET(g_object_new(dateTimePickerWidgetType, "label", "", NULL));
|
||||
setDateOnly(dateTimePickerWidget(w));
|
||||
setLabel(dateTimePickerWidget(w));
|
||||
return w;
|
||||
}
|
||||
|
||||
static GtkWidget *newTP(void)
|
||||
{
|
||||
GtkWidget *w;
|
||||
|
||||
w = GTK_WIDGET(g_object_new(dateTimePickerWidgetType, "label", "", NULL));
|
||||
setTimeOnly(dateTimePickerWidget(w));
|
||||
setLabel(dateTimePickerWidget(w));
|
||||
return w;
|
||||
}
|
||||
|
||||
struct uiDateTimePicker {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
dateTimePickerWidget *d;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiDateTimePicker)
|
||||
|
||||
uiDateTimePicker *finishNewDateTimePicker(GtkWidget *(*fn)(void))
|
||||
{
|
||||
uiDateTimePicker *d;
|
||||
|
||||
uiUnixNewControl(uiDateTimePicker, d);
|
||||
|
||||
d->widget = (*fn)();
|
||||
d->d = dateTimePickerWidget(d->widget);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewDateTimePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(newDTP);
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewDatePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(newDP);
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewTimePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(newTP);
|
||||
}
|
14
src/libui_sdl/libui/unix/debug.c
Normal file
14
src/libui_sdl/libui/unix/debug.c
Normal file
@ -0,0 +1,14 @@
|
||||
// 13 may 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// LONGTERM don't halt on release builds
|
||||
|
||||
void realbug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)
|
||||
{
|
||||
char *a, *b;
|
||||
|
||||
a = g_strdup_printf("[libui] %s:%s:%s() %s", file, line, func, prefix);
|
||||
b = g_strdup_vprintf(format, ap);
|
||||
g_critical("%s%s", a, b);
|
||||
G_BREAKPOINT();
|
||||
}
|
141
src/libui_sdl/libui/unix/draw.c
Normal file
141
src/libui_sdl/libui/unix/draw.c
Normal file
@ -0,0 +1,141 @@
|
||||
// 6 september 2015
|
||||
#include "uipriv_unix.h"
|
||||
#include "draw.h"
|
||||
|
||||
uiDrawContext *newContext(cairo_t *cr)
|
||||
{
|
||||
uiDrawContext *c;
|
||||
|
||||
c = uiNew(uiDrawContext);
|
||||
c->cr = cr;
|
||||
return c;
|
||||
}
|
||||
|
||||
void freeContext(uiDrawContext *c)
|
||||
{
|
||||
uiFree(c);
|
||||
}
|
||||
|
||||
static cairo_pattern_t *mkbrush(uiDrawBrush *b)
|
||||
{
|
||||
cairo_pattern_t *pat;
|
||||
size_t i;
|
||||
|
||||
switch (b->Type) {
|
||||
case uiDrawBrushTypeSolid:
|
||||
pat = cairo_pattern_create_rgba(b->R, b->G, b->B, b->A);
|
||||
break;
|
||||
case uiDrawBrushTypeLinearGradient:
|
||||
pat = cairo_pattern_create_linear(b->X0, b->Y0, b->X1, b->Y1);
|
||||
break;
|
||||
case uiDrawBrushTypeRadialGradient:
|
||||
// make the start circle radius 0 to make it a point
|
||||
pat = cairo_pattern_create_radial(
|
||||
b->X0, b->Y0, 0,
|
||||
b->X1, b->Y1, b->OuterRadius);
|
||||
break;
|
||||
// case uiDrawBrushTypeImage:
|
||||
}
|
||||
if (cairo_pattern_status(pat) != CAIRO_STATUS_SUCCESS)
|
||||
implbug("error creating pattern in mkbrush(): %s",
|
||||
cairo_status_to_string(cairo_pattern_status(pat)));
|
||||
switch (b->Type) {
|
||||
case uiDrawBrushTypeLinearGradient:
|
||||
case uiDrawBrushTypeRadialGradient:
|
||||
for (i = 0; i < b->NumStops; i++)
|
||||
cairo_pattern_add_color_stop_rgba(pat,
|
||||
b->Stops[i].Pos,
|
||||
b->Stops[i].R,
|
||||
b->Stops[i].G,
|
||||
b->Stops[i].B,
|
||||
b->Stops[i].A);
|
||||
}
|
||||
return pat;
|
||||
}
|
||||
|
||||
void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
|
||||
{
|
||||
cairo_pattern_t *pat;
|
||||
|
||||
runPath(path, c->cr);
|
||||
pat = mkbrush(b);
|
||||
cairo_set_source(c->cr, pat);
|
||||
switch (p->Cap) {
|
||||
case uiDrawLineCapFlat:
|
||||
cairo_set_line_cap(c->cr, CAIRO_LINE_CAP_BUTT);
|
||||
break;
|
||||
case uiDrawLineCapRound:
|
||||
cairo_set_line_cap(c->cr, CAIRO_LINE_CAP_ROUND);
|
||||
break;
|
||||
case uiDrawLineCapSquare:
|
||||
cairo_set_line_cap(c->cr, CAIRO_LINE_CAP_SQUARE);
|
||||
break;
|
||||
}
|
||||
switch (p->Join) {
|
||||
case uiDrawLineJoinMiter:
|
||||
cairo_set_line_join(c->cr, CAIRO_LINE_JOIN_MITER);
|
||||
cairo_set_miter_limit(c->cr, p->MiterLimit);
|
||||
break;
|
||||
case uiDrawLineJoinRound:
|
||||
cairo_set_line_join(c->cr, CAIRO_LINE_JOIN_ROUND);
|
||||
break;
|
||||
case uiDrawLineJoinBevel:
|
||||
cairo_set_line_join(c->cr, CAIRO_LINE_JOIN_BEVEL);
|
||||
break;
|
||||
}
|
||||
cairo_set_line_width(c->cr, p->Thickness);
|
||||
cairo_set_dash(c->cr, p->Dashes, p->NumDashes, p->DashPhase);
|
||||
cairo_stroke(c->cr);
|
||||
cairo_pattern_destroy(pat);
|
||||
}
|
||||
|
||||
void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
|
||||
{
|
||||
cairo_pattern_t *pat;
|
||||
|
||||
runPath(path, c->cr);
|
||||
pat = mkbrush(b);
|
||||
cairo_set_source(c->cr, pat);
|
||||
switch (pathFillMode(path)) {
|
||||
case uiDrawFillModeWinding:
|
||||
cairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_WINDING);
|
||||
break;
|
||||
case uiDrawFillModeAlternate:
|
||||
cairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_EVEN_ODD);
|
||||
break;
|
||||
}
|
||||
cairo_fill(c->cr);
|
||||
cairo_pattern_destroy(pat);
|
||||
}
|
||||
|
||||
void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
|
||||
{
|
||||
cairo_matrix_t cm;
|
||||
|
||||
m2c(m, &cm);
|
||||
cairo_transform(c->cr, &cm);
|
||||
}
|
||||
|
||||
void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
|
||||
{
|
||||
runPath(path, c->cr);
|
||||
switch (pathFillMode(path)) {
|
||||
case uiDrawFillModeWinding:
|
||||
cairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_WINDING);
|
||||
break;
|
||||
case uiDrawFillModeAlternate:
|
||||
cairo_set_fill_rule(c->cr, CAIRO_FILL_RULE_EVEN_ODD);
|
||||
break;
|
||||
}
|
||||
cairo_clip(c->cr);
|
||||
}
|
||||
|
||||
void uiDrawSave(uiDrawContext *c)
|
||||
{
|
||||
cairo_save(c->cr);
|
||||
}
|
||||
|
||||
void uiDrawRestore(uiDrawContext *c)
|
||||
{
|
||||
cairo_restore(c->cr);
|
||||
}
|
13
src/libui_sdl/libui/unix/draw.h
Normal file
13
src/libui_sdl/libui/unix/draw.h
Normal file
@ -0,0 +1,13 @@
|
||||
// 5 may 2016
|
||||
|
||||
// draw.c
|
||||
struct uiDrawContext {
|
||||
cairo_t *cr;
|
||||
};
|
||||
|
||||
// drawpath.c
|
||||
extern void runPath(uiDrawPath *p, cairo_t *cr);
|
||||
extern uiDrawFillMode pathFillMode(uiDrawPath *path);
|
||||
|
||||
// drawmatrix.c
|
||||
extern void m2c(uiDrawMatrix *m, cairo_matrix_t *c);
|
109
src/libui_sdl/libui/unix/drawmatrix.c
Normal file
109
src/libui_sdl/libui/unix/drawmatrix.c
Normal file
@ -0,0 +1,109 @@
|
||||
// 6 september 2015
|
||||
#include "uipriv_unix.h"
|
||||
#include "draw.h"
|
||||
|
||||
void m2c(uiDrawMatrix *m, cairo_matrix_t *c)
|
||||
{
|
||||
c->xx = m->M11;
|
||||
c->yx = m->M12;
|
||||
c->xy = m->M21;
|
||||
c->yy = m->M22;
|
||||
c->x0 = m->M31;
|
||||
c->y0 = m->M32;
|
||||
}
|
||||
|
||||
static void c2m(cairo_matrix_t *c, uiDrawMatrix *m)
|
||||
{
|
||||
m->M11 = c->xx;
|
||||
m->M12 = c->yx;
|
||||
m->M21 = c->xy;
|
||||
m->M22 = c->yy;
|
||||
m->M31 = c->x0;
|
||||
m->M32 = c->y0;
|
||||
}
|
||||
|
||||
void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
|
||||
{
|
||||
cairo_matrix_t c;
|
||||
|
||||
m2c(m, &c);
|
||||
cairo_matrix_translate(&c, x, y);
|
||||
c2m(&c, m);
|
||||
}
|
||||
|
||||
void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
|
||||
{
|
||||
cairo_matrix_t c;
|
||||
double xt, yt;
|
||||
|
||||
m2c(m, &c);
|
||||
xt = x;
|
||||
yt = y;
|
||||
scaleCenter(xCenter, yCenter, &xt, &yt);
|
||||
cairo_matrix_translate(&c, xt, yt);
|
||||
cairo_matrix_scale(&c, x, y);
|
||||
cairo_matrix_translate(&c, -xt, -yt);
|
||||
c2m(&c, m);
|
||||
}
|
||||
|
||||
void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
|
||||
{
|
||||
cairo_matrix_t c;
|
||||
|
||||
m2c(m, &c);
|
||||
cairo_matrix_translate(&c, x, y);
|
||||
cairo_matrix_rotate(&c, amount);
|
||||
cairo_matrix_translate(&c, -x, -y);
|
||||
c2m(&c, m);
|
||||
}
|
||||
|
||||
void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
|
||||
{
|
||||
fallbackSkew(m, x, y, xamount, yamount);
|
||||
}
|
||||
|
||||
void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
|
||||
{
|
||||
cairo_matrix_t c;
|
||||
cairo_matrix_t d;
|
||||
|
||||
m2c(dest, &c);
|
||||
m2c(src, &d);
|
||||
cairo_matrix_multiply(&c, &c, &d);
|
||||
c2m(&c, dest);
|
||||
}
|
||||
|
||||
int uiDrawMatrixInvertible(uiDrawMatrix *m)
|
||||
{
|
||||
cairo_matrix_t c;
|
||||
|
||||
m2c(m, &c);
|
||||
return cairo_matrix_invert(&c) == CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
int uiDrawMatrixInvert(uiDrawMatrix *m)
|
||||
{
|
||||
cairo_matrix_t c;
|
||||
|
||||
m2c(m, &c);
|
||||
if (cairo_matrix_invert(&c) != CAIRO_STATUS_SUCCESS)
|
||||
return 0;
|
||||
c2m(&c, m);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
|
||||
{
|
||||
cairo_matrix_t c;
|
||||
|
||||
m2c(m, &c);
|
||||
cairo_matrix_transform_point(&c, x, y);
|
||||
}
|
||||
|
||||
void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
|
||||
{
|
||||
cairo_matrix_t c;
|
||||
|
||||
m2c(m, &c);
|
||||
cairo_matrix_transform_distance(&c, x, y);
|
||||
}
|
199
src/libui_sdl/libui/unix/drawpath.c
Normal file
199
src/libui_sdl/libui/unix/drawpath.c
Normal file
@ -0,0 +1,199 @@
|
||||
// 6 september 2015
|
||||
#include "uipriv_unix.h"
|
||||
#include "draw.h"
|
||||
|
||||
struct uiDrawPath {
|
||||
GArray *pieces;
|
||||
uiDrawFillMode fillMode;
|
||||
gboolean ended;
|
||||
};
|
||||
|
||||
struct piece {
|
||||
int type;
|
||||
double d[8];
|
||||
int b;
|
||||
};
|
||||
|
||||
enum {
|
||||
newFigure,
|
||||
newFigureArc,
|
||||
lineTo,
|
||||
arcTo,
|
||||
bezierTo,
|
||||
closeFigure,
|
||||
addRect,
|
||||
};
|
||||
|
||||
uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
|
||||
{
|
||||
uiDrawPath *p;
|
||||
|
||||
p = uiNew(uiDrawPath);
|
||||
p->pieces = g_array_new(FALSE, TRUE, sizeof (struct piece));
|
||||
p->fillMode = mode;
|
||||
return p;
|
||||
}
|
||||
|
||||
void uiDrawFreePath(uiDrawPath *p)
|
||||
{
|
||||
g_array_free(p->pieces, TRUE);
|
||||
uiFree(p);
|
||||
}
|
||||
|
||||
static void add(uiDrawPath *p, struct piece *piece)
|
||||
{
|
||||
if (p->ended)
|
||||
userbug("You cannot modify a uiDrawPath that has been ended. (path: %p)", p);
|
||||
g_array_append_vals(p->pieces, piece, 1);
|
||||
}
|
||||
|
||||
void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
|
||||
{
|
||||
struct piece piece;
|
||||
|
||||
piece.type = newFigure;
|
||||
piece.d[0] = x;
|
||||
piece.d[1] = y;
|
||||
add(p, &piece);
|
||||
}
|
||||
|
||||
void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
|
||||
{
|
||||
struct piece piece;
|
||||
|
||||
if (sweep > 2 * uiPi)
|
||||
sweep = 2 * uiPi;
|
||||
piece.type = newFigureArc;
|
||||
piece.d[0] = xCenter;
|
||||
piece.d[1] = yCenter;
|
||||
piece.d[2] = radius;
|
||||
piece.d[3] = startAngle;
|
||||
piece.d[4] = sweep;
|
||||
piece.b = negative;
|
||||
add(p, &piece);
|
||||
}
|
||||
|
||||
void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
|
||||
{
|
||||
struct piece piece;
|
||||
|
||||
piece.type = lineTo;
|
||||
piece.d[0] = x;
|
||||
piece.d[1] = y;
|
||||
add(p, &piece);
|
||||
}
|
||||
|
||||
void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
|
||||
{
|
||||
struct piece piece;
|
||||
|
||||
if (sweep > 2 * uiPi)
|
||||
sweep = 2 * uiPi;
|
||||
piece.type = arcTo;
|
||||
piece.d[0] = xCenter;
|
||||
piece.d[1] = yCenter;
|
||||
piece.d[2] = radius;
|
||||
piece.d[3] = startAngle;
|
||||
piece.d[4] = sweep;
|
||||
piece.b = negative;
|
||||
add(p, &piece);
|
||||
}
|
||||
|
||||
void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
|
||||
{
|
||||
struct piece piece;
|
||||
|
||||
piece.type = bezierTo;
|
||||
piece.d[0] = c1x;
|
||||
piece.d[1] = c1y;
|
||||
piece.d[2] = c2x;
|
||||
piece.d[3] = c2y;
|
||||
piece.d[4] = endX;
|
||||
piece.d[5] = endY;
|
||||
add(p, &piece);
|
||||
}
|
||||
|
||||
void uiDrawPathCloseFigure(uiDrawPath *p)
|
||||
{
|
||||
struct piece piece;
|
||||
|
||||
piece.type = closeFigure;
|
||||
add(p, &piece);
|
||||
}
|
||||
|
||||
void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
|
||||
{
|
||||
struct piece piece;
|
||||
|
||||
piece.type = addRect;
|
||||
piece.d[0] = x;
|
||||
piece.d[1] = y;
|
||||
piece.d[2] = width;
|
||||
piece.d[3] = height;
|
||||
add(p, &piece);
|
||||
}
|
||||
|
||||
void uiDrawPathEnd(uiDrawPath *p)
|
||||
{
|
||||
p->ended = TRUE;
|
||||
}
|
||||
|
||||
void runPath(uiDrawPath *p, cairo_t *cr)
|
||||
{
|
||||
guint i;
|
||||
struct piece *piece;
|
||||
void (*arc)(cairo_t *, double, double, double, double, double);
|
||||
|
||||
if (!p->ended)
|
||||
userbug("You cannot draw with a uiDrawPath that has not been ended. (path: %p)", p);
|
||||
cairo_new_path(cr);
|
||||
for (i = 0; i < p->pieces->len; i++) {
|
||||
piece = &g_array_index(p->pieces, struct piece, i);
|
||||
switch (piece->type) {
|
||||
case newFigure:
|
||||
cairo_move_to(cr, piece->d[0], piece->d[1]);
|
||||
break;
|
||||
case newFigureArc:
|
||||
cairo_new_sub_path(cr);
|
||||
// fall through
|
||||
case arcTo:
|
||||
arc = cairo_arc;
|
||||
if (piece->b)
|
||||
arc = cairo_arc_negative;
|
||||
(*arc)(cr,
|
||||
piece->d[0],
|
||||
piece->d[1],
|
||||
piece->d[2],
|
||||
piece->d[3],
|
||||
piece->d[3] + piece->d[4]);
|
||||
break;
|
||||
case lineTo:
|
||||
cairo_line_to(cr, piece->d[0], piece->d[1]);
|
||||
break;
|
||||
case bezierTo:
|
||||
cairo_curve_to(cr,
|
||||
piece->d[0],
|
||||
piece->d[1],
|
||||
piece->d[2],
|
||||
piece->d[3],
|
||||
piece->d[4],
|
||||
piece->d[5]);
|
||||
break;
|
||||
case closeFigure:
|
||||
cairo_close_path(cr);
|
||||
break;
|
||||
case addRect:
|
||||
cairo_rectangle(cr,
|
||||
piece->d[0],
|
||||
piece->d[1],
|
||||
piece->d[2],
|
||||
piece->d[3]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiDrawFillMode pathFillMode(uiDrawPath *path)
|
||||
{
|
||||
return path->fillMode;
|
||||
}
|
293
src/libui_sdl/libui/unix/drawtext.c
Normal file
293
src/libui_sdl/libui/unix/drawtext.c
Normal file
@ -0,0 +1,293 @@
|
||||
// 6 september 2015
|
||||
#include "uipriv_unix.h"
|
||||
#include "draw.h"
|
||||
|
||||
struct uiDrawFontFamilies {
|
||||
PangoFontFamily **f;
|
||||
int n;
|
||||
};
|
||||
|
||||
uiDrawFontFamilies *uiDrawListFontFamilies(void)
|
||||
{
|
||||
uiDrawFontFamilies *ff;
|
||||
PangoFontMap *map;
|
||||
|
||||
ff = uiNew(uiDrawFontFamilies);
|
||||
map = pango_cairo_font_map_get_default();
|
||||
pango_font_map_list_families(map, &(ff->f), &(ff->n));
|
||||
// do not free map; it's a shared resource
|
||||
return ff;
|
||||
}
|
||||
|
||||
int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
return ff->n;
|
||||
}
|
||||
|
||||
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
|
||||
{
|
||||
PangoFontFamily *f;
|
||||
|
||||
f = ff->f[n];
|
||||
return uiUnixStrdupText(pango_font_family_get_name(f));
|
||||
}
|
||||
|
||||
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
|
||||
{
|
||||
g_free(ff->f);
|
||||
uiFree(ff);
|
||||
}
|
||||
|
||||
struct uiDrawTextFont {
|
||||
PangoFont *f;
|
||||
};
|
||||
|
||||
uiDrawTextFont *mkTextFont(PangoFont *f, gboolean ref)
|
||||
{
|
||||
uiDrawTextFont *font;
|
||||
|
||||
font = uiNew(uiDrawTextFont);
|
||||
font->f = f;
|
||||
if (ref)
|
||||
g_object_ref(font->f);
|
||||
return font;
|
||||
}
|
||||
|
||||
static const PangoWeight pangoWeights[] = {
|
||||
[uiDrawTextWeightThin] = PANGO_WEIGHT_THIN,
|
||||
[uiDrawTextWeightUltraLight] = PANGO_WEIGHT_ULTRALIGHT,
|
||||
[uiDrawTextWeightLight] = PANGO_WEIGHT_LIGHT,
|
||||
[uiDrawTextWeightBook] = PANGO_WEIGHT_BOOK,
|
||||
[uiDrawTextWeightNormal] = PANGO_WEIGHT_NORMAL,
|
||||
[uiDrawTextWeightMedium] = PANGO_WEIGHT_MEDIUM,
|
||||
[uiDrawTextWeightSemiBold] = PANGO_WEIGHT_SEMIBOLD,
|
||||
[uiDrawTextWeightBold] = PANGO_WEIGHT_BOLD,
|
||||
[uiDrawTextWeightUltraBold] = PANGO_WEIGHT_ULTRABOLD,
|
||||
[uiDrawTextWeightHeavy] = PANGO_WEIGHT_HEAVY,
|
||||
[uiDrawTextWeightUltraHeavy] = PANGO_WEIGHT_ULTRAHEAVY,
|
||||
};
|
||||
|
||||
static const PangoStyle pangoItalics[] = {
|
||||
[uiDrawTextItalicNormal] = PANGO_STYLE_NORMAL,
|
||||
[uiDrawTextItalicOblique] = PANGO_STYLE_OBLIQUE,
|
||||
[uiDrawTextItalicItalic] = PANGO_STYLE_ITALIC,
|
||||
};
|
||||
|
||||
static const PangoStretch pangoStretches[] = {
|
||||
[uiDrawTextStretchUltraCondensed] = PANGO_STRETCH_ULTRA_CONDENSED,
|
||||
[uiDrawTextStretchExtraCondensed] = PANGO_STRETCH_EXTRA_CONDENSED,
|
||||
[uiDrawTextStretchCondensed] = PANGO_STRETCH_CONDENSED,
|
||||
[uiDrawTextStretchSemiCondensed] = PANGO_STRETCH_SEMI_CONDENSED,
|
||||
[uiDrawTextStretchNormal] = PANGO_STRETCH_NORMAL,
|
||||
[uiDrawTextStretchSemiExpanded] = PANGO_STRETCH_SEMI_EXPANDED,
|
||||
[uiDrawTextStretchExpanded] = PANGO_STRETCH_EXPANDED,
|
||||
[uiDrawTextStretchExtraExpanded] = PANGO_STRETCH_EXTRA_EXPANDED,
|
||||
[uiDrawTextStretchUltraExpanded] = PANGO_STRETCH_ULTRA_EXPANDED,
|
||||
};
|
||||
|
||||
// we need a context for a few things
|
||||
// the documentation suggests creating cairo_t-specific, GdkScreen-specific, or even GtkWidget-specific contexts, but we can't really do that because we want our uiDrawTextFonts and uiDrawTextLayouts to be context-independent
|
||||
// we could use pango_font_map_create_context(pango_cairo_font_map_get_default()) but that will ignore GDK-specific settings
|
||||
// so let's use gdk_pango_context_get() instead; even though it's for the default screen only, it's good enough for us
|
||||
#define mkGenericPangoCairoContext() (gdk_pango_context_get())
|
||||
|
||||
PangoFont *pangoDescToPangoFont(PangoFontDescription *pdesc)
|
||||
{
|
||||
PangoFont *f;
|
||||
PangoContext *context;
|
||||
|
||||
// in this case, the context is necessary for the metrics to be correct
|
||||
context = mkGenericPangoCairoContext();
|
||||
f = pango_font_map_load_font(pango_cairo_font_map_get_default(), context, pdesc);
|
||||
if (f == NULL) {
|
||||
// LONGTERM
|
||||
g_error("[libui] no match in pangoDescToPangoFont(); report to andlabs");
|
||||
}
|
||||
g_object_unref(context);
|
||||
return f;
|
||||
}
|
||||
|
||||
uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
PangoFont *f;
|
||||
PangoFontDescription *pdesc;
|
||||
|
||||
pdesc = pango_font_description_new();
|
||||
pango_font_description_set_family(pdesc,
|
||||
desc->Family);
|
||||
pango_font_description_set_size(pdesc,
|
||||
(gint) (desc->Size * PANGO_SCALE));
|
||||
pango_font_description_set_weight(pdesc,
|
||||
pangoWeights[desc->Weight]);
|
||||
pango_font_description_set_style(pdesc,
|
||||
pangoItalics[desc->Italic]);
|
||||
pango_font_description_set_stretch(pdesc,
|
||||
pangoStretches[desc->Stretch]);
|
||||
f = pangoDescToPangoFont(pdesc);
|
||||
pango_font_description_free(pdesc);
|
||||
return mkTextFont(f, FALSE); // we hold the initial reference; no need to ref
|
||||
}
|
||||
|
||||
void uiDrawFreeTextFont(uiDrawTextFont *font)
|
||||
{
|
||||
g_object_unref(font->f);
|
||||
uiFree(font);
|
||||
}
|
||||
|
||||
uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
|
||||
{
|
||||
return (uintptr_t) (font->f);
|
||||
}
|
||||
|
||||
void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
|
||||
{
|
||||
PangoFontDescription *pdesc;
|
||||
|
||||
// this creates a copy; we free it later
|
||||
pdesc = pango_font_describe(font->f);
|
||||
|
||||
// TODO
|
||||
|
||||
pango_font_description_free(pdesc);
|
||||
}
|
||||
|
||||
// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description
|
||||
// Note that we convert to double before dividing to make sure the floating-point stuff is right
|
||||
#define pangoToCairo(pango) (((double) (pango)) / PANGO_SCALE)
|
||||
#define cairoToPango(cairo) ((gint) ((cairo) * PANGO_SCALE))
|
||||
|
||||
void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
|
||||
{
|
||||
PangoFontMetrics *pm;
|
||||
|
||||
pm = pango_font_get_metrics(font->f, NULL);
|
||||
metrics->Ascent = pangoToCairo(pango_font_metrics_get_ascent(pm));
|
||||
metrics->Descent = pangoToCairo(pango_font_metrics_get_descent(pm));
|
||||
// Pango doesn't seem to expose this :( Use 0 and hope for the best.
|
||||
metrics->Leading = 0;
|
||||
metrics->UnderlinePos = pangoToCairo(pango_font_metrics_get_underline_position(pm));
|
||||
metrics->UnderlineThickness = pangoToCairo(pango_font_metrics_get_underline_thickness(pm));
|
||||
pango_font_metrics_unref(pm);
|
||||
}
|
||||
|
||||
// note: PangoCairoLayouts are tied to a given cairo_t, so we can't store one in this device-independent structure
|
||||
struct uiDrawTextLayout {
|
||||
char *s;
|
||||
ptrdiff_t *graphemes;
|
||||
PangoFont *defaultFont;
|
||||
double width;
|
||||
PangoAttrList *attrs;
|
||||
};
|
||||
|
||||
uiDrawTextLayout *uiDrawNewTextLayout(const char *text, uiDrawTextFont *defaultFont, double width)
|
||||
{
|
||||
uiDrawTextLayout *layout;
|
||||
PangoContext *context;
|
||||
|
||||
layout = uiNew(uiDrawTextLayout);
|
||||
layout->s = g_strdup(text);
|
||||
context = mkGenericPangoCairoContext();
|
||||
layout->graphemes = graphemes(layout->s, context);
|
||||
g_object_unref(context);
|
||||
layout->defaultFont = defaultFont->f;
|
||||
g_object_ref(layout->defaultFont); // retain a copy
|
||||
uiDrawTextLayoutSetWidth(layout, width);
|
||||
layout->attrs = pango_attr_list_new();
|
||||
return layout;
|
||||
}
|
||||
|
||||
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
|
||||
{
|
||||
pango_attr_list_unref(layout->attrs);
|
||||
g_object_unref(layout->defaultFont);
|
||||
uiFree(layout->graphemes);
|
||||
g_free(layout->s);
|
||||
uiFree(layout);
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
|
||||
{
|
||||
layout->width = width;
|
||||
}
|
||||
|
||||
static void prepareLayout(uiDrawTextLayout *layout, PangoLayout *pl)
|
||||
{
|
||||
PangoFontDescription *desc;
|
||||
int width;
|
||||
|
||||
pango_layout_set_text(pl, layout->s, -1);
|
||||
|
||||
// again, this makes a copy
|
||||
desc = pango_font_describe(layout->defaultFont);
|
||||
// this is safe; the description is copied
|
||||
pango_layout_set_font_description(pl, desc);
|
||||
pango_font_description_free(desc);
|
||||
|
||||
width = cairoToPango(layout->width);
|
||||
if (layout->width < 0)
|
||||
width = -1;
|
||||
pango_layout_set_width(pl, width);
|
||||
|
||||
pango_layout_set_attributes(pl, layout->attrs);
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
|
||||
{
|
||||
PangoContext *context;
|
||||
PangoLayout *pl;
|
||||
PangoRectangle logical;
|
||||
|
||||
// in this case, the context is necessary to create the layout
|
||||
// the layout takes a ref on the context so we can unref it afterward
|
||||
context = mkGenericPangoCairoContext();
|
||||
pl = pango_layout_new(context);
|
||||
g_object_unref(context);
|
||||
prepareLayout(layout, pl);
|
||||
|
||||
pango_layout_get_extents(pl, NULL, &logical);
|
||||
|
||||
g_object_unref(pl);
|
||||
|
||||
*width = pangoToCairo(logical.width);
|
||||
*height = pangoToCairo(logical.height);
|
||||
}
|
||||
|
||||
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
|
||||
{
|
||||
PangoLayout *pl;
|
||||
|
||||
pl = pango_cairo_create_layout(c->cr);
|
||||
prepareLayout(layout, pl);
|
||||
|
||||
cairo_move_to(c->cr, x, y);
|
||||
pango_cairo_show_layout(c->cr, pl);
|
||||
|
||||
g_object_unref(pl);
|
||||
}
|
||||
|
||||
static void addAttr(uiDrawTextLayout *layout, PangoAttribute *attr, int startChar, int endChar)
|
||||
{
|
||||
attr->start_index = layout->graphemes[startChar];
|
||||
attr->end_index = layout->graphemes[endChar];
|
||||
pango_attr_list_insert(layout->attrs, attr);
|
||||
// pango_attr_list_insert() takes attr; we don't free it
|
||||
}
|
||||
|
||||
void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
|
||||
{
|
||||
PangoAttribute *attr;
|
||||
guint16 rr, gg, bb, aa;
|
||||
|
||||
rr = (guint16) (r * 65535);
|
||||
gg = (guint16) (g * 65535);
|
||||
bb = (guint16) (b * 65535);
|
||||
aa = (guint16) (a * 65535);
|
||||
|
||||
attr = pango_attr_foreground_new(rr, gg, bb);
|
||||
addAttr(layout, attr, startChar, endChar);
|
||||
|
||||
// TODO what if aa == 0?
|
||||
attr = FUTURE_pango_attr_foreground_alpha_new(aa);
|
||||
if (attr != NULL)
|
||||
addAttr(layout, attr, startChar, endChar);
|
||||
}
|
79
src/libui_sdl/libui/unix/editablecombo.c
Normal file
79
src/libui_sdl/libui/unix/editablecombo.c
Normal file
@ -0,0 +1,79 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiEditableCombobox {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkBin *bin;
|
||||
GtkComboBox *combobox;
|
||||
GtkComboBoxText *comboboxText;
|
||||
void (*onChanged)(uiEditableCombobox *, void *);
|
||||
void *onChangedData;
|
||||
gulong onChangedSignal;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiEditableCombobox)
|
||||
|
||||
static void onChanged(GtkComboBox *cbox, gpointer data)
|
||||
{
|
||||
uiEditableCombobox *c = uiEditableCombobox(data);
|
||||
|
||||
(*(c->onChanged))(c, c->onChangedData);
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiEditableCombobox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)
|
||||
{
|
||||
gtk_combo_box_text_append(c->comboboxText, NULL, text);
|
||||
}
|
||||
|
||||
char *uiEditableComboboxText(uiEditableCombobox *c)
|
||||
{
|
||||
char *s;
|
||||
char *out;
|
||||
|
||||
s = gtk_combo_box_text_get_active_text(c->comboboxText);
|
||||
// s will always be non-NULL in the case of a combobox with an entry (according to the source code)
|
||||
out = uiUnixStrdupText(s);
|
||||
g_free(s);
|
||||
return out;
|
||||
}
|
||||
|
||||
void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)
|
||||
{
|
||||
GtkEntry *e;
|
||||
|
||||
// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise
|
||||
g_signal_handler_block(c->combobox, c->onChangedSignal);
|
||||
// since there isn't a gtk_combo_box_text_set_active_text()...
|
||||
e = GTK_ENTRY(gtk_bin_get_child(c->bin));
|
||||
gtk_entry_set_text(e, text);
|
||||
g_signal_handler_unblock(c->combobox, c->onChangedSignal);
|
||||
}
|
||||
|
||||
void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)
|
||||
{
|
||||
c->onChanged = f;
|
||||
c->onChangedData = data;
|
||||
}
|
||||
|
||||
uiEditableCombobox *uiNewEditableCombobox(void)
|
||||
{
|
||||
uiEditableCombobox *c;
|
||||
|
||||
uiUnixNewControl(uiEditableCombobox, c);
|
||||
|
||||
c->widget = gtk_combo_box_text_new_with_entry();
|
||||
c->bin = GTK_BIN(c->widget);
|
||||
c->combobox = GTK_COMBO_BOX(c->widget);
|
||||
c->comboboxText = GTK_COMBO_BOX_TEXT(c->widget);
|
||||
|
||||
c->onChangedSignal = g_signal_connect(c->widget, "changed", G_CALLBACK(onChanged), c);
|
||||
uiEditableComboboxOnChanged(c, defaultOnChanged, NULL);
|
||||
|
||||
return c;
|
||||
}
|
97
src/libui_sdl/libui/unix/entry.c
Normal file
97
src/libui_sdl/libui/unix/entry.c
Normal file
@ -0,0 +1,97 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiEntry {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkEntry *entry;
|
||||
GtkEditable *editable;
|
||||
void (*onChanged)(uiEntry *, void *);
|
||||
void *onChangedData;
|
||||
gulong onChangedSignal;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiEntry)
|
||||
|
||||
static void onChanged(GtkEditable *editable, gpointer data)
|
||||
{
|
||||
uiEntry *e = uiEntry(data);
|
||||
|
||||
(*(e->onChanged))(e, e->onChangedData);
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiEntry *e, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
char *uiEntryText(uiEntry *e)
|
||||
{
|
||||
return uiUnixStrdupText(gtk_entry_get_text(e->entry));
|
||||
}
|
||||
|
||||
void uiEntrySetText(uiEntry *e, const char *text)
|
||||
{
|
||||
// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise
|
||||
g_signal_handler_block(e->editable, e->onChangedSignal);
|
||||
gtk_entry_set_text(e->entry, text);
|
||||
g_signal_handler_unblock(e->editable, e->onChangedSignal);
|
||||
// don't queue the control for resize; entry sizes are independent of their contents
|
||||
}
|
||||
|
||||
void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data)
|
||||
{
|
||||
e->onChanged = f;
|
||||
e->onChangedData = data;
|
||||
}
|
||||
|
||||
int uiEntryReadOnly(uiEntry *e)
|
||||
{
|
||||
return gtk_editable_get_editable(e->editable) == FALSE;
|
||||
}
|
||||
|
||||
void uiEntrySetReadOnly(uiEntry *e, int readonly)
|
||||
{
|
||||
gboolean editable;
|
||||
|
||||
editable = TRUE;
|
||||
if (readonly)
|
||||
editable = FALSE;
|
||||
gtk_editable_set_editable(e->editable, editable);
|
||||
}
|
||||
|
||||
static uiEntry *finishNewEntry(GtkWidget *w, const gchar *signal)
|
||||
{
|
||||
uiEntry *e;
|
||||
|
||||
uiUnixNewControl(uiEntry, e);
|
||||
|
||||
e->widget = w;
|
||||
e->entry = GTK_ENTRY(e->widget);
|
||||
e->editable = GTK_EDITABLE(e->widget);
|
||||
|
||||
e->onChangedSignal = g_signal_connect(e->widget, signal, G_CALLBACK(onChanged), e);
|
||||
uiEntryOnChanged(e, defaultOnChanged, NULL);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
uiEntry *uiNewEntry(void)
|
||||
{
|
||||
return finishNewEntry(gtk_entry_new(), "changed");
|
||||
}
|
||||
|
||||
uiEntry *uiNewPasswordEntry(void)
|
||||
{
|
||||
GtkWidget *e;
|
||||
|
||||
e = gtk_entry_new();
|
||||
gtk_entry_set_visibility(GTK_ENTRY(e), FALSE);
|
||||
return finishNewEntry(e, "changed");
|
||||
}
|
||||
|
||||
// TODO make it use a separate function to be type-safe
|
||||
uiEntry *uiNewSearchEntry(void)
|
||||
{
|
||||
return finishNewEntry(gtk_search_entry_new(), "search-changed");
|
||||
}
|
70
src/libui_sdl/libui/unix/fontbutton.c
Normal file
70
src/libui_sdl/libui/unix/fontbutton.c
Normal file
@ -0,0 +1,70 @@
|
||||
// 14 april 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiFontButton {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkButton *button;
|
||||
GtkFontButton *fb;
|
||||
GtkFontChooser *fc;
|
||||
void (*onChanged)(uiFontButton *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiFontButton)
|
||||
|
||||
// TODO NOTE no need to inhibit the signal; font-set is documented as only being sent when the user changes the font
|
||||
static void onFontSet(GtkFontButton *button, gpointer data)
|
||||
{
|
||||
uiFontButton *b = uiFontButton(data);
|
||||
|
||||
(*(b->onChanged))(b, b->onChangedData);
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiFontButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiDrawTextFont *uiFontButtonFont(uiFontButton *b)
|
||||
{
|
||||
PangoFont *f;
|
||||
PangoFontDescription *desc;
|
||||
|
||||
desc = gtk_font_chooser_get_font_desc(b->fc);
|
||||
f = pangoDescToPangoFont(desc);
|
||||
// desc is transfer-full and thus is a copy
|
||||
pango_font_description_free(desc);
|
||||
return mkTextFont(f, FALSE); // we hold the initial reference; no need to ref
|
||||
}
|
||||
|
||||
void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)
|
||||
{
|
||||
b->onChanged = f;
|
||||
b->onChangedData = data;
|
||||
}
|
||||
|
||||
uiFontButton *uiNewFontButton(void)
|
||||
{
|
||||
uiFontButton *b;
|
||||
|
||||
uiUnixNewControl(uiFontButton, b);
|
||||
|
||||
b->widget = gtk_font_button_new();
|
||||
b->button = GTK_BUTTON(b->widget);
|
||||
b->fb = GTK_FONT_BUTTON(b->widget);
|
||||
b->fc = GTK_FONT_CHOOSER(b->widget);
|
||||
|
||||
// match behavior on other platforms
|
||||
gtk_font_button_set_show_style(b->fb, TRUE);
|
||||
gtk_font_button_set_show_size(b->fb, TRUE);
|
||||
gtk_font_button_set_use_font(b->fb, FALSE);
|
||||
gtk_font_button_set_use_size(b->fb, FALSE);
|
||||
// other customizations
|
||||
gtk_font_chooser_set_show_preview_entry(b->fc, TRUE);
|
||||
|
||||
g_signal_connect(b->widget, "font-set", G_CALLBACK(onFontSet), b);
|
||||
uiFontButtonOnChanged(b, defaultOnChanged, NULL);
|
||||
|
||||
return b;
|
||||
}
|
159
src/libui_sdl/libui/unix/form.c
Normal file
159
src/libui_sdl/libui/unix/form.c
Normal file
@ -0,0 +1,159 @@
|
||||
// 8 june 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct formChild {
|
||||
uiControl *c;
|
||||
int stretchy;
|
||||
GtkWidget *label;
|
||||
gboolean oldhexpand;
|
||||
GtkAlign oldhalign;
|
||||
gboolean oldvexpand;
|
||||
GtkAlign oldvalign;
|
||||
GBinding *labelBinding;
|
||||
};
|
||||
|
||||
struct uiForm {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkContainer *container;
|
||||
GtkGrid *grid;
|
||||
GArray *children;
|
||||
int padded;
|
||||
GtkSizeGroup *stretchygroup; // ensures all stretchy controls have the same size
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaultsExceptDestroy(uiForm)
|
||||
|
||||
#define ctrl(f, i) &g_array_index(f->children, struct formChild, i)
|
||||
|
||||
static void uiFormDestroy(uiControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
struct formChild *fc;
|
||||
guint i;
|
||||
|
||||
// kill the size group
|
||||
g_object_unref(f->stretchygroup);
|
||||
// free all controls
|
||||
for (i = 0; i < f->children->len; i++) {
|
||||
fc = ctrl(f, i);
|
||||
uiControlSetParent(fc->c, NULL);
|
||||
uiUnixControlSetContainer(uiUnixControl(fc->c), f->container, TRUE);
|
||||
uiControlDestroy(fc->c);
|
||||
gtk_widget_destroy(fc->label);
|
||||
}
|
||||
g_array_free(f->children, TRUE);
|
||||
// and then ourselves
|
||||
g_object_unref(f->widget);
|
||||
uiFreeControl(uiControl(f));
|
||||
}
|
||||
|
||||
void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)
|
||||
{
|
||||
struct formChild fc;
|
||||
GtkWidget *widget;
|
||||
guint row;
|
||||
|
||||
fc.c = c;
|
||||
widget = GTK_WIDGET(uiControlHandle(fc.c));
|
||||
fc.stretchy = stretchy;
|
||||
fc.oldhexpand = gtk_widget_get_hexpand(widget);
|
||||
fc.oldhalign = gtk_widget_get_halign(widget);
|
||||
fc.oldvexpand = gtk_widget_get_vexpand(widget);
|
||||
fc.oldvalign = gtk_widget_get_valign(widget);
|
||||
|
||||
if (stretchy) {
|
||||
gtk_widget_set_vexpand(widget, TRUE);
|
||||
gtk_widget_set_valign(widget, GTK_ALIGN_FILL);
|
||||
gtk_size_group_add_widget(f->stretchygroup, widget);
|
||||
} else
|
||||
gtk_widget_set_vexpand(widget, FALSE);
|
||||
// and make them fill horizontally
|
||||
gtk_widget_set_hexpand(widget, TRUE);
|
||||
gtk_widget_set_halign(widget, GTK_ALIGN_FILL);
|
||||
|
||||
fc.label = gtk_label_new(label);
|
||||
gtk_widget_set_hexpand(fc.label, FALSE);
|
||||
gtk_widget_set_halign(fc.label, GTK_ALIGN_END);
|
||||
gtk_widget_set_vexpand(fc.label, FALSE);
|
||||
if (GTK_IS_SCROLLED_WINDOW(widget))
|
||||
gtk_widget_set_valign(fc.label, GTK_ALIGN_START);
|
||||
else
|
||||
gtk_widget_set_valign(fc.label, GTK_ALIGN_CENTER);
|
||||
gtk_style_context_add_class(gtk_widget_get_style_context(fc.label), "dim-label");
|
||||
row = f->children->len;
|
||||
gtk_grid_attach(f->grid, fc.label,
|
||||
0, row,
|
||||
1, 1);
|
||||
// and make them share visibility so if the control is hidden, so is its label
|
||||
fc.labelBinding = g_object_bind_property(GTK_WIDGET(uiControlHandle(fc.c)), "visible",
|
||||
fc.label, "visible",
|
||||
G_BINDING_SYNC_CREATE);
|
||||
|
||||
uiControlSetParent(fc.c, uiControl(f));
|
||||
uiUnixControlSetContainer(uiUnixControl(fc.c), f->container, FALSE);
|
||||
g_array_append_val(f->children, fc);
|
||||
|
||||
// move the widget to the correct place
|
||||
gtk_container_child_set(f->container, widget,
|
||||
"left-attach", 1,
|
||||
"top-attach", row,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void uiFormDelete(uiForm *f, int index)
|
||||
{
|
||||
struct formChild *fc;
|
||||
GtkWidget *widget;
|
||||
|
||||
fc = ctrl(f, index);
|
||||
widget = GTK_WIDGET(uiControlHandle(fc->c));
|
||||
|
||||
gtk_widget_destroy(fc->label);
|
||||
|
||||
uiControlSetParent(fc->c, NULL);
|
||||
uiUnixControlSetContainer(uiUnixControl(fc->c), f->container, TRUE);
|
||||
|
||||
if (fc->stretchy)
|
||||
gtk_size_group_remove_widget(f->stretchygroup, widget);
|
||||
gtk_widget_set_hexpand(widget, fc->oldhexpand);
|
||||
gtk_widget_set_halign(widget, fc->oldhalign);
|
||||
gtk_widget_set_vexpand(widget, fc->oldvexpand);
|
||||
gtk_widget_set_valign(widget, fc->oldvalign);
|
||||
|
||||
g_array_remove_index(f->children, index);
|
||||
}
|
||||
|
||||
int uiFormPadded(uiForm *f)
|
||||
{
|
||||
return f->padded;
|
||||
}
|
||||
|
||||
void uiFormSetPadded(uiForm *f, int padded)
|
||||
{
|
||||
f->padded = padded;
|
||||
if (f->padded) {
|
||||
gtk_grid_set_row_spacing(f->grid, gtkYPadding);
|
||||
gtk_grid_set_column_spacing(f->grid, gtkXPadding);
|
||||
} else {
|
||||
gtk_grid_set_row_spacing(f->grid, 0);
|
||||
gtk_grid_set_column_spacing(f->grid, 0);
|
||||
}
|
||||
}
|
||||
|
||||
uiForm *uiNewForm(void)
|
||||
{
|
||||
uiForm *f;
|
||||
|
||||
uiUnixNewControl(uiForm, f);
|
||||
|
||||
f->widget = gtk_grid_new();
|
||||
f->container = GTK_CONTAINER(f->widget);
|
||||
f->grid = GTK_GRID(f->widget);
|
||||
|
||||
f->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);
|
||||
|
||||
f->children = g_array_new(FALSE, TRUE, sizeof (struct formChild));
|
||||
|
||||
return f;
|
||||
}
|
42
src/libui_sdl/libui/unix/future.c
Normal file
42
src/libui_sdl/libui/unix/future.c
Normal file
@ -0,0 +1,42 @@
|
||||
// 29 june 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// functions FROM THE FUTURE!
|
||||
// in some cases, because being held back by LTS releases sucks :/
|
||||
// in others, because parts of GTK+ being unstable until recently also sucks :/
|
||||
|
||||
// added in pango 1.38; we need 1.36
|
||||
static PangoAttribute *(*newFGAlphaAttr)(guint16 alpha) = NULL;
|
||||
|
||||
// added in GTK+ 3.20; we need 3.10
|
||||
static void (*gwpIterSetObjectName)(GtkWidgetPath *path, gint pos, const char *name) = NULL;
|
||||
|
||||
// note that we treat any error as "the symbols aren't there" (and don't care if dlclose() failed)
|
||||
void loadFutures(void)
|
||||
{
|
||||
void *handle;
|
||||
|
||||
// dlsym() walks the dependency chain, so opening the current process should be sufficient
|
||||
handle = dlopen(NULL, RTLD_LAZY);
|
||||
if (handle == NULL)
|
||||
return;
|
||||
#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn)
|
||||
GET(newFGAlphaAttr, pango_attr_foreground_alpha_new);
|
||||
GET(gwpIterSetObjectName, gtk_widget_path_iter_set_object_name);
|
||||
dlclose(handle);
|
||||
}
|
||||
|
||||
PangoAttribute *FUTURE_pango_attr_foreground_alpha_new(guint16 alpha)
|
||||
{
|
||||
if (newFGAlphaAttr == NULL)
|
||||
return NULL;
|
||||
return (*newFGAlphaAttr)(alpha);
|
||||
}
|
||||
|
||||
gboolean FUTURE_gtk_widget_path_iter_set_object_name(GtkWidgetPath *path, gint pos, const char *name)
|
||||
{
|
||||
if (gwpIterSetObjectName == NULL)
|
||||
return FALSE;
|
||||
(*gwpIterSetObjectName)(path, pos, name);
|
||||
return TRUE;
|
||||
}
|
31
src/libui_sdl/libui/unix/graphemes.c
Normal file
31
src/libui_sdl/libui/unix/graphemes.c
Normal file
@ -0,0 +1,31 @@
|
||||
// 25 may 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
ptrdiff_t *graphemes(const char *text, PangoContext *context)
|
||||
{
|
||||
size_t len, lenchars;
|
||||
PangoLogAttr *logattrs;
|
||||
ptrdiff_t *out;
|
||||
ptrdiff_t *op;
|
||||
size_t i;
|
||||
|
||||
len = strlen(text);
|
||||
lenchars = g_utf8_strlen(text, -1);
|
||||
logattrs = (PangoLogAttr *) uiAlloc((lenchars + 1) * sizeof (PangoLogAttr), "PangoLogAttr[]");
|
||||
pango_get_log_attrs(text, len,
|
||||
-1, NULL,
|
||||
logattrs, lenchars + 1);
|
||||
|
||||
// should be more than enough
|
||||
out = (ptrdiff_t *) uiAlloc((lenchars + 2) * sizeof (ptrdiff_t), "ptrdiff_t[]");
|
||||
op = out;
|
||||
for (i = 0; i < lenchars; i++)
|
||||
if (logattrs[i].is_cursor_position != 0)
|
||||
// TODO optimize this
|
||||
*op++ = g_utf8_offset_to_pointer(text, i) - text;
|
||||
// and do the last one
|
||||
*op++ = len;
|
||||
|
||||
uiFree(logattrs);
|
||||
return out;
|
||||
}
|
141
src/libui_sdl/libui/unix/grid.c
Normal file
141
src/libui_sdl/libui/unix/grid.c
Normal file
@ -0,0 +1,141 @@
|
||||
// 9 june 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct gridChild {
|
||||
uiControl *c;
|
||||
GtkWidget *label;
|
||||
gboolean oldhexpand;
|
||||
GtkAlign oldhalign;
|
||||
gboolean oldvexpand;
|
||||
GtkAlign oldvalign;
|
||||
};
|
||||
|
||||
struct uiGrid {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkContainer *container;
|
||||
GtkGrid *grid;
|
||||
GArray *children;
|
||||
int padded;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaultsExceptDestroy(uiGrid)
|
||||
|
||||
#define ctrl(g, i) &g_array_index(g->children, struct gridChild, i)
|
||||
|
||||
static void uiGridDestroy(uiControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
struct gridChild *gc;
|
||||
guint i;
|
||||
|
||||
// free all controls
|
||||
for (i = 0; i < g->children->len; i++) {
|
||||
gc = ctrl(g, i);
|
||||
uiControlSetParent(gc->c, NULL);
|
||||
uiUnixControlSetContainer(uiUnixControl(gc->c), g->container, TRUE);
|
||||
uiControlDestroy(gc->c);
|
||||
}
|
||||
g_array_free(g->children, TRUE);
|
||||
// and then ourselves
|
||||
g_object_unref(g->widget);
|
||||
uiFreeControl(uiControl(g));
|
||||
}
|
||||
|
||||
#define TODO_MASSIVE_HACK(c) \
|
||||
if (!uiUnixControl(c)->addedBefore) { \
|
||||
g_object_ref_sink(GTK_WIDGET(uiControlHandle(uiControl(c)))); \
|
||||
gtk_widget_show(GTK_WIDGET(uiControlHandle(uiControl(c)))); \
|
||||
uiUnixControl(c)->addedBefore = TRUE; \
|
||||
}
|
||||
|
||||
static const GtkAlign gtkAligns[] = {
|
||||
[uiAlignFill] = GTK_ALIGN_FILL,
|
||||
[uiAlignStart] = GTK_ALIGN_START,
|
||||
[uiAlignCenter] = GTK_ALIGN_CENTER,
|
||||
[uiAlignEnd] = GTK_ALIGN_END,
|
||||
};
|
||||
|
||||
static const GtkPositionType gtkPositions[] = {
|
||||
[uiAtLeading] = GTK_POS_LEFT,
|
||||
[uiAtTop] = GTK_POS_TOP,
|
||||
[uiAtTrailing] = GTK_POS_RIGHT,
|
||||
[uiAtBottom] = GTK_POS_BOTTOM,
|
||||
};
|
||||
|
||||
static GtkWidget *prepare(struct gridChild *gc, uiControl *c, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
|
||||
{
|
||||
GtkWidget *widget;
|
||||
|
||||
gc->c = c;
|
||||
widget = GTK_WIDGET(uiControlHandle(gc->c));
|
||||
gc->oldhexpand = gtk_widget_get_hexpand(widget);
|
||||
gc->oldhalign = gtk_widget_get_halign(widget);
|
||||
gc->oldvexpand = gtk_widget_get_vexpand(widget);
|
||||
gc->oldvalign = gtk_widget_get_valign(widget);
|
||||
gtk_widget_set_hexpand(widget, hexpand != 0);
|
||||
gtk_widget_set_halign(widget, gtkAligns[halign]);
|
||||
gtk_widget_set_vexpand(widget, vexpand != 0);
|
||||
gtk_widget_set_valign(widget, gtkAligns[valign]);
|
||||
return widget;
|
||||
}
|
||||
|
||||
void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
|
||||
{
|
||||
struct gridChild gc;
|
||||
GtkWidget *widget;
|
||||
|
||||
widget = prepare(&gc, c, hexpand, halign, vexpand, valign);
|
||||
uiControlSetParent(gc.c, uiControl(g));
|
||||
TODO_MASSIVE_HACK(uiUnixControl(gc.c));
|
||||
gtk_grid_attach(g->grid, widget,
|
||||
left, top,
|
||||
xspan, yspan);
|
||||
g_array_append_val(g->children, gc);
|
||||
}
|
||||
|
||||
void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
|
||||
{
|
||||
struct gridChild gc;
|
||||
GtkWidget *widget;
|
||||
|
||||
widget = prepare(&gc, c, hexpand, halign, vexpand, valign);
|
||||
uiControlSetParent(gc.c, uiControl(g));
|
||||
TODO_MASSIVE_HACK(uiUnixControl(gc.c));
|
||||
gtk_grid_attach_next_to(g->grid, widget,
|
||||
GTK_WIDGET(uiControlHandle(existing)), gtkPositions[at],
|
||||
xspan, yspan);
|
||||
g_array_append_val(g->children, gc);
|
||||
}
|
||||
|
||||
int uiGridPadded(uiGrid *g)
|
||||
{
|
||||
return g->padded;
|
||||
}
|
||||
|
||||
void uiGridSetPadded(uiGrid *g, int padded)
|
||||
{
|
||||
g->padded = padded;
|
||||
if (g->padded) {
|
||||
gtk_grid_set_row_spacing(g->grid, gtkYPadding);
|
||||
gtk_grid_set_column_spacing(g->grid, gtkXPadding);
|
||||
} else {
|
||||
gtk_grid_set_row_spacing(g->grid, 0);
|
||||
gtk_grid_set_column_spacing(g->grid, 0);
|
||||
}
|
||||
}
|
||||
|
||||
uiGrid *uiNewGrid(void)
|
||||
{
|
||||
uiGrid *g;
|
||||
|
||||
uiUnixNewControl(uiGrid, g);
|
||||
|
||||
g->widget = gtk_grid_new();
|
||||
g->container = GTK_CONTAINER(g->widget);
|
||||
g->grid = GTK_GRID(g->widget);
|
||||
|
||||
g->children = g_array_new(FALSE, TRUE, sizeof (struct gridChild));
|
||||
|
||||
return g;
|
||||
}
|
89
src/libui_sdl/libui/unix/group.c
Normal file
89
src/libui_sdl/libui/unix/group.c
Normal file
@ -0,0 +1,89 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiGroup {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkContainer *container;
|
||||
GtkBin *bin;
|
||||
GtkFrame *frame;
|
||||
|
||||
// unfortunately, even though a GtkFrame is a GtkBin, calling gtk_container_set_border_width() on it /includes/ the GtkFrame's label; we don't want tht
|
||||
struct child *child;
|
||||
|
||||
int margined;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaultsExceptDestroy(uiGroup)
|
||||
|
||||
static void uiGroupDestroy(uiControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
if (g->child != NULL)
|
||||
childDestroy(g->child);
|
||||
g_object_unref(g->widget);
|
||||
uiFreeControl(uiControl(g));
|
||||
}
|
||||
|
||||
char *uiGroupTitle(uiGroup *g)
|
||||
{
|
||||
return uiUnixStrdupText(gtk_frame_get_label(g->frame));
|
||||
}
|
||||
|
||||
void uiGroupSetTitle(uiGroup *g, const char *text)
|
||||
{
|
||||
gtk_frame_set_label(g->frame, text);
|
||||
}
|
||||
|
||||
void uiGroupSetChild(uiGroup *g, uiControl *child)
|
||||
{
|
||||
if (g->child != NULL)
|
||||
childRemove(g->child);
|
||||
g->child = newChildWithBox(child, uiControl(g), g->container, g->margined);
|
||||
}
|
||||
|
||||
int uiGroupMargined(uiGroup *g)
|
||||
{
|
||||
return g->margined;
|
||||
}
|
||||
|
||||
void uiGroupSetMargined(uiGroup *g, int margined)
|
||||
{
|
||||
g->margined = margined;
|
||||
if (g->child != NULL)
|
||||
childSetMargined(g->child, g->margined);
|
||||
}
|
||||
|
||||
uiGroup *uiNewGroup(const char *text)
|
||||
{
|
||||
uiGroup *g;
|
||||
gfloat yalign;
|
||||
GtkLabel *label;
|
||||
PangoAttribute *bold;
|
||||
PangoAttrList *boldlist;
|
||||
|
||||
uiUnixNewControl(uiGroup, g);
|
||||
|
||||
g->widget = gtk_frame_new(text);
|
||||
g->container = GTK_CONTAINER(g->widget);
|
||||
g->bin = GTK_BIN(g->widget);
|
||||
g->frame = GTK_FRAME(g->widget);
|
||||
|
||||
// with GTK+, groupboxes by default have frames and slightly x-offset regular text
|
||||
// they should have no frame and fully left-justified, bold text
|
||||
// preserve default y-alignment
|
||||
gtk_frame_get_label_align(g->frame, NULL, &yalign);
|
||||
gtk_frame_set_label_align(g->frame, 0, yalign);
|
||||
gtk_frame_set_shadow_type(g->frame, GTK_SHADOW_NONE);
|
||||
label = GTK_LABEL(gtk_frame_get_label_widget(g->frame));
|
||||
// this is the boldness level used by GtkPrintUnixDialog
|
||||
// (it technically uses "bold" but see pango's pango-enum-types.c for the name conversion; GType is weird)
|
||||
bold = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
|
||||
boldlist = pango_attr_list_new();
|
||||
pango_attr_list_insert(boldlist, bold);
|
||||
gtk_label_set_attributes(label, boldlist);
|
||||
pango_attr_list_unref(boldlist); // thanks baedert in irc.gimp.net/#gtk+
|
||||
|
||||
return g;
|
||||
}
|
120
src/libui_sdl/libui/unix/image.c
Normal file
120
src/libui_sdl/libui/unix/image.c
Normal file
@ -0,0 +1,120 @@
|
||||
// 27 june 2016
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiImage {
|
||||
double width;
|
||||
double height;
|
||||
GPtrArray *images;
|
||||
};
|
||||
|
||||
static void freeImageRep(gpointer item)
|
||||
{
|
||||
cairo_surface_t *cs = (cairo_surface_t *) item;
|
||||
unsigned char *buf;
|
||||
|
||||
buf = cairo_image_surface_get_data(cs);
|
||||
cairo_surface_destroy(cs);
|
||||
uiFree(buf);
|
||||
}
|
||||
|
||||
uiImage *uiNewImage(double width, double height)
|
||||
{
|
||||
uiImage *i;
|
||||
|
||||
i = uiNew(uiImage);
|
||||
i->width = width;
|
||||
i->height = height;
|
||||
i->images = g_ptr_array_new_with_free_func(freeImageRep);
|
||||
return i;
|
||||
}
|
||||
|
||||
void uiFreeImage(uiImage *i)
|
||||
{
|
||||
g_ptr_array_free(i->images, TRUE);
|
||||
uiFree(i);
|
||||
}
|
||||
|
||||
void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride)
|
||||
{
|
||||
cairo_surface_t *cs;
|
||||
unsigned char *buf, *p;
|
||||
uint8_t *src = (uint8_t *) pixels;
|
||||
int cstride;
|
||||
int y;
|
||||
|
||||
cstride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixelWidth);
|
||||
buf = (unsigned char *) uiAlloc((cstride * pixelHeight * 4) * sizeof (unsigned char), "unsigned char[]");
|
||||
p = buf;
|
||||
for (y = 0; y < pixelStride * pixelHeight; y += pixelStride) {
|
||||
memmove(p, src + y, cstride);
|
||||
p += cstride;
|
||||
}
|
||||
cs = cairo_image_surface_create_for_data(buf, CAIRO_FORMAT_ARGB32,
|
||||
pixelWidth, pixelHeight,
|
||||
cstride);
|
||||
if (cairo_surface_status(cs) != CAIRO_STATUS_SUCCESS)
|
||||
/* TODO */;
|
||||
cairo_surface_flush(cs);
|
||||
g_ptr_array_add(i->images, cs);
|
||||
}
|
||||
|
||||
struct matcher {
|
||||
cairo_surface_t *best;
|
||||
int distX;
|
||||
int distY;
|
||||
int targetX;
|
||||
int targetY;
|
||||
gboolean foundLarger;
|
||||
};
|
||||
|
||||
// TODO is this the right algorithm?
|
||||
static void match(gpointer surface, gpointer data)
|
||||
{
|
||||
cairo_surface_t *cs = (cairo_surface_t *) surface;
|
||||
struct matcher *m = (struct matcher *) data;
|
||||
int x, y;
|
||||
int x2, y2;
|
||||
|
||||
x = cairo_image_surface_get_width(cs);
|
||||
y = cairo_image_surface_get_height(cs);
|
||||
if (m->best == NULL)
|
||||
goto writeMatch;
|
||||
|
||||
if (x < m->targetX && y < m->targetY)
|
||||
if (m->foundLarger)
|
||||
// always prefer larger ones
|
||||
return;
|
||||
if (x >= m->targetX && y >= m->targetY && !m->foundLarger)
|
||||
// we set foundLarger below
|
||||
goto writeMatch;
|
||||
|
||||
x2 = abs(m->targetX - x);
|
||||
y2 = abs(m->targetY - y);
|
||||
if (x2 < m->distX && y2 < m->distY)
|
||||
goto writeMatch;
|
||||
|
||||
// TODO weight one dimension? threshhold?
|
||||
return;
|
||||
|
||||
writeMatch:
|
||||
// must set this here too; otherwise the first image will never have ths set
|
||||
if (x >= m->targetX && y >= m->targetY && !m->foundLarger)
|
||||
m->foundLarger = TRUE;
|
||||
m->best = cs;
|
||||
m->distX = abs(m->targetX - x);
|
||||
m->distY = abs(m->targetY - y);
|
||||
}
|
||||
|
||||
cairo_surface_t *imageAppropriateSurface(uiImage *i, GtkWidget *w)
|
||||
{
|
||||
struct matcher m;
|
||||
|
||||
m.best = NULL;
|
||||
m.distX = G_MAXINT;
|
||||
m.distY = G_MAXINT;
|
||||
m.targetX = i->width * gtk_widget_get_scale_factor(w);
|
||||
m.targetY = i->height * gtk_widget_get_scale_factor(w);
|
||||
m.foundLarger = FALSE;
|
||||
g_ptr_array_foreach(i->images, match, &m);
|
||||
return m.best;
|
||||
}
|
36
src/libui_sdl/libui/unix/label.c
Normal file
36
src/libui_sdl/libui/unix/label.c
Normal file
@ -0,0 +1,36 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiLabel {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkMisc *misc;
|
||||
GtkLabel *label;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiLabel)
|
||||
|
||||
char *uiLabelText(uiLabel *l)
|
||||
{
|
||||
return uiUnixStrdupText(gtk_label_get_text(l->label));
|
||||
}
|
||||
|
||||
void uiLabelSetText(uiLabel *l, const char *text)
|
||||
{
|
||||
gtk_label_set_text(l->label, text);
|
||||
}
|
||||
|
||||
uiLabel *uiNewLabel(const char *text)
|
||||
{
|
||||
uiLabel *l;
|
||||
|
||||
uiUnixNewControl(uiLabel, l);
|
||||
|
||||
l->widget = gtk_label_new(text);
|
||||
l->misc = GTK_MISC(l->widget);
|
||||
l->label = GTK_LABEL(l->widget);
|
||||
|
||||
gtk_misc_set_alignment(l->misc, 0, 0);
|
||||
|
||||
return l;
|
||||
}
|
108
src/libui_sdl/libui/unix/main.c
Normal file
108
src/libui_sdl/libui/unix/main.c
Normal file
@ -0,0 +1,108 @@
|
||||
// 6 april 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
uiInitOptions options;
|
||||
|
||||
const char *uiInit(uiInitOptions *o)
|
||||
{
|
||||
GError *err = NULL;
|
||||
const char *msg;
|
||||
|
||||
options = *o;
|
||||
if (gtk_init_with_args(NULL, NULL, NULL, NULL, NULL, &err) == FALSE) {
|
||||
msg = g_strdup(err->message);
|
||||
g_error_free(err);
|
||||
return msg;
|
||||
}
|
||||
initAlloc();
|
||||
loadFutures();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void uiUninit(void)
|
||||
{
|
||||
uninitMenus();
|
||||
uninitAlloc();
|
||||
}
|
||||
|
||||
void uiFreeInitError(const char *err)
|
||||
{
|
||||
g_free((gpointer) err);
|
||||
}
|
||||
|
||||
static gboolean (*iteration)(gboolean) = NULL;
|
||||
|
||||
void uiMain(void)
|
||||
{
|
||||
iteration = gtk_main_iteration_do;
|
||||
gtk_main();
|
||||
}
|
||||
|
||||
static gboolean stepsQuit = FALSE;
|
||||
|
||||
// the only difference is we ignore the return value from gtk_main_iteration_do(), since it will always be TRUE if gtk_main() was never called
|
||||
// gtk_main_iteration_do() will still run the main loop regardless
|
||||
static gboolean stepsIteration(gboolean block)
|
||||
{
|
||||
gtk_main_iteration_do(block);
|
||||
return stepsQuit;
|
||||
}
|
||||
|
||||
void uiMainSteps(void)
|
||||
{
|
||||
iteration = stepsIteration;
|
||||
}
|
||||
|
||||
int uiMainStep(int wait)
|
||||
{
|
||||
gboolean block;
|
||||
|
||||
block = FALSE;
|
||||
if (wait)
|
||||
block = TRUE;
|
||||
return (*iteration)(block) == FALSE;
|
||||
}
|
||||
|
||||
// gtk_main_quit() may run immediately, or it may wait for other pending events; "it depends" (thanks mclasen in irc.gimp.net/#gtk+)
|
||||
// PostQuitMessage() on Windows always waits, so we must do so too
|
||||
// we'll do it by using an idle callback
|
||||
static gboolean quit(gpointer data)
|
||||
{
|
||||
if (iteration == stepsIteration)
|
||||
stepsQuit = TRUE;
|
||||
// TODO run a gtk_main() here just to do the cleanup steps of syncing the clipboard and other stuff gtk_main() does before it returns
|
||||
else
|
||||
gtk_main_quit();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void uiQuit(void)
|
||||
{
|
||||
gdk_threads_add_idle(quit, NULL);
|
||||
}
|
||||
|
||||
struct queued {
|
||||
void (*f)(void *);
|
||||
void *data;
|
||||
};
|
||||
|
||||
static gboolean doqueued(gpointer data)
|
||||
{
|
||||
struct queued *q = (struct queued *) data;
|
||||
|
||||
(*(q->f))(q->data);
|
||||
g_free(q);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void uiQueueMain(void (*f)(void *data), void *data)
|
||||
{
|
||||
struct queued *q;
|
||||
|
||||
// we have to use g_new0()/g_free() because uiAlloc() is only safe to call on the main thread
|
||||
// for some reason it didn't affect me, but it did affect krakjoe
|
||||
q = g_new0(struct queued, 1);
|
||||
q->f = f;
|
||||
q->data = data;
|
||||
gdk_threads_add_idle(doqueued, q);
|
||||
}
|
366
src/libui_sdl/libui/unix/menu.c
Normal file
366
src/libui_sdl/libui/unix/menu.c
Normal file
@ -0,0 +1,366 @@
|
||||
// 23 april 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
static GArray *menus = NULL;
|
||||
static gboolean menusFinalized = FALSE;
|
||||
static gboolean hasQuit = FALSE;
|
||||
static gboolean hasPreferences = FALSE;
|
||||
static gboolean hasAbout = FALSE;
|
||||
|
||||
struct uiMenu {
|
||||
char *name;
|
||||
GArray *items; // []*uiMenuItem
|
||||
};
|
||||
|
||||
struct uiMenuItem {
|
||||
char *name;
|
||||
int type;
|
||||
void (*onClicked)(uiMenuItem *, uiWindow *, void *);
|
||||
void *onClickedData;
|
||||
GType gtype; // template for new instances; kept in sync with everything else
|
||||
gboolean disabled;
|
||||
gboolean checked;
|
||||
GHashTable *windows; // map[GtkMenuItem]*menuItemWindow
|
||||
};
|
||||
|
||||
struct menuItemWindow {
|
||||
uiWindow *w;
|
||||
gulong signal;
|
||||
};
|
||||
|
||||
enum {
|
||||
typeRegular,
|
||||
typeCheckbox,
|
||||
typeQuit,
|
||||
typePreferences,
|
||||
typeAbout,
|
||||
typeSeparator,
|
||||
};
|
||||
|
||||
// we do NOT want programmatic updates to raise an ::activated signal
|
||||
static void singleSetChecked(GtkCheckMenuItem *menuitem, gboolean checked, gulong signal)
|
||||
{
|
||||
g_signal_handler_block(menuitem, signal);
|
||||
gtk_check_menu_item_set_active(menuitem, checked);
|
||||
g_signal_handler_unblock(menuitem, signal);
|
||||
}
|
||||
|
||||
static void setChecked(uiMenuItem *item, gboolean checked)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer widget;
|
||||
gpointer ww;
|
||||
struct menuItemWindow *w;
|
||||
|
||||
item->checked = checked;
|
||||
g_hash_table_iter_init(&iter, item->windows);
|
||||
while (g_hash_table_iter_next(&iter, &widget, &ww)) {
|
||||
w = (struct menuItemWindow *) ww;
|
||||
singleSetChecked(GTK_CHECK_MENU_ITEM(widget), item->checked, w->signal);
|
||||
}
|
||||
}
|
||||
|
||||
static void onClicked(GtkMenuItem *menuitem, gpointer data)
|
||||
{
|
||||
uiMenuItem *item = uiMenuItem(data);
|
||||
struct menuItemWindow *w;
|
||||
|
||||
// we need to manually update the checked states of all menu items if one changes
|
||||
// notice that this is getting the checked state of the menu item that this signal is sent from
|
||||
if (item->type == typeCheckbox)
|
||||
setChecked(item, gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)));
|
||||
|
||||
w = (struct menuItemWindow *) g_hash_table_lookup(item->windows, menuitem);
|
||||
(*(item->onClicked))(item, w->w, item->onClickedData);
|
||||
}
|
||||
|
||||
static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void onQuitClicked(uiMenuItem *item, uiWindow *w, void *data)
|
||||
{
|
||||
if (shouldQuit())
|
||||
uiQuit();
|
||||
}
|
||||
|
||||
static void menuItemEnableDisable(uiMenuItem *item, gboolean enabled)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer widget;
|
||||
|
||||
item->disabled = !enabled;
|
||||
g_hash_table_iter_init(&iter, item->windows);
|
||||
while (g_hash_table_iter_next(&iter, &widget, NULL))
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(widget), enabled);
|
||||
}
|
||||
|
||||
void uiMenuItemEnable(uiMenuItem *item)
|
||||
{
|
||||
menuItemEnableDisable(item, TRUE);
|
||||
}
|
||||
|
||||
void uiMenuItemDisable(uiMenuItem *item)
|
||||
{
|
||||
menuItemEnableDisable(item, FALSE);
|
||||
}
|
||||
|
||||
void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
|
||||
{
|
||||
if (item->type == typeQuit)
|
||||
userbug("You cannot call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.");
|
||||
item->onClicked = f;
|
||||
item->onClickedData = data;
|
||||
}
|
||||
|
||||
int uiMenuItemChecked(uiMenuItem *item)
|
||||
{
|
||||
return item->checked != FALSE;
|
||||
}
|
||||
|
||||
void uiMenuItemSetChecked(uiMenuItem *item, int checked)
|
||||
{
|
||||
gboolean c;
|
||||
|
||||
// use explicit values
|
||||
c = FALSE;
|
||||
if (checked)
|
||||
c = TRUE;
|
||||
setChecked(item, c);
|
||||
}
|
||||
|
||||
static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
|
||||
{
|
||||
uiMenuItem *item;
|
||||
|
||||
if (menusFinalized)
|
||||
userbug("You cannot create a new menu item after menus have been finalized.");
|
||||
|
||||
item = uiNew(uiMenuItem);
|
||||
|
||||
g_array_append_val(m->items, item);
|
||||
|
||||
item->type = type;
|
||||
switch (item->type) {
|
||||
case typeQuit:
|
||||
item->name = g_strdup("Quit");
|
||||
break;
|
||||
case typePreferences:
|
||||
item->name = g_strdup("Preferences...");
|
||||
break;
|
||||
case typeAbout:
|
||||
item->name = g_strdup("About");
|
||||
break;
|
||||
case typeSeparator:
|
||||
break;
|
||||
default:
|
||||
item->name = g_strdup(name);
|
||||
break;
|
||||
}
|
||||
|
||||
if (item->type == typeQuit) {
|
||||
// can't call uiMenuItemOnClicked() here
|
||||
item->onClicked = onQuitClicked;
|
||||
item->onClickedData = NULL;
|
||||
} else
|
||||
uiMenuItemOnClicked(item, defaultOnClicked, NULL);
|
||||
|
||||
switch (item->type) {
|
||||
case typeCheckbox:
|
||||
item->gtype = GTK_TYPE_CHECK_MENU_ITEM;
|
||||
break;
|
||||
case typeSeparator:
|
||||
item->gtype = GTK_TYPE_SEPARATOR_MENU_ITEM;
|
||||
break;
|
||||
default:
|
||||
item->gtype = GTK_TYPE_MENU_ITEM;
|
||||
break;
|
||||
}
|
||||
|
||||
item->windows = g_hash_table_new(g_direct_hash, g_direct_equal);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
|
||||
{
|
||||
return newItem(m, typeRegular, name);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)
|
||||
{
|
||||
return newItem(m, typeCheckbox, name);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)
|
||||
{
|
||||
if (hasQuit)
|
||||
userbug("You cannot have multiple Quit menu items in the same program.");
|
||||
hasQuit = TRUE;
|
||||
newItem(m, typeSeparator, NULL);
|
||||
return newItem(m, typeQuit, NULL);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
|
||||
{
|
||||
if (hasPreferences)
|
||||
userbug("You cannot have multiple Preferences menu items in the same program.");
|
||||
hasPreferences = TRUE;
|
||||
newItem(m, typeSeparator, NULL);
|
||||
return newItem(m, typePreferences, NULL);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
|
||||
{
|
||||
if (hasAbout)
|
||||
userbug("You cannot have multiple About menu items in the same program.");
|
||||
hasAbout = TRUE;
|
||||
newItem(m, typeSeparator, NULL);
|
||||
return newItem(m, typeAbout, NULL);
|
||||
}
|
||||
|
||||
void uiMenuAppendSeparator(uiMenu *m)
|
||||
{
|
||||
newItem(m, typeSeparator, NULL);
|
||||
}
|
||||
|
||||
uiMenu *uiNewMenu(const char *name)
|
||||
{
|
||||
uiMenu *m;
|
||||
|
||||
if (menusFinalized)
|
||||
userbug("You cannot create a new menu after menus have been finalized.");
|
||||
if (menus == NULL)
|
||||
menus = g_array_new(FALSE, TRUE, sizeof (uiMenu *));
|
||||
|
||||
m = uiNew(uiMenu);
|
||||
|
||||
g_array_append_val(menus, m);
|
||||
|
||||
m->name = g_strdup(name);
|
||||
m->items = g_array_new(FALSE, TRUE, sizeof (uiMenuItem *));
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
static void appendMenuItem(GtkMenuShell *submenu, uiMenuItem *item, uiWindow *w)
|
||||
{
|
||||
GtkWidget *menuitem;
|
||||
gulong signal;
|
||||
struct menuItemWindow *ww;
|
||||
|
||||
menuitem = g_object_new(item->gtype, NULL);
|
||||
if (item->name != NULL)
|
||||
gtk_menu_item_set_label(GTK_MENU_ITEM(menuitem), item->name);
|
||||
if (item->type != typeSeparator) {
|
||||
signal = g_signal_connect(menuitem, "activate", G_CALLBACK(onClicked), item);
|
||||
gtk_widget_set_sensitive(menuitem, !item->disabled);
|
||||
if (item->type == typeCheckbox)
|
||||
singleSetChecked(GTK_CHECK_MENU_ITEM(menuitem), item->checked, signal);
|
||||
}
|
||||
gtk_menu_shell_append(submenu, menuitem);
|
||||
ww = uiNew(struct menuItemWindow);
|
||||
ww->w = w;
|
||||
ww->signal = signal;
|
||||
g_hash_table_insert(item->windows, menuitem, ww);
|
||||
}
|
||||
|
||||
GtkWidget *makeMenubar(uiWindow *w)
|
||||
{
|
||||
GtkWidget *menubar;
|
||||
guint i, j;
|
||||
uiMenu *m;
|
||||
GtkWidget *menuitem;
|
||||
GtkWidget *submenu;
|
||||
|
||||
menusFinalized = TRUE;
|
||||
|
||||
menubar = gtk_menu_bar_new();
|
||||
|
||||
if (menus != NULL)
|
||||
for (i = 0; i < menus->len; i++) {
|
||||
m = g_array_index(menus, uiMenu *, i);
|
||||
menuitem = gtk_menu_item_new_with_label(m->name);
|
||||
submenu = gtk_menu_new();
|
||||
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
|
||||
for (j = 0; j < m->items->len; j++)
|
||||
appendMenuItem(GTK_MENU_SHELL(submenu), g_array_index(m->items, uiMenuItem *, j), w);
|
||||
gtk_menu_shell_append(GTK_MENU_SHELL(menubar), menuitem);
|
||||
}
|
||||
|
||||
gtk_widget_set_hexpand(menubar, TRUE);
|
||||
gtk_widget_set_halign(menubar, GTK_ALIGN_FILL);
|
||||
return menubar;
|
||||
}
|
||||
|
||||
struct freeMenuItemData {
|
||||
GArray *items;
|
||||
guint i;
|
||||
};
|
||||
|
||||
static void freeMenuItem(GtkWidget *widget, gpointer data)
|
||||
{
|
||||
struct freeMenuItemData *fmi = (struct freeMenuItemData *) data;
|
||||
uiMenuItem *item;
|
||||
struct menuItemWindow *w;
|
||||
|
||||
item = g_array_index(fmi->items, uiMenuItem *, fmi->i);
|
||||
w = (struct menuItemWindow *) g_hash_table_lookup(item->windows, widget);
|
||||
if (g_hash_table_remove(item->windows, widget) == FALSE)
|
||||
implbug("GtkMenuItem %p not in menu item's item/window map", widget);
|
||||
uiFree(w);
|
||||
fmi->i++;
|
||||
}
|
||||
|
||||
static void freeMenu(GtkWidget *widget, gpointer data)
|
||||
{
|
||||
guint *i = (guint *) data;
|
||||
uiMenu *m;
|
||||
GtkMenuItem *item;
|
||||
GtkWidget *submenu;
|
||||
struct freeMenuItemData fmi;
|
||||
|
||||
m = g_array_index(menus, uiMenu *, *i);
|
||||
item = GTK_MENU_ITEM(widget);
|
||||
submenu = gtk_menu_item_get_submenu(item);
|
||||
fmi.items = m->items;
|
||||
fmi.i = 0;
|
||||
gtk_container_foreach(GTK_CONTAINER(submenu), freeMenuItem, &fmi);
|
||||
(*i)++;
|
||||
}
|
||||
|
||||
void freeMenubar(GtkWidget *mb)
|
||||
{
|
||||
guint i;
|
||||
|
||||
i = 0;
|
||||
gtk_container_foreach(GTK_CONTAINER(mb), freeMenu, &i);
|
||||
// no need to worry about destroying any widgets; destruction of the window they're in will do it for us
|
||||
}
|
||||
|
||||
void uninitMenus(void)
|
||||
{
|
||||
uiMenu *m;
|
||||
uiMenuItem *item;
|
||||
guint i, j;
|
||||
|
||||
if (menus == NULL)
|
||||
return;
|
||||
for (i = 0; i < menus->len; i++) {
|
||||
m = g_array_index(menus, uiMenu *, i);
|
||||
g_free(m->name);
|
||||
for (j = 0; j < m->items->len; j++) {
|
||||
item = g_array_index(m->items, uiMenuItem *, j);
|
||||
if (g_hash_table_size(item->windows) != 0)
|
||||
// TODO is this really a userbug()?
|
||||
implbug("menu item %p (%s) still has uiWindows attached; did you forget to destroy some windows?", item, item->name);
|
||||
g_free(item->name);
|
||||
g_hash_table_destroy(item->windows);
|
||||
uiFree(item);
|
||||
}
|
||||
g_array_free(m->items, TRUE);
|
||||
uiFree(m);
|
||||
}
|
||||
g_array_free(menus, TRUE);
|
||||
}
|
124
src/libui_sdl/libui/unix/multilineentry.c
Normal file
124
src/libui_sdl/libui/unix/multilineentry.c
Normal file
@ -0,0 +1,124 @@
|
||||
// 6 december 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiMultilineEntry {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkContainer *scontainer;
|
||||
GtkScrolledWindow *sw;
|
||||
GtkWidget *textviewWidget;
|
||||
GtkTextView *textview;
|
||||
GtkTextBuffer *textbuf;
|
||||
void (*onChanged)(uiMultilineEntry *, void *);
|
||||
void *onChangedData;
|
||||
gulong onChangedSignal;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiMultilineEntry)
|
||||
|
||||
static void onChanged(GtkTextBuffer *textbuf, gpointer data)
|
||||
{
|
||||
uiMultilineEntry *e = uiMultilineEntry(data);
|
||||
|
||||
(*(e->onChanged))(e, e->onChangedData);
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiMultilineEntry *e, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
char *uiMultilineEntryText(uiMultilineEntry *e)
|
||||
{
|
||||
GtkTextIter start, end;
|
||||
char *tret, *out;
|
||||
|
||||
gtk_text_buffer_get_start_iter(e->textbuf, &start);
|
||||
gtk_text_buffer_get_end_iter(e->textbuf, &end);
|
||||
tret = gtk_text_buffer_get_text(e->textbuf, &start, &end, TRUE);
|
||||
// theoretically we could just return tret because uiUnixStrdupText() is just g_strdup(), but if that ever changes we can't, so let's do it this way to be safe
|
||||
out = uiUnixStrdupText(tret);
|
||||
g_free(tret);
|
||||
return out;
|
||||
}
|
||||
|
||||
void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)
|
||||
{
|
||||
// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise
|
||||
g_signal_handler_block(e->textbuf, e->onChangedSignal);
|
||||
gtk_text_buffer_set_text(e->textbuf, text, -1);
|
||||
g_signal_handler_unblock(e->textbuf, e->onChangedSignal);
|
||||
}
|
||||
|
||||
// TODO scroll to end?
|
||||
void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)
|
||||
{
|
||||
GtkTextIter end;
|
||||
|
||||
gtk_text_buffer_get_end_iter(e->textbuf, &end);
|
||||
// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise
|
||||
g_signal_handler_block(e->textbuf, e->onChangedSignal);
|
||||
gtk_text_buffer_insert(e->textbuf, &end, text, -1);
|
||||
g_signal_handler_unblock(e->textbuf, e->onChangedSignal);
|
||||
}
|
||||
|
||||
void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data)
|
||||
{
|
||||
e->onChanged = f;
|
||||
e->onChangedData = data;
|
||||
}
|
||||
|
||||
int uiMultilineEntryReadOnly(uiMultilineEntry *e)
|
||||
{
|
||||
return gtk_text_view_get_editable(e->textview) == FALSE;
|
||||
}
|
||||
|
||||
void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)
|
||||
{
|
||||
gboolean editable;
|
||||
|
||||
editable = TRUE;
|
||||
if (readonly)
|
||||
editable = FALSE;
|
||||
gtk_text_view_set_editable(e->textview, editable);
|
||||
}
|
||||
|
||||
static uiMultilineEntry *finishMultilineEntry(GtkPolicyType hpolicy, GtkWrapMode wrapMode)
|
||||
{
|
||||
uiMultilineEntry *e;
|
||||
|
||||
uiUnixNewControl(uiMultilineEntry, e);
|
||||
|
||||
e->widget = gtk_scrolled_window_new(NULL, NULL);
|
||||
e->scontainer = GTK_CONTAINER(e->widget);
|
||||
e->sw = GTK_SCROLLED_WINDOW(e->widget);
|
||||
gtk_scrolled_window_set_policy(e->sw,
|
||||
hpolicy,
|
||||
GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_shadow_type(e->sw, GTK_SHADOW_IN);
|
||||
|
||||
e->textviewWidget = gtk_text_view_new();
|
||||
e->textview = GTK_TEXT_VIEW(e->textviewWidget);
|
||||
gtk_text_view_set_wrap_mode(e->textview, wrapMode);
|
||||
|
||||
gtk_container_add(e->scontainer, e->textviewWidget);
|
||||
// and make the text view visible; only the scrolled window's visibility is controlled by libui
|
||||
gtk_widget_show(e->textviewWidget);
|
||||
|
||||
e->textbuf = gtk_text_view_get_buffer(e->textview);
|
||||
|
||||
e->onChangedSignal = g_signal_connect(e->textbuf, "changed", G_CALLBACK(onChanged), e);
|
||||
uiMultilineEntryOnChanged(e, defaultOnChanged, NULL);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
uiMultilineEntry *uiNewMultilineEntry(void)
|
||||
{
|
||||
return finishMultilineEntry(GTK_POLICY_NEVER, GTK_WRAP_WORD);
|
||||
}
|
||||
|
||||
uiMultilineEntry *uiNewNonWrappingMultilineEntry(void)
|
||||
{
|
||||
return finishMultilineEntry(GTK_POLICY_AUTOMATIC, GTK_WRAP_NONE);
|
||||
}
|
71
src/libui_sdl/libui/unix/progressbar.c
Normal file
71
src/libui_sdl/libui/unix/progressbar.c
Normal file
@ -0,0 +1,71 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiProgressBar {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkProgressBar *pbar;
|
||||
gboolean indeterminate;
|
||||
guint pulser;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaultsExceptDestroy(uiProgressBar)
|
||||
|
||||
static void uiProgressBarDestroy(uiControl *c)
|
||||
{
|
||||
uiProgressBar *p = uiProgressBar(c);
|
||||
|
||||
// be sure to stop the timeout now
|
||||
if (p->indeterminate)
|
||||
g_source_remove(p->pulser);
|
||||
g_object_unref(p->widget);
|
||||
uiFreeControl(uiControl(p));
|
||||
}
|
||||
|
||||
int uiProgressBarValue(uiProgressBar *p)
|
||||
{
|
||||
if (p->indeterminate)
|
||||
return -1;
|
||||
return (int) (gtk_progress_bar_get_fraction(p->pbar) * 100);
|
||||
}
|
||||
|
||||
static gboolean pulse(void* data)
|
||||
{
|
||||
uiProgressBar *p = uiProgressBar(data);
|
||||
|
||||
gtk_progress_bar_pulse(p->pbar);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void uiProgressBarSetValue(uiProgressBar *p, int value)
|
||||
{
|
||||
if (value == -1) {
|
||||
if (!p->indeterminate) {
|
||||
p->indeterminate = TRUE;
|
||||
// TODO verify the timeout
|
||||
p->pulser = g_timeout_add(100, pulse, p);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (p->indeterminate) {
|
||||
p->indeterminate = FALSE;
|
||||
g_source_remove(p->pulser);
|
||||
}
|
||||
|
||||
if (value < 0 || value > 100)
|
||||
userbug("Value %d is out of range for a uiProgressBar.", value);
|
||||
|
||||
gtk_progress_bar_set_fraction(p->pbar, ((gdouble) value) / 100);
|
||||
}
|
||||
|
||||
uiProgressBar *uiNewProgressBar(void)
|
||||
{
|
||||
uiProgressBar *p;
|
||||
|
||||
uiUnixNewControl(uiProgressBar, p);
|
||||
|
||||
p->widget = gtk_progress_bar_new();
|
||||
p->pbar = GTK_PROGRESS_BAR(p->widget);
|
||||
|
||||
return p;
|
||||
}
|
121
src/libui_sdl/libui/unix/radiobuttons.c
Normal file
121
src/libui_sdl/libui/unix/radiobuttons.c
Normal file
@ -0,0 +1,121 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// on GTK+ a uiRadioButtons is a GtkBox with each of the GtkRadioButtons as children
|
||||
|
||||
struct uiRadioButtons {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkContainer *container;
|
||||
GtkBox *box;
|
||||
GPtrArray *buttons;
|
||||
void (*onSelected)(uiRadioButtons *, void *);
|
||||
void *onSelectedData;
|
||||
gboolean changing;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaultsExceptDestroy(uiRadioButtons)
|
||||
|
||||
static void defaultOnSelected(uiRadioButtons *r, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void onToggled(GtkToggleButton *tb, gpointer data)
|
||||
{
|
||||
uiRadioButtons *r = uiRadioButtons(data);
|
||||
|
||||
// only care if a button is selected
|
||||
if (!gtk_toggle_button_get_active(tb))
|
||||
return;
|
||||
// ignore programmatic changes
|
||||
if (r->changing)
|
||||
return;
|
||||
(*(r->onSelected))(r, r->onSelectedData);
|
||||
}
|
||||
|
||||
static void uiRadioButtonsDestroy(uiControl *c)
|
||||
{
|
||||
uiRadioButtons *r = uiRadioButtons(c);
|
||||
GtkWidget *b;
|
||||
|
||||
while (r->buttons->len != 0) {
|
||||
b = GTK_WIDGET(g_ptr_array_remove_index(r->buttons, 0));
|
||||
gtk_widget_destroy(b);
|
||||
}
|
||||
g_ptr_array_free(r->buttons, TRUE);
|
||||
// and free ourselves
|
||||
g_object_unref(r->widget);
|
||||
uiFreeControl(uiControl(r));
|
||||
}
|
||||
|
||||
void uiRadioButtonsAppend(uiRadioButtons *r, const char *text)
|
||||
{
|
||||
GtkWidget *rb;
|
||||
GtkRadioButton *previous;
|
||||
|
||||
previous = NULL;
|
||||
if (r->buttons->len > 0)
|
||||
previous = GTK_RADIO_BUTTON(g_ptr_array_index(r->buttons, 0));
|
||||
rb = gtk_radio_button_new_with_label_from_widget(previous, text);
|
||||
g_signal_connect(rb, "toggled", G_CALLBACK(onToggled), r);
|
||||
gtk_container_add(r->container, rb);
|
||||
g_ptr_array_add(r->buttons, rb);
|
||||
gtk_widget_show(rb);
|
||||
}
|
||||
|
||||
int uiRadioButtonsSelected(uiRadioButtons *r)
|
||||
{
|
||||
GtkToggleButton *tb;
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < r->buttons->len; i++) {
|
||||
tb = GTK_TOGGLE_BUTTON(g_ptr_array_index(r->buttons, i));
|
||||
if (gtk_toggle_button_get_active(tb))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void uiRadioButtonsSetSelected(uiRadioButtons *r, int n)
|
||||
{
|
||||
GtkToggleButton *tb;
|
||||
gboolean active;
|
||||
|
||||
active = TRUE;
|
||||
// TODO this doesn't work
|
||||
if (n == -1) {
|
||||
n = uiRadioButtonsSelected(r);
|
||||
if (n == -1) // no selection; keep it that way
|
||||
return;
|
||||
active = FALSE;
|
||||
}
|
||||
tb = GTK_TOGGLE_BUTTON(g_ptr_array_index(r->buttons, n));
|
||||
// this is easier than remembering all the signals
|
||||
r->changing = TRUE;
|
||||
gtk_toggle_button_set_active(tb, active);
|
||||
r->changing = FALSE;
|
||||
}
|
||||
|
||||
void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data)
|
||||
{
|
||||
r->onSelected = f;
|
||||
r->onSelectedData = data;
|
||||
}
|
||||
|
||||
uiRadioButtons *uiNewRadioButtons(void)
|
||||
{
|
||||
uiRadioButtons *r;
|
||||
|
||||
uiUnixNewControl(uiRadioButtons, r);
|
||||
|
||||
r->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
r->container = GTK_CONTAINER(r->widget);
|
||||
r->box = GTK_BOX(r->widget);
|
||||
|
||||
r->buttons = g_ptr_array_new();
|
||||
|
||||
uiRadioButtonsOnSelected(r, defaultOnSelected, NULL);
|
||||
|
||||
return r;
|
||||
}
|
34
src/libui_sdl/libui/unix/separator.c
Normal file
34
src/libui_sdl/libui/unix/separator.c
Normal file
@ -0,0 +1,34 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiSeparator {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkSeparator *separator;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiSeparator)
|
||||
|
||||
uiSeparator *uiNewHorizontalSeparator(void)
|
||||
{
|
||||
uiSeparator *s;
|
||||
|
||||
uiUnixNewControl(uiSeparator, s);
|
||||
|
||||
s->widget = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
|
||||
s->separator = GTK_SEPARATOR(s->widget);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
uiSeparator *uiNewVerticalSeparator(void)
|
||||
{
|
||||
uiSeparator *s;
|
||||
|
||||
uiUnixNewControl(uiSeparator, s);
|
||||
|
||||
s->widget = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
|
||||
s->separator = GTK_SEPARATOR(s->widget);
|
||||
|
||||
return s;
|
||||
}
|
71
src/libui_sdl/libui/unix/slider.c
Normal file
71
src/libui_sdl/libui/unix/slider.c
Normal file
@ -0,0 +1,71 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiSlider {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkRange *range;
|
||||
GtkScale *scale;
|
||||
void (*onChanged)(uiSlider *, void *);
|
||||
void *onChangedData;
|
||||
gulong onChangedSignal;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiSlider)
|
||||
|
||||
static void onChanged(GtkRange *range, gpointer data)
|
||||
{
|
||||
uiSlider *s = uiSlider(data);
|
||||
|
||||
(*(s->onChanged))(s, s->onChangedData);
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiSlider *s, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
int uiSliderValue(uiSlider *s)
|
||||
{
|
||||
return gtk_range_get_value(s->range);
|
||||
}
|
||||
|
||||
void uiSliderSetValue(uiSlider *s, int value)
|
||||
{
|
||||
// we need to inhibit sending of ::value-changed because this WILL send a ::value-changed otherwise
|
||||
g_signal_handler_block(s->range, s->onChangedSignal);
|
||||
gtk_range_set_value(s->range, value);
|
||||
g_signal_handler_unblock(s->range, s->onChangedSignal);
|
||||
}
|
||||
|
||||
void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)
|
||||
{
|
||||
s->onChanged = f;
|
||||
s->onChangedData = data;
|
||||
}
|
||||
|
||||
uiSlider *uiNewSlider(int min, int max)
|
||||
{
|
||||
uiSlider *s;
|
||||
int temp;
|
||||
|
||||
if (min >= max) {
|
||||
temp = min;
|
||||
min = max;
|
||||
max = temp;
|
||||
}
|
||||
|
||||
uiUnixNewControl(uiSlider, s);
|
||||
|
||||
s->widget = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, min, max, 1);
|
||||
s->range = GTK_RANGE(s->widget);
|
||||
s->scale = GTK_SCALE(s->widget);
|
||||
|
||||
// ensure integers, just to be safe
|
||||
gtk_scale_set_digits(s->scale, 0);
|
||||
|
||||
s->onChangedSignal = g_signal_connect(s->scale, "value-changed", G_CALLBACK(onChanged), s);
|
||||
uiSliderOnChanged(s, defaultOnChanged, NULL);
|
||||
|
||||
return s;
|
||||
}
|
72
src/libui_sdl/libui/unix/spinbox.c
Normal file
72
src/libui_sdl/libui/unix/spinbox.c
Normal file
@ -0,0 +1,72 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiSpinbox {
|
||||
uiUnixControl c;
|
||||
GtkWidget *widget;
|
||||
GtkEntry *entry;
|
||||
GtkSpinButton *spinButton;
|
||||
void (*onChanged)(uiSpinbox *, void *);
|
||||
void *onChangedData;
|
||||
gulong onChangedSignal;
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaults(uiSpinbox)
|
||||
|
||||
static void onChanged(GtkSpinButton *sb, gpointer data)
|
||||
{
|
||||
uiSpinbox *s = uiSpinbox(data);
|
||||
|
||||
(*(s->onChanged))(s, s->onChangedData);
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiSpinbox *s, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
int uiSpinboxValue(uiSpinbox *s)
|
||||
{
|
||||
return gtk_spin_button_get_value(s->spinButton);
|
||||
}
|
||||
|
||||
void uiSpinboxSetValue(uiSpinbox *s, int value)
|
||||
{
|
||||
// we need to inhibit sending of ::value-changed because this WILL send a ::value-changed otherwise
|
||||
g_signal_handler_block(s->spinButton, s->onChangedSignal);
|
||||
// this clamps for us
|
||||
gtk_spin_button_set_value(s->spinButton, (gdouble) value);
|
||||
g_signal_handler_unblock(s->spinButton, s->onChangedSignal);
|
||||
}
|
||||
|
||||
void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data)
|
||||
{
|
||||
s->onChanged = f;
|
||||
s->onChangedData = data;
|
||||
}
|
||||
|
||||
uiSpinbox *uiNewSpinbox(int min, int max)
|
||||
{
|
||||
uiSpinbox *s;
|
||||
int temp;
|
||||
|
||||
if (min >= max) {
|
||||
temp = min;
|
||||
min = max;
|
||||
max = temp;
|
||||
}
|
||||
|
||||
uiUnixNewControl(uiSpinbox, s);
|
||||
|
||||
s->widget = gtk_spin_button_new_with_range(min, max, 1);
|
||||
s->entry = GTK_ENTRY(s->widget);
|
||||
s->spinButton = GTK_SPIN_BUTTON(s->widget);
|
||||
|
||||
// ensure integers, just to be safe
|
||||
gtk_spin_button_set_digits(s->spinButton, 0);
|
||||
|
||||
s->onChangedSignal = g_signal_connect(s->spinButton, "value-changed", G_CALLBACK(onChanged), s);
|
||||
uiSpinboxOnChanged(s, defaultOnChanged, NULL);
|
||||
|
||||
return s;
|
||||
}
|
66
src/libui_sdl/libui/unix/stddialogs.c
Normal file
66
src/libui_sdl/libui/unix/stddialogs.c
Normal file
@ -0,0 +1,66 @@
|
||||
// 26 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
// LONGTERM figure out why, and describe, that this is the desired behavior
|
||||
// LONGTERM also point out that font and color buttons also work like this
|
||||
|
||||
#define windowWindow(w) (GTK_WINDOW(uiControlHandle(uiControl(w))))
|
||||
|
||||
static char *filedialog(GtkWindow *parent, GtkFileChooserAction mode, const gchar *confirm)
|
||||
{
|
||||
GtkWidget *fcd;
|
||||
GtkFileChooser *fc;
|
||||
gint response;
|
||||
char *filename;
|
||||
|
||||
fcd = gtk_file_chooser_dialog_new(NULL, parent, mode,
|
||||
"_Cancel", GTK_RESPONSE_CANCEL,
|
||||
confirm, GTK_RESPONSE_ACCEPT,
|
||||
NULL);
|
||||
fc = GTK_FILE_CHOOSER(fcd);
|
||||
gtk_file_chooser_set_local_only(fc, FALSE);
|
||||
gtk_file_chooser_set_select_multiple(fc, FALSE);
|
||||
gtk_file_chooser_set_show_hidden(fc, TRUE);
|
||||
gtk_file_chooser_set_do_overwrite_confirmation(fc, TRUE);
|
||||
gtk_file_chooser_set_create_folders(fc, TRUE);
|
||||
response = gtk_dialog_run(GTK_DIALOG(fcd));
|
||||
if (response != GTK_RESPONSE_ACCEPT) {
|
||||
gtk_widget_destroy(fcd);
|
||||
return NULL;
|
||||
}
|
||||
filename = uiUnixStrdupText(gtk_file_chooser_get_filename(fc));
|
||||
gtk_widget_destroy(fcd);
|
||||
return filename;
|
||||
}
|
||||
|
||||
char *uiOpenFile(uiWindow *parent)
|
||||
{
|
||||
return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_OPEN, "_Open");
|
||||
}
|
||||
|
||||
char *uiSaveFile(uiWindow *parent)
|
||||
{
|
||||
return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_SAVE, "_Save");
|
||||
}
|
||||
|
||||
static void msgbox(GtkWindow *parent, const char *title, const char *description, GtkMessageType type, GtkButtonsType buttons)
|
||||
{
|
||||
GtkWidget *md;
|
||||
|
||||
md = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL,
|
||||
type, buttons,
|
||||
"%s", title);
|
||||
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(md), "%s", description);
|
||||
gtk_dialog_run(GTK_DIALOG(md));
|
||||
gtk_widget_destroy(md);
|
||||
}
|
||||
|
||||
void uiMsgBox(uiWindow *parent, const char *title, const char *description)
|
||||
{
|
||||
msgbox(windowWindow(parent), title, description, GTK_MESSAGE_OTHER, GTK_BUTTONS_OK);
|
||||
}
|
||||
|
||||
void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)
|
||||
{
|
||||
msgbox(windowWindow(parent), title, description, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK);
|
||||
}
|
97
src/libui_sdl/libui/unix/tab.c
Normal file
97
src/libui_sdl/libui/unix/tab.c
Normal file
@ -0,0 +1,97 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiTab {
|
||||
uiUnixControl c;
|
||||
|
||||
GtkWidget *widget;
|
||||
GtkContainer *container;
|
||||
GtkNotebook *notebook;
|
||||
|
||||
GArray *pages; // []*struct child
|
||||
};
|
||||
|
||||
uiUnixControlAllDefaultsExceptDestroy(uiTab)
|
||||
|
||||
static void uiTabDestroy(uiControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
guint i;
|
||||
struct child *page;
|
||||
|
||||
for (i = 0; i < t->pages->len; i++) {
|
||||
page = g_array_index(t->pages, struct child *, i);
|
||||
childDestroy(page);
|
||||
}
|
||||
g_array_free(t->pages, TRUE);
|
||||
// and free ourselves
|
||||
g_object_unref(t->widget);
|
||||
uiFreeControl(uiControl(t));
|
||||
}
|
||||
|
||||
void uiTabAppend(uiTab *t, const char *name, uiControl *child)
|
||||
{
|
||||
uiTabInsertAt(t, name, t->pages->len, child);
|
||||
}
|
||||
|
||||
void uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child)
|
||||
{
|
||||
struct child *page;
|
||||
|
||||
// this will create a tab, because of gtk_container_add()
|
||||
page = newChildWithBox(child, uiControl(t), t->container, 0);
|
||||
|
||||
gtk_notebook_set_tab_label_text(t->notebook, childBox(page), name);
|
||||
gtk_notebook_reorder_child(t->notebook, childBox(page), n);
|
||||
|
||||
g_array_insert_val(t->pages, n, page);
|
||||
}
|
||||
|
||||
void uiTabDelete(uiTab *t, int n)
|
||||
{
|
||||
struct child *page;
|
||||
|
||||
page = g_array_index(t->pages, struct child *, n);
|
||||
// this will remove the tab, because gtk_widget_destroy() calls gtk_container_remove()
|
||||
childRemove(page);
|
||||
g_array_remove_index(t->pages, n);
|
||||
}
|
||||
|
||||
int uiTabNumPages(uiTab *t)
|
||||
{
|
||||
return t->pages->len;
|
||||
}
|
||||
|
||||
int uiTabMargined(uiTab *t, int n)
|
||||
{
|
||||
struct child *page;
|
||||
|
||||
page = g_array_index(t->pages, struct child *, n);
|
||||
return childFlag(page);
|
||||
}
|
||||
|
||||
void uiTabSetMargined(uiTab *t, int n, int margined)
|
||||
{
|
||||
struct child *page;
|
||||
|
||||
page = g_array_index(t->pages, struct child *, n);
|
||||
childSetFlag(page, margined);
|
||||
childSetMargined(page, childFlag(page));
|
||||
}
|
||||
|
||||
uiTab *uiNewTab(void)
|
||||
{
|
||||
uiTab *t;
|
||||
|
||||
uiUnixNewControl(uiTab, t);
|
||||
|
||||
t->widget = gtk_notebook_new();
|
||||
t->container = GTK_CONTAINER(t->widget);
|
||||
t->notebook = GTK_NOTEBOOK(t->widget);
|
||||
|
||||
gtk_notebook_set_scrollable(t->notebook, TRUE);
|
||||
|
||||
t->pages = g_array_new(FALSE, TRUE, sizeof (struct child *));
|
||||
|
||||
return t;
|
||||
}
|
12
src/libui_sdl/libui/unix/text.c
Normal file
12
src/libui_sdl/libui/unix/text.c
Normal file
@ -0,0 +1,12 @@
|
||||
// 9 april 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
char *uiUnixStrdupText(const char *t)
|
||||
{
|
||||
return g_strdup(t);
|
||||
}
|
||||
|
||||
void uiFreeText(char *t)
|
||||
{
|
||||
g_free(t);
|
||||
}
|
65
src/libui_sdl/libui/unix/uipriv_unix.h
Normal file
65
src/libui_sdl/libui/unix/uipriv_unix.h
Normal file
@ -0,0 +1,65 @@
|
||||
// 22 april 2015
|
||||
#define GLIB_VERSION_MIN_REQUIRED GLIB_VERSION_2_40
|
||||
#define GLIB_VERSION_MAX_ALLOWED GLIB_VERSION_2_40
|
||||
#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_10
|
||||
#define GDK_VERSION_MAX_ALLOWED GDK_VERSION_3_10
|
||||
#include <gtk/gtk.h>
|
||||
#include <math.h>
|
||||
#include <dlfcn.h> // see drawtext.c
|
||||
#include <langinfo.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "../ui.h"
|
||||
#include "../ui_unix.h"
|
||||
#include "../common/uipriv.h"
|
||||
|
||||
#define gtkXMargin 12
|
||||
#define gtkYMargin 12
|
||||
#define gtkXPadding 12
|
||||
#define gtkYPadding 6
|
||||
|
||||
// menu.c
|
||||
extern GtkWidget *makeMenubar(uiWindow *);
|
||||
extern void freeMenubar(GtkWidget *);
|
||||
extern void uninitMenus(void);
|
||||
|
||||
// alloc.c
|
||||
extern void initAlloc(void);
|
||||
extern void uninitAlloc(void);
|
||||
|
||||
// util.c
|
||||
extern void setMargined(GtkContainer *, int);
|
||||
|
||||
// child.c
|
||||
extern struct child *newChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer);
|
||||
extern struct child *newChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined);
|
||||
extern void childRemove(struct child *c);
|
||||
extern void childDestroy(struct child *c);
|
||||
extern GtkWidget *childWidget(struct child *c);
|
||||
extern int childFlag(struct child *c);
|
||||
extern void childSetFlag(struct child *c, int flag);
|
||||
extern GtkWidget *childBox(struct child *c);
|
||||
extern void childSetMargined(struct child *c, int margined);
|
||||
|
||||
// draw.c
|
||||
extern uiDrawContext *newContext(cairo_t *);
|
||||
extern void freeContext(uiDrawContext *);
|
||||
|
||||
// drawtext.c
|
||||
extern uiDrawTextFont *mkTextFont(PangoFont *f, gboolean add);
|
||||
extern PangoFont *pangoDescToPangoFont(PangoFontDescription *pdesc);
|
||||
|
||||
// graphemes.c
|
||||
extern ptrdiff_t *graphemes(const char *text, PangoContext *context);
|
||||
|
||||
// image.c
|
||||
/*TODO remove this*/typedef struct uiImage uiImage;
|
||||
extern cairo_surface_t *imageAppropriateSurface(uiImage *i, GtkWidget *w);
|
||||
|
||||
// cellrendererbutton.c
|
||||
extern GtkCellRenderer *newCellRendererButton(void);
|
||||
|
||||
// future.c
|
||||
extern void loadFutures(void);
|
||||
extern PangoAttribute *FUTURE_pango_attr_foreground_alpha_new(guint16 alpha);
|
||||
extern gboolean FUTURE_gtk_widget_path_iter_set_object_name(GtkWidgetPath *path, gint pos, const char *name);
|
10
src/libui_sdl/libui/unix/util.c
Normal file
10
src/libui_sdl/libui/unix/util.c
Normal file
@ -0,0 +1,10 @@
|
||||
// 18 april 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
void setMargined(GtkContainer *c, int margined)
|
||||
{
|
||||
if (margined)
|
||||
gtk_container_set_border_width(c, gtkXMargin);
|
||||
else
|
||||
gtk_container_set_border_width(c, 0);
|
||||
}
|
279
src/libui_sdl/libui/unix/window.c
Normal file
279
src/libui_sdl/libui/unix/window.c
Normal file
@ -0,0 +1,279 @@
|
||||
// 11 june 2015
|
||||
#include "uipriv_unix.h"
|
||||
|
||||
struct uiWindow {
|
||||
uiUnixControl c;
|
||||
|
||||
GtkWidget *widget;
|
||||
GtkContainer *container;
|
||||
GtkWindow *window;
|
||||
|
||||
GtkWidget *vboxWidget;
|
||||
GtkContainer *vboxContainer;
|
||||
GtkBox *vbox;
|
||||
|
||||
GtkWidget *childHolderWidget;
|
||||
GtkContainer *childHolderContainer;
|
||||
|
||||
GtkWidget *menubar;
|
||||
|
||||
uiControl *child;
|
||||
int margined;
|
||||
|
||||
int (*onClosing)(uiWindow *, void *);
|
||||
void *onClosingData;
|
||||
void (*onContentSizeChanged)(uiWindow *, void *);
|
||||
void *onContentSizeChangedData;
|
||||
gboolean fullscreen;
|
||||
};
|
||||
|
||||
static gboolean onClosing(GtkWidget *win, GdkEvent *e, gpointer data)
|
||||
{
|
||||
uiWindow *w = uiWindow(data);
|
||||
|
||||
// manually destroy the window ourselves; don't let the delete-event handler do it
|
||||
if ((*(w->onClosing))(w, w->onClosingData))
|
||||
uiControlDestroy(uiControl(w));
|
||||
// don't continue to the default delete-event handler; we destroyed the window by now
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void onSizeAllocate(GtkWidget *widget, GdkRectangle *allocation, gpointer data)
|
||||
{
|
||||
uiWindow *w = uiWindow(data);
|
||||
|
||||
// TODO deal with spurious size-allocates
|
||||
(*(w->onContentSizeChanged))(w, w->onContentSizeChangedData);
|
||||
}
|
||||
|
||||
static int defaultOnClosing(uiWindow *w, void *data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void uiWindowDestroy(uiControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
// first hide ourselves
|
||||
gtk_widget_hide(w->widget);
|
||||
// now destroy the child
|
||||
if (w->child != NULL) {
|
||||
uiControlSetParent(w->child, NULL);
|
||||
uiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, TRUE);
|
||||
uiControlDestroy(w->child);
|
||||
}
|
||||
// now destroy the menus, if any
|
||||
if (w->menubar != NULL)
|
||||
freeMenubar(w->menubar);
|
||||
gtk_widget_destroy(w->childHolderWidget);
|
||||
gtk_widget_destroy(w->vboxWidget);
|
||||
// and finally free ourselves
|
||||
// use gtk_widget_destroy() instead of g_object_unref() because GTK+ has internal references (see #165)
|
||||
gtk_widget_destroy(w->widget);
|
||||
uiFreeControl(uiControl(w));
|
||||
}
|
||||
|
||||
uiUnixControlDefaultHandle(uiWindow)
|
||||
|
||||
uiControl *uiWindowParent(uiControl *c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void uiWindowSetParent(uiControl *c, uiControl *parent)
|
||||
{
|
||||
uiUserBugCannotSetParentOnToplevel("uiWindow");
|
||||
}
|
||||
|
||||
static int uiWindowToplevel(uiControl *c)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
uiUnixControlDefaultVisible(uiWindow)
|
||||
|
||||
static void uiWindowShow(uiControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
// don't use gtk_widget_show_all() as that will show all children, regardless of user settings
|
||||
// don't use gtk_widget_show(); that doesn't bring to front or give keyboard focus
|
||||
// (gtk_window_present() does call gtk_widget_show() though)
|
||||
gtk_window_present(w->window);
|
||||
}
|
||||
|
||||
uiUnixControlDefaultHide(uiWindow)
|
||||
uiUnixControlDefaultEnabled(uiWindow)
|
||||
uiUnixControlDefaultEnable(uiWindow)
|
||||
uiUnixControlDefaultDisable(uiWindow)
|
||||
// TODO?
|
||||
uiUnixControlDefaultSetContainer(uiWindow)
|
||||
|
||||
char *uiWindowTitle(uiWindow *w)
|
||||
{
|
||||
return uiUnixStrdupText(gtk_window_get_title(w->window));
|
||||
}
|
||||
|
||||
void uiWindowSetTitle(uiWindow *w, const char *title)
|
||||
{
|
||||
gtk_window_set_title(w->window, title);
|
||||
}
|
||||
|
||||
void uiWindowContentSize(uiWindow *w, int *width, int *height)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
|
||||
gtk_widget_get_allocation(w->childHolderWidget, &allocation);
|
||||
*width = allocation.width;
|
||||
*height = allocation.height;
|
||||
}
|
||||
|
||||
void uiWindowSetContentSize(uiWindow *w, int width, int height)
|
||||
{
|
||||
GtkAllocation childAlloc;
|
||||
gint winWidth, winHeight;
|
||||
|
||||
// we need to resize the child holder widget to the given size
|
||||
// we can't resize that without running the event loop
|
||||
// but we can do gtk_window_set_size()
|
||||
// so how do we deal with the differences in sizes?
|
||||
// simple arithmetic, of course!
|
||||
|
||||
// from what I can tell, the return from gtk_widget_get_allocation(w->window) and gtk_window_get_size(w->window) will be the same
|
||||
// this is not affected by Wayland and not affected by GTK+ builtin CSD
|
||||
// so we can safely juse use them to get the real window size!
|
||||
// since we're using gtk_window_resize(), use the latter
|
||||
gtk_window_get_size(w->window, &winWidth, &winHeight);
|
||||
|
||||
// now get the child holder widget's current allocation
|
||||
gtk_widget_get_allocation(w->childHolderWidget, &childAlloc);
|
||||
// and punch that out of the window size
|
||||
winWidth -= childAlloc.width;
|
||||
winHeight -= childAlloc.height;
|
||||
|
||||
// now we just need to add the new size back in
|
||||
winWidth += width;
|
||||
winHeight += height;
|
||||
// and set it
|
||||
// this will not move the window in my tests, so we're good
|
||||
gtk_window_resize(w->window, winWidth, winHeight);
|
||||
}
|
||||
|
||||
int uiWindowFullscreen(uiWindow *w)
|
||||
{
|
||||
return w->fullscreen;
|
||||
}
|
||||
|
||||
// TODO use window-state-event to track
|
||||
// TODO does this send an extra size changed?
|
||||
// TODO what behavior do we want?
|
||||
void uiWindowSetFullscreen(uiWindow *w, int fullscreen)
|
||||
{
|
||||
w->fullscreen = fullscreen;
|
||||
if (w->fullscreen)
|
||||
gtk_window_fullscreen(w->window);
|
||||
else
|
||||
gtk_window_unfullscreen(w->window);
|
||||
}
|
||||
|
||||
void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)
|
||||
{
|
||||
w->onContentSizeChanged = f;
|
||||
w->onContentSizeChangedData = data;
|
||||
}
|
||||
|
||||
void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)
|
||||
{
|
||||
w->onClosing = f;
|
||||
w->onClosingData = data;
|
||||
}
|
||||
|
||||
int uiWindowBorderless(uiWindow *w)
|
||||
{
|
||||
return gtk_window_get_decorated(w->window) == FALSE;
|
||||
}
|
||||
|
||||
void uiWindowSetBorderless(uiWindow *w, int borderless)
|
||||
{
|
||||
gtk_window_set_decorated(w->window, borderless == 0);
|
||||
}
|
||||
|
||||
// TODO save and restore expands and aligns
|
||||
void uiWindowSetChild(uiWindow *w, uiControl *child)
|
||||
{
|
||||
if (w->child != NULL) {
|
||||
uiControlSetParent(w->child, NULL);
|
||||
uiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, TRUE);
|
||||
}
|
||||
w->child = child;
|
||||
if (w->child != NULL) {
|
||||
uiControlSetParent(w->child, uiControl(w));
|
||||
uiUnixControlSetContainer(uiUnixControl(w->child), w->childHolderContainer, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
int uiWindowMargined(uiWindow *w)
|
||||
{
|
||||
return w->margined;
|
||||
}
|
||||
|
||||
void uiWindowSetMargined(uiWindow *w, int margined)
|
||||
{
|
||||
w->margined = margined;
|
||||
setMargined(w->childHolderContainer, w->margined);
|
||||
}
|
||||
|
||||
uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
uiUnixNewControl(uiWindow, w);
|
||||
|
||||
w->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
w->container = GTK_CONTAINER(w->widget);
|
||||
w->window = GTK_WINDOW(w->widget);
|
||||
|
||||
gtk_window_set_title(w->window, title);
|
||||
gtk_window_resize(w->window, width, height);
|
||||
|
||||
w->vboxWidget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
w->vboxContainer = GTK_CONTAINER(w->vboxWidget);
|
||||
w->vbox = GTK_BOX(w->vboxWidget);
|
||||
|
||||
// set the vbox as the GtkWindow child
|
||||
gtk_container_add(w->container, w->vboxWidget);
|
||||
|
||||
if (hasMenubar) {
|
||||
w->menubar = makeMenubar(uiWindow(w));
|
||||
gtk_container_add(w->vboxContainer, w->menubar);
|
||||
}
|
||||
|
||||
w->childHolderWidget = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
w->childHolderContainer = GTK_CONTAINER(w->childHolderWidget);
|
||||
gtk_widget_set_hexpand(w->childHolderWidget, TRUE);
|
||||
gtk_widget_set_halign(w->childHolderWidget, GTK_ALIGN_FILL);
|
||||
gtk_widget_set_vexpand(w->childHolderWidget, TRUE);
|
||||
gtk_widget_set_valign(w->childHolderWidget, GTK_ALIGN_FILL);
|
||||
gtk_container_add(w->vboxContainer, w->childHolderWidget);
|
||||
|
||||
// show everything in the vbox, but not the GtkWindow itself
|
||||
gtk_widget_show_all(w->vboxWidget);
|
||||
|
||||
// and connect our events
|
||||
g_signal_connect(w->widget, "delete-event", G_CALLBACK(onClosing), w);
|
||||
g_signal_connect(w->childHolderWidget, "size-allocate", G_CALLBACK(onSizeAllocate), w);
|
||||
uiWindowOnClosing(w, defaultOnClosing, NULL);
|
||||
uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);
|
||||
|
||||
// normally it's SetParent() that does this, but we can't call SetParent() on a uiWindow
|
||||
// TODO we really need to clean this up, especially since see uiWindowDestroy() above
|
||||
g_object_ref(w->widget);
|
||||
|
||||
return w;
|
||||
}
|
Reference in New Issue
Block a user