Honeycombs: Difference between revisions

Content deleted Content added
Insert C solution using GTK3
Line 11: Line 11:
=={{header|C}}==
=={{header|C}}==


<lang C>

/* Program for gtk3 */
/* discovery: essential to use consistent documentation */
/* compilation on linux: */
/* $ a=./hexagon && make -k "CFLAGS=$( pkg-config --cflags gtk+-3.0 )" "LOADLIBES=$( pkg-config --libs gtk+-3.0 )" $a && $a --gtk-debug=all */
/* search for to do */
/* The keyboard and mouse callbacks increment the "selected" status, */
/* of the matching hexagon, */
/* then invalidate the drawing window which triggers a draw event. */
/* The draw callback redraws the screen and tests for completion, */
/* upon which the program spits back the characters selected and exits */

#include<math.h>
#include<string.h>
#include<stdlib.h>
#include<gtk/gtk.h>

static GdkPixbuf*create_pixbuf(const gchar*filename) {
GdkPixbuf*pixbuf;
GError*error = NULL;
pixbuf = gdk_pixbuf_new_from_file(filename, &error);
if(!pixbuf) {
fprintf(stderr,"\n%s\n", error->message);
g_error_free(error);
}
return pixbuf;
}

#define NGON struct ngon
NGON {
double Cx,Cy, r;
int sides, selected;
char c;
};

GRand*random_numbers = NULL;

#define R 20
#define TAU (2*M_PI) /* http://laughingsquid.com/pi-is-wrong/ */
#define OFFSET_X (1+sin(TAU/12))
#define OFFSET_Y (cos(TAU/12))
#define ODD(A) ((A)&1)

static void initialize_hexagons(NGON*hs,size_t n) {
NGON*h;
gint i,broken;
GQueue*shuffler = g_queue_new();
if (NULL == shuffler) {
fputs("\ncannot allocate shuffling queue. quitting!\n",stderr);
exit(EXIT_FAILURE);
}
/* randomize characters by stuffing them onto a double end queue
and popping them off from random positions */
if ((broken = (NULL == random_numbers)))
random_numbers = g_rand_new();
for (i = 'A'; i <= 'Z'; ++i)
g_queue_push_head(shuffler,GINT_TO_POINTER(i));
memset(hs,0,n*(sizeof(NGON)));
hs[n-1].sides = -1; /* assign the sentinel */
for (h = hs; !h->sides; ++h) {
int div = (h-hs)/4, mod = (h-hs)%4;
h->sides = 6;
h->c = GPOINTER_TO_INT(
g_queue_pop_nth(
shuffler,
g_rand_int_range(
random_numbers,
(gint32)0,
(gint32)g_queue_get_length(shuffler))));
fputc(h->c,stderr);
h->r = R;
h->Cx = R*(2+div*OFFSET_X), h->Cy = R*(2*(1+mod*OFFSET_Y)+ODD(div)*OFFSET_Y);
fprintf(stderr,"(%g,%g)\n",h->Cx,h->Cy);
}
fputc('\n',stderr);
g_queue_free(shuffler);
if (broken)
g_rand_free(random_numbers);
}

static void add_loop(cairo_t*cr,NGON*hs,int select) {
NGON*h;
double r,Cx,Cy,x,y;
int i, sides;
for (h = hs; 0 < (sides = h->sides); ++h)
if ((select && h->selected) || (select == h->selected)) {
r = h->r, Cx = h->Cx, Cy = h->Cy;
i = 0;
x = Cx+r*cos(TAU*i/sides), y = Cy+r*sin(TAU*i/sides), cairo_move_to(cr,x,y);
for (i = 1; i < sides; ++i)
x = Cx+r*cos(TAU*i/sides), y = Cy+r*sin(TAU*i/sides), cairo_line_to(cr,x,y);
cairo_close_path(cr);
}
}

