/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* Grig: Gtk+ user interface for the Hamradio Control Libraries. Copyright (C) 2001-2006 Alexandru Csete. Authors: Alexandru Csete Comments, questions and bugreports should be submitted via http://sourceforge.net/projects/groundstation/ More details can be found at the project home page: http://groundstation.sourceforge.net/ This program 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 2 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, visit http://www.fsf.org/ */ /** \file rig-gui-lcd.c * \ingroup lcd * \brief LCD display. * * The main pupose of the LCD display widget is to show the current frequency * and to provide easy access to set the current working frequency. The display * has 6 large digits left of the decmal and three small digits right of the * decimal. It is therefore capable to display the frequency with 1 Hz of * accuracy below 1 GHz and with 1kHz of accuracy above 1 GHz. * * The master widget consists of a GtkDrawingArea holding on which the digits * are drawn (one for each display digit). The digits are pixmaps loaded from * two files, one containing the * normal sized digits and the other containing the small digits. The size of * the canvas is calculated from the size of the digits. * * In order to have predictable behaviour the pixmap files containing the * digits need to contain 12 equal-sized digits and a comma: '0123456789 -.' * Each of these digits will then be stored in a GdkPixbuf and used on the * canvas as necessary. Furthermore, the last column in the pixmaps must * contain alternting pixels with the foreground and the background color * which will be used on the canvas. * * The comma need not have the same size as the digits; it may be smaller. * When the pixmap is read, the size of the digits is calclated as follows: \code DIGIT_WIDTH = IMG_WIDTH div 12 COMMA_WIDTH = IMG_WIDTH mod 12 - 1 (first column contains color info) DIGIT_HEIGHT = IMG_HEIGHT \endcode * It is therefore quite important that IMG_WIDTH mod 11 > 0 and that the * width of the decimal separator is less than 10 pixels. * * \bug Currently it does not work with f >= 1 GHz * * \bug RIT/XIT can display up to 9.99 * * \bug Needs to be cleaned up! * * \bug Mouse events are caught only if RIG has set_freq capabilities. * set_rit / set_xit capability must be checked explicitly by the * RIT/XIT handling code. */ #include #include #include #include #include #ifdef HAVE_CONFIG_H # include #endif #include #include "rig-data.h" #include "grig-gtk-workarounds.h" #include "rig-gui-lcd.h" /** \brief Event objects. * * This enumeration is used to identify on which object * a particular event has occurred. */ typedef enum { EVENT_OBJECT_NONE = -1, /*!< No object. */ EVENT_OBJECT_FREQ_100M, /*!< 100 MHz frequency digit. */ EVENT_OBJECT_FREQ_10M, /*!< 10 MHz frequency digit. */ EVENT_OBJECT_FREQ_1M, /*!< 1 MHz frequency digit. */ EVENT_OBJECT_FREQ_100k, /*!< 100 kHz frequency digit. */ EVENT_OBJECT_FREQ_10k, /*!< 10 kHz frequency digit. */ EVENT_OBJECT_FREQ_1k, /*!< 1 kHz frequency digit. */ EVENT_OBJECT_FREQ_100, /*!< 100 Hz frequency digit. */ EVENT_OBJECT_FREQ_10, /*!< 10 Hz frequency digit. */ EVENT_OBJECT_FREQ_1, /*!< 1 Hz frequency digit. */ EVENT_OBJECT_RIT_1k, /*!< 1 kHz RIT digit. */ EVENT_OBJECT_RIT_100, /*!< 100 Hz RIT digit. */ EVENT_OBJECT_RIT_10 /*!< 10 Hz RIT digit. */ } event_object_t; /** \brief The space between the edge of the LCD and contents in pixels. */ #define LCD_MARGIN 50 /** \brief Pixmaps containing normal sized digits. */ static GdkPixbuf *digits_normal[13]; /** \brief Pixmaps containing small sized digits. */ static GdkPixbuf *digits_small[13]; static GdkPixmap *buffer; lcd_t lcd; /* private function prototypes */ static void rig_gui_lcd_load_digits (const gchar *fname); static gboolean rig_gui_lcd_expose_cb (GtkWidget *, GdkEventExpose *, gpointer); static gboolean rig_gui_lcd_handle_event (GtkWidget *, GdkEvent *, gpointer); static event_object_t rig_gui_lcd_get_event_object (GdkEvent *event); static void rig_gui_lcd_calc_dim (void); static void rig_gui_lcd_set_freq_digits (freq_t freq); static void rig_gui_lcd_set_rit_digits (shortfreq_t freq); static void rig_gui_lcd_draw_text (void); static gint rig_gui_lcd_timeout_exec (gpointer); static gint rig_gui_lcd_timeout_stop (gpointer); static void ritval_to_bytearr (gchar *, shortfreq_t); static void rig_gui_lcd_update_vfo (void); /** \brief Create LCD display widget. * \return The LCD display widget. * * This function creates and initializes the LCD display widget which is * used to diplay the frequency. */ GtkWidget * rig_gui_lcd_create () { GtkWidget *vbox; guint timerid; guint i; /* init data */ lcd.exposed = FALSE; /* load digit pixmaps from file */ rig_gui_lcd_load_digits (NULL); /* calculate frequently used sizes and positions */ rig_gui_lcd_calc_dim (); /* clear freqs buffers */ for (i=0; i<9; i++) { lcd.freqs1[i] = 'X'; } for (i=0; i<4; i++) { lcd.rits[i] = 'X'; lcd.xits[i] = 'X'; } /* create canvas */ lcd.canvas = gtk_drawing_area_new (); gtk_widget_set_size_request (lcd.canvas, lcd.width, lcd.height); /* connect expose handler which will take care of adding contents. */ g_signal_connect (G_OBJECT (lcd.canvas), "expose_event", G_CALLBACK (rig_gui_lcd_expose_cb), NULL); /* connect mouse events but only if rig has set_freq; THIS IS A BUG SINCE WE DON'T DISTINGUISH BETWEEN SET_FREQ AND SET_RIT/SET_XIT. */ #ifndef DISABLE_HW if (rig_data_has_set_freq1 ()) { #endif gtk_widget_add_events (lcd.canvas, GDK_BUTTON_PRESS_MASK); g_signal_connect (G_OBJECT (lcd.canvas), "event", G_CALLBACK (rig_gui_lcd_handle_event), NULL); #ifndef DISABLE_HW } #endif /* control widgets */ /* pack LCD and controls into a vertical box */ vbox = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), lcd.canvas, FALSE, FALSE, 5); // gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); /* start readback timer but only if service is available or we are in DISABLE_HW mode */ #ifndef DISABLE_HW if (rig_data_has_get_freq1 ()) { #endif timerid = g_timeout_add (RIG_GUI_LCD_DEF_TVAL, rig_gui_lcd_timeout_exec, NULL); /* register timer_stop function at exit */ gtk_quit_add (gtk_main_level (), rig_gui_lcd_timeout_stop, GUINT_TO_POINTER (timerid)); #ifndef DISABLE_HW } #endif return vbox; } /** \brief Load digit pixmaps into memory. * \param name The base file name. * * This function loads the pixmaps containing the digits, splits them and * stores each individual digit in memory for later use. The function also * obtains the drawing area bg/fg colors based on the loaded * digits. Because the first column in the pixmap contains the background and * foreground colors, the dimensions of each digits is calculated as follows: \code W = (pixmap_width - 1) / 12 H = pixmap_height \endcode * The parameter name is used to specify the files from which the digits should * be loaded. If name is NULL, the default pixmaps will be read. Otherwise grig * will look for $PACKAGE_PIXMAPS_DIR/name_normal.png and * $PACKAGE_PIXMAPS_DIR/name_small.png * * \bug No fallback exists if standard pixmap is not where it is supposed to be! * */ static void rig_gui_lcd_load_digits (const gchar *name) { GdkPixbuf *digits; /* pixmap in memory */ gint i; guint dw,dh,cw; /* width and height of a digit and width of comma */ gint bps,rs; /* bits pr.ample and rowstride */ gchar *fname; guchar *pixels; /* normal digits */ if (name == NULL) { fname = g_strconcat (PACKAGE_PIXMAPS_DIR, G_DIR_SEPARATOR_S, "digits_normal.png", NULL); } else { fname = g_strconcat (PACKAGE_PIXMAPS_DIR, G_DIR_SEPARATOR_S, name, "_normal.png", NULL); } /* load pixmap */ digits = gdk_pixbuf_new_from_file (fname, NULL); g_free (fname); /* calculate digit size */ dw = gdk_pixbuf_get_width (digits) / 12; dh = gdk_pixbuf_get_height (digits); /* split pixmap into digits */ for (i=0; i<12; i++) { digits_normal[i] = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, dw, dh); gdk_pixbuf_copy_area (digits, 1 + dw*i, 0, dw, dh, digits_normal[i], 0, 0); } /* decimal point */ cw = gdk_pixbuf_get_width (digits) % 12 - 1; digits_normal[12] = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, cw, dh); gdk_pixbuf_copy_area (digits, 1 + dw*12, 0, cw, dh, digits_normal[12], 0, 0); /* get background and foreground colors */ bps = gdk_pixbuf_get_bits_per_sample (digits); rs = gdk_pixbuf_get_rowstride (digits); pixels = gdk_pixbuf_get_pixels (digits); /* get each 8-bit component and scale to 16-bits; we use floating point aritmetics to allow more than 16 bits per sample. */ lcd.fg.red = (guint16) (pixels[(bps/8)*0] * (65535.0 / (pow (2, bps) - 1))); lcd.fg.green = (guint16) (pixels[(bps/8)*1] * (65535.0 / (pow (2, bps) - 1))); lcd.fg.blue = (guint16) (pixels[(bps/8)*2] * (65535.0 / (pow (2, bps) - 1))); lcd.bg.red = (guint16) (pixels[(bps/8)*0 + rs] * (65535.0 / (pow (2, bps) - 1))); lcd.bg.green = (guint16) (pixels[(bps/8)*1 + rs] * (65535.0 / (pow (2, bps) - 1))); lcd.bg.blue = (guint16) (pixels[(bps/8)*2 + rs] * (65535.0 / (pow (2, bps) - 1))); g_object_unref (digits); /* small digits */ if (name == NULL) { fname = g_strconcat (PACKAGE_PIXMAPS_DIR, G_DIR_SEPARATOR_S, "digits_small.png", NULL); } else { fname = g_strconcat (PACKAGE_PIXMAPS_DIR, G_DIR_SEPARATOR_S, name, "_small.png", NULL); } /* load pixmap */ digits = gdk_pixbuf_new_from_file (fname, NULL); g_free (fname); /* calculate digit size */ dw = gdk_pixbuf_get_width (digits) / 12; dh = gdk_pixbuf_get_height (digits); /* split pixmap into digits */ for (i=0; i<12; i++) { digits_small[i] = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, dw, dh); gdk_pixbuf_copy_area (digits, 1 + dw*i, 0, dw, dh, digits_small[i], 0, 0); } /* decimal point */ cw = gdk_pixbuf_get_width (digits) % 12 - 1; digits_small[12] = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, cw, dh); gdk_pixbuf_copy_area (digits, 1 + dw*12, 0, cw, dh, digits_small[12], 0, 0); /* get background and foreground colors */ bps = gdk_pixbuf_get_bits_per_sample (digits); rs = gdk_pixbuf_get_rowstride (digits); g_object_unref (digits); } /** \brief Handle expose events for the drawing area. * \param widget The drawing area widget. * \param event The event. * \param data User data; always NULL. * * This function is called when the rawing area widget is finalized * and exposed. Itis used to finish the initialization of those * parameters, which need attributes rom visible widgets. * * \bug canvas height is hadcoded according to smeter height. * * \bug recreates graphics context on every call (memory leak) */ static gboolean rig_gui_lcd_expose_cb (GtkWidget *widget, GdkEventExpose *event, gpointer data) { guint i; /* finalize the graphics context */ lcd.gc1 = gdk_gc_new (GDK_DRAWABLE (widget->window)); gdk_gc_set_rgb_fg_color (lcd.gc1, &lcd.fg); gdk_gc_set_rgb_bg_color (lcd.gc1, &lcd.bg); gdk_gc_set_line_attributes (lcd.gc1, 1, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND); lcd.gc2 = gdk_gc_new (GDK_DRAWABLE (widget->window)); gdk_gc_set_rgb_fg_color (lcd.gc2, &lcd.bg); gdk_gc_set_rgb_bg_color (lcd.gc2, &lcd.fg); gdk_gc_set_line_attributes (lcd.gc2, 1, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND); /* draw border around the meter */ gdk_draw_rectangle (GDK_DRAWABLE (widget->window), lcd.gc2, TRUE, 0, 0, lcd.width, lcd.height); gdk_draw_rectangle (GDK_DRAWABLE (widget->window), lcd.gc1, FALSE, 0, 0, lcd.width-1, lcd.height-1); /* force digit update by clearing internal string buffer */ for (i=0; i<9; i++) { lcd.freqs1[i] = 'X'; } rig_gui_lcd_set_freq_digits (lcd.freq1); /* force digit update by clearing internal string buffer */ for (i=0; i<4; i++) { lcd.rits[i] = 'X'; } rig_gui_lcd_set_rit_digits (lcd.rit); /* large dots */ /* gdk_draw_pixbuf (GDK_DRAWABLE (widget->window), NULL, digits_normal[12], */ /* 0, 0, lcd.others[0].x, lcd.others[0].y, -1, -1, */ /* GDK_RGB_DITHER_NONE, 0, 0); */ gdk_draw_pixbuf (GDK_DRAWABLE (widget->window), NULL, digits_normal[12], 0, 0, lcd.others[1].x, lcd.others[1].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); /* small dot */ gdk_draw_pixbuf (GDK_DRAWABLE (widget->window), NULL, digits_small[12], 0, 0, lcd.others[2].x, lcd.others[2].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); /* initialize offscreen buffer */ buffer = gdk_pixmap_new (GDK_DRAWABLE (lcd.canvas->window), lcd.width, lcd.height, -1); /* draw text */ rig_gui_lcd_draw_text (); /* indicate that widget is ready to be used */ lcd.exposed = TRUE; return TRUE; } /** \brief Handle drawing area events. * \param widget The drawing area widget receiving the evend. * \param event The received event * \param data User data; always NULL. * \return TRUE if the event can be handled, FALSE otherwise. * * This function is called every time an event occurs on the drawing area. * The function currently handles three type of events: * \li GDK_EXPOSE: The drawing area has been exposed and needs to be redrawn * \li GDK_BUTTON_PRES: One of the three mouse button has been pressed * \li GDK_SCROLL: The scroll wheel of the mouse has been used * All other events are ignored in which case FALSE is returned to notify the * parent that the event has not been handled. * * The detailed management of each of the three events is done by other internal * functions. * * \bug Cyclomatic omplexity to high? * * \sa rig_gui_lcd_expose_cb, rig_gui_lcd_get_event_object */ static gboolean rig_gui_lcd_handle_event (GtkWidget *widget, GdkEvent *event, gpointer data) { event_object_t object; /* event object */ freq_t deltaf; /* frequency change */ freq_t newfreq; /* new frequency. */ shortfreq_t deltar; /* RIT/XIT change */ shortfreq_t newrit; /* new RIT/XIT value */ guint power; /* usd for 10**power */ gchar *str; /* in case of expose-event call the expose event handler */ switch (event->type) { /* drawing area hss been exposed */ case GDK_EXPOSE: return rig_gui_lcd_expose_cb (widget, (GdkEventExpose *) event, data); break; /* button press */ case GDK_BUTTON_PRESS: object = rig_gui_lcd_get_event_object (event); /* if no object, just return */ if (object == EVENT_OBJECT_NONE) { return FALSE; } else if (object > EVENT_OBJECT_FREQ_1) { /* RIT/XIT event; object is in the range 9..11 */ power = 12 - object; deltar = pow (10, power); if (deltar < rig_data_get_ritstep ()) { // return TRUE; deltar = rig_data_get_ritstep (); } /* check which mouse button */ switch (event->button.button) { /* LEFT button: inrease frequency */ case 1: /* ensure correct sign flip */ if ((lcd.rit < 0) && (deltar > abs (lcd.rit))) newrit = -lcd.rit; else newrit = lcd.rit + deltar; /* check whether we are within current frequency range; if so, apply new frequency */ if (newrit <= rig_data_get_ritmax ()) { rig_data_set_rit (newrit); rig_gui_lcd_set_rit_digits (newrit); } break; /* MIDDLE button: clear digit */ case 2: /* convert current frequency to string and clear corresponding digit; then convert back to freq_t WARNING: don't use lcd.freqs1 because it is not 0-terminated; it's just a byte array! */ str = g_strdup_printf ("%5d", lcd.rit); str[object-8] = '0'; newrit = (freq_t) g_strtod (str, NULL); /* try new frequency */ if (newrit >= rig_data_get_ritmin ()) { rig_data_set_rit (newrit); rig_gui_lcd_set_rit_digits (newrit); } g_free (str); break; /* RIGHT button: decrease frequency */ case 3: /* ensure correct sign flip */ if ((lcd.rit > 0) && (deltar > lcd.rit)) newrit = -lcd.rit; else newrit = lcd.rit - deltar; /* check whether we are within current frequency range; if so, apply new frequency */ if (newrit >= rig_data_get_ritmin ()) { rig_data_set_rit (newrit); rig_gui_lcd_set_rit_digits (newrit); } break; default: break; } /* case */ } /* else if object > EVENT_OBJECT_FREQ_1 */ else { /* frequency event; object is in the range 0..8 with 0 corresponding to 100 MHz */ power = 8 - object; deltaf = pow (10, power); if (deltaf < rig_data_get_fstep ()) { // return TRUE; deltaf = rig_data_get_fstep (); } /* check which mouse button */ switch (event->button.button) { /* LEFT button: inrease frequency */ case 1: newfreq = lcd.freq1 + deltaf; /* check whether we are within current frequency range; if so, apply new frequency */ if (newfreq <= rig_data_get_fmax ()) { rig_data_set_freq (1, newfreq); rig_gui_lcd_set_freq_digits (newfreq); } break; /* MIDDLE button: clear digit */ case 2: /* convert current frequency to string and clear corresponding digit; then convert back to freq_t WARNING: don't use lcd.freqs1 because it is not 0-terminated; it's just a byte array! */ str = g_strdup_printf ("%9.0f", lcd.freq1); str[object] = '0'; newfreq = (freq_t) g_strtod (str, NULL); /* try new frequency */ if (newfreq >= rig_data_get_fmin ()) { rig_data_set_freq (1, newfreq); rig_gui_lcd_set_freq_digits (newfreq); } g_free (str); break; /* RIGHT button: decrease frequency */ case 3: newfreq = lcd.freq1 - deltaf; /* check whether we are within current frequency range; if so, apply new frequency */ if (newfreq >= rig_data_get_fmin ()) { rig_data_set_freq (1, newfreq); rig_gui_lcd_set_freq_digits (newfreq); } break; default: break; } /* case event->button.button */ } /* else */ return TRUE; break; /* mouse wheel */ case GDK_SCROLL: object = rig_gui_lcd_get_event_object (event); /* if no object, just return */ if (object == EVENT_OBJECT_NONE) { return FALSE; } else if (object > EVENT_OBJECT_FREQ_1) { /* RIT/XIT event; object is in the range 9..11 */ power = 12 - object; deltar = pow (10, power); if (deltar < rig_data_get_ritstep ()) { // return TRUE; deltar = rig_data_get_ritstep (); } /* check which mouse button */ switch (event->scroll.direction) { /* WHEEL UP: inrease frequency */ case GDK_SCROLL_UP: /* ensure correct sign flip */ if ((lcd.rit < 0) && (deltar > abs (lcd.rit))) newrit = -lcd.rit; else newrit = lcd.rit + deltar; /* check whether we are within current frequency range; if so, apply new frequency */ if (newrit <= rig_data_get_ritmax ()) { rig_data_set_rit (newrit); rig_gui_lcd_set_rit_digits (newrit); } break; /* SCROLL DOWN: decrease frequency */ case GDK_SCROLL_DOWN: /* ensure correct sign flip */ if ((lcd.rit > 0) && (deltar > lcd.rit)) newrit = -lcd.rit; else newrit = lcd.rit - deltar; /* check whether we are within current frequency range; if so, apply new frequency */ if (newrit >= rig_data_get_ritmin ()) { rig_data_set_rit (newrit); rig_gui_lcd_set_rit_digits (newrit); } break; default: break; } /* case */ } else { /* frequency event object is in the range 0..8 with 0 corresponding to 100 MHz */ power = 8 - object; deltaf = pow (10, power); if (deltaf < rig_data_get_fstep ()) { // return TRUE; deltaf = rig_data_get_fstep (); } /* check which mouse button */ switch (event->scroll.direction) { /* WHEEL UP: inrease frequency */ case GDK_SCROLL_UP: newfreq = lcd.freq1 + deltaf; /* check whether we are within current frequency range; if so, apply new frequency */ if (newfreq <= rig_data_get_fmax ()) { rig_data_set_freq (1, newfreq); rig_gui_lcd_set_freq_digits (newfreq); } break; /* WHEEL DOWN: decrease frequency */ case GDK_SCROLL_DOWN: newfreq = lcd.freq1 - deltaf; /* check whether we are within current frequency range; if so, apply new frequency */ if (newfreq >= rig_data_get_fmin ()) { rig_data_set_freq (1, newfreq); rig_gui_lcd_set_freq_digits (newfreq); } break; default: break; } } return TRUE; break; default: return FALSE; break; } return FALSE; } /** \brief Find event object. * \param event The occurred GdkEvent. * \return The ID of the object on which the event occurred. * * This function scans through the coordinates of the existing * digits(objects) and return the corresponding event object ID. * If no object matches the event coordinates EVENT_OBJECT_NONE * is returned. * * \bug \b CRITICAL: the commas are caughht as valid objects and are * reported back with negative object ID. Not anymore? */ static event_object_t rig_gui_lcd_get_event_object (GdkEvent *event) { guint x,y; /* coordinates */ guint i; x = (guint) ((GdkEventButton*)event)->x; y = (guint) ((GdkEventButton*)event)->y; /* check vertical range */ if ((y < lcd.digits[0].y) || (y > lcd.digits[0].y+lcd.dlh)) { return EVENT_OBJECT_NONE; } /* check x */ if ((x < lcd.digits[0].x) || (x > lcd.digits[11].x+lcd.dsw) || ((x > lcd.digits[8].x+lcd.dsw) && (x < lcd.digits[9].x)) || ((x > lcd.digits[9].x+lcd.dsw) && (x < lcd.digits[10].x))) { return EVENT_OBJECT_NONE; } else { /* loop over all digits until we find one; we loop from the end, because small frequencies are changed more often. */ /* small digits */ for (i=11; i>5; i--) { if ((x > lcd.digits[i].x) && (x < lcd.digits[i].x+lcd.dsw)) { return i; } } /* then try large digits */ for (i=5; i>=0; i--) { if ((x > lcd.digits[i].x) && (x < lcd.digits[i].x+lcd.dlw)) { return i; } } return EVENT_OBJECT_NONE; } return EVENT_OBJECT_NONE; } /** \brief Calculate and store frequently used sizes and positions. * * This function calculates and stores frequently used dimensions and * positions to avoid too much CPU-load during update of therawing area. * The involved parameters are canvas size, digit sizes and position for * each digit, which will be part of the frequency display. The calculated * quantities are stored in predefined fields of the lcd structure. * * \bug A loop could have been used, but it would be messy anyway with several * if's. */ static void rig_gui_lcd_calc_dim () { guint i; /* iterator */ /* store digit sizes to avoid frequent call to gdk_pixbuf_get_width and gdk_pixbuf_get_height */ lcd.dlw = gdk_pixbuf_get_width (digits_normal[0]); lcd.dlh = gdk_pixbuf_get_height (digits_normal[0]); lcd.clw = gdk_pixbuf_get_width (digits_normal[12]); lcd.dsw = gdk_pixbuf_get_width (digits_small[0]); lcd.dsh = gdk_pixbuf_get_height (digits_small[0]); lcd.csw = gdk_pixbuf_get_width (digits_small[12]); /* calculate drawing area dimensions */ lcd.width = 6*lcd.dlw + 2*lcd.clw + 8*lcd.dsw + lcd.csw + 2*LCD_MARGIN; lcd.height = 80; /* calculate screen position for each digit; this will ease the update of the LCD */ /* VFO digits */ lcd.digits[0].x = LCD_MARGIN / 2; lcd.digits[1].x = lcd.digits[0].x + lcd.dlw; lcd.digits[2].x = lcd.digits[1].x + lcd.dlw; lcd.digits[3].x = lcd.digits[2].x + lcd.clw + lcd.dlw; /**/ lcd.digits[4].x = lcd.digits[3].x + lcd.dlw; lcd.digits[5].x = lcd.digits[4].x + lcd.dlw; lcd.digits[6].x = lcd.digits[5].x + lcd.clw + lcd.dlw; /**/ lcd.digits[7].x = lcd.digits[6].x + lcd.dsw; lcd.digits[8].x = lcd.digits[7].x + lcd.dsw; lcd.digits[9].x = lcd.digits[8].x + 4*lcd.dsw; /**/ lcd.digits[10].x = lcd.digits[9].x + lcd.csw + lcd.dsw; /**/ lcd.digits[11].x = lcd.digits[10].x + lcd.dsw; for (i=0; i<6; i++) lcd.digits[i].y = (lcd.height - lcd.dlh)/2; for (i=6; i<13; i++) lcd.digits[i].y = lcd.digits[1].y + (lcd.dlh-lcd.dsh)-1; lcd.others[0].x = lcd.digits[2].x + lcd.dlw; lcd.others[1].x = lcd.digits[5].x + lcd.dlw; lcd.others[2].x = lcd.digits[9].x + lcd.dsw; lcd.others[0].y = (lcd.height - lcd.dlh)/2; lcd.others[1].y = (lcd.height - lcd.dlh)/2; lcd.others[2].y = lcd.digits[1].y + (lcd.dlh-lcd.dsh)-1; } /** \brief Set LCD display frequency. * \param freq The frequency. * * This function updates the digits on the LCD display with the new frequency. * The frequency is received in hamlib format (float) and converted to a * string with 9 digits. If the frequency is less than 1 GHz the obtained * resolution will be 1 Hz, while for frequenciesabove 1 GHz the resolution * will be 1 kHz. * * \note The function is optimized in the sense that before drawing of each digit * it is checked whether the new digit is different from the one already beeing * displayed. * * \bug 'default' case should send a critical error message. * * \sa rig_gui_lcd_set_rit_digits */ static void rig_gui_lcd_set_freq_digits (freq_t freq) { gchar *str; /* frequency as a string */ guint i; /* iterator */ /* is drawing area ready? */ if (!lcd.exposed || (freq < rig_data_get_fmin ())) return; /* saturate frequency */ if (freq >= GHz(1)) freq = MHz(999.999999); /* store the new frequency for later use */ lcd.freq1 = freq; /* convert frequency to string */ str = g_strdup_printf ("%9.0f", freq); /* for each digit check whether the new digit is different from the one already beeing displayed; if yes, draw the new digit, otherwise do nothing. */ for (i=0; i<9; i++) { if (str[i] != lcd.freqs1[i]) { lcd.freqs1[i] = str[i]; switch (str[i]) { case '0': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[0] : digits_small[0], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '1': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[1] : digits_small[1], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '2': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[2] : digits_small[2], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '3': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[3] : digits_small[3], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '4': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[4] : digits_small[4], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '5': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[5] : digits_small[5], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '6': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[6] : digits_small[6], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '7': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[7] : digits_small[7], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '8': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[8] : digits_small[8], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '9': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[9] : digits_small[9], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case ' ': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, (i < 6) ? digits_normal[10] : digits_small[10], 0, 0, lcd.digits[i].x, lcd.digits[i].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; default: /* critical error */ break; } } } } /** \brief Set LCD display frequency (RIT/XIT). * \param freq The frequency. * * This function updates the RIT/CIT digits on the LCD display with the new frequency. * The frequency is received in hamlib format (signed long) and converted to a * string with 3 digits with 0.01 kHz resolution. * * \note The function is optimized in the sense that before drawing of each digit * it is checked whether the new digit is different from the one already beeing * displayed. * * \bug 'default' case should send a critical error message. * * \sa rig_gui_lcd_set_freq_digits */ static void rig_gui_lcd_set_rit_digits (shortfreq_t freq) { gchar *str; guint i; /* is drawing area ready? */ if (!lcd.exposed) return; if (freq > s_kHz(9.99)) { freq = kHz(9.99); } else if (freq < s_kHz(-9.99)) { freq = s_kHz(-9.99); } /* store RIT/XIT frequency for later use */ lcd.rit = freq; /* convert frequency to string */ str = g_strdup ("-0000"); ritval_to_bytearr (str, freq); /* 0th element is the sign; must be handled separately because ' ' means clear and not 0 */ if (str[0] != lcd.rits[0]) { lcd.rits[0] = str[0]; switch (str[0]) { case ' ': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[10], 0, 0, lcd.digits[9].x - lcd.dsw, lcd.digits[9].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '-': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[11], 0, 0, lcd.digits[9].x - lcd.dsw, lcd.digits[9].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; default: /* critical internal error */ break; } } /* for each digit check whether the new digit is different from the one already beeing displayed; if yes, draw the new digit, otherwise do nothing. */ for (i=1; i<4; i++) { if (str[i] != lcd.rits[i]) { lcd.rits[i] = str[i]; switch (str[i]) { case '0': case ' ': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[0], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '1': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[1], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '2': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[2], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '3': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[3], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '4': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[4], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '5': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[5], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '6': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[6], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '7': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[7], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '8': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[8], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; case '9': gdk_draw_pixbuf (GDK_DRAWABLE (lcd.canvas->window), NULL, digits_small[9], 0, 0, lcd.digits[i+8].x, lcd.digits[i+8].y, -1, -1, GDK_RGB_DITHER_NONE, 0, 0); break; default: /* critical internal error */ break; } /* case */ } /* if */ } /* for */ g_free (str); } /** \brief Execute timeout function. * \param data User data; currently NULL. * \return Always TRUE to keep the timer running. * * This function is in charge for updating the signal strength meter. It acquires * the signal strength from the rig-data object, converts it to needle endpoint * coordinates and repaints the s-meter. * * The function is called peridically by the Gtk+ scheduler. * * \bug DDD copied from smeter (ie. wrong) * * \bug Add XIT support */ static gint rig_gui_lcd_timeout_exec (gpointer data) { static guint vfoupd; /* update frequency if applicable */ if (rig_data_has_get_freq1 ()) { lcd.freq1 = rig_data_get_freq (1); rig_gui_lcd_set_freq_digits (lcd.freq1); } /* update RIT/XIT if applicable */ if (rig_data_has_get_rit () || rig_data_has_set_rit ()) { lcd.rit = rig_data_get_rit (); rig_gui_lcd_set_rit_digits (lcd.rit); } /* VFO updated every second cycle */ if (vfoupd) { rig_gui_lcd_update_vfo (); vfoupd = 0; } else { vfoupd += 1; } return TRUE; } /** \brief Stop timeout function. * \param timer The ID of the timer to stop. * \return Always TRUE. * * This function is used to stop the readback timer just before the * program is quit. It should be called automatically by Gtk+ when * the gtk_main_loop is exited. * * \bug DDD is wrong; copied from smeter. */ static gint rig_gui_lcd_timeout_stop (gpointer timer) { g_source_remove (GPOINTER_TO_UINT (timer)); return TRUE; } /** \brief Draw miscellaneous text. * * This function is in charge of drawing miscellaneous text on the display, * like RIT, kHz and such. */ static void rig_gui_lcd_draw_text () { PangoContext *context; PangoLayout *layout; gint w,h; /* get the PangoContext of the widget */ context = gtk_widget_get_pango_context (lcd.canvas); /* create a new PangoLayout */ layout = pango_layout_new (context); /* set text: kHz */ pango_layout_set_text (layout, _("kHz"), -1); /* calculate coordinates; PanoLayoutSize is in 1000th of pixel? */ pango_layout_get_size (layout, &w, &h); w /= 1000; h /= 1000; /* draw text; frequency */ gdk_draw_layout (lcd.canvas->window, lcd.gc1, lcd.digits[8].x + lcd.dsw + 5, lcd.digits[8].y + lcd.dsh - h, layout); /* draw text; rit */ gdk_draw_layout (lcd.canvas->window, lcd.gc1, lcd.digits[11].x + lcd.dsw + 5, lcd.digits[11].y + lcd.dsh - h, layout); rig_gui_lcd_update_vfo (); /* set text: RIT */ pango_layout_set_text (layout, _("RIT"), -1); /* calculate coordinates; PanoLayoutSize is in 1000th of pixel? */ pango_layout_get_size (layout, &w, &h); w /= 1000; h /= 1000; /* draw text; RIT */ gdk_draw_layout (lcd.canvas->window, lcd.gc1, lcd.digits[10].x, lcd.digits[0].y - h, layout); /* free PangoLayout */ g_object_unref (G_OBJECT (layout)); } static void rig_gui_lcd_update_vfo () { PangoContext *context; PangoLayout *layout; gint w,h; vfo_t vfo; /* is drawing area ready? */ if (!lcd.exposed) return; /* if the VFO is the same as the displayed one, don't do anything */ vfo = rig_data_get_vfo (); if (vfo == lcd.vfo) return; lcd.vfo = vfo; /* set text: VFO */ /* get the PangoContext of the widget */ context = gtk_widget_get_pango_context (lcd.canvas); /* create a new PangoLayout */ layout = pango_layout_new (context); switch (vfo) { case RIG_VFO_A: pango_layout_set_text (layout, _("VFO A"), -1); break; case RIG_VFO_B: pango_layout_set_text (layout, _("VFO B"), -1); break; case RIG_VFO_C: pango_layout_set_text (layout, _("VFO C"), -1); break; case RIG_VFO_MAIN: pango_layout_set_text (layout, _("MAIN VFO"), -1); break; case RIG_VFO_SUB: pango_layout_set_text (layout, _("SUB VFO"), -1); break; default: pango_layout_set_text (layout, _("VFO ?"), -1); break; } /* calculate coordinates; PanoLayoutSize is in 1000th of pixel? */ pango_layout_get_size (layout, &w, &h); w /= 1000; h /= 1000; /* clear the area */ gdk_draw_rectangle (GDK_DRAWABLE (lcd.canvas->window), lcd.gc2, TRUE, lcd.digits[4].x, lcd.digits[0].y - h, 2*w, h); /* draw text */ gdk_draw_layout (lcd.canvas->window, lcd.gc1, lcd.digits[4].x, lcd.digits[0].y - h, layout); /* free PangoLayout */ g_object_unref (G_OBJECT (layout)); } /** \brief Convert RIT value to byte array. * \param array The array to store the result in. * \param freq The frequency to convert. * * This function converts an integer value in the range [-9999;+9999] to a byte array. * The byte array has to be allocated by the caller and have a length of 5 bytes not * including the trailing \0. * * \bug This is a hack! Consider implementing it in a cleaner way. */ static void ritval_to_bytearr (gchar *array, shortfreq_t freq) { gint i; shortfreq_t delta; if (freq < 0) array[0] = '-'; else array[0] = ' '; freq = abs (freq); for (i = 3; i >= 0; i--) { delta = pow (10, i); if (freq >= delta) { array[4-i] = freq / delta + '0'; freq %= delta; } else { array[4-i] = '0'; } } }