Merge pull request #3572 from techee/document_before_save_as
[geany-mirror.git] / src / gb.c
blob8c54c20ca3fed9579727bd7cd8ff3e84a57f47b2
1 /*
2 * gb.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005 The Geany contributors
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.
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.
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.
22 * A small Pong-like.
25 #include "utils.h"
27 #include <gtk/gtk.h>
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
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))
42 typedef struct _GeanyPong GeanyPong;
43 typedef struct _GeanyPongClass GeanyPongClass;
45 struct _GeanyPong
47 GtkDialog parent;
48 /* no need for private data as the whole thing is private */
50 GtkWidget *score_label;
51 GtkWidget *area;
53 gint area_height;
54 gint area_width;
56 guint ball_speed;
57 gdouble ball_pos[2];
58 gdouble ball_vec[2];
59 gint handle_width;
60 gint handle_pos;
62 guint score;
64 guint source_id;
67 struct _GeanyPongClass
69 GtkDialogClass parent_class;
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;
78 G_DEFINE_TYPE(GeanyPong, geany_pong, GTK_TYPE_DIALOG)
81 static void geany_pong_set_cairo_source_color(cairo_t *cr, GdkRGBA *c, gdouble a)
83 cairo_set_source_rgba(cr, c->red, c->green, c->blue, MIN(c->alpha, a));
87 static gboolean geany_pong_area_draw(GtkWidget *area, cairo_t *cr, GeanyPong *self)
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;
95 gtk_style_context_get_color(ctx, state, &fg);
96 gtk_style_context_get_background_color(ctx, state, &bg);
98 self->area_width = gtk_widget_get_allocated_width(area);
99 self->area_height = gtk_widget_get_allocated_height(area);
101 cairo_set_line_width(cr, BORDER_THIKNESS);
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);
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);
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);
118 /* if not running, add an info */
119 if (! self->source_id || self->handle_width < 1)
121 PangoLayout *layout;
122 gint pw, ph;
123 gdouble scale;
124 PangoFontDescription *font = NULL;
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);
130 geany_pong_set_cairo_source_color(cr, &fg, 1.0);
131 layout = pango_cairo_create_layout(cr);
133 gtk_style_context_get(ctx, state, GTK_STYLE_PROPERTY_FONT, &font, NULL);
134 if (font)
136 pango_layout_set_font_description(layout, font);
137 pango_font_description_free(font);
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);
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);
152 g_object_unref(layout);
155 return TRUE;
159 static void geany_pong_reset_ball(GeanyPong *self)
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;
171 static void geany_pong_update_score(GeanyPong *self)
173 gchar buf[16];
175 g_snprintf(buf, sizeof buf, "%u", self->score);
176 gtk_label_set_text(GTK_LABEL(self->score_label), buf);
180 static gboolean geany_pong_area_timeout(gpointer data)
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] };
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)
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)
206 self->score += self->ball_speed * 2;
207 geany_pong_update_score(self);
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)
215 self->handle_width = 0;
216 self->source_id = 0;
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);
227 if (self->source_id)
229 self->ball_pos[0] += self->ball_speed * self->ball_vec[0];
230 self->ball_pos[1] += self->ball_speed * self->ball_vec[1];
233 if (! self->source_id)
235 /* we will draw a text all over, just invalidate everything */
236 gtk_widget_queue_draw(self->area);
238 else
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
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);
255 return self->source_id != 0;
259 static gboolean geany_pong_area_button_press(GtkWidget *area, GdkEventButton *event, GeanyPong *self)
261 if (event->type == GDK_BUTTON_PRESS &&
262 self->handle_width > 0)
264 if (! self->source_id)
265 self->source_id = g_timeout_add(1000/60, geany_pong_area_timeout, self);
266 else
268 g_source_remove(self->source_id);
269 self->source_id = 0;
271 gtk_widget_queue_draw(area);
272 return TRUE;
275 return FALSE;
279 static gboolean geany_pong_area_motion_notify(GtkWidget *area, GdkEventMotion *event, GeanyPong *self)
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;
288 return TRUE;
292 static void geany_pong_class_init(GeanyPongClass *klass)
294 GObjectClass *object_class = G_OBJECT_CLASS(klass);
295 GtkDialogClass *dialog_class = GTK_DIALOG_CLASS(klass);
297 object_class->finalize = geany_pong_finalize;
298 dialog_class->response = geany_pong_response;
302 static void geany_pong_init(GeanyPong *self)
304 GtkWidget *vbox;
305 GtkWidget *hbox;
306 GtkWidget *label;
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);
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);
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);
327 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
328 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
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);
334 self->score_label = gtk_label_new("0");
335 gtk_box_pack_start(GTK_BOX(hbox), self->score_label, FALSE, FALSE, 0);
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);
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));
352 gtk_widget_show_all(vbox);
356 static void geany_pong_finalize(GObject *obj)
358 GeanyPong *self = GEANY_PONG(obj);
360 if (self->source_id)
361 g_source_remove(self->source_id);
363 G_OBJECT_CLASS(geany_pong_parent_class)->finalize(obj);
367 static void geany_pong_help(GeanyPong *self)
369 GtkWidget *dialog;
370 GtkWidget *vbox;
371 GtkWidget *scrolledwindow;
372 GtkWidget *textview;
373 GtkTextBuffer *buffer;
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);
382 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
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);
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);
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);
408 gtk_widget_show_all(dialog);
409 gtk_dialog_run(GTK_DIALOG(dialog));
410 gtk_widget_destroy(dialog);
414 static void geany_pong_response(GtkDialog *self, gint response)
416 g_return_if_fail(GEANY_IS_PONG(self));
418 switch (response)
420 case GTK_RESPONSE_HELP:
421 geany_pong_help(GEANY_PONG(self));
422 break;
424 default:
425 gtk_widget_destroy(GTK_WIDGET(self));
430 static GtkWidget *geany_pong_new(GtkWindow *parent)
432 return g_object_new(GEANY_TYPE_PONG, "transient-for", parent, NULL);
436 static gboolean gb_on_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
438 static gchar text[] = "geany";
440 if (event->keyval < 0x80)
442 memmove (text, &text[1], G_N_ELEMENTS(text) - 1);
443 text[G_N_ELEMENTS(text) - 2] = (gchar) event->keyval;
445 if (utils_str_equal(text, "geany"))
447 GtkWidget *pong = geany_pong_new(GTK_WINDOW(widget));
448 gtk_widget_show(pong);
449 return TRUE;
453 return FALSE;