#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "text.h"
#include "container.h"
#include <cx/printf.h>
#include <gdk/gdkkeysyms.h>
#include "../common/types.h"
static void selection_handler(
GtkTextBuffer *buf,
GtkTextIter *location,
GtkTextMark *mark,
UiTextArea *textview)
{
const char *mname = gtk_text_mark_get_name(mark);
if(mname) {
GtkTextIter begin;
GtkTextIter end;
int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end);
if(sel != textview->last_selection_state) {
if(sel) {
ui_set_group(textview->ctx,
UI_GROUP_SELECTION);
}
else {
ui_unset_group(textview->ctx,
UI_GROUP_SELECTION);
}
}
textview->last_selection_state = sel;
}
}
UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs args) {
UiObject* current = uic_current_obj(obj);
UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname,
UI_VAR_TEXT);
GtkWidget *text_area = gtk_text_view_new();
ui_set_name_and_style(text_area, args.name, args.style_class);
ui_set_widget_groups(obj->ctx, text_area, args.groups);
gtk_text_view_set_wrap_mode(
GTK_TEXT_VIEW(text_area),
GTK_WRAP_WORD_CHAR);
g_signal_connect(
text_area,
"realize",
G_CALLBACK(ui_textarea_realize_event),
NULL);
UiTextArea *uitext = malloc(
sizeof(UiTextArea));
uitext->obj = obj;
uitext->ctx = obj->ctx;
uitext->var = var;
uitext->last_selection_state =
0;
uitext->onchange = args.onchange;
uitext->onchangedata = args.onchangedata;
g_signal_connect(
text_area,
"destroy",
G_CALLBACK(ui_textarea_destroy),
uitext);
GtkWidget *scroll_area =
SCROLLEDWINDOW_NEW();
gtk_scrolled_window_set_policy(
GTK_SCROLLED_WINDOW(scroll_area),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
SCROLLEDWINDOW_SET_CHILD(scroll_area, text_area);
gtk_text_view_set_left_margin(
GTK_TEXT_VIEW(text_area),
2);
gtk_text_view_set_right_margin(
GTK_TEXT_VIEW(text_area),
2);
UI_APPLY_LAYOUT1(current, args);
current->container->add(current->container, scroll_area,
TRUE);
if(var) {
UiText *value = var->value;
GtkTextBuffer *buf = gtk_text_view_get_buffer(
GTK_TEXT_VIEW(text_area));
if(value->value.ptr) {
gtk_text_buffer_set_text(buf, value->value.ptr, -
1);
value->value.free(value->value.ptr);
}
value->get = ui_textarea_get;
value->set = ui_textarea_set;
value->getsubstr = ui_textarea_getsubstr;
value->insert = ui_textarea_insert;
value->setposition = ui_textarea_setposition;
value->position = ui_textarea_position;
value->selection = ui_textarea_selection;
value->length = ui_textarea_length;
value->remove = ui_textarea_remove;
value->value.ptr =
NULL;
value->value.free =
NULL;
value->obj = buf;
if(!value->undomgr) {
value->undomgr = ui_create_undomgr();
}
g_signal_connect(
buf,
"changed",
G_CALLBACK(ui_textbuf_changed),
uitext);
g_signal_connect(
buf,
"insert-text",
G_CALLBACK(ui_textbuf_insert),
var);
g_signal_connect(
buf,
"delete-range",
G_CALLBACK(ui_textbuf_delete),
var);
g_signal_connect(
buf,
"mark-set",
G_CALLBACK(selection_handler),
uitext);
}
return scroll_area;
}
void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
if(textarea->var) {
ui_destroy_boundvar(textarea->ctx, textarea->var);
}
free(textarea);
}
UIWIDGET ui_textarea_gettextwidget(
UIWIDGET textarea) {
return SCROLLEDWINDOW_GET_CHILD(textarea);
}
char* ui_textarea_get(UiText *text) {
if(text->value.ptr) {
text->value.free(text->value.ptr);
}
GtkTextBuffer *buf = text->obj;
GtkTextIter start;
GtkTextIter end;
gtk_text_buffer_get_bounds(buf, &start, &end);
char *str = gtk_text_buffer_get_text(buf, &start, &end,
FALSE);
text->value.ptr = g_strdup(str);
text->value.free = (ui_freefunc)g_free;
return str;
}
void ui_textarea_set(UiText *text,
const char *str) {
gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -
1);
if(text->value.ptr) {
text->value.free(text->value.ptr);
}
text->value.ptr =
NULL;
text->value.free =
NULL;
}
char* ui_textarea_getsubstr(UiText *text,
int begin,
int end) {
if(text->value.ptr) {
text->value.free(text->value.ptr);
}
GtkTextBuffer *buf = text->obj;
GtkTextIter ib;
GtkTextIter ie;
gtk_text_buffer_get_iter_at_offset(text->obj, &ib, begin);
gtk_text_buffer_get_iter_at_offset(text->obj, &ie, end);
char *str = gtk_text_buffer_get_text(buf, &ib, &ie,
FALSE);
text->value.ptr = g_strdup(str);
text->value.free = (ui_freefunc)g_free;
return str;
}
void ui_textarea_insert(UiText *text,
int pos,
char *str) {
GtkTextIter offset;
gtk_text_buffer_get_iter_at_offset(text->obj, &offset, pos);
gtk_text_buffer_insert(text->obj, &offset, str, -
1);
if(text->value.ptr) {
text->value.free(text->value.ptr);
}
text->value.ptr =
NULL;
text->value.free =
NULL;
}
void ui_textarea_setposition(UiText *text,
int pos) {
GtkTextIter iter;
gtk_text_buffer_get_iter_at_offset(text->obj, &iter, pos);
gtk_text_buffer_place_cursor(text->obj, &iter);
}
int ui_textarea_position(UiText *text) {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end);
text->pos = gtk_text_iter_get_offset(&begin);
return text->pos;
}
void ui_textarea_selection(UiText *text,
int *begin,
int *end) {
GtkTextIter b;
GtkTextIter e;
gtk_text_buffer_get_selection_bounds(text->obj, &b, &e);
*begin = gtk_text_iter_get_offset(&b);
*end = gtk_text_iter_get_offset(&e);
}
int ui_textarea_length(UiText *text) {
GtkTextBuffer *buf = text->obj;
GtkTextIter start;
GtkTextIter end;
gtk_text_buffer_get_bounds(buf, &start, &end);
return gtk_text_iter_get_offset(&end);
}
void ui_textarea_remove(UiText *text,
int begin,
int end) {
GtkTextBuffer *buf = text->obj;
GtkTextIter ib;
GtkTextIter ie;
gtk_text_buffer_get_iter_at_offset(buf, &ib, begin);
gtk_text_buffer_get_iter_at_offset(buf, &ie, end);
gtk_text_buffer_delete(buf, &ib, &ie);
}
void ui_textarea_realize_event(GtkWidget *widget, gpointer data) {
gtk_widget_grab_focus(widget);
}
void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
UiText *value = textarea->var->value;
UiEvent e;
e.obj = textarea->obj;
e.window = e.obj->window;
e.document = textarea->ctx->document;
e.eventdata = value;
e.intval =
0;
if(textarea->onchange) {
textarea->onchange(&e, textarea->onchangedata);
}
if(value->observers) {
ui_notify_evt(value->observers, &e);
}
}
void ui_textbuf_insert(
GtkTextBuffer *textbuffer,
GtkTextIter *location,
char *text,
int length,
void *data)
{
UiVar *var = data;
UiText *value = var->value;
if(!value->undomgr) {
value->undomgr = ui_create_undomgr();
}
UiUndoMgr *mgr = value->undomgr;
if(!mgr->event) {
return;
}
if(mgr->cur) {
UiTextBufOp *elm = mgr->cur->next;
if(elm) {
mgr->cur->next =
NULL;
mgr->end = mgr->cur;
while(elm) {
elm->prev =
NULL;
UiTextBufOp *next = elm->next;
ui_free_textbuf_op(elm);
elm = next;
}
}
UiTextBufOp *last_op = mgr->cur;
if(
last_op->type ==
UI_TEXTBUF_INSERT &&
ui_check_insertstr(last_op->text, last_op->len, text, length) ==
0)
{
int ln = last_op->len;
char *newtext = malloc(ln + length +
1);
memcpy(newtext, last_op->text, ln);
memcpy(newtext+ln, text, length);
newtext[ln+length] =
'\0';
last_op->text = newtext;
last_op->len = ln + length;
last_op->end += length;
return;
}
}
char *dpstr = malloc(length +
1);
memcpy(dpstr, text, length);
dpstr[length] =
0;
UiTextBufOp *op = malloc(
sizeof(UiTextBufOp));
op->prev =
NULL;
op->next =
NULL;
op->type =
UI_TEXTBUF_INSERT;
op->start = gtk_text_iter_get_offset(location);
op->end = op->start+length;
op->len = length;
op->text = dpstr;
cx_linked_list_add(
(
void**)&mgr->begin,
(
void**)&mgr->end,
offsetof(UiTextBufOp, prev),
offsetof(UiTextBufOp, next),
op);
mgr->cur = op;
}
void ui_textbuf_delete(
GtkTextBuffer *textbuffer,
GtkTextIter *start,
GtkTextIter *end,
void *data)
{
UiVar *var = data;
UiText *value = var->value;
if(!value->undomgr) {
value->undomgr = ui_create_undomgr();
}
UiUndoMgr *mgr = value->undomgr;
if(!mgr->event) {
return;
}
if(mgr->cur) {
UiTextBufOp *elm = mgr->cur->next;
if(elm) {
mgr->cur->next =
NULL;
mgr->end = mgr->cur;
while(elm) {
elm->prev =
NULL;
UiTextBufOp *next = elm->next;
ui_free_textbuf_op(elm);
elm = next;
}
}
}
char *text = gtk_text_buffer_get_text(value->obj, start, end,
FALSE);
UiTextBufOp *op = malloc(
sizeof(UiTextBufOp));
op->prev =
NULL;
op->next =
NULL;
op->type =
UI_TEXTBUF_DELETE;
op->start = gtk_text_iter_get_offset(start);
op->end = gtk_text_iter_get_offset(end);
op->len = op->end - op->start;
char *dpstr = malloc(op->len +
1);
memcpy(dpstr, text, op->len);
dpstr[op->len] =
0;
op->text = dpstr;
cx_linked_list_add(
(
void**)&mgr->begin,
(
void**)&mgr->end,
offsetof(UiTextBufOp, prev),
offsetof(UiTextBufOp, next),
op);
mgr->cur = op;
}
UiUndoMgr* ui_create_undomgr() {
UiUndoMgr *mgr = malloc(
sizeof(UiUndoMgr));
mgr->begin =
NULL;
mgr->end =
NULL;
mgr->cur =
NULL;
mgr->length =
0;
mgr->event =
1;
return mgr;
}
void ui_destroy_undomgr(UiUndoMgr *mgr) {
UiTextBufOp *op = mgr->begin;
while(op) {
UiTextBufOp *nextOp = op->next;
if(op->text) {
free(op->text);
}
free(op);
op = nextOp;
}
free(mgr);
}
void ui_free_textbuf_op(UiTextBufOp *op) {
if(op->text) {
free(op->text);
}
free(op);
}
int ui_check_insertstr(
char *oldstr,
int oldlen,
char *newstr,
int newlen) {
int has_space =
0;
for(
int i=
0;i<oldlen;i++) {
if(oldstr[i] <
33) {
has_space =
1;
break;
}
}
for(
int i=
0;i<newlen;i++) {
if(has_space && newstr[i] >
32) {
return 1;
}
}
return 0;
}
void ui_text_undo(UiText *value) {
UiUndoMgr *mgr = value->undomgr;
if(mgr->cur) {
UiTextBufOp *op = mgr->cur;
mgr->event =
0;
switch(op->type) {
case UI_TEXTBUF_INSERT: {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
gtk_text_buffer_delete(value->obj, &begin, &end);
break;
}
case UI_TEXTBUF_DELETE: {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
break;
}
}
mgr->event =
1;
mgr->cur = mgr->cur->prev;
}
}
void ui_text_redo(UiText *value) {
UiUndoMgr *mgr = value->undomgr;
UiTextBufOp *elm =
NULL;
if(mgr->cur) {
if(mgr->cur->next) {
elm = mgr->cur->next;
}
}
else if(mgr->begin) {
elm = mgr->begin;
}
if(elm) {
UiTextBufOp *op = elm;
mgr->event =
0;
switch(op->type) {
case UI_TEXTBUF_INSERT: {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
break;
}
case UI_TEXTBUF_DELETE: {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
gtk_text_buffer_delete(value->obj, &begin, &end);
break;
}
}
mgr->event =
1;
mgr->cur = elm;
}
}
static UIWIDGET create_textfield(UiObject *obj, UiBool frameless, UiBool password, UiTextFieldArgs args) {
GtkWidget *textfield = gtk_entry_new();
ui_set_name_and_style(textfield, args.name, args.style_class);
ui_set_widget_groups(obj->ctx, textfield, args.groups);
UiObject* current = uic_current_obj(obj);
UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname,
UI_VAR_STRING);
UiTextField *uitext = malloc(
sizeof(UiTextField));
uitext->obj = obj;
uitext->var = var;
uitext->onchange = args.onchange;
uitext->onchangedata = args.onchangedata;
uitext->onactivate = args.onactivate;
uitext->onactivatedata = args.onactivatedata;
g_signal_connect(
textfield,
"destroy",
G_CALLBACK(ui_textfield_destroy),
uitext);
if(args.width >
0) {
#if GTK_MAJOR_VERSION <=
3
gtk_entry_set_width_chars(
GTK_ENTRY(textfield), args.width);
#endif
}
if(frameless) {
gtk_entry_set_has_frame(
GTK_ENTRY(textfield),
FALSE);
}
if(password) {
gtk_entry_set_visibility(
GTK_ENTRY(textfield),
FALSE);
}
UI_APPLY_LAYOUT1(current, args);
current->container->add(current->container, textfield,
FALSE);
if(var) {
UiString *value = var->value;
if(value->value.ptr) {
ENTRY_SET_TEXT(textfield, value->value.ptr);
value->value.free(value->value.ptr);
value->value.ptr =
NULL;
value->value.free =
NULL;
}
value->get = ui_textfield_get;
value->set = ui_textfield_set;
value->value.ptr =
NULL;
value->value.free =
NULL;
value->obj =
GTK_ENTRY(textfield);
}
if(args.onchange || var) {
g_signal_connect(
textfield,
"changed",
G_CALLBACK(ui_textfield_changed),
uitext);
}
if(args.onactivate) {
g_signal_connect(
textfield,
"activate",
G_CALLBACK(ui_textfield_activate),
uitext);
}
return textfield;
}
UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs args) {
return create_textfield(obj,
FALSE,
FALSE, args);
}
UIWIDGET ui_frameless_textfield_create(UiObject* obj, UiTextFieldArgs args) {
return create_textfield(obj,
TRUE,
FALSE, args);
}
UIWIDGET ui_passwordfield_create(UiObject* obj, UiTextFieldArgs args) {
return create_textfield(obj,
FALSE,
TRUE, args);
}
void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
free(textfield);
}
void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
UiString *value = textfield->var->value;
UiEvent e;
e.obj = textfield->obj;
e.window = e.obj->window;
e.document = textfield->obj->ctx->document;
e.eventdata = value;
e.intval =
0;
if(textfield->onchange) {
textfield->onchange(&e, textfield->onchangedata);
}
if(textfield->var) {
ui_notify_evt(value->observers, &e);
}
}
void ui_textfield_activate(GtkEntry* self, UiTextField *textfield) {
if(textfield->onactivate) {
UiEvent e;
e.obj = textfield->obj;
e.window = e.obj->window;
e.document = textfield->obj->ctx->document;
e.eventdata =
NULL;
e.intval =
0;
textfield->onactivate(&e, textfield->onactivatedata);
}
}
char* ui_textfield_get(UiString *str) {
if(str->value.ptr) {
str->value.free(str->value.ptr);
}
str->value.ptr = g_strdup(
ENTRY_GET_TEXT(str->obj));
str->value.free = (ui_freefunc)g_free;
return str->value.ptr;
}
void ui_textfield_set(UiString *str,
const char *value) {
ENTRY_SET_TEXT(str->obj, value);
if(str->value.ptr) {
str->value.free(str->value.ptr);
str->value.ptr =
NULL;
str->value.free =
NULL;
}
}
static UiPathElm* default_pathelm_func(
const char* full_path,
size_t len,
size_t* ret_nelm,
void* data) {
cxstring *pathelms;
size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len),
CX_STR(
"/"),
4096, &pathelms);
if (nelm ==
0) {
*ret_nelm =
0;
return NULL;
}
UiPathElm* elms = (UiPathElm*)calloc(nelm,
sizeof(UiPathElm));
size_t n = nelm;
int j =
0;
for (
int i =
0; i < nelm; i++) {
cxstring c = pathelms[i];
if (c.length ==
0) {
if (i ==
0) {
c.length =
1;
}
else {
n--;
continue;
}
}
cxmutstr m = cx_strdup(c);
elms[j].name = m.ptr;
elms[j].name_len = m.length;
size_t elm_path_len = c.ptr + c.length - full_path;
cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len));
elms[j].path = elm_path.ptr;
elms[j].path_len = elm_path.length;
j++;
}
*ret_nelm = n;
return elms;
}
static void ui_pathelm_destroy(UiPathElm *elms,
size_t nelm) {
for(
int i=
0;i<nelm;i++) {
free(elms[i].name);
free(elms[i].path);
}
free(elms);
}
static void ui_path_textfield_destroy(GtkWidget *object, UiPathTextField *pathtf) {
g_object_unref(pathtf->entry);
free(pathtf);
}
void ui_path_button_clicked(GtkWidget *widget, UiEventDataExt *event) {
UiPathTextField *pathtf = event->customdata1;
for(
int i=
0;i<event->value1;i++) {
if(i <= event->value0) {
WIDGET_REMOVE_CSS_CLASS(pathtf->current_path_buttons[i],
"pathbar-button-inactive");
}
else {
WIDGET_ADD_CSS_CLASS(pathtf->current_path_buttons[i],
"pathbar-button-inactive");
}
}
UiPathElm *elm = event->customdata0;
cxmutstr path = cx_strdup(cx_strn(elm->path, elm->path_len));
UiEvent evt;
evt.obj = event->obj;
evt.window = evt.obj->window;
evt.document = evt.obj->ctx->document;
evt.eventdata = elm->path;
evt.intval = event->value0;
event->callback(&evt, event->userdata);
free(path.ptr);
}
int ui_pathtextfield_update(UiPathTextField* pathtf,
const char *full_path) {
size_t full_path_len = strlen(full_path);
if(full_path_len ==
0) {
return 1;
}
size_t nelm =
0;
UiPathElm* path_elm = pathtf->getpathelm(full_path, full_path_len, &nelm, pathtf->getpathelmdata);
if (!path_elm) {
return 1;
}
free(pathtf->current_path);
pathtf->current_path = strdup(full_path);
ui_pathelm_destroy(pathtf->current_pathelms, pathtf->current_nelm);
free(pathtf->current_path_buttons);
pathtf->current_path_buttons = calloc(nelm,
sizeof(GtkWidget*));
pathtf->current_pathelms = path_elm;
pathtf->current_nelm = nelm;
return ui_pathtextfield_update_widget(pathtf);
}
static GtkWidget* ui_path_elm_button(UiPathTextField *pathtf, UiPathElm *elm,
int i) {
cxmutstr name = cx_strdup(cx_strn(elm->name, elm->name_len));
GtkWidget *button = gtk_button_new_with_label(name.ptr);
pathtf->current_path_buttons[i] = button;
free(name.ptr);
if(pathtf->onactivate) {
UiEventDataExt *eventdata = malloc(
sizeof(UiEventDataExt));
memset(eventdata,
0,
sizeof(UiEventDataExt));
eventdata->callback = pathtf->onactivate;
eventdata->userdata = pathtf->onactivatedata;
eventdata->obj = pathtf->obj;
eventdata->customdata0 = elm;
eventdata->customdata1 = pathtf;
eventdata->value0 = i;
eventdata->value1 = pathtf->current_nelm;
g_signal_connect(
button,
"clicked",
G_CALLBACK(ui_path_button_clicked),
eventdata);
g_signal_connect(
button,
"destroy",
G_CALLBACK(ui_destroy_userdata),
eventdata);
}
return button;
}
static void ui_path_textfield_activate(GtkWidget *entry, UiPathTextField *pathtf) {
const gchar *text =
ENTRY_GET_TEXT(pathtf->entry);
if(strlen(text) ==
0) {
return;
}
UiObject *obj = pathtf->obj;
if(ui_pathtextfield_update(pathtf, text)) {
return;
}
if(pathtf->onactivate) {
UiEvent evt;
evt.obj = obj;
evt.window = obj->window;
evt.document = obj->ctx->document;
evt.eventdata = (
char*)text;
evt.intval = -
1;
pathtf->onactivate(&evt, pathtf->onactivatedata);
}
}
#if GTK_MAJOR_VERSION >=
4
static void pathbar_show_hbox(GtkWidget *widget, UiPathTextField *pathtf) {
if(pathtf->current_path) {
gtk_stack_set_visible_child(
GTK_STACK(pathtf->stack), pathtf->hbox);
ENTRY_SET_TEXT(pathtf->entry, pathtf->current_path);
}
}
static gboolean ui_path_textfield_key_controller(
GtkEventControllerKey* self,
guint keyval,
guint keycode,
GdkModifierType state,
UiPathTextField *pathtf)
{
if(keyval == GDK_KEY_Escape) {
pathbar_show_hbox(
NULL, pathtf);
}
return FALSE;
}
UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
UiObject* current = uic_current_obj(obj);
UiPathTextField *pathtf = malloc(
sizeof(UiPathTextField));
memset(pathtf,
0,
sizeof(UiPathTextField));
pathtf->obj = obj;
pathtf->getpathelm = args.getpathelm;
pathtf->getpathelmdata = args.getpathelmdata;
pathtf->onactivate = args.onactivate;
pathtf->onactivatedata = args.onactivatedata;
pathtf->ondragcomplete = args.ondragcomplete;
pathtf->ondragcompletedata = args.ondragcompletedata;
pathtf->ondragstart = args.ondragstart;
pathtf->ondragstartdata = args.ondragstartdata;
pathtf->ondrop = args.ondrop;
pathtf->ondropdata = args.ondropsdata;
if(!pathtf->getpathelm) {
pathtf->getpathelm = default_pathelm_func;
pathtf->getpathelmdata =
NULL;
}
pathtf->stack = gtk_stack_new();
gtk_widget_set_name(pathtf->stack,
"path-textfield-box");
UI_APPLY_LAYOUT1(current, args);
current->container->add(current->container, pathtf->stack,
FALSE);
pathtf->entry_box = gtk_box_new(
GTK_ORIENTATION_HORIZONTAL,
0);
pathtf->entry = gtk_entry_new();
gtk_box_append(
GTK_BOX(pathtf->entry_box), pathtf->entry);
gtk_widget_set_hexpand(pathtf->entry,
TRUE);
GtkWidget *cancel_button = gtk_button_new_from_icon_name(
"window-close-symbolic");
gtk_widget_add_css_class(cancel_button,
"flat");
gtk_widget_add_css_class(cancel_button,
"pathbar-extra-button");
gtk_box_append(
GTK_BOX(pathtf->entry_box), cancel_button);
g_signal_connect(
cancel_button,
"clicked",
G_CALLBACK(pathbar_show_hbox),
pathtf);
gtk_stack_add_child(
GTK_STACK(pathtf->stack), pathtf->entry_box);
g_object_ref(pathtf->entry);
g_signal_connect(
pathtf->entry,
"activate",
G_CALLBACK(ui_path_textfield_activate),
pathtf);
GtkEventController *entry_cancel = gtk_event_controller_key_new();
gtk_widget_add_controller(pathtf->entry, entry_cancel);
g_signal_connect(entry_cancel,
"key-pressed",
G_CALLBACK(ui_path_textfield_key_controller), pathtf);
gtk_stack_set_visible_child(
GTK_STACK(pathtf->stack), pathtf->entry_box);
UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname,
UI_VAR_STRING);
if (var) {
UiString* value = (UiString*)var->value;
value->obj = pathtf;
value->get = ui_path_textfield_get;
value->set = ui_path_textfield_set;
if(value->value.ptr) {
char *str = strdup(value->value.ptr);
ui_string_set(value, str);
free(str);
}
}
return pathtf->stack;
}
static void pathbar_pressed(
GtkGestureClick* self,
gint n_press,
gdouble x,
gdouble y,
UiPathTextField *pathtf)
{
gtk_stack_set_visible_child(
GTK_STACK(pathtf->stack), pathtf->entry_box);
gtk_widget_grab_focus(pathtf->entry);
}
int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
if(pathtf->hbox) {
gtk_stack_remove(
GTK_STACK(pathtf->stack), pathtf->hbox);
}
pathtf->hbox = gtk_box_new(
GTK_ORIENTATION_HORIZONTAL,
0);
gtk_box_set_homogeneous(
GTK_BOX(pathtf->hbox),
FALSE);
gtk_stack_add_child(
GTK_STACK(pathtf->stack), pathtf->hbox);
gtk_widget_set_name(pathtf->hbox,
"pathbar");
for (
int i=
0;i<pathtf->current_nelm;i++) {
UiPathElm *elm = &pathtf->current_pathelms[i];
GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
gtk_widget_add_css_class(button,
"flat");
gtk_box_append(
GTK_BOX(pathtf->hbox), button);
if(i+
1 < pathtf->current_nelm && cx_strcmp(cx_strn(elm->name, elm->name_len),
CX_STR(
"/"))) {
GtkWidget *path_separator = gtk_label_new(
"/");
gtk_widget_add_css_class(path_separator,
"pathbar-button-inactive");
gtk_box_append(
GTK_BOX(pathtf->hbox), path_separator);
}
}
gtk_stack_set_visible_child(
GTK_STACK(pathtf->stack), pathtf->hbox);
GtkWidget *event_area = gtk_box_new(
GTK_ORIENTATION_HORIZONTAL,
0);
GtkGesture *handler = gtk_gesture_click_new();
gtk_widget_add_controller(event_area,
GTK_EVENT_CONTROLLER(handler));
g_signal_connect(
handler,
"pressed",
G_CALLBACK(pathbar_pressed),
pathtf);
gtk_widget_set_hexpand(event_area,
TRUE);
gtk_widget_set_vexpand(event_area,
TRUE);
gtk_box_append(
GTK_BOX(pathtf->hbox), event_area);
return 0;
}
#else
static gboolean path_textfield_btn_pressed(GtkWidget *widget, GdkEventButton *event, UiPathTextField *pathtf) {
gtk_box_pack_start(
GTK_BOX(pathtf->hbox), pathtf->entry,
TRUE,
TRUE,
0);
gtk_container_remove(
GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
pathtf->buttonbox =
NULL;
gtk_widget_show(pathtf->entry);
gtk_widget_grab_focus(pathtf->entry);
return TRUE;
}
static gboolean ui_path_textfield_key_press(GtkWidget *self, GdkEventKey *event, UiPathTextField *pathtf) {
if (event->keyval == GDK_KEY_Escape) {
gtk_entry_set_text(
GTK_ENTRY(self), pathtf->current_path);
const gchar *text = gtk_entry_get_text(
GTK_ENTRY(self));
ui_pathtextfield_update(pathtf, text);
return TRUE;
}
return FALSE;
}
static GtkWidget* create_path_button_box() {
GtkWidget *bb = gtk_button_box_new(
GTK_ORIENTATION_HORIZONTAL);
gtk_button_box_set_layout(
GTK_BUTTON_BOX(bb),
GTK_BUTTONBOX_EXPAND);
gtk_box_set_homogeneous(
GTK_BOX(bb),
FALSE);
gtk_box_set_spacing(
GTK_BOX(bb),
0);
return bb;
}
UIWIDGET ui_path_textfield_create(UiObject* obj, UiPathTextFieldArgs args) {
UiObject* current = uic_current_obj(obj);
UiPathTextField *pathtf = malloc(
sizeof(UiPathTextField));
memset(pathtf,
0,
sizeof(UiPathTextField));
pathtf->obj = obj;
pathtf->getpathelm = args.getpathelm;
pathtf->getpathelmdata = args.getpathelmdata;
pathtf->onactivate = args.onactivate;
pathtf->onactivatedata = args.onactivatedata;
pathtf->ondragcomplete = args.ondragcomplete;
pathtf->ondragcompletedata = args.ondragcompletedata;
pathtf->ondragstart = args.ondragstart;
pathtf->ondragstartdata = args.ondragstartdata;
pathtf->ondrop = args.ondrop;
pathtf->ondropdata = args.ondropsdata;
if(!pathtf->getpathelm) {
pathtf->getpathelm = default_pathelm_func;
pathtf->getpathelmdata =
NULL;
}
GtkWidget *eventbox = gtk_event_box_new();
g_signal_connect(
eventbox,
"button-press-event",
G_CALLBACK(path_textfield_btn_pressed),
pathtf);
g_signal_connect(
eventbox,
"destroy",
G_CALLBACK(ui_path_textfield_destroy),
pathtf);
UI_APPLY_LAYOUT1(current, args);
current->container->add(current->container, eventbox,
FALSE);
GtkWidget *hbox = ui_gtk_hbox_new(
0);
pathtf->hbox = hbox;
gtk_container_add(
GTK_CONTAINER(eventbox), hbox);
gtk_widget_set_name(hbox,
"path-textfield-box");
pathtf->entry = gtk_entry_new();
g_object_ref(
G_OBJECT(pathtf->entry));
gtk_box_pack_start(
GTK_BOX(hbox), pathtf->entry,
TRUE,
TRUE,
0);
g_signal_connect(
pathtf->entry,
"activate",
G_CALLBACK(ui_path_textfield_activate),
pathtf);
g_signal_connect(
pathtf->entry,
"key-press-event",
G_CALLBACK(ui_path_textfield_key_press),
pathtf);
UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname,
UI_VAR_STRING);
if (var) {
UiString* value = (UiString*)var->value;
value->obj = pathtf;
value->get = ui_path_textfield_get;
value->set = ui_path_textfield_set;
if(value->value.ptr) {
char *str = strdup(value->value.ptr);
ui_string_set(value, str);
free(str);
}
}
return hbox;
}
int ui_pathtextfield_update_widget(UiPathTextField* pathtf) {
GtkWidget *buttonbox = create_path_button_box();
if(pathtf->buttonbox) {
gtk_container_remove(
GTK_CONTAINER(pathtf->hbox), pathtf->buttonbox);
}
else {
gtk_container_remove(
GTK_CONTAINER(pathtf->hbox), pathtf->entry);
}
gtk_box_pack_start(
GTK_BOX(pathtf->hbox), buttonbox,
FALSE,
FALSE,
0);
pathtf->buttonbox = buttonbox;
for (
int i=
0;i<pathtf->current_nelm;i++) {
UiPathElm *elm = &pathtf->current_pathelms[i];
GtkWidget *button = ui_path_elm_button(pathtf, elm, i);
gtk_box_pack_start(
GTK_BOX(buttonbox), button,
FALSE,
FALSE,
0);
}
gtk_widget_show_all(buttonbox);
return 0;
}
#endif
char* ui_path_textfield_get(UiString *str) {
if(str->value.ptr) {
str->value.free(str->value.ptr);
}
UiPathTextField *tf = str->obj;
str->value.ptr = g_strdup(
ENTRY_GET_TEXT(tf->entry));
str->value.free = (ui_freefunc)g_free;
return str->value.ptr;
}
void ui_path_textfield_set(UiString *str,
const char *value) {
UiPathTextField *tf = str->obj;
ENTRY_SET_TEXT(tf->entry, value);
ui_pathtextfield_update(tf, value);
if(str->value.ptr) {
str->value.free(str->value.ptr);
str->value.ptr =
NULL;
str->value.free =
NULL;
}
}