static int make_labels(cairo_t*cr,NGON*hs,int select) {
NGON*h;
int i = 0;
char text[2];
text[1] = 0;
for (h = hs; 0 < h->sides; ++h)
if ((select && h->selected) || (select == h->selected))
/* yuck, need to measure the font. Better to use pango_cairo */
*text = h->c, cairo_move_to(cr,h->Cx,h->Cy), cairo_show_text(cr,text), ++i;
return i;
}

static int archive(int a) {
static GQueue*q = NULL;
if ((NULL == q) && (NULL == (q = g_queue_new()))) {
fputs("\ncannot allocate archival queue. quitting!\n",stderr);
exit(EXIT_FAILURE);
}
if (a < -1) /* reset */
return g_queue_free(q), q = NULL, 0;
if (-1 == a) /* pop off tail */
return g_queue_is_empty(q) ? 0 : GPOINTER_TO_INT(g_queue_pop_tail(q));
if (!a) /* peek most recent entry */
return g_queue_is_empty(q) ? 0 : GPOINTER_TO_INT(g_queue_peek_head(q));
g_queue_push_head(q,GINT_TO_POINTER(a)); /* store */
return a;
}

/* to do: use appropriate sizing, use the cairo transformation matrix */
static gboolean draw(GtkWidget*widget,cairo_t*cr,gpointer data) {

/* unselected fill in yellow */
cairo_set_source_rgba(cr,0.8,0.8,0,1),
add_loop(cr,(NGON*)data,0);
cairo_fill(cr);

/* selected fill, purple */
cairo_set_source_rgba(cr,0.8,0,0.8,1);
add_loop(cr,(NGON*)data,1);
cairo_fill_preserve(cr);

/* all outlines gray, background shows through, fun fun! */
cairo_set_line_width (cr, 3.0);
cairo_set_source_rgba(cr,0.7,0.7,0.7,0.7);
add_loop(cr,(NGON*)data,0);
cairo_stroke(cr);

/* select labels */
cairo_set_source_rgba(cr,0,1,0,1);
make_labels(cr,(NGON*)data,1);
cairo_stroke(cr);

/* unselected labels */
cairo_set_source_rgba(cr,1,0,0,1);
/* to do: clean up this exit code */
if (!make_labels(cr,(NGON*)data,0)) {
int c;
putchar('\n');
while ((c = archive(-1)))
putchar(c);
puts("\nfinished");
archive(-2);
exit(EXIT_SUCCESS);
}
cairo_stroke(cr);

return TRUE;
}

/*the widget is a GtkDrawingArea*/
static gboolean button_press_event(GtkWidget*widget,const GdkEvent*event,gpointer data) {
NGON*h,*hs = (NGON*)data;
gdouble x_win, y_win;
if (!gdk_event_get_coords(event,&x_win,&y_win))
fputs("\nBUTTON, gdk_event_get_coords(event,&x_win,&y_win)) failed\n",stderr);
else {
fprintf(stderr,"x_win=%g y_win=%g\n",(double)x_win,(double)y_win);
for (h = hs; 0 < h->sides; ++h) /* detection algorithm: */
/* if mouse click within inner radius of hexagon */
/* Much easier than all in-order cross products have same sign test! */
if ((pow((x_win-h->Cx),2)+pow((y_win-h->Cy),2)) < pow((h->r*cos(TAU/(180/h->sides))),2)) {
++h->selected;
archive(h->c);
/* discovery: gdk_window_invalidate_region with NULL second argument does not work */
gdk_window_invalidate_rect(gtk_widget_get_window(widget),(const GdkRectangle*)NULL,TRUE);
break;
}
}
return TRUE;
}

static gboolean key_press_event(GtkWidget*widget,const GdkEvent*event,gpointer data) {
NGON*h,*hs = (NGON*)data;
guint keyval;
int unicode;
if (!gdk_event_get_keyval(event,&keyval))
fputs("\nKEY! gdk_event_get_keyval(event,&keyval)) failed.\n",stderr);
else {
unicode = (int)gdk_keyval_to_unicode(gdk_keyval_to_upper(keyval));
fprintf(stderr,"key with unicode value: %d\n",unicode);
for (h = hs; 0 < h->sides; ++h) /* look for a matching character associated with a hexagon */
if (h->c == unicode) {
++(h->selected);
archive(h->c);
/* discovery: gdk_window_invalidate_region with NULL second argument does not work */
gdk_window_invalidate_rect(gtk_widget_get_window(widget),(const GdkRectangle*)NULL,TRUE);
break;
}
}
return TRUE;
}

