From f349543e1d82839e458e03701c3f22bdadd0e0f6 Mon Sep 17 00:00:00 2001 From: Kaj-Michael Lang Date: Thu, 27 Sep 2007 18:29:23 +0300 Subject: [PATCH] Make a widget of the heading/compass display --- src/gtkcompass.c | 442 +++++++++++++++++++++++++++++++++++++++++++++++ src/gtkcompass.h | 67 +++++++ 2 files changed, 509 insertions(+) create mode 100644 src/gtkcompass.c create mode 100644 src/gtkcompass.h diff --git a/src/gtkcompass.c b/src/gtkcompass.c new file mode 100644 index 0000000..c24bc02 --- /dev/null +++ b/src/gtkcompass.c @@ -0,0 +1,442 @@ +/* + * + * GtkCompass: Display heading/compass information + * Also bearing to target and next waypoint. + * + * Copyright (C) 2007 Kaj-Michael Lang + * Originanl non-widget version Copyright Cezary Jackiewicz + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include + +#include +#include +#include "gtkcompass.h" + +static void gtk_compass_finalize (GObject *object); +static void gtk_compass_size_request (GtkWidget *widget, GtkRequisition *requisition); +static void gtk_compass_size_allocate (GtkWidget *widget, GtkAllocation *allocate); +static gint gtk_compass_expose (GtkWidget *widget, GdkEventExpose *event); +static void gtk_compass_realize (GtkWidget *widget); +static void gtk_compass_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); +static void gtk_compass_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); +static void gtk_compass_paint(GtkCompass *compass); + +G_DEFINE_TYPE(GtkCompass, gtk_compass, GTK_TYPE_WIDGET); + +#define PADDING (15) + +#define BOUND(x, a, b) { \ + if((x) < (a)) \ + (x) = (a); \ + else if((x) > (b)) \ + (x) = (b); \ +} + +static void +gtk_compass_class_init (GtkCompassClass *class) +{ +GObjectClass *object_class; +GtkWidgetClass *widget_class; + +object_class = (GObjectClass*) class; +widget_class = (GtkWidgetClass*) class; + +object_class->finalize = gtk_compass_finalize; +object_class->set_property = gtk_compass_set_property; +object_class->get_property = gtk_compass_get_property; + +widget_class->size_request = gtk_compass_size_request; +widget_class->expose_event = gtk_compass_expose; +widget_class->realize = gtk_compass_realize; +widget_class->size_allocate = gtk_compass_size_allocate; +} + +static void +gtk_compass_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ +GtkCompass *compass; +g_return_if_fail(GTK_IS_COMPASS(object)); +compass=GTK_COMPASS(object); +switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; +} +} + +static void +gtk_compass_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ +GtkCompass *compass; +g_return_if_fail(GTK_IS_COMPASS(object)); +compass=GTK_COMPASS(object); +switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; +} +} + +static void +gtk_compass_init (GtkCompass *compass) +{ +g_printf("%s()\n", __PRETTY_FUNCTION__); + +compass->heading=0; +compass->gc_h=NULL; +compass->gc_d=NULL; +compass->gc_w=NULL; +compass->dest_valid=FALSE; +compass->way_valid=FALSE; +compass->width=400; +compass->height=300; +} + +static gboolean +gtk_compass_cb_button_press(GtkWidget * widget, GdkEventButton * event) +{ +GtkCompass *compass; + +compass=GTK_COMPASS(widget); + +return FALSE; +} + +GtkWidget* +gtk_compass_new(void) +{ +GtkCompass *compass; +GtkWidget *widget; + +compass=gtk_type_new(gtk_compass_get_type ()); +widget=GTK_WIDGET(compass); +g_signal_connect(G_OBJECT(widget), "button_press_event", G_CALLBACK(gtk_compass_cb_button_press), NULL); +return widget; +} + +static void +gtk_compass_finalize(GObject *object) +{ +GtkCompass *compass; + +g_printf("%s()\n", __PRETTY_FUNCTION__); + +g_return_if_fail(GTK_IS_COMPASS(object)); +compass=GTK_COMPASS(object); + +if (GTK_WIDGET(object)->parent && GTK_WIDGET_MAPPED(object)) { + gtk_widget_unmap(GTK_WIDGET(object)); +} + +G_OBJECT_CLASS(gtk_compass_parent_class)->finalize(object); +} + +static void +gtk_compass_size_request(GtkWidget *widget, GtkRequisition *requisition) +{ +GtkCompass *compass; + +g_printf("%s()\n", __PRETTY_FUNCTION__); + +g_return_if_fail(GTK_IS_COMPASS(widget)); +g_return_if_fail(requisition != NULL); + +compass = GTK_COMPASS (widget); + +requisition->width=400; +requisition->height=300; +compass->width=400 - 10; +compass->height=300 - 10; +} + +static void +gtk_compass_size_allocate(GtkWidget *widget, GtkAllocation *allocation) +{ +GtkCompass *compass; + +g_printf("%s()\n", __PRETTY_FUNCTION__); + +g_return_if_fail(GTK_IS_COMPASS(widget)); +g_return_if_fail(allocation!=NULL); + +compass=GTK_COMPASS(widget); + +widget->allocation=*allocation; + +if (GTK_WIDGET_REALIZED (widget)) { + gdk_window_move_resize (widget->window, + allocation->x, allocation->y, + allocation->width, allocation->height); +} + +compass->width=allocation->width-PADDING; +compass->height=allocation->height-PADDING; + +compass->size = MIN(widget->allocation.width, widget->allocation.height); +if (widget->allocation.width > widget->allocation.height) { + compass->xoffset = (widget->allocation.width - widget->allocation.height) / 2; + compass->yoffset = 0; +} else { + compass->xoffset = 0; + compass->yoffset = (widget->allocation.height - widget->allocation.width) / 2; +} + +} + +static void +gtk_compass_realize (GtkWidget *widget) +{ +GtkCompass *compass; +GdkColor color; +GdkWindowAttr attributes; +gint attributes_mask; + +g_printf("%s()\n", __PRETTY_FUNCTION__); + +g_return_if_fail (GTK_IS_COMPASS(widget)); +GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); +compass=GTK_COMPASS(widget); + +attributes.x=widget->allocation.x; +attributes.y=widget->allocation.y; +attributes.width=widget->allocation.width; +attributes.height=widget->allocation.height; +attributes.wclass=GDK_INPUT_OUTPUT; +attributes.window_type=GDK_WINDOW_CHILD; +attributes.event_mask=gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK; +attributes.visual=gtk_widget_get_visual(widget); +attributes.colormap=gtk_widget_get_colormap(widget); + +attributes_mask=GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; + +widget->window=gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask); +widget->style=gtk_style_attach(widget->style, widget->window); + +gdk_window_set_user_data(widget->window, widget); +gtk_style_set_background(widget->style, widget->window, GTK_STATE_ACTIVE); + +compass->context=gtk_widget_get_pango_context(widget); +compass->layout=pango_layout_new(compass->context); +compass->fontdesc=pango_font_description_new(); +pango_font_description_set_family(compass->fontdesc, "Sans Serif"); +pango_font_description_set_size(compass->fontdesc, 12*PANGO_SCALE); +pango_layout_set_font_description(compass->layout, compass->fontdesc); +pango_layout_set_alignment(compass->layout, PANGO_ALIGN_CENTER); + +if (!compass->gc_h) { + color.red=0x0000; + color.green=0x0000; + color.blue=0x0000; + compass->gc_h=gdk_gc_new(widget->window); + gdk_gc_set_rgb_fg_color(compass->gc_h, &color); + gdk_gc_set_line_attributes(compass->gc_h, 6, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND); +} + +if (!compass->gc_d) { + color.red=0xffff; + color.green=0x0000; + color.blue=0xffff; + compass->gc_d=gdk_gc_new(widget->window); + gdk_gc_set_rgb_fg_color(compass->gc_d, &color); + gdk_gc_set_line_attributes(compass->gc_d, 6, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND); +} + +if (!compass->gc_w) { + color.red=0x0000; + color.green=0xffff; + color.blue=0x0000; + compass->gc_w=gdk_gc_new(widget->window); + gdk_gc_set_rgb_fg_color(compass->gc_w, &color); + gdk_gc_set_line_attributes(compass->gc_w, 6, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND); +} + +} + +static void +gtk_compass_draw_mark(GtkCompass *compass, GdkGC *gc, gdouble t) +{ +GtkWidget *widget; +gint hs; + +widget=GTK_WIDGET(compass); + +hs=compass->size/2; + +gdk_draw_line(widget->window,gc, + compass->xoffset+hs+((hs-5)*sin(t)), + compass->yoffset+compass->size-((hs-5)*cos(t)), + compass->xoffset+hs+((hs+5)*sin(t)), + compass->yoffset+compass->size-((hs+5)*cos(t))); +} + +static void +gtk_compass_paint(GtkCompass *compass) +{ +GtkWidget *widget; +guint i, x, y, size, hsize; +gint dir; +gfloat tmp; +gchar *text; +gchar htext[8]; +gint angle[5] = { -90, -45, 0, 45, 90 }; +gint fsize[5] = { 0, 4, 10, 4, 0 }; + +widget=GTK_WIDGET(compass); +size=compass->size; +hsize=size/2; + +g_snprintf(htext, 8, "%3.0f°", compass->heading); +pango_layout_set_text(compass->layout, htext, -1); +pango_layout_get_pixel_size(compass->layout, &x, &y); + +gdk_draw_layout(widget->window, + widget->style->fg_gc[GTK_STATE_NORMAL], + compass->xoffset+size/2-x/2, + compass->yoffset+size-y-2, compass->layout); + +gdk_draw_arc(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + FALSE, + compass->xoffset, compass->yoffset+size/2, size, size, 0, 64 * 180); + +/* Simple arrow for heading */ +gdk_draw_line(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + compass->xoffset + size / 2 + 3, + compass->yoffset + size - y - 5, + compass->xoffset + size / 2, compass->yoffset + size / 2 + 5); + +gdk_draw_line(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + compass->xoffset + size / 2 - 3, + compass->yoffset + size - y - 5, + compass->xoffset + size / 2, compass->yoffset + size / 2 + 5); + +gdk_draw_line(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + compass->xoffset + size / 2 - 3, + compass->yoffset + size - y - 5, + compass->xoffset + size / 2, compass->yoffset + size - y - 8); + +gdk_draw_line(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + compass->xoffset + size / 2 + 3, + compass->yoffset + size - y - 5, + compass->xoffset + size / 2, compass->yoffset + size - y - 8); + +for (i = 0; i < 5; i++) { + dir = (gint) (compass->heading / 45) * 45 + angle[i]; + + switch (dir) { + case 0: + case 360: + text = "N"; + break; + case 45: + case 405: + text = "NE"; + break; + case 90: + text = "E"; + break; + case 135: + text = "SE"; + break; + case 180: + text = "S"; + break; + case 225: + text = "SW"; + break; + case 270: + case -90: + text = "W"; + break; + case 315: + case -45: + text = "NW"; + break; + default: + text = "??"; + break; + } + + tmp = ((dir - compass->heading) * (1.f / 180.f)) * G_PI; + gdk_draw_line(widget->window, + widget->style->fg_gc[GTK_WIDGET_STATE(widget)], + compass->xoffset + size / 2 + ((size / 2 - 5) * sinf(tmp)), + compass->yoffset + size - ((size / 2 - 5) * cosf(tmp)), + compass->xoffset + size / 2 + ((size / 2 + 5) * sinf(tmp)), + compass->yoffset + size - ((size / 2 + 5) * cosf(tmp))); + + x = fsize[i]; + if (abs((guint) (compass->heading / 45) * 45 - compass->heading) + > abs((guint) (compass->heading / 45) * 45 + 45 - compass->heading) && (i > 0)) + x = fsize[i - 1]; + + pango_font_description_set_size(compass->fontdesc, (10 + x) * PANGO_SCALE); + pango_layout_set_font_description(compass->layout, compass->fontdesc); + pango_layout_set_text(compass->layout, text, -1); + pango_layout_get_pixel_size(compass->layout, &x, &y); + x = compass->xoffset + size / 2 + ((size / 2 + 15) * sinf(tmp)) - x / 2, + y = compass->yoffset + size - ((size / 2 + 15) * cosf(tmp)) - y / 2, + gdk_draw_layout(widget->window, + widget->style->fg_gc[GTK_STATE_NORMAL], + x, y, compass->layout); + + if (compass->dest_valid) { + gtk_compass_draw_mark(compass, compass->gc_d, compass->dest_heading); + } + + if (compass->way_valid) { + gtk_compass_draw_mark(compass, compass->gc_w, compass->way_heading); + } +} + +return TRUE; +} + + +static gint +gtk_compass_expose(GtkWidget *widget, GdkEventExpose *event) +{ +GtkCompass *compass; +g_return_val_if_fail(GTK_IS_COMPASS(widget), FALSE); +g_return_val_if_fail(event != NULL, FALSE); + +compass=GTK_COMPASS(widget); +gtk_compass_paint(compass); +return TRUE; +} + +void +gtk_compass_set_heading(GtkWidget *widget, gfloat heading) +{ +GtkCompass *compass; + +g_printf("%s()\n", __PRETTY_FUNCTION__); +g_return_if_fail(GTK_IS_COMPASS(widget)); + +compass=GTK_COMPASS(widget); + +compass->heading=heading; +gtk_compass_paint(compass); + +g_printf("%s(): return\n", __PRETTY_FUNCTION__); +} diff --git a/src/gtkcompass.h b/src/gtkcompass.h new file mode 100644 index 0000000..e33ada0 --- /dev/null +++ b/src/gtkcompass.h @@ -0,0 +1,67 @@ +/* + * GtkCompass: Display a compass with heading and target + * Copyright (C) 2007 Kaj-Michael Lang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef __GTK_COMPASS_H__ +#define __GTK_COMPASS_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_COMPASS_TYPE (gtk_compass_get_type ()) +#define GTK_COMPASS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_COMPASS_TYPE, GtkCompass)) +#define GTK_COMPASS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_COMPASS_TYPE, GtkCompassClass)) +#define GTK_IS_COMPASS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_COMPASS_TYPE)) + +#define GTK_COMPASS_SAT_MAX (12) + +typedef struct _GtkCompass GtkCompass; +typedef struct _GtkCompassClass GtkCompassClass; + +struct _GtkCompass { + GtkWidget parent; + GtkWidget widget; + GdkGC *gc_h; + GdkGC *gc_w; + GdkGC *gc_d; + guint width, height; + guint size; + guint xoffset, yoffset; + + PangoContext *context; + PangoLayout *layout; + PangoFontDescription *fontdesc; + + gfloat heading; + gfloat dest_heading; + gfloat way_heading; + gboolean dest_valid; + gboolean way_valid; +}; + +struct _GtkCompassClass { + GtkWidgetClass parent_class; +}; + +GType gtk_compass_get_type(void); +GtkWidget* gtk_compass_new(void); +void gtk_compass_set_heading(GtkWidget *widget, gfloat heading); + +G_END_DECLS + +#endif /* __GTK_COMPASS_H__ */ -- 2.39.5