2 * gb.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
6 * Copyright 2014 Colomban Wendling <ban(at)herbesfolles(dot)org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 #include "gtkcompat.h"
34 #define BORDER_THIKNESS 4
35 #define HANDLE_THIKNESS 4
36 #define HANDLE_SHRINK 3
39 #define GEANY_TYPE_PONG (geany_pong_get_type())
40 #define GEANY_PONG(o) (G_TYPE_CHECK_INSTANCE_CAST((o), GEANY_TYPE_PONG, GeanyPong))
41 #define GEANY_IS_PONG(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), GEANY_TYPE_PONG))
44 typedef struct _GeanyPong GeanyPong
;
45 typedef struct _GeanyPongClass GeanyPongClass
;
50 /* no need for private data as the whole thing is private */
52 GtkWidget
*score_label
;
69 struct _GeanyPongClass
71 GtkDialogClass parent_class
;
75 static void geany_pong_finalize(GObject
*obj
);
76 static void geany_pong_response(GtkDialog
*self
, gint response
);
77 static GType
geany_pong_get_type(void) G_GNUC_CONST
;
80 G_DEFINE_TYPE(GeanyPong
, geany_pong
, GTK_TYPE_DIALOG
)
83 #if GTK_CHECK_VERSION(3, 0, 0)
84 static void geany_pong_set_cairo_source_color(cairo_t
*cr
, GdkRGBA
*c
, gdouble a
)
86 cairo_set_source_rgba(cr
, c
->red
, c
->green
, c
->blue
, MIN(c
->alpha
, a
));
89 static void geany_pong_set_cairo_source_color(cairo_t
*cr
, GdkColor
*c
, gdouble a
)
91 cairo_set_source_rgba(cr
, c
->red
/65535.0, c
->green
/65535.0, c
->blue
/65535.0, a
);
96 static gboolean
geany_pong_area_draw(GtkWidget
*area
, cairo_t
*cr
, GeanyPong
*self
)
98 #if GTK_CHECK_VERSION(3, 0, 0)
99 /* we use the window style context because the area one has a transparent
100 * background and we want something to paint for the overlay */
101 GtkStyleContext
*ctx
= gtk_widget_get_style_context(GTK_WIDGET(self
));
104 gtk_style_context_get_color(ctx
, GTK_STATE_FLAG_ACTIVE
, &fg
);
105 gtk_style_context_get_background_color(ctx
, GTK_STATE_FLAG_BACKDROP
, &bg
);
107 GtkStyle
*style
= gtk_widget_get_style(area
);
108 GdkColor fg
= style
->fg
[GTK_STATE_NORMAL
];
109 GdkColor bg
= style
->bg
[GTK_STATE_NORMAL
];
112 self
->area_width
= gtk_widget_get_allocated_width(area
);
113 self
->area_height
= gtk_widget_get_allocated_height(area
);
115 cairo_set_line_width(cr
, BORDER_THIKNESS
);
117 /* draw the border */
118 cairo_rectangle(cr
, BORDER_THIKNESS
/2, BORDER_THIKNESS
/2,
119 self
->area_width
- BORDER_THIKNESS
, self
->area_height
/* we don't wanna see the bottom */);
120 geany_pong_set_cairo_source_color(cr
, &fg
, 1.0);
123 /* draw the handle */
124 cairo_rectangle(cr
, self
->handle_pos
- self
->handle_width
/2, self
->area_height
- HANDLE_THIKNESS
,
125 self
->handle_width
, HANDLE_THIKNESS
);
129 cairo_arc(cr
, self
->ball_pos
[0], self
->ball_pos
[1], BALL_SIZE
, 0, 2*G_PI
);
132 /* if not running, add an info */
133 if (! self
->source_id
|| self
->handle_width
< 1)
139 geany_pong_set_cairo_source_color(cr
, &bg
, 0.8);
140 cairo_rectangle(cr
, 0, 0, self
->area_width
, self
->area_height
);
143 geany_pong_set_cairo_source_color(cr
, &fg
, 1.0);
144 layout
= pango_cairo_create_layout(cr
);
145 #if GTK_CHECK_VERSION(3, 0, 0)
146 PangoFontDescription
*font
= NULL
;
147 gtk_style_context_get(ctx
, GTK_STATE_FLAG_NORMAL
, GTK_STYLE_PROPERTY_FONT
, &font
, NULL
);
150 pango_layout_set_font_description(layout
, font
);
151 pango_font_description_free(font
);
154 pango_layout_set_font_description(layout
, style
->font_desc
);
156 if (! self
->handle_width
)
157 pango_layout_set_markup(layout
, "<b>You won!</b>\n<small>OK, go back to work now.</small>", -1);
159 pango_layout_set_text(layout
, "Click to Play", -1);
160 pango_layout_set_alignment(layout
, PANGO_ALIGN_CENTER
);
161 pango_layout_get_pixel_size(layout
, &pw
, &ph
);
163 scale
= MIN(0.9 * self
->area_width
/ pw
, 0.9 * self
->area_height
/ ph
);
164 cairo_move_to(cr
, (self
->area_width
- pw
* scale
) / 2, (self
->area_height
- ph
* scale
) / 2);
165 cairo_scale(cr
, scale
, scale
);
166 pango_cairo_show_layout(cr
, layout
);
168 g_object_unref(layout
);
175 #if ! GTK_CHECK_VERSION(3, 0, 0)
176 static gboolean
geany_pong_area_expose(GtkWidget
*area
, GdkEventExpose
*event
, GeanyPong
*self
)
178 cairo_t
*cr
= gdk_cairo_create(gtk_widget_get_window(area
));
181 ret
= geany_pong_area_draw(area
, cr
, self
);
189 static void geany_pong_reset_ball(GeanyPong
*self
)
191 self
->ball_speed
= 5;
192 self
->ball_pos
[0] = self
->area_width
/ 2;
193 self
->ball_pos
[1] = self
->area_height
/ 2;
194 self
->ball_vec
[0] = g_random_double_range(.2, .8);
195 self
->ball_vec
[1] = 1.0 - self
->ball_vec
[0];
196 if (g_random_boolean())
197 self
->ball_vec
[0] *= -1;
201 static void geany_pong_update_score(GeanyPong
*self
)
205 g_snprintf(buf
, sizeof buf
, "%u", self
->score
);
206 gtk_label_set_text(GTK_LABEL(self
->score_label
), buf
);
210 static gboolean
geany_pong_area_timeout(gpointer data
)
212 GeanyPong
*self
= data
;
213 const gdouble x
= BORDER_THIKNESS
+ BALL_SIZE
/2;
214 const gdouble y
= BORDER_THIKNESS
+ BALL_SIZE
/2;
215 const gdouble w
= self
->area_width
- BORDER_THIKNESS
- BALL_SIZE
/2;
216 const gdouble h
= self
->area_height
- HANDLE_THIKNESS
- BALL_SIZE
/2;
217 const gdouble old_ball_pos
[2] = { self
->ball_pos
[0], self
->ball_pos
[1] };
218 const gdouble step
[2] = { self
->ball_speed
* self
->ball_vec
[0],
219 self
->ball_speed
* self
->ball_vec
[1] };
222 if (self
->ball_pos
[0] + step
[0] >= w
||
223 self
->ball_pos
[0] + step
[0] <= x
)
224 self
->ball_vec
[0] = -self
->ball_vec
[0];
226 if (self
->ball_pos
[1] + step
[1] <= y
)
227 self
->ball_vec
[1] = -self
->ball_vec
[1];
229 if (self
->ball_pos
[1] + step
[1] >= h
)
231 if (self
->ball_pos
[0] + step
[0] >= self
->handle_pos
- self
->handle_width
/2 &&
232 self
->ball_pos
[0] + step
[0] <= self
->handle_pos
+ self
->handle_width
/2 &&
233 /* only bounce *above* the handle, not below */
234 self
->ball_pos
[1] <= h
)
236 self
->score
+= self
->ball_speed
* 2;
237 geany_pong_update_score(self
);
239 self
->ball_vec
[1] = -self
->ball_vec
[1];
241 self
->handle_width
-= HANDLE_SHRINK
;
242 /* we don't allow a handle smaller than a shrink step */
243 if (self
->handle_width
< HANDLE_SHRINK
)
245 self
->handle_width
= 0;
249 /* let the ball fall completely off before losing */
250 else if (self
->ball_pos
[1] + step
[1] >= self
->area_height
+ BALL_SIZE
)
253 geany_pong_reset_ball(self
);
259 self
->ball_pos
[0] += self
->ball_speed
* self
->ball_vec
[0];
260 self
->ball_pos
[1] += self
->ball_speed
* self
->ball_vec
[1];
263 if (! self
->source_id
)
265 /* we will draw a text all over, just invalidate everything */
266 gtk_widget_queue_draw(self
->area
);
270 /* compute the rough bounding box to redraw the ball */
272 (gint
) MIN(self
->ball_pos
[0], old_ball_pos
[0]) - BALL_SIZE
- 1,
273 (gint
) MIN(self
->ball_pos
[1], old_ball_pos
[1]) - BALL_SIZE
- 1,
274 (gint
) MAX(self
->ball_pos
[0], old_ball_pos
[0]) + BALL_SIZE
+ 1,
275 (gint
) MAX(self
->ball_pos
[1], old_ball_pos
[1]) + BALL_SIZE
+ 1
278 gtk_widget_queue_draw_area(self
->area
, bb
[0], bb
[1], bb
[2] - bb
[0], bb
[3] - bb
[1]);
279 /* always redraw the handle in case it has moved */
280 gtk_widget_queue_draw_area(self
->area
,
281 BORDER_THIKNESS
, self
->area_height
- HANDLE_THIKNESS
,
282 self
->area_width
- BORDER_THIKNESS
*2, HANDLE_THIKNESS
);
285 return self
->source_id
!= 0;
289 static gboolean
geany_pong_area_button_press(GtkWidget
*area
, GdkEventButton
*event
, GeanyPong
*self
)
291 if (event
->type
== GDK_BUTTON_PRESS
&&
292 self
->handle_width
> 0)
294 if (! self
->source_id
)
295 self
->source_id
= g_timeout_add(1000/60, geany_pong_area_timeout
, self
);
298 g_source_remove(self
->source_id
);
301 gtk_widget_queue_draw(area
);
309 static gboolean
geany_pong_area_motion_notify(GtkWidget
*area
, GdkEventMotion
*event
, GeanyPong
*self
)
311 self
->handle_pos
= (gint
) event
->x
;
312 /* clamp so the handle is always fully in */
313 if (self
->handle_pos
< self
->handle_width
/2 + BORDER_THIKNESS
)
314 self
->handle_pos
= self
->handle_width
/2 + BORDER_THIKNESS
;
315 else if (self
->handle_pos
> self
->area_width
- self
->handle_width
/2 - BORDER_THIKNESS
)
316 self
->handle_pos
= self
->area_width
- self
->handle_width
/2 - BORDER_THIKNESS
;
322 static void geany_pong_class_init(GeanyPongClass
*klass
)
324 GObjectClass
*object_class
= G_OBJECT_CLASS(klass
);
325 GtkDialogClass
*dialog_class
= GTK_DIALOG_CLASS(klass
);
327 object_class
->finalize
= geany_pong_finalize
;
328 dialog_class
->response
= geany_pong_response
;
332 static void geany_pong_init(GeanyPong
*self
)
340 self
->area_height
= AREA_SIZE
;
341 self
->area_width
= AREA_SIZE
;
342 self
->handle_width
= self
->area_width
/ 2;
343 self
->handle_pos
= self
->area_width
/ 2;
344 geany_pong_reset_ball(self
);
346 gtk_window_set_title(GTK_WINDOW(self
), "Happy Easter!");
347 gtk_window_set_position(GTK_WINDOW(self
), GTK_WIN_POS_CENTER_ON_PARENT
);
348 gtk_window_set_destroy_with_parent(GTK_WINDOW(self
), TRUE
);
349 gtk_window_set_modal(GTK_WINDOW(self
), TRUE
);
350 gtk_window_set_skip_pager_hint(GTK_WINDOW(self
), TRUE
);
351 gtk_window_set_resizable(GTK_WINDOW(self
), FALSE
);
353 vbox
= gtk_vbox_new(FALSE
, 0);
354 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 5);
355 gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(self
))), vbox
, TRUE
, TRUE
, 0);
357 hbox
= gtk_hbox_new(FALSE
, 6);
358 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, FALSE
, FALSE
, 0);
360 label
= gtk_label_new("Score:");
361 gtk_misc_set_alignment(GTK_MISC(label
), 1.0, 0.5);
362 gtk_box_pack_start(GTK_BOX(hbox
), label
, TRUE
, TRUE
, 0);
364 self
->score_label
= gtk_label_new("0");
365 gtk_box_pack_start(GTK_BOX(hbox
), self
->score_label
, FALSE
, FALSE
, 0);
367 self
->area
= gtk_drawing_area_new();
368 gtk_widget_add_events(self
->area
, GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK
);
369 #if GTK_CHECK_VERSION(3, 0, 0)
370 g_signal_connect(self
->area
, "draw", G_CALLBACK(geany_pong_area_draw
), self
);
372 g_signal_connect(self
->area
, "expose-event", G_CALLBACK(geany_pong_area_expose
), self
);
374 g_signal_connect(self
->area
, "button-press-event", G_CALLBACK(geany_pong_area_button_press
), self
);
375 g_signal_connect(self
->area
, "motion-notify-event", G_CALLBACK(geany_pong_area_motion_notify
), self
);
376 gtk_widget_set_size_request(self
->area
, AREA_SIZE
, AREA_SIZE
);
377 gtk_box_pack_start(GTK_BOX(vbox
), self
->area
, TRUE
, TRUE
, 0);
379 gtk_dialog_add_buttons(GTK_DIALOG(self
),
380 GTK_STOCK_HELP
, GTK_RESPONSE_HELP
,
381 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
,
383 gtk_dialog_set_default_response(GTK_DIALOG(self
), GTK_RESPONSE_HELP
);
384 gtk_widget_grab_focus(gtk_dialog_get_widget_for_response(GTK_DIALOG(self
), GTK_RESPONSE_HELP
));
386 gtk_widget_show_all(vbox
);
390 static void geany_pong_finalize(GObject
*obj
)
392 GeanyPong
*self
= GEANY_PONG(obj
);
395 g_source_remove(self
->source_id
);
397 G_OBJECT_CLASS(geany_pong_parent_class
)->finalize(obj
);
401 static void geany_pong_help(GeanyPong
*self
)
405 GtkWidget
*scrolledwindow
;
407 GtkTextBuffer
*buffer
;
409 dialog
= gtk_dialog_new_with_buttons("Help", GTK_WINDOW(self
),
410 GTK_DIALOG_DESTROY_WITH_PARENT
| GTK_DIALOG_MODAL
,
411 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
, NULL
);
412 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_CLOSE
);
413 gtk_container_set_border_width(GTK_CONTAINER(dialog
), 1);
414 gtk_window_set_type_hint(GTK_WINDOW(dialog
), GDK_WINDOW_TYPE_HINT_DIALOG
);
416 vbox
= gtk_dialog_get_content_area(GTK_DIALOG(dialog
));
418 scrolledwindow
= gtk_scrolled_window_new(NULL
, NULL
);
419 gtk_box_pack_start(GTK_BOX(vbox
), scrolledwindow
, TRUE
, TRUE
, 0);
420 gtk_container_set_border_width(GTK_CONTAINER(scrolledwindow
), 5);
421 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow
), GTK_POLICY_NEVER
, GTK_POLICY_NEVER
);
422 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwindow
), GTK_SHADOW_IN
);
424 textview
= gtk_text_view_new();
425 gtk_container_add(GTK_CONTAINER(scrolledwindow
), textview
);
426 gtk_widget_set_size_request(textview
, 450, -1);
427 gtk_text_view_set_editable(GTK_TEXT_VIEW(textview
), FALSE
);
428 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview
), GTK_WRAP_WORD
);
429 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(textview
), FALSE
);
430 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(textview
), 2);
431 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(textview
), 2);
433 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview
));
434 gtk_text_buffer_set_text(buffer
,
435 "A small Pong-like\n"
437 "Click to start, and then bounce the ball off the walls without it "
438 "falling down the bottom edge. You control the bottom handle with "
439 "the mouse, but beware! the ball goes faster and faster and the "
440 "handle grows smaller and smaller!", -1);
442 gtk_widget_show_all(dialog
);
443 gtk_dialog_run(GTK_DIALOG(dialog
));
444 gtk_widget_destroy(dialog
);
448 static void geany_pong_response(GtkDialog
*self
, gint response
)
450 g_return_if_fail(GEANY_IS_PONG(self
));
454 case GTK_RESPONSE_HELP
:
455 geany_pong_help(GEANY_PONG(self
));
459 gtk_widget_destroy(GTK_WIDGET(self
));
464 static GtkWidget
*geany_pong_new(GtkWindow
*parent
)
466 return g_object_new(GEANY_TYPE_PONG
, "transient-for", parent
, NULL
);
470 static gboolean
gb_on_key_pressed(GtkWidget
*widget
, GdkEventKey
*event
, gpointer user_data
)
472 static gchar text
[] = "geany";
474 if (event
->keyval
< 0x80)
476 memmove (text
, &text
[1], G_N_ELEMENTS(text
) - 1);
477 text
[G_N_ELEMENTS(text
) - 2] = (gchar
) event
->keyval
;
479 if (utils_str_equal(text
, "geany"))
481 GtkWidget
*pong
= geany_pong_new(GTK_WINDOW(widget
));
482 gtk_widget_show(pong
);