1 /*
2  *      gb.c - this file is part of Geany, a fast and lightweight IDE
3  *
4  *      Copyright 2005 The Geany contributors
5  *
6  *      This program is free software; you can redistribute it and/or modify
7  *      it under the terms of the GNU General Public License as published by
8  *      the Free Software Foundation; either version 2 of the License, or
9  *      (at your option) any later version.
10  *
11  *      This program is distributed in the hope that it will be useful,
12  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *      GNU General Public License for more details.
15  *
16  *      You should have received a copy of the GNU General Public License along
17  *      with this program; if not, write to the Free Software Foundation, Inc.,
18  *      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 /*
22  * A small Pong-like.
23  */
24 
25 #include "utils.h"
26 
27 #include <gtk/gtk.h>
28 
29 
30 #define AREA_SIZE 300
31 #define BALL_SIZE 4
32 #define BORDER_THIKNESS 4
33 #define HANDLE_THIKNESS 4
34 #define HANDLE_SHRINK 3
35 
36 
37 #define GEANY_TYPE_PONG		(geany_pong_get_type())
38 #define GEANY_PONG(o)		(G_TYPE_CHECK_INSTANCE_CAST((o), GEANY_TYPE_PONG, GeanyPong))
39 #define GEANY_IS_PONG(o)	(G_TYPE_CHECK_INSTANCE_TYPE((o), GEANY_TYPE_PONG))
40 
41 
42 typedef struct _GeanyPong GeanyPong;
43 typedef struct _GeanyPongClass GeanyPongClass;
44 
45 struct _GeanyPong
46 {
47 	GtkDialog parent;
48 	/* no need for private data as the whole thing is private */
49 
50 	GtkWidget *score_label;
51 	GtkWidget *area;
52 
53 	gint area_height;
54 	gint area_width;
55 
56 	guint ball_speed;
57 	gdouble ball_pos[2];
58 	gdouble ball_vec[2];
59 	gint handle_width;
60 	gint handle_pos;
61 
62 	guint score;
63 
64 	guint source_id;
65 };
66 
67 struct _GeanyPongClass
68 {
69 	GtkDialogClass parent_class;
70 };
71 
72 
73 static void geany_pong_finalize(GObject *obj);
74 static void geany_pong_response(GtkDialog *self, gint response);
75 static GType geany_pong_get_type(void) G_GNUC_CONST;
76 
77 
G_DEFINE_TYPE(GeanyPong,geany_pong,GTK_TYPE_DIALOG)78 G_DEFINE_TYPE(GeanyPong, geany_pong, GTK_TYPE_DIALOG)
79 
80 
81 static void geany_pong_set_cairo_source_color(cairo_t *cr, GdkRGBA *c, gdouble a)
82 {
83 	cairo_set_source_rgba(cr, c->red, c->green, c->blue, MIN(c->alpha, a));
84 }
85 
86 
geany_pong_area_draw(GtkWidget * area,cairo_t * cr,GeanyPong * self)87 static gboolean geany_pong_area_draw(GtkWidget *area, cairo_t *cr, GeanyPong *self)
88 {
89 	/* we use the window style context because the area one has a transparent
90 	 * background and we want something to paint for the overlay */
91 	GtkStyleContext *ctx = gtk_widget_get_style_context(GTK_WIDGET(self));
92 	GtkStateFlags state = gtk_style_context_get_state(ctx);
93 	GdkRGBA fg, bg;
94 
95 	gtk_style_context_get_color(ctx, state, &fg);
96 	gtk_style_context_get_background_color(ctx, state, &bg);
97 
98 	self->area_width = gtk_widget_get_allocated_width(area);
99 	self->area_height = gtk_widget_get_allocated_height(area);
100 
101 	cairo_set_line_width(cr, BORDER_THIKNESS);
102 
103 	/* draw the border */
104 	cairo_rectangle(cr, BORDER_THIKNESS/2, BORDER_THIKNESS/2,
105 			self->area_width - BORDER_THIKNESS, self->area_height /* we don't wanna see the bottom */);
106 	geany_pong_set_cairo_source_color(cr, &fg, 1.0);
107 	cairo_stroke(cr);
108 
109 	/* draw the handle */
110 	cairo_rectangle(cr, self->handle_pos - self->handle_width/2, self->area_height - HANDLE_THIKNESS,
111 						self->handle_width, HANDLE_THIKNESS);
112 	cairo_fill(cr);
113 
114 	/* draw the ball */
115 	cairo_arc(cr, self->ball_pos[0], self->ball_pos[1], BALL_SIZE, 0, 2*G_PI);
116 	cairo_fill(cr);
117 
118 	/* if not running, add an info */
119 	if (! self->source_id || self->handle_width < 1)
120 	{
121 		PangoLayout *layout;
122 		gint pw, ph;
123 		gdouble scale;
124 		PangoFontDescription *font = NULL;
125 
126 		geany_pong_set_cairo_source_color(cr, &bg, 0.8);
127 		cairo_rectangle(cr, 0, 0, self->area_width, self->area_height);
128 		cairo_paint(cr);
129 
130 		geany_pong_set_cairo_source_color(cr, &fg, 1.0);
131 		layout = pango_cairo_create_layout(cr);
132 
133 		gtk_style_context_get(ctx, state, GTK_STYLE_PROPERTY_FONT, &font, NULL);
134 		if (font)
135 		{
136 			pango_layout_set_font_description(layout, font);
137 			pango_font_description_free(font);
138 		}
139 
140 		if (! self->handle_width)
141 			pango_layout_set_markup(layout, "<b>You won!</b>\n<small>OK, go back to work now.</small>", -1);
142 		else
143 			pango_layout_set_text(layout, "Click to Play", -1);
144 		pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
145 		pango_layout_get_pixel_size(layout, &pw, &ph);
146 
147 		scale = MIN(0.9 * self->area_width / pw, 0.9 * self->area_height / ph);
148 		cairo_move_to(cr, (self->area_width - pw * scale) / 2, (self->area_height - ph * scale) / 2);
149 		cairo_scale(cr, scale, scale);
150 		pango_cairo_show_layout(cr, layout);
151 
152 		g_object_unref(layout);
153 	}
154 
155 	return TRUE;
156 }
157 
158 
geany_pong_reset_ball(GeanyPong * self)159 static void geany_pong_reset_ball(GeanyPong *self)
160 {
161 	self->ball_speed = 5;
162 	self->ball_pos[0] = self->area_width / 2;
163 	self->ball_pos[1] = self->area_height / 2;
164 	self->ball_vec[0] = g_random_double_range(.2, .8);
165 	self->ball_vec[1] = 1.0 - self->ball_vec[0];
166 	if (g_random_boolean())
167 		self->ball_vec[0] *= -1;
168 }
169 
170 
geany_pong_update_score(GeanyPong * self)171 static void geany_pong_update_score(GeanyPong *self)
172 {
173 	gchar buf[16];
174 
175 	g_snprintf(buf, sizeof buf, "%u", self->score);
176 	gtk_label_set_text(GTK_LABEL(self->score_label), buf);
177 }
178 
179 
geany_pong_area_timeout(gpointer data)180 static gboolean geany_pong_area_timeout(gpointer data)
181 {
182 	GeanyPong *self = data;
183 	const gdouble x = BORDER_THIKNESS + BALL_SIZE/2;
184 	const gdouble y = BORDER_THIKNESS + BALL_SIZE/2;
185 	const gdouble w = self->area_width - BORDER_THIKNESS - BALL_SIZE/2;
186 	const gdouble h = self->area_height - HANDLE_THIKNESS - BALL_SIZE/2;
187 	const gdouble old_ball_pos[2] = { self->ball_pos[0], self->ball_pos[1] };
188 	const gdouble step[2] = { self->ball_speed * self->ball_vec[0],
189 							  self->ball_speed * self->ball_vec[1] };
190 
191 	/* left & right */
192 	if (self->ball_pos[0] + step[0] >= w ||
193 		self->ball_pos[0] + step[0] <= x)
194 		self->ball_vec[0] = -self->ball_vec[0];
195 	/* top */
196 	if (self->ball_pos[1] + step[1] <= y)
197 		self->ball_vec[1] = -self->ball_vec[1];
198 	/* bottom */
199 	if (self->ball_pos[1] + step[1] >= h)
200 	{
201 		if (self->ball_pos[0] + step[0] >= self->handle_pos - self->handle_width/2 &&
202 			self->ball_pos[0] + step[0] <= self->handle_pos + self->handle_width/2 &&
203 			/* only bounce *above* the handle, not below */
204 			self->ball_pos[1] <= h)
205 		{
206 			self->score += self->ball_speed * 2;
207 			geany_pong_update_score(self);
208 
209 			self->ball_vec[1] = -self->ball_vec[1];
210 			self->ball_speed++;
211 			self->handle_width -= HANDLE_SHRINK;
212 			/* we don't allow a handle smaller than a shrink step */
213 			if (self->handle_width < HANDLE_SHRINK)
214 			{
215 				self->handle_width = 0;
216 				self->source_id = 0;
217 			}
218 		}
219 		/* let the ball fall completely off before losing */
220 		else if (self->ball_pos[1] + step[1] >= self->area_height + BALL_SIZE)
221 		{	/* lost! */
222 			self->source_id = 0;
223 			geany_pong_reset_ball(self);
224 		}
225 	}
226 
227 	if (self->source_id)
228 	{
229 		self->ball_pos[0] += self->ball_speed * self->ball_vec[0];
230 		self->ball_pos[1] += self->ball_speed * self->ball_vec[1];
231 	}
232 
233 	if (! self->source_id)
234 	{
235 		/* we will draw a text all over, just invalidate everything */
236 		gtk_widget_queue_draw(self->area);
237 	}
238 	else
239 	{
240 		/* compute the rough bounding box to redraw the ball */
241 		const gint bb[4] = {
242 			(gint) MIN(self->ball_pos[0], old_ball_pos[0]) - BALL_SIZE - 1,
243 			(gint) MIN(self->ball_pos[1], old_ball_pos[1]) - BALL_SIZE - 1,
244 			(gint) MAX(self->ball_pos[0], old_ball_pos[0]) + BALL_SIZE + 1,
245 			(gint) MAX(self->ball_pos[1], old_ball_pos[1]) + BALL_SIZE + 1
246 		};
247 
248 		gtk_widget_queue_draw_area(self->area, bb[0], bb[1], bb[2] - bb[0], bb[3] - bb[1]);
249 		/* always redraw the handle in case it has moved */
250 		gtk_widget_queue_draw_area(self->area,
251 				BORDER_THIKNESS, self->area_height - HANDLE_THIKNESS,
252 				self->area_width - BORDER_THIKNESS*2, HANDLE_THIKNESS);
253 	}
254 
255 	return self->source_id != 0;
256 }
257 
258 
geany_pong_area_button_press(GtkWidget * area,GdkEventButton * event,GeanyPong * self)259 static gboolean geany_pong_area_button_press(GtkWidget *area, GdkEventButton *event, GeanyPong *self)
260 {
261 	if (event->type == GDK_BUTTON_PRESS &&
262 		self->handle_width > 0)
263 	{
264 		if (! self->source_id)
265 			self->source_id = g_timeout_add(1000/60, geany_pong_area_timeout, self);
266 		else
267 		{
268 			g_source_remove(self->source_id);
269 			self->source_id = 0;
270 		}
271 		gtk_widget_queue_draw(area);
272 		return TRUE;
273 	}
274 
275 	return FALSE;
276 }
277 
278 
geany_pong_area_motion_notify(GtkWidget * area,GdkEventMotion * event,GeanyPong * self)279 static gboolean geany_pong_area_motion_notify(GtkWidget *area, GdkEventMotion *event, GeanyPong *self)
280 {
281 	self->handle_pos = (gint) event->x;
282 	/* clamp so the handle is always fully in */
283 	if (self->handle_pos < self->handle_width/2 + BORDER_THIKNESS)
284 		self->handle_pos = self->handle_width/2 + BORDER_THIKNESS;
285 	else if (self->handle_pos > self->area_width - self->handle_width/2 - BORDER_THIKNESS)
286 		self->handle_pos = self->area_width - self->handle_width/2 - BORDER_THIKNESS;
287 
288 	return TRUE;
289 }
290 
291 
geany_pong_class_init(GeanyPongClass * klass)292 static void geany_pong_class_init(GeanyPongClass *klass)
293 {
294 	GObjectClass *object_class = G_OBJECT_CLASS(klass);
295 	GtkDialogClass *dialog_class = GTK_DIALOG_CLASS(klass);
296 
297 	object_class->finalize = geany_pong_finalize;
298 	dialog_class->response = geany_pong_response;
299 }
300 
301 
geany_pong_init(GeanyPong * self)302 static void geany_pong_init(GeanyPong *self)
303 {
304 	GtkWidget *vbox;
305 	GtkWidget *hbox;
306 	GtkWidget *label;
307 
308 	self->score = 0;
309 	self->source_id = 0;
310 	self->area_height = AREA_SIZE;
311 	self->area_width = AREA_SIZE;
312 	self->handle_width = self->area_width / 2;
313 	self->handle_pos = self->area_width / 2;
314 	geany_pong_reset_ball(self);
315 
316 	gtk_window_set_title(GTK_WINDOW(self), "Happy Easter!");
317 	gtk_window_set_position(GTK_WINDOW(self), GTK_WIN_POS_CENTER_ON_PARENT);
318 	gtk_window_set_destroy_with_parent(GTK_WINDOW(self), TRUE);
319 	gtk_window_set_modal(GTK_WINDOW(self), TRUE);
320 	gtk_window_set_skip_pager_hint(GTK_WINDOW(self), TRUE);
321 	gtk_window_set_resizable(GTK_WINDOW(self), FALSE);
322 
323 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
324 	gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
325 	gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(self))), vbox, TRUE, TRUE, 0);
326 
327 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
328 	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
329 
330 	label = gtk_label_new("Score:");
331 	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
332 	gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
333 
334 	self->score_label = gtk_label_new("0");
335 	gtk_box_pack_start(GTK_BOX(hbox), self->score_label, FALSE, FALSE, 0);
336 
337 	self->area = gtk_drawing_area_new();
338 	gtk_widget_add_events(self->area, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
339 	g_signal_connect(self->area, "draw", G_CALLBACK(geany_pong_area_draw), self);
340 	g_signal_connect(self->area, "button-press-event", G_CALLBACK(geany_pong_area_button_press), self);
341 	g_signal_connect(self->area, "motion-notify-event", G_CALLBACK(geany_pong_area_motion_notify), self);
342 	gtk_widget_set_size_request(self->area, AREA_SIZE, AREA_SIZE);
343 	gtk_box_pack_start(GTK_BOX(vbox), self->area, TRUE, TRUE, 0);
344 
345 	gtk_dialog_add_buttons(GTK_DIALOG(self),
346 		GTK_STOCK_HELP, GTK_RESPONSE_HELP,
347 		GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
348 		NULL);
349 	gtk_dialog_set_default_response(GTK_DIALOG(self), GTK_RESPONSE_HELP);
350 	gtk_widget_grab_focus(gtk_dialog_get_widget_for_response(GTK_DIALOG(self), GTK_RESPONSE_HELP));
351 
352 	gtk_widget_show_all(vbox);
353 }
354 
355 
geany_pong_finalize(GObject * obj)356 static void geany_pong_finalize(GObject *obj)
357 {
358 	GeanyPong *self = GEANY_PONG(obj);
359 
360 	if (self->source_id)
361 		g_source_remove(self->source_id);
362 
363 	G_OBJECT_CLASS(geany_pong_parent_class)->finalize(obj);
364 }
365 
366 
geany_pong_help(GeanyPong * self)367 static void geany_pong_help(GeanyPong *self)
368 {
369 	GtkWidget *dialog;
370 	GtkWidget *vbox;
371 	GtkWidget *scrolledwindow;
372 	GtkWidget *textview;
373 	GtkTextBuffer *buffer;
374 
375 	dialog = gtk_dialog_new_with_buttons("Help", GTK_WINDOW(self),
376 		GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
377 		GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
378 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
379 	gtk_container_set_border_width(GTK_CONTAINER(dialog), 1);
380 	gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
381 
382 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
383 
384 	scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
385 	gtk_box_pack_start(GTK_BOX(vbox), scrolledwindow, TRUE, TRUE, 0);
386 	gtk_container_set_border_width(GTK_CONTAINER(scrolledwindow), 5);
387 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
388 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_SHADOW_IN);
389 
390 	textview = gtk_text_view_new();
391 	gtk_container_add(GTK_CONTAINER(scrolledwindow), textview);
392 	gtk_widget_set_size_request(textview, 450, -1);
393 	gtk_text_view_set_editable(GTK_TEXT_VIEW(textview), FALSE);
394 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_WORD);
395 	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview), FALSE);
396 	gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview), 2);
397 	gtk_text_view_set_right_margin(GTK_TEXT_VIEW(textview), 2);
398 
399 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
400 	gtk_text_buffer_set_text(buffer,
401 		"A small Pong-like\n"
402 		"\n"
403 		"Click to start, and then bounce the ball off the walls without it "
404 		"falling down the bottom edge. You control the bottom handle with "
405 		"the mouse, but beware! the ball goes faster and faster and the "
406 		"handle grows smaller and smaller!", -1);
407 
408 	gtk_widget_show_all(dialog);
409 	gtk_dialog_run(GTK_DIALOG(dialog));
410 	gtk_widget_destroy(dialog);
411 }
412 
413 
geany_pong_response(GtkDialog * self,gint response)414 static void geany_pong_response(GtkDialog *self, gint response)
415 {
416 	g_return_if_fail(GEANY_IS_PONG(self));
417 
418 	switch (response)
419 	{
420 		case GTK_RESPONSE_HELP:
421 			geany_pong_help(GEANY_PONG(self));
422 			break;
423 
424 		default:
425 			gtk_widget_destroy(GTK_WIDGET(self));
426 	}
427 }
428 
429 
geany_pong_new(GtkWindow * parent)430 static GtkWidget *geany_pong_new(GtkWindow *parent)
431 {
432 	return g_object_new(GEANY_TYPE_PONG, "transient-for", parent, NULL);
433 }
434 
435 
gb_on_key_pressed(GtkWidget * widget,GdkEventKey * event,gpointer user_data)436 static gboolean gb_on_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
437 {
438 	static gchar text[] = "geany";
439 
440 	if (event->keyval < 0x80)
441 	{
442 		memmove (text, &text[1], G_N_ELEMENTS(text) - 1);
443 		text[G_N_ELEMENTS(text) - 2] = (gchar) event->keyval;
444 
445 		if (utils_str_equal(text, "geany"))
446 		{
447 			GtkWidget *pong = geany_pong_new(GTK_WINDOW(widget));
448 			gtk_widget_show(pong);
449 			return TRUE;
450 		}
451 	}
452 
453 	return FALSE;
454 }
455