summaryrefslogtreecommitdiff
path: root/src/display.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/display.c')
-rw-r--r--src/display.c655
1 files changed, 655 insertions, 0 deletions
diff --git a/src/display.c b/src/display.c
new file mode 100644
index 0000000..72dfb2b
--- /dev/null
+++ b/src/display.c
@@ -0,0 +1,655 @@
+/*
+ * display.c
+ *
+ * Copyright © 2019 Thomas White <taw@bitwiz.me.uk>
+ *
+ * This file is part of NanoLight.
+ *
+ * NanoLight is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <gtk/gtk.h>
+#include <getopt.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <libintl.h>
+#define _(x) gettext(x)
+
+#include "nanolight.h"
+#include "command.h"
+
+#define OVERALL_BORDER (20.0)
+#define OVERALL_SPLIT (0.5)
+#define FIXTURE_BORDER (5.0)
+
+enum attr_class key_attrs[] = {
+ 0,
+ ATT_INTENSITY, /* F1 */
+ ATT_TILT, /* F2 */
+ ATT_STROBE, /* F3 */
+ ATT_CYAN, /* F4 */
+ ATT_MAGENTA, /* F5 */
+ ATT_YELLOW, /* F6 */
+ ATT_GOBO, /* F7 */
+ ATT_RGOBO, /* F8 */
+ ATT_PRISM, /* F9 */
+ ATT_FOCUS, /* F10 */
+ ATT_ZOOM, /* F11 */
+ ATT_ZOOM, /* F12 */
+};
+
+static double get_attr_val(struct fixture *fix, enum attr_class acls)
+{
+ int i;
+ for ( i=0; i<fix->cls->n_attrs; i++ ) {
+ if ( fix->cls->attrs[i].cls == acls ) {
+ int max = 255;
+ if ( fix->cls->attrs[i].props & ATTR_16BIT ) max = 65535;
+ return (double)fix->attr_vals[i] / max;
+ }
+ }
+ return 0.0;
+}
+
+
+static int fixture_selected(struct nanolight *nl, struct fixture *fix)
+{
+ int i;
+ for ( i=0; i<nl->n_sel; i++ ) {
+ if ( &nl->fixtures[nl->selection[i]] == fix ) return 1;
+ }
+ return 0;
+}
+
+
+static int find_attribute(struct fixture *fix, enum attr_class cls, int *n)
+{
+ int j;
+ for ( j=0; j<fix->cls->n_attrs; j++ ) {
+ if ( fix->cls->attrs[j].cls == cls ) {
+ *n = j;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+static void draw_fixture(cairo_t *cr, PangoContext *pc, PangoFontDescription *fontdesc,
+ struct nanolight *nl, struct fixture *fix)
+{
+ PangoLayout *layout;
+ const double w = 40.0;
+ const double h = 3.0/2.0*w;
+ char tmp[32];
+ int n;
+
+ /* Pan/tilt (underneath rectangle) */
+ if ( find_attribute(fix, ATT_PAN, &n) ) {
+ double x = w*fix->attr_vals[n] / 65535;
+ cairo_move_to(cr, x, -1.0);
+ cairo_line_to(cr, x, h+1.0);
+ cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
+ cairo_set_line_width(cr, 1.0);
+ cairo_stroke(cr);
+ }
+ if ( find_attribute(fix, ATT_TILT, &n) ) {
+ double y = h*(1.0 - (double)fix->attr_vals[n] / 65535);
+ cairo_move_to(cr, -1.0, y);
+ cairo_line_to(cr, w+1.0, y);
+ cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
+ cairo_set_line_width(cr, 1.0);
+ cairo_stroke(cr);
+ }
+
+ cairo_rectangle(cr, 0.0, 0.0, w, h);
+ if ( fixture_selected(nl, fix) ) {
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.9, 0.9);
+ } else {
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 0.9);
+ }
+ cairo_fill_preserve(cr);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_set_line_width(cr, 1.0);
+ cairo_stroke(cr);
+
+ /* Label */
+ layout = pango_layout_new(pc);
+ pango_layout_set_text(layout, fix->label, -1);
+ pango_layout_set_width(layout, (w*PANGO_SCALE)-4.0);
+ pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
+ pango_layout_set_font_description(layout, fontdesc);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_move_to(cr, 0.0, 2.0);
+ pango_cairo_show_layout(cr, layout);
+ g_object_unref(layout);
+
+ /* Intensity */
+ snprintf(tmp, 32, "%.0f %%", get_attr_val(fix, ATT_INTENSITY)*100.0);
+ layout = pango_layout_new(pc);
+ pango_layout_set_text(layout, tmp, -1);
+ pango_layout_set_width(layout, (w*PANGO_SCALE)-4.0);
+ pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
+ pango_layout_set_font_description(layout, fontdesc);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_move_to(cr, 0.0, 15.0);
+ pango_cairo_show_layout(cr, layout);
+ g_object_unref(layout);
+}
+
+
+static const char *attr_text(enum attr_class cls)
+{
+ switch ( cls ) {
+ case ATT_INTENSITY : return "Intensity";
+ case ATT_PAN : return "(pan)";
+ case ATT_TILT : return "Pan/tilt";
+ case ATT_STROBE : return "Strobe";
+ case ATT_CYAN : return "Cyan";
+ case ATT_MAGENTA : return "Magenta";
+ case ATT_YELLOW : return "Yellow";
+ case ATT_RGOBO : return "RGobo";
+ case ATT_GOBO : return "Gobo";
+ case ATT_PRISM : return "Prism";
+ case ATT_FOCUS : return "Focus";
+ case ATT_ZOOM : return "Zoom";
+ }
+ return "(unknown)";
+}
+
+
+static gboolean draw_sig(GtkWidget *widget, cairo_t *cr, struct nanolight *nl)
+{
+ int w, h;
+ int ch;
+ int i;
+ PangoContext *pc;
+ PangoFontDescription *fontdesc;
+ double x, y;
+ PangoRectangle cursor;
+
+ w = gtk_widget_get_allocated_width(widget);
+ h = gtk_widget_get_allocated_height(widget);
+ pc = gtk_widget_get_pango_context(widget);
+
+ /* Overall background */
+ cairo_set_source_rgb(cr, 0.0, 0.0, 0.2);
+ cairo_paint(cr);
+
+ /* Separator between fixture and cue areas */
+ cairo_move_to(cr, w*OVERALL_SPLIT, OVERALL_BORDER);
+ cairo_line_to(cr, w*OVERALL_SPLIT, h - OVERALL_BORDER);
+ cairo_set_line_width(cr, 3.0);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
+ cairo_stroke(cr);
+
+ /* Fixtures */
+ cairo_save(cr);
+ cairo_translate(cr, OVERALL_BORDER, OVERALL_BORDER);
+ x = FIXTURE_BORDER;
+ y = FIXTURE_BORDER;
+ fontdesc = pango_font_description_from_string("Comfortaa Bold 8");
+ for ( i=0; i<nl->n_fixtures; i++ ) {
+ cairo_save(cr);
+ cairo_translate(cr, x, y);
+ cairo_scale(cr, nl->fixture_width/40.0, nl->fixture_width/40.0);
+ draw_fixture(cr, pc, fontdesc, nl, &nl->fixtures[i]);
+ cairo_restore(cr);
+ x += nl->fixture_width + FIXTURE_BORDER*2;
+ if ( x + nl->fixture_width + FIXTURE_BORDER*2 > w*OVERALL_SPLIT ) {
+ x = FIXTURE_BORDER;
+ y += nl->fixture_width*3.0/2.0 + FIXTURE_BORDER*2;
+ }
+ }
+ cairo_restore(cr);
+
+ /* Command line */
+ pango_layout_set_text(nl->layout, nl->cmdline, -1);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_save(cr);
+ cairo_translate(cr, OVERALL_BORDER, h - OVERALL_BORDER - 20.0);
+ cairo_move_to(cr, 0.0, 0.0);
+ pango_cairo_show_layout(cr, nl->layout);
+ pango_layout_get_cursor_pos(nl->layout, nl->cursor_idx, &cursor, NULL);
+ x = pango_units_to_double(cursor.x);
+ y = pango_units_to_double(cursor.y);
+ ch = pango_units_to_double(cursor.height);
+ cairo_move_to(cr, x, y);
+ cairo_line_to(cr, x, y+ch);
+ cairo_set_line_width(cr, 3.0);
+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_source_rgb(cr, 0.8, 0.4, 0.4);
+ cairo_stroke(cr);
+ cairo_restore(cr);
+
+ /* Selected attribute indicator */
+ if ( nl->n_sel > 0 ) {
+ pango_layout_set_text(nl->sa_layout, attr_text(nl->sel_attr), -1);
+ cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);
+ pango_layout_set_width(nl->sa_layout, pango_units_from_double(200.0));
+ cairo_move_to(cr, w - 200.0 - OVERALL_BORDER, h-OVERALL_BORDER-20.0);
+ pango_cairo_show_layout(cr, nl->sa_layout);
+ }
+
+ return FALSE;
+}
+
+
+static void redraw(struct nanolight *nl)
+{
+ gint w, h;
+ w = gtk_widget_get_allocated_width(GTK_WIDGET(nl->da));
+ h = gtk_widget_get_allocated_height(GTK_WIDGET(nl->da));
+ gtk_widget_queue_draw_area(GTK_WIDGET(nl->da), 0, 0, w, h);
+}
+
+
+static gboolean im_commit_sig(GtkIMContext *im, gchar *str, struct nanolight *nl)
+{
+ size_t cmd_len = strlen(nl->cmdline);
+ if ( cmd_len+strlen(str) > 1023 ) return FALSE;
+ strcat(nl->cmdline, str);
+ nl->cursor_idx += strlen(str);
+ redraw(nl);
+ return FALSE;
+}
+
+
+static size_t delete_char(char *str)
+{
+ char *last;
+ size_t len;
+ if ( str[0] == '\0' ) return 0;
+ last = g_utf8_find_prev_char(str, str+strlen(str));
+ len = strlen(last);
+ last[0] = '\0';
+ return len;
+}
+
+
+static void cap_value(struct fixture *fix, int n, signed int *v)
+{
+ if ( *v < 0 ) *v = 0;
+ if ( fix->cls->attrs[n].props & ATTR_16BIT ) {
+ if ( *v > 65535 ) *v = 65535;
+ } else {
+ if ( *v > 255 ) *v = 255;
+ }
+}
+
+
+static void set_start_attrs(struct nanolight *nl, enum attr_class cls)
+{
+ int i;
+ for ( i=0; i<nl->n_sel; i++ ) {
+ int n;
+ struct fixture *fix = &nl->fixtures[nl->selection[i]];
+ if ( find_attribute(fix, cls, &n) ) {
+ fix->attr_vals_start[n] = fix->attr_vals[n];
+ }
+ }
+
+ /* If altering tilt, also change pan */
+ if ( cls == ATT_TILT ) {
+ set_start_attrs(nl, ATT_PAN);
+ }
+}
+
+
+static gboolean button_press_sig(GtkWidget *da, GdkEventButton *event, struct nanolight *nl)
+{
+#if 0
+ GdkSeat *seat;
+ GdkWindow *win = gtk_widget_get_window(nl->da);
+
+ seat = gdk_display_get_default_seat(gdk_display_get_default());
+ nl->pointer = gdk_seat_get_pointer(seat);
+#endif
+
+ set_start_attrs(nl, nl->sel_attr);
+
+ nl->x_orig = event->x;
+ nl->y_orig = event->y;
+ nl->dragging = 1;
+
+ return FALSE;
+}
+
+
+static gboolean button_release_sig(GtkWidget *da, GdkEventButton *event, struct nanolight *nl)
+{
+ nl->dragging = 0;
+ return FALSE;
+}
+
+
+static double maybe_fine(struct fixture *fix, int n, double inc, int shift)
+{
+ if ( !(fix->cls->attrs[n].props & ATTR_16BIT) ) return inc;
+ if ( shift ) return inc;
+ return inc * 100.0;
+}
+
+
+static gboolean motion_sig(GtkWidget *da, GdkEventMotion *event, struct nanolight *nl)
+{
+ int i;
+ double x_inc, y_inc;
+ int shift;
+
+ if ( !nl->dragging ) return FALSE;
+
+ x_inc = (event->x - nl->x_orig)/3;
+ y_inc = (nl->y_orig - event->y)/3; /* Mouse up means increase */
+
+ shift = event->state & GDK_SHIFT_MASK;
+ if ( shift != nl->fine ) {
+ nl->fine = shift;
+ set_start_attrs(nl, nl->sel_attr);
+ nl->x_orig = event->x;
+ nl->y_orig = event->y;
+ return FALSE;
+ }
+
+ if ( nl->sel_attr == ATT_TILT ) {
+ for ( i=0; i<nl->n_sel; i++ ) {
+ int n;
+ struct fixture *fix = &nl->fixtures[nl->selection[i]];
+ if ( find_attribute(fix, ATT_PAN, &n) ) {
+ double inc = maybe_fine(fix, n, x_inc,
+ event->state & GDK_SHIFT_MASK);
+ signed int nv = fix->attr_vals_start[n] + inc;
+ cap_value(fix, n, &nv);
+ fix->attr_vals[n] = nv;
+ }
+ if ( find_attribute(fix, ATT_TILT, &n) ) {
+ double inc = maybe_fine(fix, n, y_inc,
+ event->state & GDK_SHIFT_MASK);
+ signed int nv = fix->attr_vals_start[n] + inc;
+ cap_value(fix, n, &nv);
+ fix->attr_vals[n] = nv;
+ }
+ }
+ } else {
+ for ( i=0; i<nl->n_sel; i++ ) {
+ int n;
+ struct fixture *fix = &nl->fixtures[nl->selection[i]];
+ if ( find_attribute(fix, nl->sel_attr, &n) ) {
+ double inc = maybe_fine(fix, n, y_inc,
+ event->state & GDK_SHIFT_MASK);
+ signed int nv = fix->attr_vals_start[n] + inc;
+ cap_value(fix, n, &nv);
+ if ( !(fix->cls->attrs[n].props & ATTR_STOP) ) {
+ fix->attr_vals[n] = nv;
+ } else {
+ printf("Can't change step attr with mouse\n");
+ }
+ }
+ }
+ }
+
+ redraw(nl);
+ return FALSE;
+}
+
+
+static void change_stop_attr(struct nanolight *nl, signed int inc)
+{
+ int i;
+ for ( i=0; i<nl->n_sel; i++ ) {
+
+ struct fixture *fix = &nl->fixtures[nl->selection[i]];
+ int n;
+
+ if ( find_attribute(fix, nl->sel_attr, &n) ) {
+ signed int nv;
+ if ( !(fix->cls->attrs[n].props & ATTR_STOP) ) {
+ printf("Can't change continuous attr with keys\n");
+ continue;
+ }
+ nv = fix->attr_vals[n] + inc;
+ if ( (nv>=0) && (nv<fix->cls->attrs[n].n_stops) ) {
+ fix->attr_vals[n] = nv;
+ }
+ }
+
+ }
+ redraw(nl);
+}
+
+static void home_value(struct nanolight *nl)
+{
+ int i;
+ for ( i=0; i<nl->n_sel; i++ ) {
+
+ struct fixture *fix = &nl->fixtures[nl->selection[i]];
+ int n;
+
+ if ( nl->sel_attr == ATT_TILT ) {
+ if ( find_attribute(fix, ATT_PAN, &n) ) {
+ fix->attr_vals[n] = fix->cls->attrs[n].home;
+ }
+ if ( find_attribute(fix, ATT_TILT, &n) ) {
+ fix->attr_vals[n] = fix->cls->attrs[n].home;
+ }
+ nl->dragging = 0;
+ } else {
+ if ( find_attribute(fix, nl->sel_attr, &n) ) {
+ fix->attr_vals[n] = fix->cls->attrs[n].home;
+ }
+ }
+
+ }
+ redraw(nl);
+}
+
+
+static void execute_command(struct nanolight *nl)
+{
+ if ( command_run(nl->cmdline, nl) == 0 ) {
+ nl->cmdline[0] = '\0';
+ nl->cursor_idx = 0;
+ }
+ redraw(nl);
+}
+
+
+static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event, struct nanolight *nl)
+{
+ gboolean r;
+ int claim = 1;
+
+ switch ( event->keyval ) {
+
+ case GDK_KEY_Left :
+ break;
+
+ case GDK_KEY_Right :
+ break;
+
+ case GDK_KEY_Up :
+ change_stop_attr(nl, +1);
+ break;
+
+ case GDK_KEY_Down :
+ change_stop_attr(nl, -1);
+ break;
+
+ case GDK_KEY_Return :
+ execute_command(nl);
+ break;
+
+ case GDK_KEY_Escape :
+ nl->cmdline[0] = '\0';
+ nl->cursor_idx = 0;
+ nl->n_sel = 0;
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_BackSpace :
+ nl->cursor_idx -= delete_char(nl->cmdline);
+ break;
+
+ case GDK_KEY_KP_Enter :
+ printf("Go!\n");
+ break;
+
+ case GDK_KEY_KP_Add :
+ printf("Stop/back!\n");
+ break;
+
+ case GDK_KEY_Home :
+ home_value(nl);
+ break;
+
+ case GDK_KEY_F1 :
+ nl->sel_attr = key_attrs[1];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F2 :
+ nl->sel_attr = key_attrs[2];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F3 :
+ nl->sel_attr = key_attrs[3];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F4 :
+ nl->sel_attr = key_attrs[4];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F5 :
+ nl->sel_attr = key_attrs[5];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F6 :
+ nl->sel_attr = key_attrs[6];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F7 :
+ nl->sel_attr = key_attrs[7];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F8 :
+ nl->sel_attr = key_attrs[8];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F9 :
+ nl->sel_attr = key_attrs[9];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F10 :
+ nl->sel_attr = key_attrs[10];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F11 :
+ nl->sel_attr = key_attrs[11];
+ nl->dragging = 0;
+ break;
+
+ case GDK_KEY_F12 :
+ nl->sel_attr = key_attrs[12];
+ nl->dragging = 0;
+ break;
+
+ default :
+ claim = 0;
+ break;
+
+ }
+
+ if ( !claim ) {
+ /* Throw the event to the IM context and let it sort things out */
+ r = gtk_im_context_filter_keypress(GTK_IM_CONTEXT(nl->im_context), event);
+ if ( r ) claim = 1;
+ } else {
+ redraw(nl);
+ }
+
+ if ( claim ) return TRUE;
+ return FALSE;
+}
+
+
+static gint realise_sig(GtkWidget *da, struct nanolight *nl)
+{
+ GdkWindow *win = gtk_widget_get_window(da);
+ PangoContext *pc;
+ PangoFontDescription *fontdesc;
+
+ /* Keyboard and input method stuff */
+ nl->im_context = gtk_im_multicontext_new();
+ gtk_im_context_set_client_window(GTK_IM_CONTEXT(nl->im_context), win);
+ gdk_window_set_accept_focus(win, TRUE);
+ g_signal_connect(G_OBJECT(nl->im_context), "commit", G_CALLBACK(im_commit_sig), nl);
+ g_signal_connect(G_OBJECT(da), "key-press-event", G_CALLBACK(key_press_sig), nl);
+ g_signal_connect(G_OBJECT(da), "button-press-event", G_CALLBACK(button_press_sig), nl);
+ g_signal_connect(G_OBJECT(da), "button-release-event", G_CALLBACK(button_release_sig), nl);
+ g_signal_connect(G_OBJECT(da), "motion-notify-event", G_CALLBACK(motion_sig), nl);
+
+ pc = gtk_widget_get_pango_context(da);
+ fontdesc = pango_font_description_from_string("Comfortaa Bold 16");
+
+ nl->layout = pango_layout_new(pc);
+ pango_layout_set_alignment(nl->layout, PANGO_ALIGN_LEFT);
+ pango_layout_set_font_description(nl->layout, fontdesc);
+
+ nl->sa_layout = pango_layout_new(pc);
+ pango_layout_set_alignment(nl->sa_layout, PANGO_ALIGN_RIGHT);
+ pango_layout_set_font_description(nl->sa_layout, fontdesc);
+
+ return FALSE;
+}
+
+
+void create_main_window(struct nanolight *nl)
+{
+ GtkWidget *mainwindow;
+ GtkWidget *da;
+
+ /* Create main window */
+ mainwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_fullscreen(GTK_WINDOW(mainwindow));
+ g_signal_connect_swapped(G_OBJECT(mainwindow), "destroy", gtk_main_quit, NULL);
+
+ da = gtk_drawing_area_new();
+ nl->da = da;
+ gtk_container_add(GTK_CONTAINER(mainwindow), GTK_WIDGET(da));
+ gtk_widget_set_can_focus(GTK_WIDGET(da), TRUE);
+ gtk_widget_add_events(da, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK
+ | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_BUTTON_MOTION_MASK);
+ g_signal_connect(G_OBJECT(da), "draw", G_CALLBACK(draw_sig), nl);
+ g_signal_connect(G_OBJECT(da), "realize", G_CALLBACK(realise_sig), nl);
+
+ gtk_widget_grab_focus(GTK_WIDGET(da));
+ gtk_widget_show_all(mainwindow);
+}