another UI attempt, I guess.

sorry.
This commit is contained in:
StapleButter
2017-09-09 02:30:51 +02:00
parent 81747d6c34
commit 70e4841d31
244 changed files with 35965 additions and 0 deletions

View 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)

View 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);
}

View 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;
}

View 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);
}

View 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;
}

View 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));
}

View 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;
}

View 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);
}

View 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;
}

View 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;
}

View 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));
}

View 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);
}

View 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();
}

View 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);
}

View 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);

View 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);
}

View 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;
}

View 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);
}

View 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;
}

View 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");
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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);
}

View 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);
}

View 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);
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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);
}

View 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;
}

View 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);
}

View 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);

View 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);
}

View 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;
}