1 // Copyright 2008 Philip Allison <sane@not.co.uk>
3 // This file is part of cteddy.
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.
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/>.
29 #include <gdk/gdkkeysyms.h>
32 #include <sys/types.h>
44 // GTK/GDK returned pointer
47 // Image to display and its dimensions
56 // Re-paint the window when necessary
57 gboolean
handle_expose(GtkWidget
* widget
, GdkEventExpose
* event
)
59 cairo_t
* cairo
= gdk_cairo_create(widget
->window
);
62 cairo_set_source_rgba (cairo
, 1.0f
, 1.0f
, 1.0f
, 0.0f
);
63 cairo_set_operator (cairo
, CAIRO_OPERATOR_SOURCE
);
66 // Set cairo's source pattern from the desired image
67 gdk_cairo_set_source_pixbuf(cairo
, pixbuf
, 0.0f
, 0.0f
);
70 cairo_rectangle(cairo
, 0.0f
, 0.0f
,
71 static_cast<double>(width
), static_cast<double>(height
));
79 // Handle mouse click events
80 gboolean
window_clicked(GtkWidget
* widget
, GdkEventButton
* event
)
82 if (event
->type
== GDK_BUTTON_PRESS
)
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
);
94 // Quit the app with any other
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
);
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
)
127 // Ignore all other keys
134 int main (int argc
, char* argv
[])
136 gtk_init(&argc
, &argv
);
138 // Whether or not the "q to quit" keybinding is disabled
141 // Whether or not to set the window's keep-on-top hint
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);
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)
161 // Allow disablement of "q" keybinding
162 else if (strncmp(argv
[i
], "-noquit", 7) == 0)
165 else if (strncmp(argv
[i
], "-float", 6) == 0)
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];
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
]);
191 std::cout
<< searchimage
<< std::endl
;
192 if (stat(searchimage
.c_str(), &s
) == 0)
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)
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)
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)
237 std::cerr
<< "Cannot find image!" << std::endl
;
242 // Load the image from the supplied file path
243 pixbuf
= gdk_pixbuf_new_from_file(image
.c_str(), &gerr
);
246 std::cerr
<< "Cannot load image \"" << image
<< "\": " << gerr
->message
<< std::endl
;
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
);
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
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
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
);
307 std::cerr
<< "Cannot load image: " << gerr
->message
<< std::endl
;
311 GdkCursor
* cursor
= gdk_cursor_new_from_pixbuf(gdk_display_get_default(),
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
);
323 gtk_window_set_keep_above(window
, true);
325 gtk_widget_show_all(window
);