9b6250426c69ff3141b6a1014066e698972deed3
[lxde/gpicview.git] / src / main-win.c
1 /***************************************************************************
2 * Copyright (C) 2007, 2008 by PCMan (Hong Jen Yee) *
3 * pcman.tw@gmail.com *
4 * *
5 * This program 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 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program 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. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include "main-win.h"
26
27 #include <glib/gi18n.h>
28 #include <glib/gstdio.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <gdk/gdkkeysyms-compat.h>
31
32 #include <unistd.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37
38 #include "pref.h"
39
40 #include "image-view.h"
41 #include "image-list.h"
42 #include "working-area.h"
43 #include "ptk-menu.h"
44 #include "file-dlgs.h"
45 #include "jpeg-tran.h"
46
47 /* For drag & drop */
48 static GtkTargetEntry drop_targets[] =
49 {
50 {"text/uri-list", 0, 0},
51 {"text/plain", 0, 1}
52 };
53
54 extern int ExifRotate(const char * fname, int new_angle);
55 // defined in exif.c
56 extern int ExifRotateFlipMapping[9][9];
57
58 static void main_win_init( MainWin*mw );
59 static void main_win_finalize( GObject* obj );
60
61 static void create_nav_bar( MainWin* mw, GtkWidget* box);
62 GtkWidget* add_nav_btn( MainWin* mw, const char* icon, const char* tip, GCallback cb, gboolean toggle);
63 GtkWidget* add_nav_btn_img( MainWin* mw, const char* icon, const char* tip, GCallback cb, gboolean toggle, GtkWidget** ret_img);
64 // GtkWidget* add_menu_item( GtkMenuShell* menu, const char* label, const char* icon, GCallback cb, gboolean toggle=FALSE );
65 static void rotate_image( MainWin* mw, int angle );
66 static void show_popup_menu( MainWin* mw, GdkEventButton* evt );
67
68 /* signal handlers */
69 static gboolean on_delete_event( GtkWidget* widget, GdkEventAny* evt );
70 static void on_size_allocate( GtkWidget* widget, GtkAllocation *allocation );
71 static gboolean on_win_state_event( GtkWidget* widget, GdkEventWindowState* state );
72 static void on_scroll_size_allocate(GtkWidget* widget, GtkAllocation* allocation, MainWin* mv);
73 static void on_zoom_fit( GtkToggleButton* btn, MainWin* mw );
74 static void on_zoom_fit_menu( GtkMenuItem* item, MainWin* mw );
75 static void on_full_screen( GtkWidget* btn, MainWin* mw );
76 static void on_next( GtkWidget* btn, MainWin* mw );
77 static void on_orig_size( GtkToggleButton* btn, MainWin* mw );
78 static void on_orig_size_menu( GtkToggleButton* btn, MainWin* mw );
79 static void on_prev( GtkWidget* btn, MainWin* mw );
80 static void on_rotate_auto_save( GtkWidget* btn, MainWin* mw );
81 static void on_rotate_clockwise( GtkWidget* btn, MainWin* mw );
82 static void on_rotate_counterclockwise( GtkWidget* btn, MainWin* mw );
83 static void on_save_as( GtkWidget* btn, MainWin* mw );
84 static void on_save( GtkWidget* btn, MainWin* mw );
85 static void cancel_slideshow(MainWin* mw);
86 static gboolean next_slide(MainWin* mw);
87 static void on_slideshow_menu( GtkMenuItem* item, MainWin* mw );
88 static void on_slideshow( GtkToggleButton* btn, MainWin* mw );
89 static void on_open( GtkWidget* btn, MainWin* mw );
90 static void on_zoom_in( GtkWidget* btn, MainWin* mw );
91 static void on_zoom_out( GtkWidget* btn, MainWin* mw );
92 static void on_preference( GtkWidget* btn, MainWin* mw );
93 static void on_toggle_toolbar( GtkMenuItem* item, MainWin* mw );
94 static void on_quit( GtkWidget* btn, MainWin* mw );
95 static gboolean on_button_press( GtkWidget* widget, GdkEventButton* evt, MainWin* mw );
96 static gboolean on_button_release( GtkWidget* widget, GdkEventButton* evt, MainWin* mw );
97 static gboolean on_mouse_move( GtkWidget* widget, GdkEventMotion* evt, MainWin* mw );
98 static gboolean on_scroll_event( GtkWidget* widget, GdkEventScroll* evt, MainWin* mw );
99 static gboolean on_key_press_event(GtkWidget* widget, GdkEventKey * key);
100 static gboolean save_confirm( MainWin* mw, const char* file_path );
101 static void on_drag_data_received( GtkWidget* widget, GdkDragContext *drag_context,
102 int x, int y, GtkSelectionData* data, guint info, guint time, MainWin* mw );
103 static void on_delete( GtkWidget* btn, MainWin* mw );
104 static void on_about( GtkWidget* menu, MainWin* mw );
105 static gboolean on_animation_timeout( MainWin* mw );
106
107 static void update_title(const char *filename, MainWin *mw );
108
109 void on_flip_vertical( GtkWidget* btn, MainWin* mw );
110 void on_flip_horizontal( GtkWidget* btn, MainWin* mw );
111 static int trans_angle_to_id(int i);
112 static int get_new_angle( int orig_angle, int rotate_angle );
113
114 static void main_win_set_zoom_scale(MainWin* mw, double scale);
115 static void main_win_set_zoom_mode(MainWin* mw, ZoomMode mode);
116 static void main_win_update_zoom_buttons_state(MainWin* mw);
117
118 // Begin of GObject-related stuff
119
120 G_DEFINE_TYPE( MainWin, main_win, GTK_TYPE_WINDOW )
121
122 void main_win_class_init( MainWinClass* klass )
123 {
124 GObjectClass * obj_class;
125 GtkWidgetClass *widget_class;
126
127 obj_class = ( GObjectClass * ) klass;
128 // obj_class->set_property = _set_property;
129 // obj_class->get_property = _get_property;
130 obj_class->finalize = main_win_finalize;
131
132 widget_class = GTK_WIDGET_CLASS ( klass );
133 widget_class->delete_event = on_delete_event;
134 widget_class->size_allocate = on_size_allocate;
135 widget_class->key_press_event = on_key_press_event;
136 widget_class->window_state_event = on_win_state_event;
137 }
138
139 void main_win_finalize( GObject* obj )
140 {
141 MainWin *mw = (MainWin*)obj;
142
143 main_win_close(mw);
144
145 if( G_LIKELY(mw->img_list) )
146 image_list_free( mw->img_list );
147 #if GTK_CHECK_VERSION(3, 0, 0)
148 g_object_unref( mw->hand_cursor );
149 #else
150 gdk_cursor_unref( mw->hand_cursor );
151 #endif
152 // FIXME: Put this here is weird
153 gtk_main_quit();
154 }
155
156 GtkWidget* main_win_new()
157 {
158 return (GtkWidget*)g_object_new ( MAIN_WIN_TYPE, NULL );
159 }
160
161 // End of GObject-related stuff
162
163 void main_win_init( MainWin*mw )
164 {
165 gtk_window_set_title( (GtkWindow*)mw, _("Image Viewer"));
166 if (gtk_icon_theme_has_icon(gtk_icon_theme_get_default(), "gpicview"))
167 {
168 gtk_window_set_icon_name((GtkWindow*)mw, "gpicview");
169 }
170 else
171 {
172 gtk_window_set_icon_from_file((GtkWindow*)mw, PACKAGE_DATA_DIR "/icons/hicolor/48x48/apps/gpicview.png", NULL);
173 }
174 gtk_window_set_default_size( (GtkWindow*)mw, 640, 480 );
175
176 #if GTK_CHECK_VERSION(3, 0, 0)
177 GtkWidget* box = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
178 #else
179 GtkWidget* box = gtk_vbox_new( FALSE, 0 );
180 #endif
181 gtk_container_add( (GtkContainer*)mw, box);
182
183 // image area
184 mw->evt_box = gtk_event_box_new();
185 #if GTK_CHECK_VERSION(2, 18, 0)
186 gtk_widget_set_can_focus(mw->evt_box,TRUE);
187 #else
188 GTK_WIDGET_SET_FLAGS( mw->evt_box, GTK_CAN_FOCUS );
189 #endif
190 gtk_widget_add_events( mw->evt_box,
191 GDK_POINTER_MOTION_MASK|GDK_BUTTON_PRESS_MASK|
192 GDK_BUTTON_RELEASE_MASK|GDK_SCROLL_MASK );
193 g_signal_connect( mw->evt_box, "button-press-event", G_CALLBACK(on_button_press), mw );
194 g_signal_connect( mw->evt_box, "button-release-event", G_CALLBACK(on_button_release), mw );
195 g_signal_connect( mw->evt_box, "motion-notify-event", G_CALLBACK(on_mouse_move), mw );
196 g_signal_connect( mw->evt_box, "scroll-event", G_CALLBACK(on_scroll_event), mw );
197 // Set bg color to white
198
199 gtk_widget_modify_bg( mw->evt_box, GTK_STATE_NORMAL, &pref.bg );
200
201 mw->img_view = image_view_new();
202 gtk_container_add( (GtkContainer*)mw->evt_box, (GtkWidget*)mw->img_view);
203
204 #if GTK_CHECK_VERSION(3, 0, 0)
205 #else
206 const char scroll_style[]=
207 "style \"gpicview-scroll\" {"
208 "GtkScrolledWindow::scrollbar-spacing=0"
209 "}"
210 "class \"GtkScrolledWindow\" style \"gpicview-scroll\"";
211 gtk_rc_parse_string( scroll_style );
212 #endif
213 mw->scroll = gtk_scrolled_window_new( NULL, NULL );
214 g_signal_connect(G_OBJECT(mw->scroll), "size-allocate", G_CALLBACK(on_scroll_size_allocate), (gpointer) mw);
215 gtk_scrolled_window_set_shadow_type( (GtkScrolledWindow*)mw->scroll, GTK_SHADOW_NONE );
216 gtk_scrolled_window_set_policy((GtkScrolledWindow*)mw->scroll,
217 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
218 GtkAdjustment *hadj, *vadj;
219 hadj = gtk_scrolled_window_get_hadjustment((GtkScrolledWindow*)mw->scroll);
220 #if GTK_CHECK_VERSION(2, 14, 0)
221 gtk_adjustment_set_page_increment(hadj, 10);
222 #else
223 hadj->page_increment = 10;
224 #endif
225 gtk_adjustment_changed(hadj);
226 vadj = gtk_scrolled_window_get_vadjustment((GtkScrolledWindow*)mw->scroll);
227 #if GTK_CHECK_VERSION(2, 14, 0)
228 gtk_adjustment_set_page_increment(vadj, 10);
229 #else
230 vadj->page_increment = 10;
231 #endif
232 gtk_adjustment_changed(vadj);
233
234 image_view_set_adjustments( IMAGE_VIEW(mw->img_view), hadj, vadj ); // dirty hack :-(
235 gtk_scrolled_window_add_with_viewport( (GtkScrolledWindow*)mw->scroll, mw->evt_box );
236 GtkWidget* viewport = gtk_bin_get_child( (GtkBin*)mw->scroll );
237 gtk_viewport_set_shadow_type( (GtkViewport*)viewport, GTK_SHADOW_NONE );
238 gtk_container_set_border_width( (GtkContainer*)viewport, 0 );
239
240 gtk_box_pack_start( (GtkBox*)box, mw->scroll, TRUE, TRUE, 0 );
241
242 // build toolbar
243 create_nav_bar( mw, box );
244 gtk_widget_show_all( box );
245
246 if (pref.show_toolbar)
247 gtk_widget_show(gtk_widget_get_parent(mw->nav_bar));
248 else
249 gtk_widget_hide(gtk_widget_get_parent(mw->nav_bar));
250
251
252 mw->hand_cursor = gdk_cursor_new_for_display( gtk_widget_get_display((GtkWidget*)mw), GDK_FLEUR );
253
254 // zoom_mode = ZOOM_NONE;
255 mw->zoom_mode = ZOOM_FIT;
256
257 // Set up drag & drop
258 gtk_drag_dest_set( (GtkWidget*)mw, GTK_DEST_DEFAULT_ALL,
259 drop_targets,
260 G_N_ELEMENTS(drop_targets),
261 GDK_ACTION_COPY | GDK_ACTION_ASK );
262 g_signal_connect( mw, "drag-data-received", G_CALLBACK(on_drag_data_received), mw );
263
264 mw->img_list = image_list_new();
265
266 // rotation angle is zero on startup
267 mw->rotation_angle = 0;
268 }
269
270 void create_nav_bar( MainWin* mw, GtkWidget* box )
271 {
272 mw->nav_bar = gtk_hbox_new( FALSE, 0 );
273
274 add_nav_btn( mw, GTK_STOCK_GO_BACK, _("Previous"), G_CALLBACK(on_prev), FALSE );
275 add_nav_btn( mw, GTK_STOCK_GO_FORWARD, _("Next"), G_CALLBACK(on_next), FALSE );
276 mw->btn_play_stop = add_nav_btn_img( mw, GTK_STOCK_MEDIA_PLAY, _("Start Slideshow"), G_CALLBACK(on_slideshow), TRUE, &mw->img_play_stop );
277
278 gtk_box_pack_start( (GtkBox*)mw->nav_bar, gtk_vseparator_new(), FALSE, FALSE, 0 );
279
280 add_nav_btn( mw, GTK_STOCK_ZOOM_OUT, _("Zoom Out"), G_CALLBACK(on_zoom_out), FALSE );
281 add_nav_btn( mw, GTK_STOCK_ZOOM_IN, _("Zoom In"), G_CALLBACK(on_zoom_in), FALSE );
282
283 // percent = gtk_entry_new(); // show scale (in percentage)
284 // g_signal_connect( percent, "activate", G_CALLBACK(on_percentage), mw );
285 // gtk_widget_set_size_request( percent, 45, -1 );
286 // gtk_box_pack_start( (GtkBox*)nav_bar, percent, FALSE, FALSE, 2 );
287
288 mw->btn_fit = add_nav_btn( mw, GTK_STOCK_ZOOM_FIT, _("Fit Image To Window Size"),
289 G_CALLBACK(on_zoom_fit), TRUE );
290 mw->btn_orig = add_nav_btn( mw, GTK_STOCK_ZOOM_100, _("Original Size"),
291 G_CALLBACK(on_orig_size), TRUE );
292 gtk_toggle_button_set_active( (GtkToggleButton*)mw->btn_fit, TRUE );
293
294 #ifndef GTK_STOCK_FULLSCREEN
295 #define GTK_STOCK_FULLSCREEN "gtk-fullscreen"
296 #endif
297 add_nav_btn( mw, GTK_STOCK_FULLSCREEN, _("Full Screen"), G_CALLBACK(on_full_screen), FALSE ); // gtk+ 2.8+
298
299 gtk_box_pack_start( (GtkBox*)mw->nav_bar, gtk_vseparator_new(), FALSE, FALSE, 0 );
300
301 mw->btn_rotate_ccw = add_nav_btn( mw, "object-rotate-left", _("Rotate Counterclockwise"), G_CALLBACK(on_rotate_counterclockwise), FALSE );
302 mw->btn_rotate_cw = add_nav_btn( mw, "object-rotate-right", _("Rotate Clockwise"), G_CALLBACK(on_rotate_clockwise), FALSE );
303
304 mw->btn_flip_h = add_nav_btn( mw, "object-flip-horizontal", _("Flip Horizontal"), G_CALLBACK(on_flip_horizontal), FALSE );
305 mw->btn_flip_v = add_nav_btn( mw, "object-flip-vertical", _("Flip Vertical"), G_CALLBACK(on_flip_vertical), FALSE );
306
307 gtk_box_pack_start( (GtkBox*)mw->nav_bar, gtk_vseparator_new(), FALSE, FALSE, 0 );
308
309 add_nav_btn( mw, GTK_STOCK_OPEN, _("Open File"), G_CALLBACK(on_open), FALSE );
310 add_nav_btn( mw, GTK_STOCK_SAVE, _("Save File"), G_CALLBACK(on_save), FALSE );
311 add_nav_btn( mw, GTK_STOCK_SAVE_AS, _("Save File As"), G_CALLBACK(on_save_as), FALSE );
312 add_nav_btn( mw, GTK_STOCK_DELETE, _("Delete File"), G_CALLBACK(on_delete), FALSE );
313
314 gtk_box_pack_start( (GtkBox*)mw->nav_bar, gtk_vseparator_new(), FALSE, FALSE, 0 );
315 add_nav_btn( mw, GTK_STOCK_PREFERENCES, _("Preferences"), G_CALLBACK(on_preference), FALSE );
316 add_nav_btn( mw, GTK_STOCK_QUIT, _("Quit"), G_CALLBACK(on_quit), FALSE );
317
318 GtkWidget* align = gtk_alignment_new( 0.5, 0, 0, 0 );
319 gtk_container_add( (GtkContainer*)align, mw->nav_bar );
320 gtk_box_pack_start( (GtkBox*)box, align, FALSE, TRUE, 2 );
321 }
322
323 gboolean on_delete_event( GtkWidget* widget, GdkEventAny* evt )
324 {
325 gtk_widget_destroy( widget );
326 return TRUE;
327 }
328
329 static void update_title(const char *filename, MainWin *mw )
330 {
331 static char fname[50];
332 static int wid, hei;
333
334 char buf[100];
335
336 if(filename != NULL)
337 {
338 strncpy(fname, filename, 49);
339 fname[49] = '\0';
340
341 wid = gdk_pixbuf_get_width( mw->pix );
342 hei = gdk_pixbuf_get_height( mw->pix );
343 }
344
345 snprintf(buf, 100, "%s (%dx%d) %d%%", fname, wid, hei, (int)(mw->scale * 100));
346 gtk_window_set_title( (GtkWindow*)mw, buf );
347
348 return;
349 }
350
351 gboolean on_animation_timeout( MainWin* mw )
352 {
353 int delay;
354 if ( gdk_pixbuf_animation_iter_advance( mw->animation_iter, NULL ) )
355 {
356 mw->pix = gdk_pixbuf_animation_iter_get_pixbuf( mw->animation_iter );
357 image_view_set_pixbuf( (ImageView*)mw->img_view, mw->pix );
358 }
359 delay = gdk_pixbuf_animation_iter_get_delay_time( mw->animation_iter );
360 mw->animation_timeout = g_timeout_add(delay, (GSourceFunc) on_animation_timeout, mw );
361 return FALSE;
362 }
363
364 static void update_btns(MainWin* mw)
365 {
366 gboolean enable = (mw->animation == NULL);
367 gtk_widget_set_sensitive(mw->btn_rotate_cw, enable);
368 gtk_widget_set_sensitive(mw->btn_rotate_ccw, enable);
369 gtk_widget_set_sensitive(mw->btn_flip_v, enable);
370 gtk_widget_set_sensitive(mw->btn_flip_h, enable);
371 }
372
373 gboolean main_win_open( MainWin* mw, const char* file_path, ZoomMode zoom )
374 {
375 if (g_file_test(file_path, G_FILE_TEST_IS_DIR))
376 {
377 image_list_open_dir( mw->img_list, file_path, NULL );
378 image_list_sort_by_name( mw->img_list, GTK_SORT_DESCENDING );
379 if (image_list_get_first(mw->img_list))
380 main_win_open(mw, image_list_get_current_file_path(mw->img_list), zoom);
381 return;
382 }
383
384
385 GError* err = NULL;
386 GdkPixbufFormat* info;
387 info = gdk_pixbuf_get_file_info( file_path, NULL, NULL );
388 char* type = ((info != NULL) ? gdk_pixbuf_format_get_name(info) : "");
389
390 main_win_close( mw );
391
392 /* grabs a file as if it were an animation */
393 mw->animation = gdk_pixbuf_animation_new_from_file( file_path, &err );
394 if( ! mw->animation )
395 {
396 main_win_show_error( mw, err->message );
397 g_error_free(err);
398 update_btns( mw );
399 return FALSE;
400 }
401
402 /* tests if the file is actually just a normal picture */
403 if ( gdk_pixbuf_animation_is_static_image( mw->animation ) )
404 {
405 mw->pix = gdk_pixbuf_animation_get_static_image( mw->animation );
406 g_object_ref(mw->pix);
407 g_object_unref(mw->animation);
408 mw->animation = NULL;
409 }
410 else
411 {
412 int delay;
413 /* we found an animation */
414 mw->animation_iter = gdk_pixbuf_animation_get_iter( mw->animation, NULL );
415 mw->pix = gdk_pixbuf_animation_iter_get_pixbuf( mw->animation_iter );
416 delay = gdk_pixbuf_animation_iter_get_delay_time( mw->animation_iter );
417 mw->animation_timeout = g_timeout_add( delay, (GSourceFunc) on_animation_timeout, mw );
418 }
419 update_btns( mw );
420
421 if(!strcmp(type,"jpeg"))
422 {
423 GdkPixbuf* tmp;
424 // Only jpeg should rotate by EXIF
425 tmp = gdk_pixbuf_apply_embedded_orientation(mw->pix);
426 g_object_unref(mw->pix);
427 mw->pix = tmp;
428 }
429
430 mw->zoom_mode = zoom;
431
432 // select most suitable viewing mode
433 if( zoom == ZOOM_NONE )
434 {
435 int w = gdk_pixbuf_get_width( mw->pix );
436 int h = gdk_pixbuf_get_height( mw->pix );
437
438 GdkRectangle area;
439 get_working_area( gtk_widget_get_screen((GtkWidget*)mw), &area );
440 // g_debug("determine best zoom mode: orig size: w=%d, h=%d", w, h);
441 // FIXME: actually this is a little buggy :-(
442 if( w < area.width && h < area.height && (w >= 640 || h >= 480) )
443 {
444 gtk_scrolled_window_set_policy( (GtkScrolledWindow*)mw->scroll, GTK_POLICY_NEVER, GTK_POLICY_NEVER );
445 gtk_widget_set_size_request( (GtkWidget*)mw->img_view, w, h );
446 GtkRequisition req;
447 gtk_widget_size_request ( (GtkWidget*)mw, &req );
448 if( req.width < 640 ) req.width = 640;
449 if( req.height < 480 ) req.height = 480;
450 gtk_window_resize( (GtkWindow*)mw, req.width, req.height );
451 gtk_widget_set_size_request( (GtkWidget*)mw->img_view, -1, -1 );
452 gtk_scrolled_window_set_policy( (GtkScrolledWindow*)mw->scroll, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
453 mw->zoom_mode = ZOOM_ORIG;
454 mw->scale = 1.0;
455 }
456 else
457 mw->zoom_mode = ZOOM_FIT;
458 }
459
460 if( mw->zoom_mode == ZOOM_FIT )
461 {
462 main_win_fit_window_size( mw, FALSE, GDK_INTERP_BILINEAR );
463 }
464 else if( mw->zoom_mode == ZOOM_SCALE ) // scale
465 {
466 main_win_scale_image( mw, mw->scale, GDK_INTERP_BILINEAR );
467 }
468 else if( mw->zoom_mode == ZOOM_ORIG ) // original size
469 {
470 image_view_set_scale( (ImageView*)mw->img_view, mw->scale, GDK_INTERP_BILINEAR );
471 main_win_center_image( mw );
472 }
473
474 image_view_set_pixbuf( (ImageView*)mw->img_view, mw->pix );
475
476 // while (gtk_events_pending ())
477 // gtk_main_iteration ();
478
479 // build file list
480 char* dir_path = g_path_get_dirname( file_path );
481 image_list_open_dir( mw->img_list, dir_path, NULL );
482 image_list_sort_by_name( mw->img_list, GTK_SORT_DESCENDING );
483 g_free( dir_path );
484
485 char* base_name = g_path_get_basename( file_path );
486 image_list_set_current( mw->img_list, base_name );
487
488 char* disp_name = g_filename_display_name( base_name );
489 g_free( base_name );
490
491 update_title( disp_name, mw );
492 g_free( disp_name );
493
494 main_win_update_zoom_buttons_state(mw);
495
496 return TRUE;
497 }
498
499 void main_win_start_slideshow( MainWin* mw )
500 {
501 on_slideshow_menu(NULL, mw);
502 }
503
504 void main_win_close( MainWin* mw )
505 {
506 if( mw->animation )
507 {
508 g_object_unref( mw->animation );
509 mw->animation = NULL;
510 if( mw->animation_timeout );
511 {
512 g_source_remove( mw->animation_timeout );
513 mw->animation_timeout = 0;
514 }
515 }
516 else if( mw->pix )
517 {
518 g_object_unref( mw->pix );
519 }
520 mw->pix = NULL;
521 }
522
523 void main_win_show_error( MainWin* mw, const char* message )
524 {
525 GtkWidget* dlg = gtk_message_dialog_new( (GtkWindow*)mw,
526 GTK_DIALOG_MODAL,
527 GTK_MESSAGE_ERROR,
528 GTK_BUTTONS_OK,
529 "%s", message );
530 gtk_dialog_run( (GtkDialog*)dlg );
531 gtk_widget_destroy( dlg );
532 }
533
534 void on_size_allocate( GtkWidget* widget, GtkAllocation *allocation )
535 {
536 GTK_WIDGET_CLASS(main_win_parent_class)->size_allocate( widget, allocation );
537 #if GTK_CHECK_VERSION(2, 20, 0)
538 if(gtk_widget_get_realized (widget) )
539 #else
540 if( GTK_WIDGET_REALIZED (widget) )
541 #endif
542 {
543 MainWin* mw = (MainWin*)widget;
544
545 if( mw->zoom_mode == ZOOM_FIT )
546 {
547 while(gtk_events_pending ())
548 gtk_main_iteration(); // makes it more fluid
549
550 main_win_fit_window_size( mw, FALSE, GDK_INTERP_BILINEAR );
551 }
552 }
553 }
554
555 gboolean on_win_state_event( GtkWidget* widget, GdkEventWindowState* state )
556 {
557 MainWin* mw = (MainWin*)widget;
558 if( state->new_window_state == GDK_WINDOW_STATE_FULLSCREEN )
559 {
560 gtk_widget_modify_bg( mw->evt_box, GTK_STATE_NORMAL, &pref.bg_full );
561 gtk_widget_hide( gtk_widget_get_parent(mw->nav_bar) );
562 mw->full_screen = TRUE;
563 }
564 else
565 {
566 gtk_widget_modify_bg( mw->evt_box, GTK_STATE_NORMAL, &pref.bg );
567 if (pref.show_toolbar)
568 gtk_widget_show( gtk_widget_get_parent(mw->nav_bar) );
569 mw->full_screen = FALSE;
570 }
571
572 int previous = pref.open_maximized;
573 pref.open_maximized = (state->new_window_state == GDK_WINDOW_STATE_MAXIMIZED);
574 if (previous != pref.open_maximized)
575 save_preferences();
576 return TRUE;
577 }
578
579 void main_win_fit_size( MainWin* mw, int width, int height, gboolean can_strech, GdkInterpType type )
580 {
581 if( ! mw->pix )
582 return;
583
584 int orig_w = gdk_pixbuf_get_width( mw->pix );
585 int orig_h = gdk_pixbuf_get_height( mw->pix );
586
587 if( can_strech || (orig_w > width || orig_h > height) )
588 {
589 gdouble xscale = ((gdouble)width) / orig_w;
590 gdouble yscale = ((gdouble)height)/ orig_h;
591 gdouble final_scale = xscale < yscale ? xscale : yscale;
592
593 main_win_scale_image( mw, final_scale, type );
594 }
595 else // use original size if the image is smaller than the window
596 {
597 mw->scale = 1.0;
598 image_view_set_scale( (ImageView*)mw->img_view, 1.0, type );
599
600 update_title(NULL, mw);
601 }
602 }
603
604 void main_win_fit_window_size( MainWin* mw, gboolean can_strech, GdkInterpType type )
605 {
606 mw->zoom_mode = ZOOM_FIT;
607
608 if( mw->pix == NULL )
609 return;
610 main_win_fit_size( mw, mw->scroll_allocation.width, mw->scroll_allocation.height, can_strech, type );
611 }
612
613 GtkWidget* add_nav_btn( MainWin* mw, const char* icon, const char* tip, GCallback cb, gboolean toggle)
614 {
615 GtkWidget* unused;
616 return add_nav_btn_img(mw, icon, tip, cb, toggle, &unused);
617 }
618
619 GtkWidget* add_nav_btn_img( MainWin* mw, const char* icon, const char* tip, GCallback cb, gboolean toggle, GtkWidget** ret_img )
620 {
621 GtkWidget* img;
622 if( g_str_has_prefix(icon, "gtk-") )
623 img = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_SMALL_TOOLBAR);
624 else
625 img = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_SMALL_TOOLBAR);
626 GtkWidget* btn;
627 if( G_UNLIKELY(toggle) )
628 {
629 btn = gtk_toggle_button_new();
630 g_signal_connect( btn, "toggled", cb, mw );
631 }
632 else
633 {
634 btn = gtk_button_new();
635 g_signal_connect( btn, "clicked", cb, mw );
636 }
637 gtk_button_set_relief( (GtkButton*)btn, GTK_RELIEF_NONE );
638 gtk_button_set_focus_on_click( (GtkButton*)btn, FALSE );
639 gtk_container_add( (GtkContainer*)btn, img );
640 gtk_widget_set_tooltip_text( btn, tip );
641 gtk_box_pack_start( (GtkBox*)mw->nav_bar, btn, FALSE, FALSE, 0 );
642 *ret_img = img;
643 return btn;
644 }
645
646 void on_scroll_size_allocate(GtkWidget* widget, GtkAllocation* allocation, MainWin* mv)
647 {
648 mv->scroll_allocation = *allocation;
649 }
650
651 void on_zoom_fit_menu( GtkMenuItem* item, MainWin* mw )
652 {
653 gtk_button_clicked( (GtkButton*)mw->btn_fit );
654 }
655
656 void on_zoom_fit( GtkToggleButton* btn, MainWin* mw )
657 {
658 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(btn)))
659 main_win_set_zoom_mode(mw, ZOOM_FIT);
660 }
661
662 void on_full_screen( GtkWidget* btn, MainWin* mw )
663 {
664 if( ! mw->full_screen )
665 gtk_window_fullscreen( (GtkWindow*)mw );
666 else
667 gtk_window_unfullscreen( (GtkWindow*)mw );
668 }
669
670 void on_orig_size_menu( GtkToggleButton* btn, MainWin* mw )
671 {
672 gtk_button_clicked( (GtkButton*)mw->btn_orig );
673 }
674
675 void on_orig_size( GtkToggleButton* btn, MainWin* mw )
676 {
677 // this callback could be called from activate signal of menu item.
678 if( GTK_IS_MENU_ITEM(btn) )
679 {
680 gtk_button_clicked( (GtkButton*)mw->btn_orig );
681 return;
682 }
683
684 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(btn)))
685 main_win_set_zoom_mode(mw, ZOOM_ORIG);
686 }
687
688 void on_prev( GtkWidget* btn, MainWin* mw )
689 {
690 const char* name;
691 if( image_list_is_empty( mw->img_list ) )
692 return;
693
694 name = image_list_get_prev( mw->img_list );
695
696 if( ! name && image_list_has_multiple_files( mw->img_list ) )
697 {
698 // FIXME: need to ask user first?
699 name = image_list_get_last( mw->img_list );
700 }
701
702 if( name )
703 {
704 char* file_path = image_list_get_current_file_path( mw->img_list );
705 main_win_open( mw, file_path, mw->zoom_mode );
706 g_free( file_path );
707 }
708 }
709
710 void on_next( GtkWidget* btn, MainWin* mw )
711 {
712 if( image_list_is_empty( mw->img_list ) )
713 return;
714
715 const char* name = image_list_get_next( mw->img_list );
716
717 if( ! name && image_list_has_multiple_files( mw->img_list ) )
718 {
719 // FIXME: need to ask user first?
720 name = image_list_get_first( mw->img_list );
721 }
722
723 if( name )
724 {
725 char* file_path = image_list_get_current_file_path( mw->img_list );
726 main_win_open( mw, file_path, mw->zoom_mode );
727 g_free( file_path );
728 }
729 }
730
731 void cancel_slideshow(MainWin* mw)
732 {
733 mw->slideshow_cancelled = TRUE;
734 mw->slideshow_running = FALSE;
735 if (mw->slide_timeout != 0)
736 g_source_remove(mw->slide_timeout);
737 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(mw->btn_play_stop), FALSE );
738 }
739
740 gboolean next_slide(MainWin* mw)
741 {
742 /* Timeout causes an implicit "Next". */
743 if (mw->slideshow_running)
744 on_next( NULL, mw );
745
746 return mw->slideshow_running;
747 }
748
749 void on_slideshow_menu( GtkMenuItem* item, MainWin* mw )
750 {
751 gtk_button_clicked( (GtkButton*)mw->btn_play_stop );
752 }
753
754 void on_slideshow( GtkToggleButton* btn, MainWin* mw )
755 {
756 if ((mw->slideshow_running) || (mw->slideshow_cancelled))
757 {
758 mw->slideshow_running = FALSE;
759 mw->slideshow_cancelled = FALSE;
760 gtk_image_set_from_stock( GTK_IMAGE(mw->img_play_stop), GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_SMALL_TOOLBAR );
761 gtk_widget_set_tooltip_text( GTK_WIDGET(btn), _("Start Slideshow") );
762 gtk_toggle_button_set_active( btn, FALSE );
763 }
764 else
765 {
766 gtk_toggle_button_set_active( btn, TRUE );
767 mw->slideshow_running = TRUE;
768 gtk_image_set_from_stock( GTK_IMAGE(mw->img_play_stop), GTK_STOCK_MEDIA_STOP, GTK_ICON_SIZE_SMALL_TOOLBAR );
769 gtk_widget_set_tooltip_text( GTK_WIDGET(btn), _("Stop Slideshow") );
770 mw->slide_timeout = g_timeout_add(1000 * pref.slide_delay, (GSourceFunc) next_slide, mw);
771 }
772 }
773
774 //////////////////// rotate & flip
775
776 static int trans_angle_to_id(int i)
777 {
778 if(i == 0) return 1;
779 else if(i == 90) return 6;
780 else if(i == 180) return 3;
781 else if(i == 270) return 8;
782 else if(i == -45) return 7;
783 else if(i == -90) return 2;
784 else if(i == -135) return 5;
785 else /* -180 */ return 4;
786 }
787
788 static int get_new_angle( int orig_angle, int rotate_angle )
789 {
790 // defined in exif.c
791 static int angle_trans_back[] = {0, 0, -90, 180, -180, -135, 90, -45, 270};
792
793 orig_angle = trans_angle_to_id(orig_angle);
794 rotate_angle = trans_angle_to_id(rotate_angle);
795
796 return angle_trans_back[ ExifRotateFlipMapping[orig_angle][rotate_angle] ];
797 }
798
799 void on_rotate_auto_save( GtkWidget* btn, MainWin* mw )
800 {
801 if(pref.auto_save_rotated){
802 // gboolean ask_before_save = pref.ask_before_save;
803 // pref.ask_before_save = FALSE;
804 on_save(btn,mw);
805 // pref.ask_before_save = ask_before_save;
806 }
807 }
808
809 void on_rotate_clockwise( GtkWidget* btn, MainWin* mw )
810 {
811 cancel_slideshow(mw);
812 rotate_image( mw, GDK_PIXBUF_ROTATE_CLOCKWISE );
813 mw->rotation_angle = get_new_angle(mw->rotation_angle, 90);
814 on_rotate_auto_save(btn, mw);
815 }
816
817 void on_rotate_counterclockwise( GtkWidget* btn, MainWin* mw )
818 {
819 cancel_slideshow(mw);
820 rotate_image( mw, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE );
821 mw->rotation_angle = get_new_angle(mw->rotation_angle, 270);
822 on_rotate_auto_save(btn, mw);
823 }
824
825 void on_flip_vertical( GtkWidget* btn, MainWin* mw )
826 {
827 cancel_slideshow(mw);
828 rotate_image( mw, -180 );
829 mw->rotation_angle = get_new_angle(mw->rotation_angle, -180);
830 on_rotate_auto_save(btn, mw);
831 }
832
833 void on_flip_horizontal( GtkWidget* btn, MainWin* mw )
834 {
835 cancel_slideshow(mw);
836 rotate_image( mw, -90 );
837 mw->rotation_angle = get_new_angle(mw->rotation_angle, -90);
838 on_rotate_auto_save(btn, mw);
839 }
840
841 /* end of rotate & flip */
842
843 void on_save_as( GtkWidget* btn, MainWin* mw )
844 {
845 char *file, *type;
846
847 cancel_slideshow(mw);
848 if( ! mw->pix )
849 return;
850
851 file = get_save_filename( GTK_WINDOW(mw), image_list_get_dir( mw->img_list ), &type );
852 if( file )
853 {
854 char* dir;
855 main_win_save( mw, file, type, TRUE );
856 dir = g_path_get_dirname(file);
857 const char* name = file + strlen(dir) + 1;
858
859 if( strcmp( image_list_get_dir(mw->img_list), dir ) == 0 )
860 {
861 /* if the saved file is located in the same dir */
862 /* simply add it to image list */
863 image_list_add_sorted( mw->img_list, name, TRUE );
864 }
865 else /* otherwise reload the whole image list. */
866 {
867 /* switch to the dir containing the saved file. */
868 image_list_open_dir( mw->img_list, dir, NULL );
869 }
870 update_title( name, mw );
871 g_free( dir );
872 g_free( file );
873 g_free( type );
874 }
875 }
876
877 void on_save( GtkWidget* btn, MainWin* mw )
878 {
879 cancel_slideshow(mw);
880 if( ! mw->pix )
881 return;
882
883 char* file_name = g_build_filename( image_list_get_dir( mw->img_list ),
884 image_list_get_current( mw->img_list ), NULL );
885 GdkPixbufFormat* info;
886 info = gdk_pixbuf_get_file_info( file_name, NULL, NULL );
887 char* type = gdk_pixbuf_format_get_name( info );
888
889 /* Confirm save if requested. */
890 if ((pref.ask_before_save) && ( ! save_confirm(mw, file_name)))
891 return;
892
893 if(strcmp(type,"jpeg")==0)
894 {
895 if(!pref.rotate_exif_only || ExifRotate(file_name, mw->rotation_angle) == FALSE)
896 {
897 // hialan notes:
898 // ExifRotate retrun FALSE when
899 // 1. Can not read file
900 // 2. Exif do not have TAG_ORIENTATION tag
901 // 3. Format unknown
902 // And then we apply rotate_and_save_jpeg_lossless() ,
903 // the result would not effected by EXIF Orientation...
904 #ifdef HAVE_LIBJPEG
905 int status = rotate_and_save_jpeg_lossless(file_name,mw->rotation_angle);
906 if(status != 0)
907 {
908 main_win_show_error( mw, g_strerror(status) );
909 }
910 #else
911 main_win_save( mw, file_name, type, pref.ask_before_save );
912 #endif
913 }
914 } else
915 main_win_save( mw, file_name, type, pref.ask_before_save );
916 mw->rotation_angle = 0;
917 g_free( file_name );
918 g_free( type );
919 }
920
921 void on_open( GtkWidget* btn, MainWin* mw )
922 {
923 cancel_slideshow(mw);
924 char* file = get_open_filename( (GtkWindow*)mw, image_list_get_dir( mw->img_list ) );
925 if( file )
926 {
927 main_win_open( mw, file, ZOOM_NONE );
928 g_free( file );
929 }
930 }
931
932 void on_zoom_in( GtkWidget* btn, MainWin* mw )
933 {
934 double scale = mw->scale;
935 scale *= 1.05;
936 main_win_set_zoom_scale(mw, scale);
937 }
938
939 void on_zoom_out( GtkWidget* btn, MainWin* mw )
940 {
941 double scale = mw->scale;
942 scale /= 1.05;
943 main_win_set_zoom_scale(mw, scale);
944 }
945
946 void on_preference( GtkWidget* btn, MainWin* mw )
947 {
948 edit_preferences( (GtkWindow*)mw );
949 }
950
951 void on_quit( GtkWidget* btn, MainWin* mw )
952 {
953 cancel_slideshow(mw);
954 gtk_widget_destroy( (GtkWidget*)mw );
955 }
956
957 gboolean on_button_press( GtkWidget* widget, GdkEventButton* evt, MainWin* mw )
958 {
959 #if GTK_CHECK_VERSION(2, 14, 0)
960 if( ! gtk_widget_has_focus( widget ) )
961 #else
962 if( ! GTK_WIDGET_HAS_FOCUS( widget ) )
963 #endif
964 gtk_widget_grab_focus( widget );
965
966 if( evt->type == GDK_BUTTON_PRESS)
967 {
968 if( evt->button == 1 ) // left button
969 {
970 if( ! mw->pix )
971 return FALSE;
972 mw->dragging = TRUE;
973 gtk_widget_get_pointer( (GtkWidget*)mw, &mw->drag_old_x ,&mw->drag_old_y );
974 gdk_window_set_cursor( gtk_widget_get_window(widget), mw->hand_cursor );
975 }
976 else if( evt->button == 3 ) // right button
977 {
978 show_popup_menu( mw, evt );
979 }
980 }
981 else if( evt->type == GDK_2BUTTON_PRESS && evt->button == 1 ) // double clicked
982 {
983 on_full_screen( NULL, mw );
984 }
985 return FALSE;
986 }
987
988 gboolean on_mouse_move( GtkWidget* widget, GdkEventMotion* evt, MainWin* mw )
989 {
990 if( ! mw->dragging )
991 return FALSE;
992
993 int cur_x, cur_y;
994 gtk_widget_get_pointer( (GtkWidget*)mw, &cur_x ,&cur_y );
995
996 int dx = (mw->drag_old_x - cur_x);
997 int dy = (mw->drag_old_y - cur_y);
998
999 GtkAdjustment *hadj, *vadj;
1000 hadj = gtk_scrolled_window_get_hadjustment((GtkScrolledWindow*)mw->scroll);
1001 vadj = gtk_scrolled_window_get_vadjustment((GtkScrolledWindow*)mw->scroll);
1002
1003 GtkRequisition req;
1004 gtk_widget_size_request( (GtkWidget*)mw->img_view, &req );
1005
1006 #if GTK_CHECK_VERSION(2, 14, 0)
1007 gdouble hadj_page_size = gtk_adjustment_get_page_size(hadj);
1008 gdouble hadj_lower = gtk_adjustment_get_lower(hadj);
1009 gdouble hadj_upper = gtk_adjustment_get_upper(hadj);
1010 #else
1011 gdouble hadj_page_size = hadj->page_size;
1012 gdouble hadj_lower = hadj->lower;
1013 gdouble hadj_upper = hadj->upper;
1014 #endif
1015
1016 if( ABS(dx) > 4 )
1017 {
1018 mw->drag_old_x = cur_x;
1019 if( req.width > hadj_page_size )
1020 {
1021 gdouble value = gtk_adjustment_get_value (hadj);
1022 gdouble x = value + dx;
1023 if( x < hadj_lower )
1024 x = hadj_lower;
1025 else if( (x + hadj_page_size) > hadj_upper )
1026 x = hadj_upper - hadj_page_size;
1027
1028 if( x != value )
1029 gtk_adjustment_set_value (hadj, x );
1030 }
1031 }
1032
1033 #if GTK_CHECK_VERSION(2, 14, 0)
1034 gdouble vadj_page_size = gtk_adjustment_get_page_size(vadj);
1035 gdouble vadj_lower = gtk_adjustment_get_lower(vadj);
1036 gdouble vadj_upper = gtk_adjustment_get_upper(vadj);
1037 #else
1038 gdouble vadj_page_size = vadj->page_size;
1039 gdouble vadj_lower = vadj->lower;
1040 gdouble vadj_upper = vadj->upper;
1041 #endif
1042
1043 if( ABS(dy) > 4 )
1044 {
1045 if( req.height > vadj_page_size )
1046 {
1047 mw->drag_old_y = cur_y;
1048 gdouble value = gtk_adjustment_get_value (vadj);
1049 gdouble y = value + dy;
1050 if( y < vadj_lower )
1051 y = vadj_lower;
1052 else if( (y + vadj_page_size) > vadj_upper )
1053 y = vadj_upper - vadj_page_size;
1054
1055 if( y != value )
1056 gtk_adjustment_set_value (vadj, y );
1057 }
1058 }
1059 return FALSE;
1060 }
1061
1062 gboolean on_button_release( GtkWidget* widget, GdkEventButton* evt, MainWin* mw )
1063 {
1064 mw->dragging = FALSE;
1065 gdk_window_set_cursor( gtk_widget_get_window(widget), NULL );
1066 return FALSE;
1067 }
1068
1069 gboolean on_scroll_event( GtkWidget* widget, GdkEventScroll* evt, MainWin* mw )
1070 {
1071 guint modifiers = gtk_accelerator_get_default_mod_mask();
1072 switch( evt->direction )
1073 {
1074 case GDK_SCROLL_UP:
1075 if ((evt->state & modifiers) == GDK_CONTROL_MASK)
1076 on_zoom_in( NULL, mw );
1077 else
1078 on_prev( NULL, mw );
1079 break;
1080 case GDK_SCROLL_DOWN:
1081 if ((evt->state & modifiers) == GDK_CONTROL_MASK)
1082 on_zoom_out( NULL, mw );
1083 else
1084 on_next( NULL, mw );
1085 break;
1086 case GDK_SCROLL_LEFT:
1087 if( gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL )
1088 on_next( NULL, mw );
1089 else
1090 on_prev( NULL, mw );
1091 break;
1092 case GDK_SCROLL_RIGHT:
1093 if( gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL )
1094 on_prev( NULL, mw );
1095 else
1096 on_next( NULL, mw );
1097 break;
1098 }
1099 return TRUE;
1100 }
1101
1102 gboolean on_key_press_event(GtkWidget* widget, GdkEventKey * key)
1103 {
1104 MainWin* mw = (MainWin*)widget;
1105 switch( key->keyval )
1106 {
1107 case GDK_Right:
1108 case GDK_KP_Right:
1109 case GDK_rightarrow:
1110 if( gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL )
1111 on_prev( NULL, mw );
1112 else
1113 on_next( NULL, mw );
1114 break;
1115 case GDK_Return:
1116 case GDK_space:
1117 case GDK_Next:
1118 case GDK_KP_Down:
1119 case GDK_Down:
1120 case GDK_downarrow:
1121 on_next( NULL, mw );
1122 break;
1123 case GDK_Left:
1124 case GDK_KP_Left:
1125 case GDK_leftarrow:
1126 if( gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL )
1127 on_next( NULL, mw );
1128 else
1129 on_prev( NULL, mw );
1130 break;
1131 case GDK_Prior:
1132 case GDK_BackSpace:
1133 case GDK_KP_Up:
1134 case GDK_Up:
1135 case GDK_uparrow:
1136 on_prev( NULL, mw );
1137 break;
1138 case GDK_w:
1139 case GDK_W:
1140 on_slideshow_menu( NULL, mw );
1141 break;
1142 case GDK_KP_Add:
1143 case GDK_plus:
1144 case GDK_equal:
1145 on_zoom_in( NULL, mw );
1146 break;
1147 case GDK_KP_Subtract:
1148 case GDK_minus:
1149 on_zoom_out( NULL, mw );
1150 break;
1151 case GDK_s:
1152 case GDK_S:
1153 on_save( NULL, mw );
1154 break;
1155 case GDK_a:
1156 case GDK_A:
1157 on_save_as( NULL, mw );
1158 break;
1159 case GDK_l:
1160 case GDK_L:
1161 on_rotate_counterclockwise( NULL, mw );
1162 break;
1163 case GDK_r:
1164 case GDK_R:
1165 on_rotate_clockwise( NULL, mw );
1166 break;
1167 case GDK_f:
1168 case GDK_F:
1169 if( mw->zoom_mode != ZOOM_FIT )
1170 gtk_button_clicked((GtkButton*)mw->btn_fit );
1171 break;
1172 case GDK_g:
1173 case GDK_G:
1174 if( mw->zoom_mode != ZOOM_ORIG )
1175 gtk_button_clicked((GtkButton*)mw->btn_orig );
1176 break;
1177 case GDK_h:
1178 case GDK_H:
1179 on_flip_horizontal( NULL, mw );
1180 break;
1181 case GDK_v:
1182 case GDK_V:
1183 on_flip_vertical( NULL, mw );
1184 break;
1185 case GDK_o:
1186 case GDK_O:
1187 on_open( NULL, mw );
1188 break;
1189 case GDK_Delete:
1190 case GDK_d:
1191 case GDK_D:
1192 on_delete( NULL, mw );
1193 break;
1194 case GDK_p:
1195 case GDK_P:
1196 on_preference( NULL, mw );
1197 break;
1198 case GDK_t:
1199 case GDK_T:
1200 on_toggle_toolbar( NULL, mw );
1201 break;
1202 case GDK_Escape:
1203 if( mw->full_screen )
1204 on_full_screen( NULL, mw );
1205 else
1206 on_quit( NULL, mw );
1207 break;
1208 case GDK_q:
1209 case GDK_Q:
1210 on_quit( NULL, mw );
1211 break;
1212 case GDK_F11:
1213 on_full_screen( NULL, mw );
1214 break;
1215
1216 default:
1217 GTK_WIDGET_CLASS(main_win_parent_class)->key_press_event( widget, key );
1218 }
1219 return FALSE;
1220 }
1221
1222 void main_win_center_image( MainWin* mw )
1223 {
1224 GtkAdjustment *hadj, *vadj;
1225 hadj = gtk_scrolled_window_get_hadjustment((GtkScrolledWindow*)mw->scroll);
1226 vadj = gtk_scrolled_window_get_vadjustment((GtkScrolledWindow*)mw->scroll);
1227
1228 GtkRequisition req;
1229 gtk_widget_size_request( (GtkWidget*)mw->img_view, &req );
1230
1231 #if GTK_CHECK_VERSION(2, 14, 0)
1232 gdouble hadj_page_size = gtk_adjustment_get_page_size(hadj);
1233 gdouble hadj_upper = gtk_adjustment_get_upper(hadj);
1234 #else
1235 gdouble hadj_page_size = hadj->page_size;
1236 gdouble hadj_upper = hadj->upper;
1237 #endif
1238
1239 if( req.width > hadj_page_size )
1240 gtk_adjustment_set_value(hadj, ( hadj_upper - hadj_page_size ) / 2 );
1241
1242 #if GTK_CHECK_VERSION(2, 14, 0)
1243 gdouble vadj_page_size = gtk_adjustment_get_page_size(vadj);
1244 gdouble vadj_upper = gtk_adjustment_get_upper(vadj);
1245 #else
1246 gdouble vadj_page_size = vadj->page_size;
1247 gdouble vadj_upper = vadj->upper;
1248 #endif
1249
1250 if( req.height > vadj_page_size )
1251 gtk_adjustment_set_value(vadj, ( vadj_upper - vadj_page_size ) / 2 );
1252 }
1253
1254 void rotate_image( MainWin* mw, int angle )
1255 {
1256 GdkPixbuf* rpix = NULL;
1257
1258 if( ! mw->pix )
1259 return;
1260
1261 if(angle > 0)
1262 {
1263 rpix = gdk_pixbuf_rotate_simple( mw->pix, angle );
1264 }
1265 else
1266 {
1267 if(angle == -90)
1268 rpix = gdk_pixbuf_flip( mw->pix, TRUE );
1269 else if(angle == -180)
1270 rpix = gdk_pixbuf_flip( mw->pix, FALSE );
1271 }
1272
1273 if (!rpix) {
1274 return;
1275 }
1276
1277 g_object_unref( mw->pix );
1278
1279 mw->pix = rpix;
1280 image_view_set_pixbuf( (ImageView*)mw->img_view, mw->pix );
1281
1282 if( mw->zoom_mode == ZOOM_FIT )
1283 main_win_fit_window_size( mw, FALSE, GDK_INTERP_BILINEAR );
1284 }
1285
1286 gboolean main_win_scale_image( MainWin* mw, double new_scale, GdkInterpType type )
1287 {
1288 if( G_UNLIKELY( new_scale == 1.0 ) )
1289 {
1290 gtk_toggle_button_set_active( (GtkToggleButton*)mw->btn_orig, TRUE );
1291 mw->scale = 1.0;
1292 return TRUE;
1293 }
1294 mw->scale = new_scale;
1295 image_view_set_scale( (ImageView*)mw->img_view, new_scale, type );
1296
1297 update_title( NULL, mw );
1298
1299 return TRUE;
1300 }
1301
1302 gboolean save_confirm( MainWin* mw, const char* file_path )
1303 {
1304 if( g_file_test( file_path, G_FILE_TEST_EXISTS ) )
1305 {
1306 GtkWidget* dlg = gtk_message_dialog_new( (GtkWindow*)mw,
1307 GTK_DIALOG_MODAL,
1308 GTK_MESSAGE_QUESTION,
1309 GTK_BUTTONS_YES_NO,
1310 _("The file name you selected already exists.\nDo you want to overwrite existing file?\n(Warning: The quality of original image might be lost)") );
1311 if( gtk_dialog_run( (GtkDialog*)dlg ) != GTK_RESPONSE_YES )
1312 {
1313 gtk_widget_destroy( dlg );
1314 return FALSE;
1315 }
1316 gtk_widget_destroy( dlg );
1317 }
1318 return TRUE;
1319 }
1320
1321 gboolean main_win_save( MainWin* mw, const char* file_path, const char* type, gboolean confirm )
1322 {
1323 gboolean result1,gdk_save_supported;
1324 GSList *gdk_formats;
1325 GSList *gdk_formats_i;
1326 if( ! mw->pix )
1327 return FALSE;
1328
1329 /* detect if the current type can be save by gdk_pixbuf_save() */
1330 gdk_save_supported = FALSE;
1331 gdk_formats = gdk_pixbuf_get_formats();
1332 for (gdk_formats_i = gdk_formats; gdk_formats_i;
1333 gdk_formats_i = g_slist_next(gdk_formats_i))
1334 {
1335 GdkPixbufFormat *data;
1336 data = gdk_formats_i->data;
1337 if (gdk_pixbuf_format_is_writable(data))
1338 {
1339 if ( strcmp(type, gdk_pixbuf_format_get_name(data))==0)
1340 {
1341 gdk_save_supported = TRUE;
1342 break;
1343 }
1344 }
1345 }
1346 g_slist_free (gdk_formats);
1347
1348 GError* err = NULL;
1349 if (!gdk_save_supported)
1350 {
1351 main_win_show_error( mw, _("Writing this image format is not supported.") );
1352 return FALSE;
1353 }
1354 if( strcmp( type, "jpeg" ) == 0 )
1355 {
1356 char tmp[32];
1357 g_sprintf(tmp, "%d", pref.jpg_quality);
1358 result1 = gdk_pixbuf_save( mw->pix, file_path, type, &err, "quality", tmp, NULL );
1359 }
1360 else if( strcmp( type, "png" ) == 0 )
1361 {
1362 char tmp[32];
1363 g_sprintf(tmp, "%d", pref.png_compression);
1364 result1 = gdk_pixbuf_save( mw->pix, file_path, type, &err, "compression", tmp, NULL );
1365 }
1366 else
1367 result1 = gdk_pixbuf_save( mw->pix, file_path, type, &err, NULL );
1368 if( ! result1 )
1369 {
1370 main_win_show_error( mw, err->message );
1371 return FALSE;
1372 }
1373 return TRUE;
1374 }
1375
1376 void on_delete( GtkWidget* btn, MainWin* mw )
1377 {
1378 cancel_slideshow(mw);
1379 char* file_path = image_list_get_current_file_path( mw->img_list );
1380 if( file_path )
1381 {
1382 int resp = GTK_RESPONSE_YES;
1383 if ( pref.ask_before_delete )
1384 {
1385 GtkWidget* dlg = gtk_message_dialog_new( (GtkWindow*)mw,
1386 GTK_DIALOG_MODAL,
1387 GTK_MESSAGE_QUESTION,
1388 GTK_BUTTONS_YES_NO,
1389 _("Are you sure you want to delete current file?\n\nWarning: Once deleted, the file cannot be recovered.") );
1390 resp = gtk_dialog_run( (GtkDialog*)dlg );
1391 gtk_widget_destroy( dlg );
1392 }
1393
1394 if( resp == GTK_RESPONSE_YES )
1395 {
1396 const char* name = image_list_get_current( mw->img_list );
1397
1398 if( g_unlink( file_path ) != 0 )
1399 main_win_show_error( mw, g_strerror(errno) );
1400 else
1401 {
1402 const char* next_name = image_list_get_next( mw->img_list );
1403 if( ! next_name )
1404 next_name = image_list_get_prev( mw->img_list );
1405
1406 if( next_name )
1407 {
1408 char* next_file_path = image_list_get_current_file_path( mw->img_list );
1409 main_win_open( mw, next_file_path, ZOOM_FIT );
1410 g_free( next_file_path );
1411 }
1412
1413 image_list_remove ( mw->img_list, name );
1414
1415 if ( ! next_name )
1416 {
1417 main_win_close( mw );
1418 image_list_close( mw->img_list );
1419 image_view_set_pixbuf( (ImageView*)mw->img_view, NULL );
1420 gtk_window_set_title( (GtkWindow*) mw, _("Image Viewer"));
1421 }
1422 }
1423 }
1424 g_free( file_path );
1425 }
1426 }
1427
1428 void on_toggle_toolbar( GtkMenuItem* item, MainWin* mw )
1429 {
1430 pref.show_toolbar = !pref.show_toolbar;
1431
1432 if (pref.show_toolbar)
1433 gtk_widget_show(gtk_widget_get_parent(mw->nav_bar));
1434 else
1435 gtk_widget_hide(gtk_widget_get_parent(mw->nav_bar));
1436
1437 save_preferences();
1438 }
1439
1440
1441 void show_popup_menu( MainWin* mw, GdkEventButton* evt )
1442 {
1443 static PtkMenuItemEntry menu_def[] =
1444 {
1445 PTK_IMG_MENU_ITEM( N_( "Previous" ), GTK_STOCK_GO_BACK, on_prev, GDK_leftarrow, 0 ),
1446 PTK_IMG_MENU_ITEM( N_( "Next" ), GTK_STOCK_GO_FORWARD, on_next, GDK_rightarrow, 0 ),
1447 PTK_IMG_MENU_ITEM( N_( "Start/Stop Slideshow" ), GTK_STOCK_MEDIA_PLAY, on_slideshow_menu, GDK_W, 0 ),
1448 PTK_SEPARATOR_MENU_ITEM,
1449 PTK_IMG_MENU_ITEM( N_( "Zoom Out" ), GTK_STOCK_ZOOM_OUT, on_zoom_out, GDK_minus, 0 ),
1450 PTK_IMG_MENU_ITEM( N_( "Zoom In" ), GTK_STOCK_ZOOM_IN, on_zoom_in, GDK_plus, 0 ),
1451 PTK_IMG_MENU_ITEM( N_( "Fit Image To Window Size" ), GTK_STOCK_ZOOM_FIT, on_zoom_fit_menu, GDK_F, 0 ),
1452 PTK_IMG_MENU_ITEM( N_( "Original Size" ), GTK_STOCK_ZOOM_100, on_orig_size_menu, GDK_G, 0 ),
1453 PTK_SEPARATOR_MENU_ITEM,
1454 PTK_IMG_MENU_ITEM( N_( "Full Screen" ), GTK_STOCK_FULLSCREEN, on_full_screen, GDK_F11, 0 ),
1455 PTK_SEPARATOR_MENU_ITEM,
1456 PTK_IMG_MENU_ITEM( N_( "Rotate Counterclockwise" ), "object-rotate-left", on_rotate_counterclockwise, GDK_L, 0 ),
1457 PTK_IMG_MENU_ITEM( N_( "Rotate Clockwise" ), "object-rotate-right", on_rotate_clockwise, GDK_R, 0 ),
1458 PTK_IMG_MENU_ITEM( N_( "Flip Horizontal" ), "object-flip-horizontal", on_flip_horizontal, GDK_H, 0 ),
1459 PTK_IMG_MENU_ITEM( N_( "Flip Vertical" ), "object-flip-vertical", on_flip_vertical, GDK_V, 0 ),
1460 PTK_SEPARATOR_MENU_ITEM,
1461 PTK_IMG_MENU_ITEM( N_("Open File"), GTK_STOCK_OPEN, G_CALLBACK(on_open), GDK_O, 0 ),
1462 PTK_IMG_MENU_ITEM( N_("Save File"), GTK_STOCK_SAVE, G_CALLBACK(on_save), GDK_S, 0 ),
1463 PTK_IMG_MENU_ITEM( N_("Save As"), GTK_STOCK_SAVE_AS, G_CALLBACK(on_save_as), GDK_A, 0 ),
1464 // PTK_IMG_MENU_ITEM( N_("Save As Other Size"), GTK_STOCK_SAVE_AS, G_CALLBACK(on_save_as), GDK_A, 0 ),
1465 PTK_IMG_MENU_ITEM( N_("Delete File"), GTK_STOCK_DELETE, G_CALLBACK(on_delete), GDK_Delete, 0 ),
1466 PTK_SEPARATOR_MENU_ITEM,
1467 PTK_IMG_MENU_ITEM( N_("Preferences"), GTK_STOCK_PREFERENCES, G_CALLBACK(on_preference), GDK_P, 0 ),
1468 PTK_IMG_MENU_ITEM( N_("Show/Hide toolbar"), NULL, G_CALLBACK(on_toggle_toolbar), GDK_T, 0 ),
1469 PTK_STOCK_MENU_ITEM( GTK_STOCK_ABOUT, on_about ),
1470 PTK_SEPARATOR_MENU_ITEM,
1471 PTK_IMG_MENU_ITEM( N_("Quit"), GTK_STOCK_QUIT, G_CALLBACK(on_quit), GDK_Q, 0 ),
1472 PTK_MENU_END
1473 };
1474 GtkWidget* rotate_cw;
1475 GtkWidget* rotate_ccw;
1476 GtkWidget* flip_v;
1477 GtkWidget* flip_h;
1478
1479 menu_def[10].ret = &rotate_ccw;
1480 menu_def[11].ret = &rotate_cw;
1481 menu_def[12].ret = &flip_h;
1482 menu_def[13].ret = &flip_v;
1483
1484 // mw accel group is useless. It's only used to display accels in popup menu
1485 GtkAccelGroup* accel_group = gtk_accel_group_new();
1486 GtkMenuShell* popup = (GtkMenuShell*)ptk_menu_new_from_data( menu_def, mw, accel_group );
1487
1488 if( mw->animation )
1489 {
1490 gtk_widget_set_sensitive( rotate_ccw, FALSE );
1491 gtk_widget_set_sensitive( rotate_cw, FALSE );
1492 gtk_widget_set_sensitive( flip_h, FALSE );
1493 gtk_widget_set_sensitive( flip_v, FALSE );
1494 }
1495
1496 gtk_widget_show_all( (GtkWidget*)popup );
1497 g_signal_connect( popup, "selection-done", G_CALLBACK(gtk_widget_destroy), NULL );
1498 gtk_menu_popup( (GtkMenu*)popup, NULL, NULL, NULL, NULL, evt->button, evt->time );
1499 }
1500
1501 void on_about( GtkWidget* menu, MainWin* mw )
1502 {
1503 GtkWidget * about_dlg;
1504 const gchar *authors[] =
1505 {
1506 "洪任諭 Hong Jen Yee <pcman.tw@gmail.com>",
1507 "Martin Siggel <martinsiggel@googlemail.com>",
1508 "Hialan Liu <hialan.liu@gmail.com>",
1509 "Marty Jack <martyj19@comcast.net>",
1510 "Louis Casillas <oxaric@gmail.com>",
1511 "Will Davies",
1512 _(" * Refer to source code of EOG image viewer and GThumb"),
1513 NULL
1514 };
1515 /* TRANSLATORS: Replace this string with your names, one name per line. */
1516 gchar *translators = _( "translator-credits" );
1517
1518 about_dlg = gtk_about_dialog_new ();
1519
1520 gtk_container_set_border_width ( ( GtkContainer*)about_dlg , 2 );
1521 gtk_about_dialog_set_version ( (GtkAboutDialog*)about_dlg, VERSION );
1522 gtk_about_dialog_set_program_name ( (GtkAboutDialog*)about_dlg, _( "GPicView" ) );
1523 gtk_about_dialog_set_logo( (GtkAboutDialog*)about_dlg, gdk_pixbuf_new_from_file( PACKAGE_DATA_DIR"/pixmaps/gpicview.png", NULL ) );
1524 gtk_about_dialog_set_copyright ( (GtkAboutDialog*)about_dlg, _( "Copyright (C) 2007 - 2011" ) );
1525 gtk_about_dialog_set_comments ( (GtkAboutDialog*)about_dlg, _( "Lightweight image viewer from LXDE project" ) );
1526 gtk_about_dialog_set_license ( (GtkAboutDialog*)about_dlg, "GPicView\n\nCopyright (C) 2007 Hong Jen Yee (PCMan)\n\nThis program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA." );
1527 gtk_about_dialog_set_website ( (GtkAboutDialog*)about_dlg, "http://wiki.lxde.org/en/GPicView" );
1528 gtk_about_dialog_set_authors ( (GtkAboutDialog*)about_dlg, authors );
1529 gtk_about_dialog_set_translator_credits ( (GtkAboutDialog*)about_dlg, translators );
1530 gtk_window_set_transient_for( (GtkWindow*) about_dlg, GTK_WINDOW( mw ) );
1531
1532 gtk_dialog_run( ( GtkDialog*)about_dlg );
1533 gtk_widget_destroy( about_dlg );
1534 }
1535
1536 void on_drag_data_received( GtkWidget* widget, GdkDragContext *drag_context,
1537 int x, int y, GtkSelectionData* data, guint info, guint time, MainWin* mw )
1538 {
1539 if( !data)
1540 return;
1541
1542 #if GTK_CHECK_VERSION(2, 14, 0)
1543 int data_length = gtk_selection_data_get_length(data);
1544 #else
1545 int data_length = data->length;
1546 #endif
1547
1548 if( data_length <= 0)
1549 return;
1550
1551 char* file = NULL;
1552 if( info == 0 ) // text/uri-list
1553 {
1554 char** uris = gtk_selection_data_get_uris( data );
1555 if( uris )
1556 {
1557 file = g_filename_from_uri(*uris, NULL, NULL);
1558 g_strfreev( uris );
1559 }
1560 }
1561 else if( info == 1 ) // text/plain
1562 {
1563 file = (char*)gtk_selection_data_get_text( data );
1564 }
1565 if( file )
1566 {
1567 main_win_open( mw, file, ZOOM_FIT );
1568 g_free( file );
1569 }
1570 }
1571
1572 static void main_win_set_zoom_scale(MainWin* mw, double scale)
1573 {
1574 main_win_set_zoom_mode(mw, ZOOM_SCALE);
1575
1576 if (scale > 20.0)
1577 scale = 20.0;
1578 if (scale < 0.02)
1579 scale = 0.02;
1580
1581 if (mw->scale != scale)
1582 main_win_scale_image(mw, scale, GDK_INTERP_BILINEAR);
1583 }
1584
1585 static void main_win_set_zoom_mode(MainWin* mw, ZoomMode mode)
1586 {
1587 if (mw->zoom_mode == mode)
1588 return;
1589
1590 mw->zoom_mode = mode;
1591
1592 main_win_update_zoom_buttons_state(mw);
1593
1594 if (mode == ZOOM_ORIG)
1595 {
1596 mw->scale = 1.0;
1597 if (!mw->pix)
1598 return;
1599
1600 update_title(NULL, mw);
1601
1602 image_view_set_scale( (ImageView*)mw->img_view, 1.0, GDK_INTERP_BILINEAR );
1603
1604 while (gtk_events_pending ())
1605 gtk_main_iteration ();
1606
1607 main_win_center_image( mw ); // FIXME: mw doesn't work well. Why?
1608 }
1609 else if (mode == ZOOM_FIT)
1610 {
1611 main_win_fit_window_size( mw, FALSE, GDK_INTERP_BILINEAR );
1612 }
1613 }
1614
1615 static void main_win_update_zoom_buttons_state(MainWin* mw)
1616 {
1617 gboolean button_fit_active = mw->zoom_mode == ZOOM_FIT;
1618 gboolean button_orig_active = mw->zoom_mode == ZOOM_ORIG;
1619
1620 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mw->btn_fit)) != button_fit_active)
1621 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mw->btn_fit), button_fit_active);
1622
1623 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mw->btn_orig)) != button_orig_active)
1624 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mw->btn_orig), button_orig_active);
1625 }
1626
1627