Add -float and -noquit options
[cteddy.git] / src / cteddy.cxx
blobe5d31a21601324b4cbebfb71afc36c7319d8da64
1 // Copyright 2008 Philip Allison <sane@not.co.uk>
3 // This file is part of cteddy.
4 //
5 // cteddy is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // cteddy is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with cteddy. If not, see <http://www.gnu.org/licenses/>.
20 // Includes
23 // Language headers
24 #include <string>
25 #include <iostream>
27 // Library headers
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
31 // System headers
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
38 // Globals
41 // Main window
42 GtkWidget* window;
44 // GTK/GDK returned pointer
45 GError* gerr = NULL;
47 // Image to display and its dimensions
48 GdkPixbuf* pixbuf;
49 gint width, height;
53 // Implementation
56 // Re-paint the window when necessary
57 gboolean handle_expose(GtkWidget* widget, GdkEventExpose* event)
59 cairo_t* cairo = gdk_cairo_create(widget->window);
61 // Blank the surface
62 cairo_set_source_rgba (cairo, 1.0f, 1.0f, 1.0f, 0.0f);
63 cairo_set_operator (cairo, CAIRO_OPERATOR_SOURCE);
64 cairo_paint (cairo);
66 // Set cairo's source pattern from the desired image
67 gdk_cairo_set_source_pixbuf(cairo, pixbuf, 0.0f, 0.0f);
69 // Draw the image
70 cairo_rectangle(cairo, 0.0f, 0.0f,
71 static_cast<double>(width), static_cast<double>(height));
72 cairo_fill(cairo);
74 cairo_destroy(cairo);
76 return false;
79 // Handle mouse click events
80 gboolean window_clicked(GtkWidget* widget, GdkEventButton* event)
82 if (event->type == GDK_BUTTON_PRESS)
84 switch(event->button)
86 case 1:
87 // Move the window with the left mouse button
88 gtk_window_begin_move_drag(GTK_WINDOW(window),
89 event->button, static_cast<gint>(event->x_root),
90 static_cast<gint>(event->y_root), event->time);
91 break;
93 default:
94 // Quit the app with any other
95 gtk_main_quit();
96 break;
99 return true;
102 // Set up an RGBA colourmap for the window
103 void handle_screen(GtkWidget* window, GdkScreen* old, gpointer data)
105 GdkScreen* s = gtk_widget_get_screen(window);
106 GdkColormap* c = gdk_screen_get_rgba_colormap(s);
108 if (!c)
109 // Use standard RGB colourmap if we must. Window will have
110 // an ugly black border if this is the case.
111 c = gdk_screen_get_rgb_colormap(s);
113 gtk_widget_set_colormap(window, c);
116 // Handle key press events
117 void handle_key_press(GtkWidget *Window, GdkEventKey *theEvent)
119 switch( theEvent->keyval )
121 case GDK_q:
122 // Quit on "q"
123 gtk_main_quit();
124 break;
126 default:
127 // Ignore all other keys
128 break;
133 // Entry point
134 int main (int argc, char* argv[])
136 gtk_init(&argc, &argv);
138 // Whether or not the "q to quit" keybinding is disabled
139 bool noquit = false;
141 // Whether or not to set the window's keep-on-top hint
142 bool ontop = false;
144 // Mimic the image loading behaviour of xteddy, to an extent.
146 // Start from the name of the executable itself
147 std::string image(argv[0]);
149 // Take just the last component of the path
150 std::string::size_type s = image.find_last_of('/');
151 if (s != std::string::npos)
152 image = image.substr(s + 1);
154 // Parse options
155 // XXX This code is really quick and nasty
156 for (int i = 1; i < argc; ++i)
158 // Allow image specification with -F<file>
159 if (strncmp(argv[i], "-F", 2) == 0)
160 image = argv[i] + 2;
161 // Allow disablement of "q" keybinding
162 else if (strncmp(argv[i], "-noquit", 7) == 0)
163 noquit = true;
164 // Allow keep-on-top
165 else if (strncmp(argv[i], "-float", 6) == 0)
166 ontop = true;
169 // If the given path contains slashes, assume it is absolute or
170 // relative to the current working directory and use it verbatim.
171 // Otherwise, search for it.
172 if (image.find_last_of('/') == std::string::npos)
174 // Append extensions one by one and search for the image
175 // XXX This code is really, REALLY quick and nasty
176 char* extensions[17];
177 bool found = false;
178 extensions[0] = '\0';
179 extensions[1] = ".png"; extensions[2] = ".gif"; extensions[3] = ".jpg";
180 extensions[4] = ".jpeg"; extensions[5] = ".pnm"; extensions[6] = ".xpm";
181 extensions[7] = ".tif"; extensions[8] = ".tiff";
182 extensions[9] = ".PNG"; extensions[10] = ".GIF"; extensions[11] = ".JPG";
183 extensions[12] = ".JPEG"; extensions[13] = ".PNM"; extensions[14] = ".XPM";
184 extensions[15] = ".TIF"; extensions[16] = ".TIFF";
185 for (size_t i = 0; i < 17; ++i)
187 std::string searchimage(PKGDATADIR "/");
188 searchimage.append(image);
189 if (i > 0) searchimage.append(extensions[i]);
190 struct stat s;
191 std::cout << searchimage << std::endl;
192 if (stat(searchimage.c_str(), &s) == 0)
194 image = searchimage;
195 found = true;
196 break;
198 // Try converting a leading x to a c
199 if (image.at(0) == 'x')
201 std::string ximage(PKGDATADIR "/c");
202 ximage.append(image.substr(1));
203 if (i > 0) ximage.append(extensions[i]);
204 std::cout << ximage << std::endl;
205 if (stat(ximage.c_str(), &s) == 0)
207 image = ximage;
208 found = true;
209 break;
212 // Try prepending c or x
213 std::string ximage(PKGDATADIR "/c");
214 ximage.append(image);
215 if (i > 0) ximage.append(extensions[i]);
216 std::cout << ximage << std::endl;
217 if (stat(ximage.c_str(), &s) == 0)
219 image = ximage;
220 found = true;
221 break;
223 ximage = PKGDATADIR "/x";
224 ximage.append(image);
225 if (i > 0) ximage.append(extensions[i]);
226 std::cout << ximage << std::endl;
227 if (stat(ximage.c_str(), &s) == 0)
229 image = ximage;
230 found = true;
231 break;
235 if (!found)
237 std::cerr << "Cannot find image!" << std::endl;
238 return 1;
242 // Load the image from the supplied file path
243 pixbuf = gdk_pixbuf_new_from_file(image.c_str(), &gerr);
244 if (gerr)
246 std::cerr << "Cannot load image \"" << image << "\": " << gerr->message << std::endl;
247 g_error_free(gerr);
248 return 1;
252 // Create the window
253 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
255 // Top-level windows don't normally respond to mouse button events directly.
256 // We want this one to, though.
257 gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
259 // Set up event callbacks
260 g_signal_connect(G_OBJECT(window), "expose-event", G_CALLBACK(handle_expose), NULL);
261 g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
262 g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(window_clicked), NULL);
263 g_signal_connect(G_OBJECT(window), "screen-changed", G_CALLBACK(handle_screen), NULL);
265 if (!noquit)
266 g_signal_connect(G_OBJECT(window), "key-press-event", G_CALLBACK(handle_key_press), NULL);
269 // Grap pixmap and alpha-thresholded bitmap from original pixmap
270 GdkPixmap* pixmap;
271 GdkBitmap* bitmap;
272 gdk_pixbuf_render_pixmap_and_mask(pixbuf, &pixmap, &bitmap, 128);
274 // Set window size to image size
275 gdk_drawable_get_size(pixmap, &width, &height);
276 gtk_widget_set_size_request(window, width, height);
278 // If we have an alpha channel on the image, set the window's input shape,
279 // clearing it completely first
280 if (bitmap)
282 gtk_widget_input_shape_combine_mask(window, NULL, 0, 0);
283 gtk_widget_input_shape_combine_mask(window, bitmap, 0, 0);
286 g_object_unref(G_OBJECT(pixmap));
287 g_object_unref(G_OBJECT(bitmap));
290 // Set up various other properties we're interested in
291 gtk_window_set_title(GTK_WINDOW(window), "cteddy");
292 gtk_window_set_resizable(GTK_WINDOW(window), false);
293 gtk_window_set_decorated(GTK_WINDOW(window), false);
294 gtk_widget_set_app_paintable(window, true);
296 // Trigger initial attempt to switch to an RGBA colourmap
297 handle_screen(window, NULL, NULL);
299 // Have to do this before we can use window->window as a GdkWindow*
300 gtk_widget_realize(window);
303 // Load in the heart-shaped cursor image and use it for our window's cursor
304 GdkPixbuf* heart = gdk_pixbuf_new_from_file(PKGDATADIR "/heart.png", &gerr);
305 if (gerr)
307 std::cerr << "Cannot load image: " << gerr->message << std::endl;
308 g_error_free(gerr);
309 return 1;
311 GdkCursor* cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(),
312 heart, 12, 12);
313 gdk_window_set_cursor(window->window, cursor);
316 // Clear the window's default background
317 gdk_window_set_back_pixmap(window->window, NULL, false);
319 // Don't steal input focus when we start
320 g_object_set(G_OBJECT(window), "focus-on-map", false, NULL);
322 if (ontop)
323 gtk_window_set_keep_above(window, true);
325 gtk_widget_show_all(window);
327 gtk_main();
329 return 0;