/* * display.c * * Copyright © 2019 Thomas White * * 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 . * */ #include #include #include #include #include #include #include #include #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; icls->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; in_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; jcls->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; in_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; in_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; in_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; in_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; in_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) && (nvcls->attrs[n].n_stops) ) { fix->attr_vals[n] = nv; } } } redraw(nl); } static void home_value(struct nanolight *nl) { int i; for ( i=0; in_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); }