/* * 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) int key_attrs[] = { 0, INTENSITY, /* F1 */ PANTILT, /* F2 */ COLOUR, /* F3 */ GOBO, /* F4 */ PRISM, /* F5 */ FOCUS, /* F6 */ ZOOM, /* F7 */ IRIS, /* F8 */ FROST, /* F9 */ GOBO_ROTATE, /* F10 */ PRISM_ROTATE, /* F11 */ GOBO_SPIN, /* F12 */ }; 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 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]; /* Pan/tilt (underneath rectangle) */ if ( fix->cls->attributes & PANTILT ) { double x = w*(1.0 + fix->pan)/2.0; double y = h*(1.0 - fix->tilt)/2.0; 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); 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 %%", fix->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(int cls) { switch ( cls ) { case INTENSITY : return "Intensity"; case PANTILT : return "Pan/tilt"; case COLOUR : return "Colour"; case GOBO : return "Gobo"; case PRISM : return "Prism"; case FOCUS : return "Focus"; case ZOOM : return "Zoom"; case IRIS : return "Iris"; case FROST : return "Frost"; case GOBO_ROTATE : return "Gobo rotate"; case PRISM_ROTATE : return "Prism rotate"; case GOBO_SPIN : return "Gobo spin"; case PRISM_SPIN : return "Prism spin"; } 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 set_start_pantilt(struct nanolight *nl) { int i; for ( i=0; in_sel; i++ ) { struct fixture *fix = &nl->fixtures[nl->selection[i]]; fix->pan_start = fix->pan; fix->tilt_start = fix->tilt; } } 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_pantilt(nl); 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 gboolean motion_sig(GtkWidget *da, GdkEventMotion *event, struct nanolight *nl) { int i; double x_inc, y_inc; int shift; double speed; if ( !nl->dragging ) return FALSE; if ( nl->sel_attr != PANTILT ) return FALSE; x_inc = event->x - nl->x_orig; y_inc = nl->y_orig - event->y; /* Mouse up means increase */ shift = event->state & GDK_SHIFT_MASK; if ( shift != nl->fine ) { nl->fine = shift; set_start_pantilt(nl); nl->x_orig = event->x; nl->y_orig = event->y; return FALSE; } speed = shift ? (1.0/(256.0*500.0)) : (1.0/500.0); for ( i=0; in_sel; i++ ) { struct fixture *fix = &nl->fixtures[nl->selection[i]]; if ( !(fix->cls->attributes & PANTILT) ) continue; fix->pan = fix->pan_start + x_inc*speed; fix->tilt = fix->tilt_start + y_inc*speed; if ( fix->pan > 1.0 ) fix->pan = 1.0; if ( fix->pan < -1.0 ) fix->pan = -1.0; if ( fix->tilt > 1.0 ) fix->tilt = 1.0; if ( fix->tilt < -1.0 ) fix->tilt = -1.0; } redraw(nl); return FALSE; } static void home_value(struct nanolight *nl) { int i; for ( i=0; in_sel; i++ ) { #if 0 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; } } #endif } 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_release_sig(GtkWidget *da, GdkEventKey *event, struct nanolight *nl) { switch ( event->keyval ) { case GDK_KEY_KP_Enter : nl->go_lock = 0; break; case GDK_KEY_KP_Add : nl->sb_lock = 0; break; } return FALSE; } 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 : if ( !nl->go_lock ) { printf("Go!\n"); nl->go_lock = 1; } break; case GDK_KEY_KP_Add : if ( !nl->sb_lock ) { printf("Stop/back!\n"); nl->sb_lock = 1; } 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), "key-release-event", G_CALLBACK(key_release_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); }