int main(int argc,char*argv[]) {
GtkWidget *window, *vbox, /* *label, */ *drawing_area;
NGON ngons[21]; /* sentinal has negative number of sides */

/* discovery: gtk_init removes gtk debug flags, such as --gtk-debug=all */
/* also calls gdk_init which handles --display and --screen or other X11 communication issues */
gtk_init(&argc, &argv);

/* GTK VERSION 3.2.0 */
fprintf(stderr,"GTK VERSION %d.%d.%d\n",GTK_MAJOR_VERSION,GTK_MINOR_VERSION,GTK_MICRO_VERSION);

window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

/* discovery: to make window transparent I have to use the alpha channel correctly */

/* discovery: GTK_WINDOW(GtkWidget*) casts the widget to window */
/* discovery: window in the function name? use GTK_WINDOW. g_ in function name? use G_OBJECT */
gtk_window_set_title(GTK_WINDOW(window), "Rosetta Code Honeycomb, C with GTK");
gtk_window_set_default_size(GTK_WINDOW(window), 308, 308+12+8); /* XxY */
/* discovery: making the window vanish does not stop the program */
/* discovery: NULL is placeholder for extra data sent to the callback */
g_signal_connect_swapped(G_OBJECT(window),"destroy",G_CALLBACK(gtk_main_quit),NULL);

/* I created /tmp/favicon.ico from http://rosettacode.org/favicon.ico */
/* Your window manager could use the icon, if it exists, and you fix the file name */
gtk_window_set_icon(GTK_WINDOW(window),create_pixbuf("/tmp/favicon.ico"));

vbox = gtk_vbox_new(TRUE,1);
gtk_container_add(GTK_CONTAINER(window),vbox);

/* to do: fix the label widget */
/* I did not learn to control multiple box packing, and I was */
/* too lazy to make the label widget accessible. Plan was to */
/* insert the most recent character using "peek" option of the archive */
#if 0
label = gtk_label_new("None Selected");
gtk_widget_set_size_request(label,308,20);
gtk_box_pack_end(GTK_BOX(vbox),label,FALSE,TRUE,4);
#endif

drawing_area = gtk_drawing_area_new();
gtk_widget_set_events(drawing_area,GDK_BUTTON_PRESS_MASK|GDK_KEY_PRESS_MASK|GDK_EXPOSURE_MASK);

random_numbers = g_rand_new();
initialize_hexagons(ngons,G_N_ELEMENTS(ngons));
/* Discovery: expose_event changed to draw signal. We no longer need configure-event */
g_signal_connect(G_OBJECT(drawing_area),"draw",G_CALLBACK(draw),(gpointer)ngons);

g_signal_connect(G_OBJECT(drawing_area),"button-press-event",G_CALLBACK(button_press_event),(gpointer)ngons);
g_signal_connect(G_OBJECT(drawing_area),"key-press-event",G_CALLBACK(key_press_event),(gpointer)ngons);
gtk_widget_set_size_request(drawing_area, 308, 308); /* XxY */
gtk_box_pack_start(GTK_BOX(vbox),drawing_area,TRUE,TRUE,4);

/* Discovery: must allow focus to receive keyboard events */
gtk_widget_set_can_focus(drawing_area,TRUE);

/* Discovery: can set show for individual widgets or use show_all */
gtk_widget_show_all(window);
gtk_main();
g_rand_free(random_numbers);
return EXIT_SUCCESS;
}
</lang>


=={{header|Icon}} and {{header|Unicon}}==
=={{header|Icon}} and {{header|Unicon}}==