diff --git a/test/queuemaintest.cpp b/test/queuemaintest.cpp new file mode 100644 index 00000000..1314cf5f --- /dev/null +++ b/test/queuemaintest.cpp @@ -0,0 +1,89 @@ +// 6 december 2015 +#include +#include +#include +#include +#include +#include +#include +#include "../ui.h" +using namespace std; + +uiMultilineEntry *e; +condition_variable cv; +mutex m; +unique_lock ourlock(m); +thread *timeThread; + +void sayTime(void *data) +{ + char *s = (char *) data; + + uiMultilineEntryAppend(e, s); + delete s; +} + +void threadproc(void) +{ + while (cv.wait_for(ourlock, chrono::seconds(1)) == cv_status::timeout) { + time_t t; + char *base; + char *s; + + t = time(NULL); + base = ctime(&t); + s = new char[strlen(base) + 1]; + strcpy(s, base); + uiQueueMain(sayTime, s); + } +} + +int onClosing(uiWindow *w, void *data) +{ + cv.notify_all(); + // C++ throws a hissy fit if you don't do this + // we might as well, to ensure no uiQueueMain() gets in after uiQuit() + timeThread->join(); + uiQuit(); + return 1; +} + +void saySomething(uiButton *b, void *data) +{ + uiMultilineEntryAppend(e, "Saying something\n"); +} + +int main(void) +{ + uiInitOptions o; + uiWindow *w; + uiBox *b; + uiButton *btn; + + memset(&o, 0, sizeof (uiInitOptions)); + if (uiInit(&o) != NULL) + abort(); + + w = uiNewWindow("Hello", 320, 240, 0); + uiWindowSetMargined(w, 1); + + b = uiNewVerticalBox(); + uiBoxSetPadded(b, 1); + uiWindowSetChild(w, uiControl(b)); + + e = uiNewMultilineEntry(); + uiMultilineEntrySetReadOnly(e, 1); + + btn = uiNewButton("Say Something"); + uiButtonOnClicked(btn, saySomething, NULL); + uiBoxAppend(b, uiControl(btn), 0); + + uiBoxAppend(b, uiControl(e), 1); + + timeThread = new thread(threadproc); + + uiWindowOnClosing(w, onClosing, NULL); + uiControlShow(uiControl(w)); + uiMain(); + return 0; +} diff --git a/ui.h b/ui.h index 31db9c32..8fbef32d 100644 --- a/ui.h +++ b/ui.h @@ -229,6 +229,21 @@ _UI_EXTERN uiDateTimePicker *uiNewDateTimePicker(void); _UI_EXTERN uiDateTimePicker *uiNewDatePicker(void); _UI_EXTERN uiDateTimePicker *uiNewTimePicker(void); +// TODO merge with uiEntry? some things can't be shared (for instance, the future Invalid() +// TODO how are line endings converted? +// TODO provide a facility for allowing horizontal scrolling +// TODO provide a facility for entering tab stops? +typedef struct uiMultilineEntry uiMultilineEntry; +_UI_EXTERN uintmax_t uiMultilineEntryType(void); +#define uiMultilineEntry(this) ((uiMultilineEntry *) uiIsA((this), uiMultilineEntryType(), 1)) +_UI_EXTERN char *uiMultilineEntryText(uiMultilineEntry *e); +_UI_EXTERN void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text); +_UI_EXTERN void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text); +_UI_EXTERN void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data); +_UI_EXTERN int uiMultilineEntryReadOnly(uiMultilineEntry *e); +_UI_EXTERN void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly); +_UI_EXTERN uiMultilineEntry *uiNewMultilineEntry(void); + typedef struct uiMenu uiMenu; typedef struct uiMenuItem uiMenuItem; _UI_EXTERN uintmax_t uiMenuType(void); diff --git a/unix/GNUmakeinc.mk b/unix/GNUmakeinc.mk index 972c471e..f8eba4ee 100644 --- a/unix/GNUmakeinc.mk +++ b/unix/GNUmakeinc.mk @@ -16,6 +16,7 @@ CFILES += \ unix/label.c \ unix/main.c \ unix/menu.c \ + unix/multilineentry.c \ unix/progressbar.c \ unix/radiobuttons.c \ unix/separator.c \ diff --git a/unix/multilineentry.c b/unix/multilineentry.c new file mode 100644 index 00000000..9b18ee39 --- /dev/null +++ b/unix/multilineentry.c @@ -0,0 +1,117 @@ +// 6 december 2015 +#include "uipriv_unix.h" + +// TODO: ensure this can only be used to enter plain text + +struct uiMultilineEntry { + uiUnixControl c; + GtkWidget *widget; + GtkContainer *scontainer; + GtkScrolledWindow *sw; + GtkWidget *textviewWidget; + GtkTextView *textview; + GtkTextBuffer *textbuf; + void (*onChanged)(uiMultilineEntry *, void *); + void *onChangedData; + gulong onChangedSignal; +}; + +uiUnixDefineControl( + uiMultilineEntry, // type name + uiMultilineEntryType // type function +) + +static void onChanged(GtkTextBuffer *textbuf, gpointer data) +{ + uiMultilineEntry *e = uiMultilineEntry(data); + + (*(e->onChanged))(e, e->onChangedData); +} + +static void defaultOnChanged(uiMultilineEntry *e, void *data) +{ + // do nothing +} + +char *uiMultilineEntryText(uiMultilineEntry *e) +{ + GtkTextIter start, end; + char *tret, *out; + + gtk_text_buffer_get_start_iter(e->textbuf, &start); + gtk_text_buffer_get_end_iter(e->textbuf, &end); + tret = gtk_text_buffer_get_text(e->textbuf, &start, &end, TRUE); + // theoretically we could just return tret because uiUnixStrdupText() is just g_strdup(), but if that ever changes we can't, so let's do it this way to be safe + out = uiUnixStrdupText(tret); + g_free(tret); + return out; +} + +void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text) +{ + // TODO does this send a changed signal? + gtk_text_buffer_set_text(e->textbuf, text, -1); +} + +// TODO scroll to end? +void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text) +{ + GtkTextIter end; + + gtk_text_buffer_get_end_iter(e->textbuf, &end); + // TODO does this send a changed signal? + gtk_text_buffer_insert(e->textbuf, &end, text, -1); +} + +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); +} + +uiMultilineEntry *uiNewMultilineEntry(void) +{ + uiMultilineEntry *e; + + e = (uiMultilineEntry *) uiNewControl(uiMultilineEntryType()); + + 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, + GTK_POLICY_NEVER, + 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, GTK_WRAP_WORD); + + 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); + + uiUnixFinishNewControl(e, uiMultilineEntry); + + return e; +}