First Commit

This commit is contained in:
dsutanto committed 2025-11-19 16:30:37 +07:00
commit ae0fab9400
424 files changed
+71006

No files matched your search

+406
View File
@@ -0,0 +1,406 @@
// 26 june 2016
#include "uipriv_unix.h"
void *uiTableModelStrdup(const char *str)
{
return g_strdup(str);
}
void *uiTableModelGiveColor(double r, double g, double b, double a)
{
GdkRGBA rgba;
rgba.red = r;
rgba.green = g;
rgba.blue = b;
rgba.alpha = a;
return gdk_rgba_copy(&rgba);
}
uiTableModel *uiNewTableModel(uiTableModelHandler *mh)
{
uiTableModel *m;
m = uiTableModel(g_object_new(uiTableModelType, NULL));
m->mh = mh;
return m;
}
void uiFreeTableModel(uiTableModel *m)
{
g_object_unref(m);
}
void uiTableModelRowInserted(uiTableModel *m, int newIndex)
{
GtkTreePath *path;
GtkTreeIter iter;
path = gtk_tree_path_new_from_indices(newIndex, -1);
iter.stamp = STAMP_GOOD;
iter.user_data = GINT_TO_POINTER(newIndex);
gtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter);
gtk_tree_path_free(path);
}
void uiTableModelRowChanged(uiTableModel *m, int index)
{
GtkTreePath *path;
GtkTreeIter iter;
path = gtk_tree_path_new_from_indices(index, -1);
iter.stamp = STAMP_GOOD;
iter.user_data = GINT_TO_POINTER(index);
gtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter);
gtk_tree_path_free(path);
}
void uiTableModelRowDeleted(uiTableModel *m, int oldIndex)
{
GtkTreePath *path;
path = gtk_tree_path_new_from_indices(oldIndex, -1);
gtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path);
gtk_tree_path_free(path);
}
enum {
partText,
partImage,
partButton,
partCheckbox,
partProgressBar,
};
struct tablePart {
int type;
int textColumn;
int imageColumn;
int valueColumn;
int colorColumn;
GtkCellRenderer *r;
uiTable *tv; // for pixbufs and background color
};
struct uiTableColumn {
GtkTreeViewColumn *c;
uiTable *tv; // for pixbufs and background color
GPtrArray *parts;
};
struct uiTable {
uiUnixControl c;
GtkWidget *widget;
GtkContainer *scontainer;
GtkScrolledWindow *sw;
GtkWidget *treeWidget;
GtkTreeView *tv;
GPtrArray *columns;
uiTableModel *model;
int backgroundColumn;
};
// use the same size as GtkFileChooserWidget's treeview
// TODO refresh when icon theme changes
// TODO doesn't work when scaled
// TODO is this even necessary?
static void setImageSize(GtkCellRenderer *r)
{
gint size;
gint width, height;
gint xpad, ypad;
size = 16; // fallback used by GtkFileChooserWidget
if (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height) != FALSE)
size = MAX(width, height);
gtk_cell_renderer_get_padding(r, &xpad, &ypad);
gtk_cell_renderer_set_fixed_size(r,
2 * xpad + size,
2 * ypad + size);
}
static void applyColor(GtkTreeModel *mm, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet)
{
GValue value = G_VALUE_INIT;
GdkRGBA *rgba;
gtk_tree_model_get_value(mm, iter, modelColumn, &value);
rgba = (GdkRGBA *) g_value_get_boxed(&value);
if (rgba != NULL)
g_object_set(r, prop, rgba, NULL);
else
g_object_set(r, propSet, FALSE, NULL);
g_value_unset(&value);
}
static void dataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *mm, GtkTreeIter *iter, gpointer data)
{
struct tablePart *part = (struct tablePart *) data;
GValue value = G_VALUE_INIT;
const gchar *str;
uiImage *img;
int pval;
switch (part->type) {
case partText:
gtk_tree_model_get_value(mm, iter, part->textColumn, &value);
str = g_value_get_string(&value);
g_object_set(r, "text", str, NULL);
if (part->colorColumn != -1)
applyColor(mm, iter,
part->colorColumn,
r, "foreground-rgba", "foreground-set");
break;
case partImage:
//TODO setImageSize(r);
gtk_tree_model_get_value(mm, iter, part->imageColumn, &value);
img = (uiImage *) g_value_get_pointer(&value);
g_object_set(r, "surface",
uiprivImageAppropriateSurface(img, part->tv->treeWidget),
NULL);
break;
case partButton:
gtk_tree_model_get_value(mm, iter, part->textColumn, &value);
str = g_value_get_string(&value);
g_object_set(r, "text", str, NULL);
break;
case partCheckbox:
gtk_tree_model_get_value(mm, iter, part->valueColumn, &value);
g_object_set(r, "active", g_value_get_int(&value) != 0, NULL);
break;
case partProgressBar:
gtk_tree_model_get_value(mm, iter, part->valueColumn, &value);
pval = g_value_get_int(&value);
if (pval == -1) {
// TODO
} else
g_object_set(r,
"pulse", -1,
"value", pval,
NULL);
break;
}
g_value_unset(&value);
if (part->tv->backgroundColumn != -1)
applyColor(mm, iter,
part->tv->backgroundColumn,
r, "cell-background-rgba", "cell-background-set");
}
static void onEdited(struct tablePart *part, int column, const char *pathstr, const void *data)
{
GtkTreePath *path;
int row;
uiTableModel *m;
path = gtk_tree_path_new_from_string(pathstr);
row = gtk_tree_path_get_indices(path)[0];
gtk_tree_path_free(path);
m = part->tv->model;
(*(m->mh->SetCellValue))(m->mh, m, row, column, data);
// and update
uiTableModelRowChanged(m, row);
}
static void appendPart(uiTableColumn *c, struct tablePart *part, GtkCellRenderer *r, int expand)
{
part->r = r;
gtk_tree_view_column_pack_start(c->c, part->r, expand != 0);
gtk_tree_view_column_set_cell_data_func(c->c, part->r, dataFunc, part, NULL);
g_ptr_array_add(c->parts, part);
}
static void textEdited(GtkCellRendererText *renderer, gchar *path, gchar *newText, gpointer data)
{
struct tablePart *part = (struct tablePart *) data;
onEdited(part, part->textColumn, path, newText);
}
void uiTableColumnAppendTextPart(uiTableColumn *c, int modelColumn, int expand)
{
struct tablePart *part;
GtkCellRenderer *r;
part = uiprivNew(struct tablePart);
part->type = partText;
part->textColumn = modelColumn;
part->tv = c->tv;
part->colorColumn = -1;
r = gtk_cell_renderer_text_new();
g_object_set(r, "editable", FALSE, NULL);
g_signal_connect(r, "edited", G_CALLBACK(textEdited), part);
appendPart(c, part, r, expand);
}
void uiTableColumnAppendImagePart(uiTableColumn *c, int modelColumn, int expand)
{
struct tablePart *part;
part = uiprivNew(struct tablePart);
part->type = partImage;
part->imageColumn = modelColumn;
part->tv = c->tv;
appendPart(c, part,
gtk_cell_renderer_pixbuf_new(),
expand);
}
// TODO wrong type here
static void buttonClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data)
{
struct tablePart *part = (struct tablePart *) data;
onEdited(part, part->textColumn, pathstr, NULL);
}
void uiTableColumnAppendButtonPart(uiTableColumn *c, int modelColumn, int expand)
{
struct tablePart *part;
GtkCellRenderer *r;
part = uiprivNew(struct tablePart);
part->type = partButton;
part->textColumn = modelColumn;
part->tv = c->tv;
r = uiprivNewCellRendererButton();
g_object_set(r, "sensitive", TRUE, NULL); // editable by default
g_signal_connect(r, "clicked", G_CALLBACK(buttonClicked), part);
appendPart(c, part, r, expand);
}
// yes, we need to do all this twice :|
static void checkboxToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data)
{
struct tablePart *part = (struct tablePart *) data;
GtkTreePath *path;
int row;
uiTableModel *m;
void *value;
int intval;
path = gtk_tree_path_new_from_string(pathstr);
row = gtk_tree_path_get_indices(path)[0];
gtk_tree_path_free(path);
m = part->tv->model;
value = (*(m->mh->CellValue))(m->mh, m, row, part->valueColumn);
intval = !uiTableModelTakeInt(value);
onEdited(part, part->valueColumn, pathstr, uiTableModelGiveInt(intval));
}
void uiTableColumnAppendCheckboxPart(uiTableColumn *c, int modelColumn, int expand)
{
struct tablePart *part;
GtkCellRenderer *r;
part = uiprivNew(struct tablePart);
part->type = partCheckbox;
part->valueColumn = modelColumn;
part->tv = c->tv;
r = gtk_cell_renderer_toggle_new();
g_object_set(r, "sensitive", TRUE, NULL); // editable by default
g_signal_connect(r, "toggled", G_CALLBACK(checkboxToggled), part);
appendPart(c, part, r, expand);
}
void uiTableColumnAppendProgressBarPart(uiTableColumn *c, int modelColumn, int expand)
{
struct tablePart *part;
part = uiprivNew(struct tablePart);
part->type = partProgressBar;
part->valueColumn = modelColumn;
part->tv = c->tv;
appendPart(c, part,
gtk_cell_renderer_progress_new(),
expand);
}
void uiTableColumnPartSetEditable(uiTableColumn *c, int part, int editable)
{
struct tablePart *p;
p = (struct tablePart *) g_ptr_array_index(c->parts, part);
switch (p->type) {
case partImage:
case partProgressBar:
return;
case partButton:
case partCheckbox:
g_object_set(p->r, "sensitive", editable != 0, NULL);
return;
}
g_object_set(p->r, "editable", editable != 0, NULL);
}
void uiTableColumnPartSetTextColor(uiTableColumn *c, int part, int modelColumn)
{
struct tablePart *p;
p = (struct tablePart *) g_ptr_array_index(c->parts, part);
p->colorColumn = modelColumn;
// TODO refresh table
}
uiUnixControlAllDefaultsExceptDestroy(uiTable)
static void uiTableDestroy(uiControl *c)
{
uiTable *t = uiTable(c);
// TODO
g_object_unref(t->widget);
uiFreeControl(uiControl(t));
}
uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name)
{
uiTableColumn *c;
c = uiprivNew(uiTableColumn);
c->c = gtk_tree_view_column_new();
gtk_tree_view_column_set_resizable(c->c, TRUE);
gtk_tree_view_column_set_title(c->c, name);
gtk_tree_view_append_column(t->tv, c->c);
c->tv = t; // TODO rename field to t, cascade
c->parts = g_ptr_array_new();
return c;
}
void uiTableSetRowBackgroundColorModelColumn(uiTable *t, int modelColumn)
{
t->backgroundColumn = modelColumn;
// TODO refresh table
}
uiTable *uiNewTable(uiTableModel *model)
{
uiTable *t;
uiUnixNewControl(uiTable, t);
t->model = model;
t->backgroundColumn = -1;
t->widget = gtk_scrolled_window_new(NULL, NULL);
t->scontainer = GTK_CONTAINER(t->widget);
t->sw = GTK_SCROLLED_WINDOW(t->widget);
gtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN);
t->treeWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(t->model));
t->tv = GTK_TREE_VIEW(t->treeWidget);
// TODO set up t->tv
gtk_container_add(t->scontainer, t->treeWidget);
// and make the tree view visible; only the scrolled window's visibility is controlled by libui
gtk_widget_show(t->treeWidget);
return t;
}
+84
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 uiprivInitAlloc(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 uiprivUninitAlloc(void)
{
char *str = NULL;
if (allocations->len == 0) {
g_ptr_array_free(allocations, TRUE);
return;
}
g_ptr_array_foreach(allocations, uninitComplain, &str);
uiprivUserBug("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 *uiprivAlloc(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 *uiprivRealloc(void *p, size_t new, const char *type)
{
void *out;
size_t *s;
if (p == NULL)
return uiprivAlloc(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)
uiprivImplBug("%p not found in allocations array in uiprivRealloc()", p);
g_ptr_array_add(allocations, out);
return DATA(out);
}
void uiprivFree(void *p)
{
if (p == NULL)
uiprivImplBug("attempt to uiprivFree(NULL)");
p = BASE(p);
g_free(p);
if (g_ptr_array_remove(allocations, p) == FALSE)
uiprivImplBug("%p not found in allocations array in uiprivFree()", p);
}
+637
View File
@@ -0,0 +1,637 @@
// 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
uiprivClickCounter 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
uiprivClickCounter *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);
uiprivClickCounterReset(&(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
// TODO https://developer.gnome.org/gtk3/3.10/GtkWidget.html#gtk-widget-set-redraw-on-allocate ?
// TODO drop this rule; it was stupid and documenting this was stupid — let platforms where it matters do it on their own
// TODO or do we not, for parity of performance?
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 = uiprivNewContext(cr,
gtk_widget_get_style_context(a->widget));
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);
uiprivFreeContext(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);
me.Down = e->button;
me.Up = 0;
// 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 = uiprivClickCounterClick(a->cc, me.Down,
e->x, e->y,
e->time, maxTime,
maxDistance, maxDistance);
// 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);
uiprivClickCounterReset(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 (uiprivFromScancode(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)
uiprivUserBug("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)
uiprivUserBug("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)
uiprivUserBug("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;
}
+145
View File
@@ -0,0 +1,145 @@
// 12 february 2017
#include "uipriv_unix.h"
#include "attrstr.h"
// TODO pango alpha attributes turn 0 into 65535 :|
// TODO make this name less generic?
struct foreachParams {
PangoAttrList *attrs;
};
static void addattr(struct foreachParams *p, size_t start, size_t end, PangoAttribute *attr)
{
if (attr == NULL) // in case of a future attribute
return;
attr->start_index = start;
attr->end_index = end;
pango_attr_list_insert(p->attrs, attr);
}
static uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data)
{
struct foreachParams *p = (struct foreachParams *) data;
double r, g, b, a;
PangoUnderline underline;
uiUnderlineColor colorType;
const uiOpenTypeFeatures *features;
GString *featurestr;
switch (uiAttributeGetType(attr)) {
case uiAttributeTypeFamily:
addattr(p, start, end,
pango_attr_family_new(uiAttributeFamily(attr)));
break;
case uiAttributeTypeSize:
addattr(p, start, end,
pango_attr_size_new(cairoToPango(uiAttributeSize(attr))));
break;
case uiAttributeTypeWeight:
// TODO reverse the misalignment from drawtext.c if it is corrected
addattr(p, start, end,
pango_attr_weight_new(uiprivWeightToPangoWeight(uiAttributeWeight(attr))));
break;
case uiAttributeTypeItalic:
addattr(p, start, end,
pango_attr_style_new(uiprivItalicToPangoStyle(uiAttributeItalic(attr))));
break;
case uiAttributeTypeStretch:
addattr(p, start, end,
pango_attr_stretch_new(uiprivStretchToPangoStretch(uiAttributeStretch(attr))));
break;
case uiAttributeTypeColor:
uiAttributeColor(attr, &r, &g, &b, &a);
addattr(p, start, end,
pango_attr_foreground_new(
(guint16) (r * 65535.0),
(guint16) (g * 65535.0),
(guint16) (b * 65535.0)));
addattr(p, start, end,
uiprivFUTURE_pango_attr_foreground_alpha_new(
(guint16) (a * 65535.0)));
break;
case uiAttributeTypeBackground:
// TODO make sure this works properly with line paragraph spacings (after figuring out what that means, of course)
uiAttributeColor(attr, &r, &g, &b, &a);
addattr(p, start, end,
pango_attr_background_new(
(guint16) (r * 65535.0),
(guint16) (g * 65535.0),
(guint16) (b * 65535.0)));
addattr(p, start, end,
uiprivFUTURE_pango_attr_background_alpha_new(
(guint16) (a * 65535.0)));
break;
case uiAttributeTypeUnderline:
switch (uiAttributeUnderline(attr)) {
case uiUnderlineNone:
underline = PANGO_UNDERLINE_NONE;
break;
case uiUnderlineSingle:
underline = PANGO_UNDERLINE_SINGLE;
break;
case uiUnderlineDouble:
underline = PANGO_UNDERLINE_DOUBLE;
break;
case uiUnderlineSuggestion:
underline = PANGO_UNDERLINE_ERROR;
break;
}
addattr(p, start, end,
pango_attr_underline_new(underline));
break;
case uiAttributeTypeUnderlineColor:
uiAttributeUnderlineColor(attr, &colorType, &r, &g, &b, &a);
switch (colorType) {
case uiUnderlineColorCustom:
addattr(p, start, end,
pango_attr_underline_color_new(
(guint16) (r * 65535.0),
(guint16) (g * 65535.0),
(guint16) (b * 65535.0)));
break;
case uiUnderlineColorSpelling:
// TODO GtkTextView style property error-underline-color
addattr(p, start, end,
pango_attr_underline_color_new(65535, 0, 0));
break;
case uiUnderlineColorGrammar:
// TODO find a more appropriate color
addattr(p, start, end,
pango_attr_underline_color_new(0, 65535, 0));
break;
case uiUnderlineColorAuxiliary:
// TODO find a more appropriate color
addattr(p, start, end,
pango_attr_underline_color_new(0, 0, 65535));
break;
}
break;
case uiAttributeTypeFeatures:
// only generate an attribute if the features object is not NULL
// TODO state that this is allowed
features = uiAttributeFeatures(attr);
if (features == NULL)
break;
featurestr = uiprivOpenTypeFeaturesToPangoCSSFeaturesString(features);
addattr(p, start, end,
uiprivFUTURE_pango_attr_font_features_new(featurestr->str));
g_string_free(featurestr, TRUE);
break;
default:
// TODO complain
;
}
return uiForEachContinue;
}
PangoAttrList *uiprivAttributedStringToPangoAttrList(uiDrawTextLayoutParams *p)
{
struct foreachParams fep;
fep.attrs = pango_attr_list_new();
uiAttributedStringForEachAttribute(p->String, processAttribute, &fep);
return fep.attrs;
}
+20
View File
@@ -0,0 +1,20 @@
// 11 march 2018
#include "../common/attrstr.h"
// See https://developer.gnome.org/pango/1.30/pango-Cairo-Rendering.html#pango-Cairo-Rendering.description
// For the conversion, see https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-to-double and https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-from-double
#define pangoToCairo(pango) (pango_units_to_double(pango))
#define cairoToPango(cairo) (pango_units_from_double(cairo))
// opentype.c
extern GString *uiprivOpenTypeFeaturesToPangoCSSFeaturesString(const uiOpenTypeFeatures *otf);
// fontmatch.c
extern PangoWeight uiprivWeightToPangoWeight(uiTextWeight w);
extern PangoStyle uiprivItalicToPangoStyle(uiTextItalic i);
extern PangoStretch uiprivStretchToPangoStretch(uiTextStretch s);
extern PangoFontDescription *uiprivFontDescriptorToPangoFontDescription(const uiFontDescriptor *uidesc);
extern void uiprivFontDescriptorFromPangoFontDescription(PangoFontDescription *pdesc, uiFontDescriptor *uidesc);
// attrstr.c
extern PangoAttrList *uiprivAttributedStringToPangoAttrList(uiDrawTextLayoutParams *p);
+164
View File
@@ -0,0 +1,164 @@
// 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 uiBoxNumChildren(uiBox *b)
{
return (int) b->controls->len;
}
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, uiprivGTKYPadding);
else
gtk_box_set_spacing(b->box, uiprivGTKXPadding);
else
gtk_box_set_spacing(b->box, 0);
}
static uiBox *finishNewBox(GtkOrientation orientation)
{
uiBox *b;
uiUnixNewControl(uiBox, b);
b->widget = gtk_box_new(orientation, 0);
b->container = GTK_CONTAINER(b->widget);
b->box = GTK_BOX(b->widget);
b->vertical = orientation == GTK_ORIENTATION_VERTICAL;
if (b->vertical)
b->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_VERTICAL);
else
b->stretchygroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
b->controls = g_array_new(FALSE, TRUE, sizeof (struct boxChild));
return b;
}
uiBox *uiNewHorizontalBox(void)
{
return finishNewBox(GTK_ORIENTATION_HORIZONTAL);
}
uiBox *uiNewVerticalBox(void)
{
return finishNewBox(GTK_ORIENTATION_VERTICAL);
}
+55
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;
}
+332
View File
@@ -0,0 +1,332 @@
// 28 june 2016
#include "uipriv_unix.h"
// TODOs
// - it's a rather tight fit
// - selected row text color is white (TODO not on 3.22)
// - accessibility
// - right side too big? (TODO reverify)
#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
// TODO how does this seem to work with highlight on 3.22, and does that work with 3.10 too
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 (!uiprivFUTURE_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 in GTK+ 3.22.30 does
// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)
static PangoLayout *cellRendererButtonPangoLayout(cellRendererButton *c, GtkWidget *widget)
{
PangoLayout *layout;
layout = gtk_widget_create_pango_layout(widget, c->text);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
pango_layout_set_width(layout, -1);
pango_layout_set_wrap(layout, PANGO_WRAP_CHAR);
pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
return layout;
}
// this is based on what GtkCellRendererText in GTK+ 3.22.30 does
// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)
static void cellRendererButtonSize(cellRendererButton *c, GtkWidget *widget, PangoLayout *layout, const GdkRectangle *cell_area, gint *xoff, gint *yoff, gint *width, gint *height)
{
PangoRectangle rect;
gint xpad, ypad;
gfloat xalign, yalign;
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);
pango_layout_get_pixel_extents(layout, NULL, &rect);
if (rect.width > cell_area->width - (2 * xpad))
rect.width = cell_area->width - (2 * xpad);
if (rect.height > cell_area->height - (2 * ypad))
rect.height = cell_area->height - (2 * ypad);
gtk_cell_renderer_get_alignment(GTK_CELL_RENDERER(c), &xalign, &yalign);
if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
xalign = 1.0 - xalign;
if (xoff != NULL) {
*xoff = cell_area->width - (rect.width + (2 * xpad));
*xoff = (gint) ((gfloat) (*xoff) * xalign);
}
if (yoff != NULL) {
*yoff = cell_area->height - (rect.height + (2 * ypad));
*yoff = (gint) ((gfloat) (*yoff) * yalign);
if (*yoff < 0)
*yoff = 0;
}
if (width != NULL)
*width = rect.width - (2 * xpad);
if (height != NULL)
*height = rect.height - (2 * ypad);
}
// this is based on what GtkCellRendererText in GTK+ 3.22.30 does
// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)
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 = cellRendererButtonPangoLayout(c, widget);
pango_layout_get_extents(layout, NULL, &rect);
g_object_unref(layout);
out = PANGO_PIXELS_CEIL(rect.width) + (2 * xpad);
if (rect.x > 0)
out += rect.x;
if (minimum != NULL)
*minimum = out;
if (natural != NULL)
*natural = out;
}
// this is based on what GtkCellRendererText in GTK+ 3.22.30 does
// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)
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 = cellRendererButtonPangoLayout(c, widget);
pango_layout_set_width(layout, (width + (xpad * 2)) * PANGO_SCALE);
pango_layout_get_pixel_size(layout, NULL, &height);
g_object_unref(layout);
out = height + (ypad * 2);
if (minimum != NULL)
*minimum = out;
if (natural != NULL)
*natural = out;
}
// this is based on what GtkCellRendererText in GTK+ 3.22.30 does
// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)
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 in GTK+ 3.22.30 does
// TODO compare to 3.10.9 (https://gitlab.gnome.org/GNOME/gtk/blob/3.10.9/gtk/gtkcellrenderertext.c)
static void cellRendererButton_get_aligned_area(GtkCellRenderer *r, GtkWidget *widget, GtkCellRendererState flags, const GdkRectangle *cell_area, GdkRectangle *aligned_area)
{
cellRendererButton *c = cellRendererButton(r);
PangoLayout *layout;
gint xoff, yoff;
gint width, height;
layout = cellRendererButtonPangoLayout(c, widget);
cellRendererButtonSize(c, widget, layout, cell_area,
&xoff, &yoff, &width, &height);
aligned_area->x = cell_area->x + xoff;
aligned_area->y = cell_area->y + yoff;
aligned_area->width = width;
aligned_area->height = height;
g_object_unref(layout);
}
// this is based on both what GtkCellRendererText on 3.22.30 does and what GtkCellRendererToggle does (TODO verify the latter; both on 3.10.9)
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 xoff, yoff;
GtkStyleContext *context;
PangoLayout *layout;
PangoRectangle rect;
gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(c), &xpad, &ypad);
layout = cellRendererButtonPangoLayout(c, widget);
cellRendererButtonSize(c, widget, layout, cell_area,
&xoff, &yoff, NULL, NULL);
context = setButtonStyle(widget);
gtk_render_background(context, cr,
background_area->x + xpad,
background_area->y + ypad,
background_area->width - (xpad * 2),
background_area->height - (ypad * 2));
gtk_render_frame(context, cr,
background_area->x + xpad,
background_area->y + ypad,
background_area->width - (xpad * 2),
background_area->height - (ypad * 2));
pango_layout_get_pixel_extents(layout, NULL, &rect);
xoff -= rect.x;
gtk_render_layout(context, cr,
cell_area->x + xoff + xpad,
cell_area->y + yoff + ypad,
layout);
unsetButtonStyle(context);
g_object_unref(layout);
}
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 *uiprivNewCellRendererButton(void)
{
return GTK_CELL_RENDERER(g_object_new(cellRendererButtonType, NULL));
}
+78
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;
}
+120
View File
@@ -0,0 +1,120 @@
// 28 august 2015
#include "uipriv_unix.h"
// This file contains helpers for managing child controls.
struct uiprivChild {
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;
};
uiprivChild *uiprivNewChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer)
{
uiprivChild *c;
if (child == NULL)
return NULL;
c = uiprivNew(uiprivChild);
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;
}
uiprivChild *uiprivNewChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined)
{
uiprivChild *c;
GtkWidget *box;
if (child == NULL)
return NULL;
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_show(box);
c = uiprivNewChild(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);
uiprivChildSetMargined(c, margined);
return c;
}
void uiprivChildRemove(uiprivChild *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);
uiprivFree(c);
}
void uiprivChildDestroy(uiprivChild *c)
{
uiControl *child;
child = c->c;
uiprivChildRemove(c);
uiControlDestroy(child);
}
GtkWidget *uiprivChildWidget(uiprivChild *c)
{
return c->widget;
}
int uiprivChildFlag(uiprivChild *c)
{
return c->flag;
}
void uiprivChildSetFlag(uiprivChild *c, int flag)
{
c->flag = flag;
}
GtkWidget *uiprivChildBox(uiprivChild *c)
{
return c->box;
}
void uiprivChildSetMargined(uiprivChild *c, int margined)
{
uiprivSetMargined(GTK_CONTAINER(c->box), margined);
}
+80
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;
}
+89
View File
@@ -0,0 +1,89 @@
// 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);
}
void uiComboboxInsertAt(uiCombobox *c, int n, const char *text)
{
gtk_combo_box_text_insert(c->comboboxText, n, NULL, text);
}
void uiComboboxDelete(uiCombobox *c, int n)
{
gtk_combo_box_text_remove(c->comboboxText, n);
}
void uiComboboxClear(uiCombobox *c)
{
gtk_combo_box_text_remove_all(c->comboboxText);
}
int uiComboboxNumItems(uiCombobox *c)
{
GtkListStore *store;
store = GTK_LIST_STORE(gtk_combo_box_get_model(c->combobox));
return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL);
}
int uiComboboxSelected(uiCombobox *c)
{
return gtk_combo_box_get_active(c->combobox);
}
void uiComboboxSetSelected(uiCombobox *c, int n)
{
// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise
g_signal_handler_block(c->combobox, c->onSelectedSignal);
gtk_combo_box_set_active(c->combobox, n);
g_signal_handler_unblock(c->combobox, c->onSelectedSignal);
}
void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)
{
c->onSelected = f;
c->onSelectedData = data;
}
uiCombobox *uiNewCombobox(void)
{
uiCombobox *c;
uiUnixNewControl(uiCombobox, c);
c->widget = gtk_combo_box_text_new();
c->combobox = GTK_COMBO_BOX(c->widget);
c->comboboxText = GTK_COMBO_BOX_TEXT(c->widget);
c->onSelectedSignal = g_signal_connect(c->widget, "changed", G_CALLBACK(onChanged), c);
uiComboboxOnSelected(c, defaultOnSelected, NULL);
return c;
}
+14
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));
}
+675
View File
@@ -0,0 +1,675 @@
// 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 uiprivDateTimePickerWidgetType (uiprivDateTimePickerWidget_get_type())
#define uiprivDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidget))
#define isDateTimePickerWidget(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiprivDateTimePickerWidgetType))
#define uiprivDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidgetClass))
#define isDateTimePickerWidgetClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiprivDateTimePickerWidget))
#define getDateTimePickerWidgetClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiprivDateTimePickerWidgetType, uiprivDateTimePickerWidgetClass))
typedef struct uiprivDateTimePickerWidget uiprivDateTimePickerWidget;
typedef struct uiprivDateTimePickerWidgetClass uiprivDateTimePickerWidgetClass;
struct uiprivDateTimePickerWidget {
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 calendarBlock;
gulong hoursBlock;
gulong minutesBlock;
gulong secondsBlock;
gulong ampmBlock;
GdkDevice *keyboard;
GdkDevice *mouse;
};
struct uiprivDateTimePickerWidgetClass {
GtkToggleButtonClass parent_class;
};
G_DEFINE_TYPE(uiprivDateTimePickerWidget, uiprivDateTimePickerWidget, 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(uiprivDateTimePickerWidget *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(uiprivDateTimePickerWidget *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 int changedSignal;
static void dateTimeChanged(uiprivDateTimePickerWidget *d)
{
g_signal_emit(d, changedSignal, 0);
setLabel(d);
// TODO fire event here instead?
}
// we don't want ::toggled to be sent again
static void setActive(uiprivDateTimePickerWidget *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(uiprivDateTimePickerWidget *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(uiprivDateTimePickerWidget *d)
{
endGrab(d);
gtk_widget_hide(d->window);
setActive(d, FALSE);
}
// this consolidates a good chunk of what GtkComboBox does
static gboolean startGrab(uiprivDateTimePickerWidget *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(uiprivDateTimePickerWidget *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(uiprivDateTimePickerWidget *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)
{
uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(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)
{
uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data);
hidePopup(d);
return TRUE; // this is what GtkComboBox does
}
static gboolean buttonReleased(GtkWidget *w, GdkEventButton *e, gpointer data)
{
uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(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)
{
uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data);
dateTimeChanged(d);
}
static GtkWidget *newSpinbox(uiprivDateTimePickerWidget *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)
{
uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(data);
dateTimeChanged(d);
}
static void setDateOnly(uiprivDateTimePickerWidget *d)
{
d->hasTime = FALSE;
gtk_container_remove(GTK_CONTAINER(d->box), d->timebox);
}
static void setTimeOnly(uiprivDateTimePickerWidget *d)
{
d->hasDate = FALSE;
gtk_container_remove(GTK_CONTAINER(d->box), d->calendar);
}
static void uiprivDateTimePickerWidget_setTime(uiprivDateTimePickerWidget *d, GDateTime *dt)
{
gint year, month, day;
gint hour;
// notice how we block signals from firing
if (d->hasDate) {
g_date_time_get_ymd(dt, &year, &month, &day);
month--; // GDateTime/GtkCalendar differences
g_signal_handler_block(d->calendar, d->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, d->calendarBlock);
}
if (d->hasTime) {
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 uiprivDateTimePickerWidget_init(uiprivDateTimePickerWidget *d)
{
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();
d->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
uiprivDateTimePickerWidget_setTime(d, g_date_time_new_now_local());
}
static void uiprivDateTimePickerWidget_dispose(GObject *obj)
{
uiprivDateTimePickerWidget *d = uiprivDateTimePickerWidget(obj);
if (d->window != NULL) {
gtk_widget_destroy(d->window);
d->window = NULL;
}
G_OBJECT_CLASS(uiprivDateTimePickerWidget_parent_class)->dispose(obj);
}
static void uiprivDateTimePickerWidget_finalize(GObject *obj)
{
G_OBJECT_CLASS(uiprivDateTimePickerWidget_parent_class)->finalize(obj);
}
static void uiprivDateTimePickerWidget_class_init(uiprivDateTimePickerWidgetClass *class)
{
G_OBJECT_CLASS(class)->dispose = uiprivDateTimePickerWidget_dispose;
G_OBJECT_CLASS(class)->finalize = uiprivDateTimePickerWidget_finalize;
changedSignal = g_signal_new("changed",
G_TYPE_FROM_CLASS(class),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL, NULL,
G_TYPE_NONE,
0);
}
struct uiDateTimePicker {
uiUnixControl c;
GtkWidget *widget;
uiprivDateTimePickerWidget *d;
void (*onChanged)(uiDateTimePicker *, void *);
void *onChangedData;
gulong setBlock;
};
uiUnixControlAllDefaults(uiDateTimePicker)
static void defaultOnChanged(uiDateTimePicker *d, void *data)
{
// do nothing
}
void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time)
{
time_t t;
struct tm tmbuf;
GDateTime *dt;
dt = selected(d->d);
t = g_date_time_to_unix(dt);
g_date_time_unref(dt);
// Copy time to minimize a race condition
// time.h functions use global non-thread-safe data
tmbuf = *localtime(&t);
memcpy(time, &tmbuf, sizeof (struct tm));
}
void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time)
{
time_t t;
struct tm tmbuf;
// TODO find a better way to avoid this; possibly by removing the signal entirely, or the call to dateTimeChanged() (most likely both)
g_signal_handler_block(d->d, d->setBlock);
// Copy time because mktime() modifies its argument
memcpy(&tmbuf, time, sizeof (struct tm));
t = mktime(&tmbuf);
uiprivDateTimePickerWidget_setTime(d->d, g_date_time_new_from_unix_local(t));
dateTimeChanged(d->d);
g_signal_handler_unblock(d->d, d->setBlock);
}
void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data)
{
d->onChanged = f;
d->onChangedData = data;
}
static void onChanged(uiprivDateTimePickerWidget *d, gpointer data)
{
uiDateTimePicker *c;
c = uiDateTimePicker(data);
(*(c->onChanged))(c, c->onChangedData);
}
static GtkWidget *newDTP(void)
{
GtkWidget *w;
w = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, "label", "", NULL));
setLabel(uiprivDateTimePickerWidget(w));
return w;
}
static GtkWidget *newDP(void)
{
GtkWidget *w;
w = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, "label", "", NULL));
setDateOnly(uiprivDateTimePickerWidget(w));
setLabel(uiprivDateTimePickerWidget(w));
return w;
}
static GtkWidget *newTP(void)
{
GtkWidget *w;
w = GTK_WIDGET(g_object_new(uiprivDateTimePickerWidgetType, "label", "", NULL));
setTimeOnly(uiprivDateTimePickerWidget(w));
setLabel(uiprivDateTimePickerWidget(w));
return w;
}
uiDateTimePicker *finishNewDateTimePicker(GtkWidget *(*fn)(void))
{
uiDateTimePicker *d;
uiUnixNewControl(uiDateTimePicker, d);
d->widget = (*fn)();
d->d = uiprivDateTimePickerWidget(d->widget);
d->setBlock = g_signal_connect(d->widget, "changed", G_CALLBACK(onChanged), d);
uiDateTimePickerOnChanged(d, defaultOnChanged, NULL);
return d;
}
uiDateTimePicker *uiNewDateTimePicker(void)
{
return finishNewDateTimePicker(newDTP);
}
uiDateTimePicker *uiNewDatePicker(void)
{
return finishNewDateTimePicker(newDP);
}
uiDateTimePicker *uiNewTimePicker(void)
{
return finishNewDateTimePicker(newTP);
}
+14
View File
@@ -0,0 +1,14 @@
// 13 may 2016
#include "uipriv_unix.h"
// LONGTERM don't halt on release builds
void uiprivRealBug(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();
}
+143
View File
@@ -0,0 +1,143 @@
// 6 september 2015
#include "uipriv_unix.h"
#include "draw.h"
uiDrawContext *uiprivNewContext(cairo_t *cr, GtkStyleContext *style)
{
uiDrawContext *c;
c = uiprivNew(uiDrawContext);
c->cr = cr;
c->style = style;
return c;
}
void uiprivFreeContext(uiDrawContext *c)
{
// free neither cr nor style; we own neither
uiprivFree(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)
uiprivImplBug("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;
uiprivRunPath(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;
uiprivRunPath(path, c->cr);
pat = mkbrush(b);
cairo_set_source(c->cr, pat);
switch (uiprivPathFillMode(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;
uiprivM2C(m, &cm);
cairo_transform(c->cr, &cm);
}
void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
{
uiprivRunPath(path, c->cr);
switch (uiprivPathFillMode(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);
}
+14
View File
@@ -0,0 +1,14 @@
// 5 may 2016
// draw.c
struct uiDrawContext {
cairo_t *cr;
GtkStyleContext *style;
};
// drawpath.c
extern void uiprivRunPath(uiDrawPath *p, cairo_t *cr);
extern uiDrawFillMode uiprivPathFillMode(uiDrawPath *path);
// drawmatrix.c
extern void uiprivM2C(uiDrawMatrix *m, cairo_matrix_t *c);
+115
View File
@@ -0,0 +1,115 @@
// 6 september 2015
#include "uipriv_unix.h"
#include "draw.h"
static 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;
}
// needed by uiDrawTransform()
void uiprivM2C(uiDrawMatrix *m, cairo_matrix_t *c)
{
m2c(m, c);
}
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;
uiprivScaleCenter(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)
{
uiprivFallbackSkew(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);
}
+204
View File
@@ -0,0 +1,204 @@
// 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 = uiprivNew(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);
uiprivFree(p);
}
static void add(uiDrawPath *p, struct piece *piece)
{
if (p->ended)
uiprivUserBug("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;
}
int uiDrawPathEnded(uiDrawPath *p)
{
return p->ended == TRUE ? 1 : 0;
}
void uiprivRunPath(uiDrawPath *p, cairo_t *cr)
{
guint i;
struct piece *piece;
void (*arc)(cairo_t *, double, double, double, double, double);
if (!p->ended)
uiprivUserBug("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 uiprivPathFillMode(uiDrawPath *path)
{
return path->fillMode;
}
+103
View File
@@ -0,0 +1,103 @@
// 11 march 2018
#include "uipriv_unix.h"
#include "draw.h"
#include "attrstr.h"
struct uiDrawTextLayout {
PangoLayout *layout;
};
// 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())
static const PangoAlignment pangoAligns[] = {
[uiDrawTextAlignLeft] = PANGO_ALIGN_LEFT,
[uiDrawTextAlignCenter] = PANGO_ALIGN_CENTER,
[uiDrawTextAlignRight] = PANGO_ALIGN_RIGHT,
};
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
{
uiDrawTextLayout *tl;
PangoContext *context;
PangoFontDescription *desc;
PangoAttrList *attrs;
int pangoWidth;
tl = uiprivNew(uiDrawTextLayout);
// 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();
tl->layout = pango_layout_new(context);
g_object_unref(context);
// this is safe; pango_layout_set_text() copies the string
pango_layout_set_text(tl->layout, uiAttributedStringString(p->String), -1);
desc = uiprivFontDescriptorToPangoFontDescription(p->DefaultFont);
pango_layout_set_font_description(tl->layout, desc);
// this is safe; the description is copied
pango_font_description_free(desc);
pangoWidth = cairoToPango(p->Width);
if (p->Width < 0)
pangoWidth = -1;
pango_layout_set_width(tl->layout, pangoWidth);
pango_layout_set_alignment(tl->layout, pangoAligns[p->Align]);
attrs = uiprivAttributedStringToPangoAttrList(p);
pango_layout_set_attributes(tl->layout, attrs);
pango_attr_list_unref(attrs);
return tl;
}
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
{
g_object_unref(tl->layout);
uiprivFree(tl);
}
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
{
// TODO have an implicit save/restore on each drawing functions instead? and is this correct?
cairo_set_source_rgb(c->cr, 0.0, 0.0, 0.0);
cairo_move_to(c->cr, x, y);
pango_cairo_show_layout(c->cr, tl->layout);
}
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
{
PangoRectangle logical;
pango_layout_get_extents(tl->layout, NULL, &logical);
*width = pangoToCairo(logical.width);
*height = pangoToCairo(logical.height);
}
void uiLoadControlFont(uiFontDescriptor *f)
{
GtkWidget *widget;
GtkStyleContext *style;
PangoFontDescription *fontdesc;
widget = g_object_ref_sink(gtk_label_new(""));
style = gtk_widget_get_style_context(widget);
gtk_style_context_get(style, GTK_STATE_FLAG_NORMAL,
"font", &fontdesc, NULL);
uiprivFontDescriptorFromPangoFontDescription(fontdesc, f);
// pdesc is transfer-full and thus is a copy
pango_font_description_free(fontdesc);
g_object_unref(widget);
}
void uiFreeFontDescriptor(uiFontDescriptor *desc)
{
// TODO ensure this is synchronized with fontmatch.c
uiFreeText((char *) (desc->Family));
}
+72
View File
@@ -0,0 +1,72 @@
// 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)
{
return gtk_combo_box_text_get_active_text(c->comboboxText);
}
void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)
{
GtkEntry *e;
// we need to inhibit sending of ::changed because this WILL send a ::changed otherwise
g_signal_handler_block(c->combobox, c->onChangedSignal);
// since there isn't a gtk_combo_box_text_set_active_text()...
e = GTK_ENTRY(gtk_bin_get_child(c->bin));
gtk_entry_set_text(e, text);
g_signal_handler_unblock(c->combobox, c->onChangedSignal);
}
void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)
{
c->onChanged = f;
c->onChangedData = data;
}
uiEditableCombobox *uiNewEditableCombobox(void)
{
uiEditableCombobox *c;
uiUnixNewControl(uiEditableCombobox, c);
c->widget = gtk_combo_box_text_new_with_entry();
c->bin = GTK_BIN(c->widget);
c->combobox = GTK_COMBO_BOX(c->widget);
c->comboboxText = GTK_COMBO_BOX_TEXT(c->widget);
c->onChangedSignal = g_signal_connect(c->widget, "changed", G_CALLBACK(onChanged), c);
uiEditableComboboxOnChanged(c, defaultOnChanged, NULL);
return c;
}
+97
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");
}
+74
View File
@@ -0,0 +1,74 @@
// 14 april 2016
#include "uipriv_unix.h"
#include "attrstr.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
}
void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc)
{
PangoFontDescription *pdesc;
pdesc = gtk_font_chooser_get_font_desc(b->fc);
uiprivFontDescriptorFromPangoFontDescription(pdesc, desc);
// pdesc is transfer-full and thus is a copy
pango_font_description_free(pdesc);
}
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;
}
void uiFreeFontButtonFont(uiFontDescriptor *desc)
{
uiFreeFontDescriptor(desc);
}
+76
View File
@@ -0,0 +1,76 @@
// 11 march 2018
#include "uipriv_unix.h"
#include "attrstr.h"
static const PangoStyle pangoItalics[] = {
[uiTextItalicNormal] = PANGO_STYLE_NORMAL,
[uiTextItalicOblique] = PANGO_STYLE_OBLIQUE,
[uiTextItalicItalic] = PANGO_STYLE_ITALIC,
};
static const PangoStretch pangoStretches[] = {
[uiTextStretchUltraCondensed] = PANGO_STRETCH_ULTRA_CONDENSED,
[uiTextStretchExtraCondensed] = PANGO_STRETCH_EXTRA_CONDENSED,
[uiTextStretchCondensed] = PANGO_STRETCH_CONDENSED,
[uiTextStretchSemiCondensed] = PANGO_STRETCH_SEMI_CONDENSED,
[uiTextStretchNormal] = PANGO_STRETCH_NORMAL,
[uiTextStretchSemiExpanded] = PANGO_STRETCH_SEMI_EXPANDED,
[uiTextStretchExpanded] = PANGO_STRETCH_EXPANDED,
[uiTextStretchExtraExpanded] = PANGO_STRETCH_EXTRA_EXPANDED,
[uiTextStretchUltraExpanded] = PANGO_STRETCH_ULTRA_EXPANDED,
};
// for the most part, pango weights correlate to ours
// the differences:
// - Book — libui: 350, Pango: 380
// - Ultra Heavy — libui: 950, Pango: 1000
// TODO figure out what to do about this misalignment
PangoWeight uiprivWeightToPangoWeight(uiTextWeight w)
{
return (PangoWeight) w;
}
PangoStyle uiprivItalicToPangoStyle(uiTextItalic i)
{
return pangoItalics[i];
}
PangoStretch uiprivStretchToPangoStretch(uiTextStretch s)
{
return pangoStretches[s];
}
PangoFontDescription *uiprivFontDescriptorToPangoFontDescription(const uiFontDescriptor *uidesc)
{
PangoFontDescription *desc;
desc = pango_font_description_new();
pango_font_description_set_family(desc, uidesc->Family);
// see https://developer.gnome.org/pango/1.30/pango-Fonts.html#pango-font-description-set-size and https://developer.gnome.org/pango/1.30/pango-Glyph-Storage.html#pango-units-from-double
pango_font_description_set_size(desc, pango_units_from_double(uidesc->Size));
pango_font_description_set_weight(desc, uiprivWeightToPangoWeight(uidesc->Weight));
pango_font_description_set_style(desc, uiprivItalicToPangoStyle(uidesc->Italic));
pango_font_description_set_stretch(desc, uiprivStretchToPangoStretch(uidesc->Stretch));
return desc;
}
void uiprivFontDescriptorFromPangoFontDescription(PangoFontDescription *pdesc, uiFontDescriptor *uidesc)
{
PangoStyle pitalic;
PangoStretch pstretch;
uidesc->Family = uiUnixStrdupText(pango_font_description_get_family(pdesc));
pitalic = pango_font_description_get_style(pdesc);
// TODO reverse the above misalignment if it is corrected
uidesc->Weight = pango_font_description_get_weight(pdesc);
pstretch = pango_font_description_get_stretch(pdesc);
// absolute size does not matter because, as above, 1 device unit == 1 cairo point
uidesc->Size = pango_units_to_double(pango_font_description_get_size(pdesc));
for (uidesc->Italic = uiTextItalicNormal; uidesc->Italic < uiTextItalicItalic; uidesc->Italic++)
if (pangoItalics[uidesc->Italic] == pitalic)
break;
for (uidesc->Stretch = uiTextStretchUltraCondensed; uidesc->Stretch < uiTextStretchUltraExpanded; uidesc->Stretch++)
if (pangoStretches[uidesc->Stretch] == pstretch)
break;
}
+164
View File
@@ -0,0 +1,164 @@
// 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 uiFormNumChildren(uiForm *f)
{
return (int) f->children->len;
}
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, uiprivGTKYPadding);
gtk_grid_set_column_spacing(f->grid, uiprivGTKXPadding);
} 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;
}
+60
View File
@@ -0,0 +1,60 @@
// 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 *(*newFeaturesAttr)(const gchar *features) = NULL;
static PangoAttribute *(*newFGAlphaAttr)(guint16 alpha) = NULL;
static PangoAttribute *(*newBGAlphaAttr)(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 uiprivLoadFutures(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(newFeaturesAttr, pango_attr_font_features_new);
GET(newFGAlphaAttr, pango_attr_foreground_alpha_new);
GET(newBGAlphaAttr, pango_attr_background_alpha_new);
GET(gwpIterSetObjectName, gtk_widget_path_iter_set_object_name);
dlclose(handle);
}
PangoAttribute *uiprivFUTURE_pango_attr_font_features_new(const gchar *features)
{
if (newFeaturesAttr == NULL)
return NULL;
return (*newFeaturesAttr)(features);
}
PangoAttribute *uiprivFUTURE_pango_attr_foreground_alpha_new(guint16 alpha)
{
if (newFGAlphaAttr == NULL)
return NULL;
return (*newFGAlphaAttr)(alpha);
}
PangoAttribute *uiprivFUTURE_pango_attr_background_alpha_new(guint16 alpha)
{
if (newBGAlphaAttr == NULL)
return NULL;
return (*newBGAlphaAttr)(alpha);
}
gboolean uiprivFUTURE_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;
}
+63
View File
@@ -0,0 +1,63 @@
// 25 may 2016
#include "uipriv_unix.h"
#include "attrstr.h"
int uiprivGraphemesTakesUTF16(void)
{
return 0;
}
uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len)
{
uiprivGraphemes *g;
char *text = (char *) s;
size_t lenchars;
PangoLogAttr *logattrs;
size_t i;
size_t *op;
g = uiprivNew(uiprivGraphemes);
// TODO see if we can use the utf routines
lenchars = g_utf8_strlen(text, -1);
logattrs = (PangoLogAttr *) uiprivAlloc((lenchars + 1) * sizeof (PangoLogAttr), "PangoLogAttr[] (graphemes)");
pango_get_log_attrs(text, len,
-1, NULL,
logattrs, lenchars + 1);
// first figure out how many graphemes there are
g->len = 0;
for (i = 0; i < lenchars; i++)
if (logattrs[i].is_cursor_position != 0)
g->len++;
g->pointsToGraphemes = (size_t *) uiprivAlloc((len + 1) * sizeof (size_t), "size_t[] (graphemes)");
g->graphemesToPoints = (size_t *) uiprivAlloc((g->len + 1) * sizeof (size_t), "size_t[] (graphemes)");
// compute the graphemesToPoints array
// TODO merge with the next for loop somehow?
op = g->graphemesToPoints;
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;
// and finally build the pointsToGraphemes array
op = g->pointsToGraphemes;
for (i = 0; i < g->len; i++) {
size_t j;
size_t first, last;
first = g->graphemesToPoints[i];
last = g->graphemesToPoints[i + 1];
for (j = first; j < last; j++)
*op++ = i;
}
// and do the last one
*op++ = i;
uiprivFree(logattrs);
return g;
}
+141
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, uiprivGTKYPadding);
gtk_grid_set_column_spacing(g->grid, uiprivGTKXPadding);
} else {
gtk_grid_set_row_spacing(g->grid, 0);
gtk_grid_set_column_spacing(g->grid, 0);
}
}
uiGrid *uiNewGrid(void)
{
uiGrid *g;
uiUnixNewControl(uiGrid, g);
g->widget = gtk_grid_new();
g->container = GTK_CONTAINER(g->widget);
g->grid = GTK_GRID(g->widget);
g->children = g_array_new(FALSE, TRUE, sizeof (struct gridChild));
return g;
}
+89
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 that
uiprivChild *child;
int margined;
};
uiUnixControlAllDefaultsExceptDestroy(uiGroup)
static void uiGroupDestroy(uiControl *c)
{
uiGroup *g = uiGroup(c);
if (g->child != NULL)
uiprivChildDestroy(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)
uiprivChildRemove(g->child);
g->child = uiprivNewChildWithBox(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)
uiprivChildSetMargined(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;
}
+134
View File
@@ -0,0 +1,134 @@
// 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;
cairo_surface_destroy(cs);
}
uiImage *uiNewImage(double width, double height)
{
uiImage *i;
i = uiprivNew(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);
uiprivFree(i);
}
void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride)
{
cairo_surface_t *cs;
uint8_t *data, *pix;
int realStride;
int x, y;
// note that this is native-endian
cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
pixelWidth, pixelHeight);
if (cairo_surface_status(cs) != CAIRO_STATUS_SUCCESS)
/* TODO */;
cairo_surface_flush(cs);
pix = (uint8_t *) pixels;
data = (uint8_t *) cairo_image_surface_get_data(cs);
realStride = cairo_image_surface_get_stride(cs);
for (y = 0; y < pixelHeight; y++) {
for (x = 0; x < pixelWidth * 4; x += 4) {
union {
uint32_t v32;
uint8_t v8[4];
} v;
v.v32 = ((uint32_t) (pix[x + 3])) << 24;
v.v32 |= ((uint32_t) (pix[x])) << 16;
v.v32 |= ((uint32_t) (pix[x + 1])) << 8;
v.v32 |= ((uint32_t) (pix[x + 2]));
data[x] = v.v8[0];
data[x + 1] = v.v8[1];
data[x + 2] = v.v8[2];
data[x + 3] = v.v8[3];
}
pix += byteStride;
data += realStride;
}
cairo_surface_mark_dirty(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 *uiprivImageAppropriateSurface(uiImage *i, GtkWidget *w)
{
struct matcher m;
m.best = NULL;
m.distX = G_MAXINT;
m.distY = G_MAXINT;
m.targetX = i->width * gtk_widget_get_scale_factor(w);
m.targetY = i->height * gtk_widget_get_scale_factor(w);
m.foundLarger = FALSE;
g_ptr_array_foreach(i->images, match, &m);
return m.best;
}
+36
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;
}
+148
View File
@@ -0,0 +1,148 @@
// 6 april 2015
#include "uipriv_unix.h"
uiInitOptions uiprivOptions;
static GHashTable *timers;
const char *uiInit(uiInitOptions *o)
{
GError *err = NULL;
const char *msg;
uiprivOptions = *o;
if (gtk_init_with_args(NULL, NULL, NULL, NULL, NULL, &err) == FALSE) {
msg = g_strdup(err->message);
g_error_free(err);
return msg;
}
uiprivInitAlloc();
uiprivLoadFutures();
timers = g_hash_table_new(g_direct_hash, g_direct_equal);
return NULL;
}
struct timer; // TODO get rid of forward declaration
static void uninitTimer(gpointer key, gpointer value, gpointer data)
{
uiprivFree((struct timer *) key);
}
void uiUninit(void)
{
g_hash_table_foreach(timers, uninitTimer, NULL);
g_hash_table_destroy(timers);
uiprivUninitMenus();
uiprivUninitAlloc();
}
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 uiprivAlloc() 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);
}
struct timer {
int (*f)(void *);
void *data;
};
static gboolean doTimer(gpointer data)
{
struct timer *t = (struct timer *) data;
if (!(*(t->f))(t->data)) {
g_hash_table_remove(timers, t);
uiprivFree(t);
return FALSE;
}
return TRUE;
}
void uiTimer(int milliseconds, int (*f)(void *data), void *data)
{
struct timer *t;
t = uiprivNew(struct timer);
t->f = f;
t->data = data;
g_timeout_add(milliseconds, doTimer, t);
g_hash_table_add(timers, t);
}
+366
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 (uiprivShouldQuit())
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)
uiprivUserBug("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)
uiprivUserBug("You cannot create a new menu item after menus have been finalized.");
item = uiprivNew(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)
uiprivUserBug("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)
uiprivUserBug("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)
uiprivUserBug("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)
uiprivUserBug("You cannot create a new menu after menus have been finalized.");
if (menus == NULL)
menus = g_array_new(FALSE, TRUE, sizeof (uiMenu *));
m = uiprivNew(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 = uiprivNew(struct menuItemWindow);
ww->w = w;
ww->signal = signal;
g_hash_table_insert(item->windows, menuitem, ww);
}
GtkWidget *uiprivMakeMenubar(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)
uiprivImplBug("GtkMenuItem %p not in menu item's item/window map", widget);
uiprivFree(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 uiprivFreeMenubar(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 uiprivUninitMenus(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 uiprivUserBug()?
uiprivImplBug("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);
uiprivFree(item);
}
g_array_free(m->items, TRUE);
uiprivFree(m);
}
g_array_free(menus, TRUE);
}
+62
View File
@@ -0,0 +1,62 @@
# 23 march 2019
libui_sources += [
'unix/alloc.c',
'unix/area.c',
'unix/attrstr.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/fontmatch.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/opentype.c',
'unix/progressbar.c',
'unix/radiobuttons.c',
'unix/separator.c',
'unix/slider.c',
'unix/spinbox.c',
'unix/stddialogs.c',
'unix/tab.c',
'unix/table.c',
'unix/tablemodel.c',
'unix/text.c',
'unix/util.c',
'unix/window.c',
]
libui_deps += [
dependency('gtk+-3.0',
version: '>=3.10.0',
method: 'pkg-config',
required: true),
# We specify these as not required because some Unix systems include them with libc instead of providing them as separate files (thanks textshell and jpakkane in freenode #mesonbuild)
meson.get_compiler('c').find_library('m',
required: false),
meson.get_compiler('c').find_library('dl',
required: false),
]
libui_soversion = '0'
libui_rpath = '$ORIGIN'
+122
View File
@@ -0,0 +1,122 @@
// 6 december 2015
#include "uipriv_unix.h"
// TODO GTK_WRAP_WORD_CHAR to avoid spurious resizes?
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;
gtk_text_buffer_get_start_iter(e->textbuf, &start);
gtk_text_buffer_get_end_iter(e->textbuf, &end);
return gtk_text_buffer_get_text(e->textbuf, &start, &end, TRUE);
}
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);
}
+26
View File
@@ -0,0 +1,26 @@
// 11 may 2017
#include "uipriv_unix.h"
#include "attrstr.h"
// see https://developer.mozilla.org/en/docs/Web/CSS/font-feature-settings
static uiForEach toCSS(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)
{
GString *s = (GString *) data;
// the last trailing comma is removed after foreach is done
g_string_append_printf(s, "\"%c%c%c%c\" %" PRIu32 ", ",
a, b, c, d, value);
return uiForEachContinue;
}
GString *uiprivOpenTypeFeaturesToPangoCSSFeaturesString(const uiOpenTypeFeatures *otf)
{
GString *s;
s = g_string_new("");
uiOpenTypeFeaturesForEach(otf, toCSS, s);
if (s->len != 0)
// and remove the last comma
g_string_truncate(s, s->len - 2);
return s;
}
+74
View File
@@ -0,0 +1,74 @@
// 11 june 2015
#include "uipriv_unix.h"
// LONGTERM:
// - in GTK+ 3.22 at least, running both a GtkProgressBar and a GtkCellRendererProgress in pulse mode with our code will cause the former to slow down and eventually stop, and I can't tell why at all
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)
uiprivUserBug("Value %d is out of range for a uiProgressBar.", value);
gtk_progress_bar_set_fraction(p->pbar, ((gdouble) value) / 100);
}
uiProgressBar *uiNewProgressBar(void)
{
uiProgressBar *p;
uiUnixNewControl(uiProgressBar, p);
p->widget = gtk_progress_bar_new();
p->pbar = GTK_PROGRESS_BAR(p->widget);
return p;
}
+121
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;
}
+34
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;
}
+143
View File
@@ -0,0 +1,143 @@
// 11 june 2015
#include "uipriv_unix.h"
/* Number of digits to represent an n-bit number in decimal: log10(2^n)
* Simplify, truncate & add 1 for full digits, add 1 for negative numbers:
* -> n log10(2) + 2 <= n * 0.302 + 2
*/
#define MAX_STRLEN_FOR_NBITS_IN_DECIMAL(n) ((int)((n) * 302 / 1000) + 2)
struct uiSlider {
uiUnixControl c;
GtkWidget *widget;
GtkRange *range;
GtkScale *scale;
void (*onChanged)(uiSlider *, void *);
void *onChangedData;
void (*onReleased)(uiSlider *, void *);
void *onReleasedData;
gulong onChangedSignal;
gchar tooltip[MAX_STRLEN_FOR_NBITS_IN_DECIMAL(sizeof(int) * CHAR_BIT) + 1];
};
uiUnixControlAllDefaults(uiSlider)
static void _uiSliderUpdateToolTip(uiSlider *s)
{
g_snprintf(s->tooltip, sizeof(s->tooltip)/sizeof(s->tooltip[0]), "%d", uiSliderValue(s));
gtk_widget_set_tooltip_text(s->widget, s->tooltip);
}
static void onChanged(GtkRange *range, gpointer data)
{
uiSlider *s = uiSlider(data);
(*(s->onChanged))(s, s->onChangedData);
if (uiSliderHasToolTip(s))
_uiSliderUpdateToolTip(s);
}
static void defaultOnChanged(uiSlider *s, void *data)
{
// do nothing
}
static gboolean onReleased(GtkWidget *w, GdkEventButton *event, gpointer data)
{
uiSlider *s = uiSlider(data);
(*(s->onReleased))(s, s->onReleasedData);
return FALSE;
}
static void defaultOnReleased(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);
}
int uiSliderHasToolTip(uiSlider *s)
{
return gtk_widget_get_has_tooltip(s->widget);
}
void uiSliderSetHasToolTip(uiSlider *s, int hasToolTip)
{
gtk_widget_set_has_tooltip(s->widget, hasToolTip);
if (hasToolTip)
_uiSliderUpdateToolTip(s);
}
void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)
{
s->onChanged = f;
s->onChangedData = data;
}
void uiSliderOnReleased(uiSlider *s, void (*f)(uiSlider *, void *), void *data)
{
s->onReleased = f;
s->onReleasedData = data;
}
void uiSliderSetRange(uiSlider *s, int min, int max)
{
int temp;
if (min >= max) {
temp = min;
min = max;
max = temp;
}
// 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_range(s->range, min, max);
g_signal_handler_unblock(s->range, s->onChangedSignal);
}
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);
// do not draw value, show tooltip instead
gtk_scale_set_draw_value(s->scale, 0);
uiSliderSetHasToolTip(s, 1);
// 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);
g_signal_connect(s->scale, "button-release-event", G_CALLBACK(onReleased), s);
uiSliderOnChanged(s, defaultOnChanged, NULL);
uiSliderOnReleased(s, defaultOnReleased, NULL);
return s;
}
+72
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;
}
+71
View File
@@ -0,0 +1,71 @@
// 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 = 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 *uiOpenFolder(uiWindow *parent)
{
return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, "_Open");
}
char *uiSaveFile(uiWindow *parent)
{
return filedialog(windowWindow(parent), GTK_FILE_CHOOSER_ACTION_SAVE, "_Save");
}
static void msgbox(GtkWindow *parent, const char *title, const char *description, GtkMessageType type, GtkButtonsType buttons)
{
GtkWidget *md;
md = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL,
type, buttons,
"%s", title);
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(md), "%s", description);
gtk_dialog_run(GTK_DIALOG(md));
gtk_widget_destroy(md);
}
void uiMsgBox(uiWindow *parent, const char *title, const char *description)
{
msgbox(windowWindow(parent), title, description, GTK_MESSAGE_OTHER, GTK_BUTTONS_OK);
}
void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)
{
msgbox(windowWindow(parent), title, description, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK);
}
+97
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; // []*uiprivChild
};
uiUnixControlAllDefaultsExceptDestroy(uiTab)
static void uiTabDestroy(uiControl *c)
{
uiTab *t = uiTab(c);
guint i;
uiprivChild *page;
for (i = 0; i < t->pages->len; i++) {
page = g_array_index(t->pages, uiprivChild *, i);
uiprivChildDestroy(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)
{
uiprivChild *page;
// this will create a tab, because of gtk_container_add()
page = uiprivNewChildWithBox(child, uiControl(t), t->container, 0);
gtk_notebook_set_tab_label_text(t->notebook, uiprivChildBox(page), name);
gtk_notebook_reorder_child(t->notebook, uiprivChildBox(page), n);
g_array_insert_val(t->pages, n, page);
}
void uiTabDelete(uiTab *t, int n)
{
uiprivChild *page;
page = g_array_index(t->pages, uiprivChild *, n);
// this will remove the tab, because gtk_widget_destroy() calls gtk_container_remove()
uiprivChildRemove(page);
g_array_remove_index(t->pages, n);
}
int uiTabNumPages(uiTab *t)
{
return t->pages->len;
}
int uiTabMargined(uiTab *t, int n)
{
uiprivChild *page;
page = g_array_index(t->pages, uiprivChild *, n);
return uiprivChildFlag(page);
}
void uiTabSetMargined(uiTab *t, int n, int margined)
{
uiprivChild *page;
page = g_array_index(t->pages, uiprivChild *, n);
uiprivChildSetFlag(page, margined);
uiprivChildSetMargined(page, uiprivChildFlag(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 (uiprivChild *));
return t;
}
+605
View File
@@ -0,0 +1,605 @@
// 26 june 2016
#include "uipriv_unix.h"
#include "table.h"
// TODO with GDK_SCALE set to 2 the 32x32 images are scaled up to 64x64?
struct uiTable {
uiUnixControl c;
GtkWidget *widget;
GtkContainer *scontainer;
GtkScrolledWindow *sw;
GtkWidget *treeWidget;
GtkTreeView *tv;
uiTableModel *model;
GPtrArray *columnParams;
int backgroundColumn;
// keys are struct rowcol, values are gint
// TODO document this properly
GHashTable *indeterminatePositions;
guint indeterminateTimer;
void (*headerOnClicked)(uiTable *, int, void *);
void *headerOnClickedData;
};
// use the same size as GtkFileChooserWidget's treeview
// TODO refresh when icon theme changes
// TODO doesn't work when scaled?
// TODO is this even necessary?
static void setImageSize(GtkCellRenderer *r)
{
gint size;
gint width, height;
gint xpad, ypad;
size = 16; // fallback used by GtkFileChooserWidget
if (gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height) != FALSE)
size = MAX(width, height);
gtk_cell_renderer_get_padding(r, &xpad, &ypad);
gtk_cell_renderer_set_fixed_size(r,
2 * xpad + size,
2 * ypad + size);
}
static void applyColor(GtkTreeModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop, const char *propSet)
{
GValue value = G_VALUE_INIT;
GdkRGBA *rgba;
gtk_tree_model_get_value(m, iter, modelColumn, &value);
rgba = (GdkRGBA *) g_value_get_boxed(&value);
if (rgba != NULL)
g_object_set(r, prop, rgba, NULL);
else
g_object_set(r, propSet, FALSE, NULL);
g_value_unset(&value);
}
static void setEditable(uiTableModel *m, GtkTreeIter *iter, int modelColumn, GtkCellRenderer *r, const char *prop)
{
GtkTreePath *path;
int row;
gboolean editable;
// TODO avoid the need for this
path = gtk_tree_model_get_path(GTK_TREE_MODEL(m), iter);
row = gtk_tree_path_get_indices(path)[0];
gtk_tree_path_free(path);
editable = uiprivTableModelCellEditable(m, row, modelColumn) != 0;
g_object_set(r, prop, editable, NULL);
}
static void applyBackgroundColor(uiTable *t, GtkTreeModel *m, GtkTreeIter *iter, GtkCellRenderer *r)
{
if (t->backgroundColumn != -1)
applyColor(m, iter, t->backgroundColumn,
r, "cell-background-rgba", "cell-background-set");
}
static void onEdited(uiTableModel *m, int column, const char *pathstr, const uiTableValue *tvalue, GtkTreeIter *iter)
{
GtkTreePath *path;
int row;
path = gtk_tree_path_new_from_string(pathstr);
row = gtk_tree_path_get_indices(path)[0];
if (iter != NULL)
gtk_tree_model_get_iter(GTK_TREE_MODEL(m), iter, path);
gtk_tree_path_free(path);
uiprivTableModelSetCellValue(m, row, column, tvalue);
}
struct textColumnParams {
uiTable *t;
uiTableModel *m;
int modelColumn;
int editableColumn;
uiTableTextColumnOptionalParams params;
};
static void textColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)
{
struct textColumnParams *p = (struct textColumnParams *) data;
GValue value = G_VALUE_INIT;
const gchar *str;
gtk_tree_model_get_value(m, iter, p->modelColumn, &value);
str = g_value_get_string(&value);
g_object_set(r, "text", str, NULL);
g_value_unset(&value);
setEditable(p->m, iter, p->editableColumn, r, "editable");
if (p->params.ColorModelColumn != -1)
applyColor(m, iter, p->params.ColorModelColumn,
r, "foreground-rgba", "foreground-set");
applyBackgroundColor(p->t, m, iter, r);
}
static void textColumnEdited(GtkCellRendererText *r, gchar *path, gchar *newText, gpointer data)
{
struct textColumnParams *p = (struct textColumnParams *) data;
uiTableValue *tvalue;
GtkTreeIter iter;
tvalue = uiNewTableValueString(newText);
onEdited(p->m, p->modelColumn, path, tvalue, &iter);
uiFreeTableValue(tvalue);
// and update the column TODO copy comment here
textColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data);
}
struct imageColumnParams {
uiTable *t;
int modelColumn;
};
static void imageColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)
{
struct imageColumnParams *p = (struct imageColumnParams *) data;
GValue value = G_VALUE_INIT;
uiImage *img;
//TODO setImageSize(r);
gtk_tree_model_get_value(m, iter, p->modelColumn, &value);
img = (uiImage *) g_value_get_pointer(&value);
g_object_set(r, "surface",
uiprivImageAppropriateSurface(img, p->t->treeWidget),
NULL);
g_value_unset(&value);
applyBackgroundColor(p->t, m, iter, r);
}
struct checkboxColumnParams {
uiTable *t;
uiTableModel *m;
int modelColumn;
int editableColumn;
};
static void checkboxColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)
{
struct checkboxColumnParams *p = (struct checkboxColumnParams *) data;
GValue value = G_VALUE_INIT;
gboolean active;
gtk_tree_model_get_value(m, iter, p->modelColumn, &value);
active = g_value_get_int(&value) != 0;
g_object_set(r, "active", active, NULL);
g_value_unset(&value);
setEditable(p->m, iter, p->editableColumn, r, "activatable");
applyBackgroundColor(p->t, m, iter, r);
}
static void checkboxColumnToggled(GtkCellRendererToggle *r, gchar *pathstr, gpointer data)
{
struct checkboxColumnParams *p = (struct checkboxColumnParams *) data;
GValue value = G_VALUE_INIT;
int v;
uiTableValue *tvalue;
GtkTreePath *path;
GtkTreeIter iter;
path = gtk_tree_path_new_from_string(pathstr);
gtk_tree_model_get_iter(GTK_TREE_MODEL(p->m), &iter, path);
gtk_tree_path_free(path);
gtk_tree_model_get_value(GTK_TREE_MODEL(p->m), &iter, p->modelColumn, &value);
v = g_value_get_int(&value);
g_value_unset(&value);
tvalue = uiNewTableValueInt(!v);
onEdited(p->m, p->modelColumn, pathstr, tvalue, NULL);
uiFreeTableValue(tvalue);
// and update the column TODO copy comment here
// TODO avoid fetching the model data twice
checkboxColumnDataFunc(NULL, GTK_CELL_RENDERER(r), GTK_TREE_MODEL(p->m), &iter, data);
}
struct progressBarColumnParams {
uiTable *t;
int modelColumn;
};
struct rowcol {
int row;
int col;
};
static guint rowcolHash(gconstpointer key)
{
const struct rowcol *rc = (const struct rowcol *) key;
guint row, col;
row = (guint) (rc->row);
col = (guint) (rc->col);
return row ^ col;
}
static gboolean rowcolEqual(gconstpointer a, gconstpointer b)
{
const struct rowcol *ra = (const struct rowcol *) a;
const struct rowcol *rb = (const struct rowcol *) b;
return (ra->row == rb->row) && (ra->col == rb->col);
}
static void pulseOne(gpointer key, gpointer value, gpointer data)
{
uiTable *t = uiTable(data);
struct rowcol *rc = (struct rowcol *) key;
// TODO this is bad: it produces changed handlers for every table because that's how GtkTreeModel works, yet this is per-table because that's how it works
// however, a proper fix would require decoupling progress from normal integers, which we could do...
uiTableModelRowChanged(t->model, rc->row);
}
static gboolean indeterminatePulse(gpointer data)
{
uiTable *t = uiTable(data);
g_hash_table_foreach(t->indeterminatePositions, pulseOne, t);
return TRUE;
}
static void progressBarColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)
{
struct progressBarColumnParams *p = (struct progressBarColumnParams *) data;
GValue value = G_VALUE_INIT;
int pval;
struct rowcol *rc;
gint *val;
GtkTreePath *path;
gtk_tree_model_get_value(m, iter, p->modelColumn, &value);
pval = g_value_get_int(&value);
rc = uiprivNew(struct rowcol);
// TODO avoid the need for this
path = gtk_tree_model_get_path(GTK_TREE_MODEL(m), iter);
rc->row = gtk_tree_path_get_indices(path)[0];
gtk_tree_path_free(path);
rc->col = p->modelColumn;
val = (gint *) g_hash_table_lookup(p->t->indeterminatePositions, rc);
if (pval == -1) {
if (val == NULL) {
val = uiprivNew(gint);
*val = 1;
g_hash_table_insert(p->t->indeterminatePositions, rc, val);
} else {
uiprivFree(rc);
(*val)++;
if (*val == G_MAXINT)
*val = 1;
}
g_object_set(r,
"pulse", *val,
NULL);
if (p->t->indeterminateTimer == 0)
// TODO verify the timeout
p->t->indeterminateTimer = g_timeout_add(100, indeterminatePulse, p->t);
} else {
if (val != NULL) {
g_hash_table_remove(p->t->indeterminatePositions, rc);
if (g_hash_table_size(p->t->indeterminatePositions) == 0) {
g_source_remove(p->t->indeterminateTimer);
p->t->indeterminateTimer = 0;
}
}
uiprivFree(rc);
g_object_set(r,
"pulse", -1,
"value", pval,
NULL);
}
g_value_unset(&value);
applyBackgroundColor(p->t, m, iter, r);
}
struct buttonColumnParams {
uiTable *t;
uiTableModel *m;
int modelColumn;
int clickableColumn;
};
static void buttonColumnDataFunc(GtkTreeViewColumn *c, GtkCellRenderer *r, GtkTreeModel *m, GtkTreeIter *iter, gpointer data)
{
struct buttonColumnParams *p = (struct buttonColumnParams *) data;
GValue value = G_VALUE_INIT;
const gchar *str;
gtk_tree_model_get_value(m, iter, p->modelColumn, &value);
str = g_value_get_string(&value);
g_object_set(r, "text", str, NULL);
g_value_unset(&value);
setEditable(p->m, iter, p->clickableColumn, r, "sensitive");
applyBackgroundColor(p->t, m, iter, r);
}
// TODO wrong type here
static void buttonColumnClicked(GtkCellRenderer *r, gchar *pathstr, gpointer data)
{
struct buttonColumnParams *p = (struct buttonColumnParams *) data;
onEdited(p->m, p->modelColumn, pathstr, NULL, NULL);
}
uiSortIndicator uiTableHeaderSortIndicator(uiTable *t, int lcol)
{
GtkTreeViewColumn *c = gtk_tree_view_get_column(t->tv, lcol);
if (c == NULL || gtk_tree_view_column_get_sort_indicator(c) == FALSE)
return uiSortIndicatorNone;
if (gtk_tree_view_column_get_sort_order(c) == GTK_SORT_ASCENDING)
return uiSortIndicatorAscending;
else
return uiSortIndicatorDescending;
}
void uiTableHeaderSetSortIndicator(uiTable *t, int lcol, uiSortIndicator indicator)
{
GtkTreeViewColumn *c = gtk_tree_view_get_column(t->tv, lcol);
if (c == NULL)
return;
if (indicator == uiSortIndicatorNone) {
gtk_tree_view_column_set_sort_indicator(c, FALSE);
return;
}
gtk_tree_view_column_set_sort_indicator(c, TRUE);
if (indicator == uiSortIndicatorAscending)
gtk_tree_view_column_set_sort_order(c, GTK_SORT_ASCENDING);
else
gtk_tree_view_column_set_sort_order(c, GTK_SORT_DESCENDING);
}
void uiTableHeaderOnClicked(uiTable *t, void (*f)(uiTable *, int, void *), void *data)
{
t->headerOnClicked = f;
t->headerOnClickedData = data;
}
static void defaultHeaderOnClicked(uiTable *table, int column, void *data)
{
// do nothing
}
static void headerOnClicked(GtkTreeViewColumn *c, gpointer data)
{
guint i;
uiTable *t = uiTable(data);
for (i = 0; i < gtk_tree_view_get_n_columns(t->tv); ++i)
if (gtk_tree_view_get_column(t->tv, i) == c)
t->headerOnClicked(t, i, t->headerOnClickedData);
}
static GtkTreeViewColumn *addColumn(uiTable *t, const char *name)
{
GtkTreeViewColumn *c;
c = gtk_tree_view_column_new();
gtk_tree_view_column_set_resizable(c, TRUE);
gtk_tree_view_column_set_title(c, name);
gtk_tree_view_column_set_clickable(c, 1);
g_signal_connect(c, "clicked", G_CALLBACK(headerOnClicked), t);
gtk_tree_view_append_column(t->tv, c);
return c;
}
static void addTextColumn(uiTable *t, GtkTreeViewColumn *c, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)
{
struct textColumnParams *p;
GtkCellRenderer *r;
p = uiprivNew(struct textColumnParams);
p->t = t;
// TODO get rid of these fields AND rename t->model in favor of t->m
p->m = t->model;
p->modelColumn = textModelColumn;
p->editableColumn = textEditableModelColumn;
if (textParams != NULL)
p->params = *textParams;
else
p->params = uiprivDefaultTextColumnOptionalParams;
r = gtk_cell_renderer_text_new();
gtk_tree_view_column_pack_start(c, r, TRUE);
gtk_tree_view_column_set_cell_data_func(c, r, textColumnDataFunc, p, NULL);
g_signal_connect(r, "edited", G_CALLBACK(textColumnEdited), p);
g_ptr_array_add(t->columnParams, p);
}
// TODO rename modelCOlumn and params everywhere
void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)
{
GtkTreeViewColumn *c;
c = addColumn(t, name);
addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams);
}
static void addImageColumn(uiTable *t, GtkTreeViewColumn *c, int imageModelColumn)
{
struct imageColumnParams *p;
GtkCellRenderer *r;
p = uiprivNew(struct imageColumnParams);
p->t = t;
p->modelColumn = imageModelColumn;
r = gtk_cell_renderer_pixbuf_new();
gtk_tree_view_column_pack_start(c, r, FALSE);
gtk_tree_view_column_set_cell_data_func(c, r, imageColumnDataFunc, p, NULL);
g_ptr_array_add(t->columnParams, p);
}
void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn)
{
GtkTreeViewColumn *c;
c = addColumn(t, name);
addImageColumn(t, c, imageModelColumn);
}
void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)
{
GtkTreeViewColumn *c;
c = addColumn(t, name);
addImageColumn(t, c, imageModelColumn);
addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams);
}
static void addCheckboxColumn(uiTable *t, GtkTreeViewColumn *c, int checkboxModelColumn, int checkboxEditableModelColumn)
{
struct checkboxColumnParams *p;
GtkCellRenderer *r;
p = uiprivNew(struct checkboxColumnParams);
p->t = t;
p->m = t->model;
p->modelColumn = checkboxModelColumn;
p->editableColumn = checkboxEditableModelColumn;
r = gtk_cell_renderer_toggle_new();
gtk_tree_view_column_pack_start(c, r, FALSE);
gtk_tree_view_column_set_cell_data_func(c, r, checkboxColumnDataFunc, p, NULL);
g_signal_connect(r, "toggled", G_CALLBACK(checkboxColumnToggled), p);
g_ptr_array_add(t->columnParams, p);
}
void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn)
{
GtkTreeViewColumn *c;
c = addColumn(t, name);
addCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn);
}
void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)
{
GtkTreeViewColumn *c;
c = addColumn(t, name);
addCheckboxColumn(t, c, checkboxModelColumn, checkboxEditableModelColumn);
addTextColumn(t, c, textModelColumn, textEditableModelColumn, textParams);
}
void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn)
{
GtkTreeViewColumn *c;
struct progressBarColumnParams *p;
GtkCellRenderer *r;
c = addColumn(t, name);
p = uiprivNew(struct progressBarColumnParams);
p->t = t;
// TODO make progress and progressBar consistent everywhere
p->modelColumn = progressModelColumn;
r = gtk_cell_renderer_progress_new();
gtk_tree_view_column_pack_start(c, r, TRUE);
gtk_tree_view_column_set_cell_data_func(c, r, progressBarColumnDataFunc, p, NULL);
g_ptr_array_add(t->columnParams, p);
}
void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn)
{
GtkTreeViewColumn *c;
struct buttonColumnParams *p;
GtkCellRenderer *r;
c = addColumn(t, name);
p = uiprivNew(struct buttonColumnParams);
p->t = t;
p->m = t->model;
p->modelColumn = buttonModelColumn;
p->clickableColumn = buttonClickableModelColumn;
r = uiprivNewCellRendererButton();
gtk_tree_view_column_pack_start(c, r, TRUE);
gtk_tree_view_column_set_cell_data_func(c, r, buttonColumnDataFunc, p, NULL);
g_signal_connect(r, "clicked", G_CALLBACK(buttonColumnClicked), p);
g_ptr_array_add(t->columnParams, p);
}
int uiTableHeaderVisible(uiTable *t)
{
return gtk_tree_view_get_headers_visible(t->tv);
}
void uiTableHeaderSetVisible(uiTable *t, int visible)
{
gtk_tree_view_set_headers_visible(t->tv, visible);
}
uiUnixControlAllDefaultsExceptDestroy(uiTable)
static void uiTableDestroy(uiControl *c)
{
uiTable *t = uiTable(c);
guint i;
for (i = 0; i < t->columnParams->len; i++)
uiprivFree(g_ptr_array_index(t->columnParams, i));
g_ptr_array_free(t->columnParams, TRUE);
if (g_hash_table_size(t->indeterminatePositions) != 0)
g_source_remove(t->indeterminateTimer);
g_hash_table_destroy(t->indeterminatePositions);
g_object_unref(t->widget);
uiFreeControl(uiControl(t));
}
uiTable *uiNewTable(uiTableParams *p)
{
uiTable *t;
uiUnixNewControl(uiTable, t);
t->model = p->Model;
t->columnParams = g_ptr_array_new();
t->backgroundColumn = p->RowBackgroundColorModelColumn;
t->widget = gtk_scrolled_window_new(NULL, NULL);
t->scontainer = GTK_CONTAINER(t->widget);
t->sw = GTK_SCROLLED_WINDOW(t->widget);
gtk_scrolled_window_set_shadow_type(t->sw, GTK_SHADOW_IN);
t->treeWidget = gtk_tree_view_new_with_model(GTK_TREE_MODEL(t->model));
t->tv = GTK_TREE_VIEW(t->treeWidget);
// TODO set up t->tv
gtk_container_add(t->scontainer, t->treeWidget);
// and make the tree view visible; only the scrolled window's visibility is controlled by libui
gtk_widget_show(t->treeWidget);
t->indeterminatePositions = g_hash_table_new_full(rowcolHash, rowcolEqual,
uiprivFree, uiprivFree);
uiTableHeaderOnClicked(t, defaultHeaderOnClicked, NULL);
return t;
}
int uiTableColumnWidth(uiTable *t, int column)
{
GtkTreeViewColumn *c = gtk_tree_view_get_column(t->tv, column);
return gtk_tree_view_column_get_width(c);
}
void uiTableColumnSetWidth(uiTable *t, int column, int width)
{
GtkTreeViewColumn *c = gtk_tree_view_get_column(t->tv, column);
gtk_tree_view_column_set_fixed_width(c, width);
}
+20
View File
@@ -0,0 +1,20 @@
// 4 june 2018
#include "../common/table.h"
// tablemodel.c
#define uiTableModelType (uiTableModel_get_type())
#define uiTableModel(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), uiTableModelType, uiTableModel))
#define isuiTableModel(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), uiTableModelType))
#define uiTableModelClass(class) (G_TYPE_CHECK_CLASS_CAST((class), uiTableModelType, uiTableModelClass))
#define isuiTableModelClass(class) (G_TYPE_CHECK_CLASS_TYPE((class), uiTableModel))
#define getuiTableModelClass(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), uiTableModelType, uiTableModelClass))
typedef struct uiTableModelClass uiTableModelClass;
struct uiTableModel {
GObject parent_instance;
gint stamp;
uiTableModelHandler *mh;
};
struct uiTableModelClass {
GObjectClass parent_class;
};
extern GType uiTableModel_get_type(void);
+285
View File
@@ -0,0 +1,285 @@
// 26 june 2016
#include "uipriv_unix.h"
#include "table.h"
static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface);
G_DEFINE_TYPE_WITH_CODE(uiTableModel, uiTableModel, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, uiTableModel_gtk_tree_model_interface_init))
static void uiTableModel_init(uiTableModel *m)
{
// nothing to do
}
static void uiTableModel_dispose(GObject *obj)
{
G_OBJECT_CLASS(uiTableModel_parent_class)->dispose(obj);
}
static void uiTableModel_finalize(GObject *obj)
{
G_OBJECT_CLASS(uiTableModel_parent_class)->finalize(obj);
}
static GtkTreeModelFlags uiTableModel_get_flags(GtkTreeModel *mm)
{
return GTK_TREE_MODEL_LIST_ONLY;
}
static gint uiTableModel_get_n_columns(GtkTreeModel *mm)
{
uiTableModel *m = uiTableModel(mm);
return uiprivTableModelNumColumns(m);
}
static GType uiTableModel_get_column_type(GtkTreeModel *mm, gint index)
{
uiTableModel *m = uiTableModel(mm);
switch (uiprivTableModelColumnType(m, index)) {
case uiTableValueTypeString:
return G_TYPE_STRING;
case uiTableValueTypeImage:
return G_TYPE_POINTER;
case uiTableValueTypeInt:
return G_TYPE_INT;
case uiTableValueTypeColor:
return GDK_TYPE_RGBA;
}
// TODO
return G_TYPE_INVALID;
}
static gboolean uiTableModel_get_iter(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreePath *path)
{
uiTableModel *m = uiTableModel(mm);
gint row;
if (gtk_tree_path_get_depth(path) != 1)
goto bad;
row = gtk_tree_path_get_indices(path)[0];
if (row < 0)
goto bad;
if (row >= uiprivTableModelNumRows(m))
goto bad;
iter->stamp = m->stamp;
iter->user_data = GINT_TO_POINTER(row);
return TRUE;
bad:
iter->stamp = 0;
return FALSE;
}
// GtkListStore returns NULL on error; let's do that too
static GtkTreePath *uiTableModel_get_path(GtkTreeModel *mm, GtkTreeIter *iter)
{
uiTableModel *m = uiTableModel(mm);
gint row;
g_return_val_if_fail(iter->stamp == m->stamp, NULL);
row = GPOINTER_TO_INT(iter->user_data);
return gtk_tree_path_new_from_indices(row, -1);
}
// GtkListStore leaves value empty on failure; let's do the same
static void uiTableModel_get_value(GtkTreeModel *mm, GtkTreeIter *iter, gint column, GValue *value)
{
uiTableModel *m = uiTableModel(mm);
gint row;
uiTableValue *tvalue;
double r, g, b, a;
GdkRGBA rgba;
g_return_if_fail(iter->stamp == m->stamp);
row = GPOINTER_TO_INT(iter->user_data);
tvalue = uiprivTableModelCellValue(m, row, column);
switch (uiprivTableModelColumnType(m, column)) {
case uiTableValueTypeString:
g_value_init(value, G_TYPE_STRING);
g_value_set_string(value, uiTableValueString(tvalue));
uiFreeTableValue(tvalue);
return;
case uiTableValueTypeImage:
g_value_init(value, G_TYPE_POINTER);
g_value_set_pointer(value, uiTableValueImage(tvalue));
uiFreeTableValue(tvalue);
return;
case uiTableValueTypeInt:
g_value_init(value, G_TYPE_INT);
g_value_set_int(value, uiTableValueInt(tvalue));
uiFreeTableValue(tvalue);
return;
case uiTableValueTypeColor:
g_value_init(value, GDK_TYPE_RGBA);
if (tvalue == NULL) {
g_value_set_boxed(value, NULL);
return;
}
uiTableValueColor(tvalue, &r, &g, &b, &a);
uiFreeTableValue(tvalue);
rgba.red = r;
rgba.green = g;
rgba.blue = b;
rgba.alpha = a;
g_value_set_boxed(value, &rgba);
return;
}
// TODO
}
static gboolean uiTableModel_iter_next(GtkTreeModel *mm, GtkTreeIter *iter)
{
uiTableModel *m = uiTableModel(mm);
gint row;
g_return_val_if_fail(iter->stamp == m->stamp, FALSE);
row = GPOINTER_TO_INT(iter->user_data);
row++;
if (row >= uiprivTableModelNumRows(m)) {
iter->stamp = 0;
return FALSE;
}
iter->user_data = GINT_TO_POINTER(row);
return TRUE;
}
static gboolean uiTableModel_iter_previous(GtkTreeModel *mm, GtkTreeIter *iter)
{
uiTableModel *m = uiTableModel(mm);
gint row;
g_return_val_if_fail(iter->stamp == m->stamp, FALSE);
row = GPOINTER_TO_INT(iter->user_data);
row--;
if (row < 0) {
iter->stamp = 0;
return FALSE;
}
iter->user_data = GINT_TO_POINTER(row);
return TRUE;
}
static gboolean uiTableModel_iter_children(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent)
{
return gtk_tree_model_iter_nth_child(mm, iter, parent, 0);
}
static gboolean uiTableModel_iter_has_child(GtkTreeModel *mm, GtkTreeIter *iter)
{
return FALSE;
}
static gint uiTableModel_iter_n_children(GtkTreeModel *mm, GtkTreeIter *iter)
{
uiTableModel *m = uiTableModel(mm);
if (iter != NULL)
return 0;
return uiprivTableModelNumRows(m);
}
static gboolean uiTableModel_iter_nth_child(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *parent, gint n)
{
uiTableModel *m = uiTableModel(mm);
if (parent != NULL || n < 0 || n >= uiprivTableModelNumRows(m)) {
iter->stamp = 0;
return FALSE;
}
iter->stamp = m->stamp;
iter->user_data = GINT_TO_POINTER(n);
return TRUE;
}
gboolean uiTableModel_iter_parent(GtkTreeModel *mm, GtkTreeIter *iter, GtkTreeIter *child)
{
iter->stamp = 0;
return FALSE;
}
static void uiTableModel_class_init(uiTableModelClass *class)
{
G_OBJECT_CLASS(class)->dispose = uiTableModel_dispose;
G_OBJECT_CLASS(class)->finalize = uiTableModel_finalize;
}
static void uiTableModel_gtk_tree_model_interface_init(GtkTreeModelIface *iface)
{
iface->get_flags = uiTableModel_get_flags;
iface->get_n_columns = uiTableModel_get_n_columns;
iface->get_column_type = uiTableModel_get_column_type;
iface->get_iter = uiTableModel_get_iter;
iface->get_path = uiTableModel_get_path;
iface->get_value = uiTableModel_get_value;
iface->iter_next = uiTableModel_iter_next;
iface->iter_previous = uiTableModel_iter_previous;
iface->iter_children = uiTableModel_iter_children;
iface->iter_has_child = uiTableModel_iter_has_child;
iface->iter_n_children = uiTableModel_iter_n_children;
iface->iter_nth_child = uiTableModel_iter_nth_child;
iface->iter_parent = uiTableModel_iter_parent;
// don't specify ref_node() or unref_node()
}
uiTableModel *uiNewTableModel(uiTableModelHandler *mh)
{
uiTableModel *m;
m = uiTableModel(g_object_new(uiTableModelType, NULL));
while ((m->stamp = g_random_int()) == 0) {
//iter of 0 means invalid
}
m->mh = mh;
return m;
}
void uiFreeTableModel(uiTableModel *m)
{
g_object_unref(m);
}
void uiTableModelRowInserted(uiTableModel *m, int newIndex)
{
GtkTreePath *path;
GtkTreeIter iter;
path = gtk_tree_path_new_from_indices(newIndex, -1);
iter.stamp = m->stamp;
iter.user_data = GINT_TO_POINTER(newIndex);
gtk_tree_model_row_inserted(GTK_TREE_MODEL(m), path, &iter);
gtk_tree_path_free(path);
}
void uiTableModelRowChanged(uiTableModel *m, int index)
{
GtkTreePath *path;
GtkTreeIter iter;
path = gtk_tree_path_new_from_indices(index, -1);
iter.stamp = m->stamp;
iter.user_data = GINT_TO_POINTER(index);
gtk_tree_model_row_changed(GTK_TREE_MODEL(m), path, &iter);
gtk_tree_path_free(path);
}
void uiTableModelRowDeleted(uiTableModel *m, int oldIndex)
{
GtkTreePath *path;
path = gtk_tree_path_new_from_indices(oldIndex, -1);
gtk_tree_model_row_deleted(GTK_TREE_MODEL(m), path);
gtk_tree_path_free(path);
}
uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m)
{
return m->mh;
}
+17
View File
@@ -0,0 +1,17 @@
// 9 april 2015
#include "uipriv_unix.h"
char *uiUnixStrdupText(const char *t)
{
return g_strdup(t);
}
void uiFreeText(char *t)
{
g_free(t);
}
int uiprivStricmp(const char *a, const char *b)
{
return strcasecmp(a, b);
}
+61
View File
@@ -0,0 +1,61 @@
// 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 future.c
#include <langinfo.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "../ui.h"
#include "../ui_unix.h"
#include "../common/uipriv.h"
#define uiprivGTKXMargin 12
#define uiprivGTKYMargin 12
#define uiprivGTKXPadding 12
#define uiprivGTKYPadding 6
// menu.c
extern GtkWidget *uiprivMakeMenubar(uiWindow *);
extern void uiprivFreeMenubar(GtkWidget *);
extern void uiprivUninitMenus(void);
// alloc.c
extern void uiprivInitAlloc(void);
extern void uiprivUninitAlloc(void);
// util.c
extern void uiprivSetMargined(GtkContainer *, int);
// child.c
typedef struct uiprivChild uiprivChild;
extern uiprivChild *uiprivNewChild(uiControl *child, uiControl *parent, GtkContainer *parentContainer);
extern uiprivChild *uiprivNewChildWithBox(uiControl *child, uiControl *parent, GtkContainer *parentContainer, int margined);
extern void uiprivChildRemove(uiprivChild *c);
extern void uiprivChildDestroy(uiprivChild *c);
extern GtkWidget *uiprivChildWidget(uiprivChild *c);
extern int uiprivChildFlag(uiprivChild *c);
extern void uiprivChildSetFlag(uiprivChild *c, int flag);
extern GtkWidget *uiprivChildBox(uiprivChild *c);
extern void uiprivChildSetMargined(uiprivChild *c, int margined);
// draw.c
extern uiDrawContext *uiprivNewContext(cairo_t *cr, GtkStyleContext *style);
extern void uiprivFreeContext(uiDrawContext *);
// image.c
extern cairo_surface_t *uiprivImageAppropriateSurface(uiImage *i, GtkWidget *w);
// cellrendererbutton.c
extern GtkCellRenderer *uiprivNewCellRendererButton(void);
// future.c
extern void uiprivLoadFutures(void);
extern PangoAttribute *uiprivFUTURE_pango_attr_font_features_new(const gchar *features);
extern PangoAttribute *uiprivFUTURE_pango_attr_foreground_alpha_new(guint16 alpha);
extern PangoAttribute *uiprivFUTURE_pango_attr_background_alpha_new(guint16 alpha);
extern gboolean uiprivFUTURE_gtk_widget_path_iter_set_object_name(GtkWidgetPath *path, gint pos, const char *name);
+10
View File
@@ -0,0 +1,10 @@
// 18 april 2015
#include "uipriv_unix.h"
void uiprivSetMargined(GtkContainer *c, int margined)
{
if (margined)
gtk_container_set_border_width(c, uiprivGTKXMargin);
else
gtk_container_set_border_width(c, 0);
}
+342
View File
@@ -0,0 +1,342 @@
// 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 resizeable;
int focused;
int (*onClosing)(uiWindow *, void *);
void *onClosingData;
void (*onContentSizeChanged)(uiWindow *, void *);
void *onContentSizeChangedData;
void (*onFocusChanged)(uiWindow *, void *);
void *onFocusChangedData;
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 gboolean onGetFocus(GtkWidget *win, GdkEvent *e, gpointer data)
{
uiWindow *w = uiWindow(data);
w->focused = 1;
w->onFocusChanged(w, w->onFocusChangedData);
return FALSE;
}
static gboolean onLoseFocus(GtkWidget *win, GdkEvent *e, gpointer data)
{
uiWindow *w = uiWindow(data);
w->focused = 0;
w->onFocusChanged(w, w->onFocusChangedData);
return FALSE;
}
static int defaultOnClosing(uiWindow *w, void *data)
{
return 0;
}
static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)
{
// do nothing
}
static void defaultOnFocusChanged(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)
uiprivFreeMenubar(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 uiWindowFocused(uiWindow *w)
{
return w->focused;
}
void uiWindowOnFocusChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)
{
w->onFocusChanged = f;
w->onFocusChangedData = 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;
uiprivSetMargined(w->childHolderContainer, w->margined);
}
int uiWindowResizeable(uiWindow *w)
{
return w->resizeable;
}
void uiWindowSetResizeable(uiWindow *w, int resizeable)
{
// workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/4945
// calling gtk_window_set_resizable(w->window, 0) will cause the window to resize to default size
// (default is smallest size here because we're using gtk_window_resize() when creating the window)
// to prevent this we call gtk_window_set_default_size() on the current window size so that it doesn't resize
if (resizeable == 0) {
gint width, height;
gtk_window_get_size(w->window, &width, &height);
gtk_window_set_default_size(w->window, width, height);
}
w->resizeable = resizeable;
gtk_window_set_resizable(w->window, resizeable);
}
uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)
{
uiWindow *w;
uiUnixNewControl(uiWindow, w);
w->resizeable = TRUE;
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 = uiprivMakeMenubar(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);
g_signal_connect(w->widget, "focus-in-event", G_CALLBACK(onGetFocus), w);
g_signal_connect(w->widget, "focus-out-event", G_CALLBACK(onLoseFocus), w);
uiWindowOnClosing(w, defaultOnClosing, NULL);
uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);
uiWindowOnFocusChanged(w, defaultOnFocusChanged, 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;
}