/* * Copyright (C) 2007 Ross Burton * * Author: Ross Burton * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include #endif /* HAVE_CONFIG_H */ #include #include #include #include #include #include #include "katachi-fullscreen.h" #include "katachi-image-cache.h" #include "katachi-image-store.h" #include "thumbnailer.h" static int load_depth; /* Used to know when to restore the cursor */ static GdkCursor *busy_cursor; static GtkTreeModel *file_store; static GtkUIManager *ui_manager; static GtkActionGroup *single_action_group; static GtkActionGroup *many_action_group; static GtkWidget *window; static GtkWidget *file_list, *preview; static void update_actions (int selected) { gtk_action_group_set_sensitive (single_action_group, selected == 1); gtk_action_group_set_sensitive (many_action_group, selected > 0); } static void got_pixbuf (GFile *file, GdkPixbuf *pixbuf, gpointer user_data) { gtk_image_view_set_pixbuf (GTK_IMAGE_VIEW (preview), pixbuf, TRUE); /* The image view takes it's own reference, so unref our copy */ g_object_unref (pixbuf); if (--load_depth == 0) gdk_window_set_cursor (window->window, NULL); } /* * GtkIconView selection changed. Cancels an existing loader, and starts a new * one. */ static void on_selection_changed (GtkIconView *view, gpointer user_data) { GtkTreeModel *model; GList *list; int len; GtkTreeIter iter; GFile *file; model = gtk_icon_view_get_model (view); list = gtk_icon_view_get_selected_items (view); if (list == NULL) { gtk_image_view_set_pixbuf (GTK_IMAGE_VIEW (preview), NULL, FALSE); return; } len = g_list_length (list); /* Set the state of the action groups */ update_actions (len); if (len > 1) { gtk_image_view_set_pixbuf (GTK_IMAGE_VIEW (preview), NULL, FALSE); goto done; } gtk_tree_model_get_iter (model, &iter, list->data); gtk_tree_model_get (model, &iter, COL_FILE, &file, -1); if (!file) { g_warning (G_STRLOC ": cannot get file from store"); gtk_image_view_set_pixbuf (GTK_IMAGE_VIEW (preview), NULL, FALSE); goto done; } gdk_window_set_cursor (window->window, busy_cursor); load_depth++; katachi_image_cache_get_pixbuf (preview, file, TRUE, got_pixbuf, NULL); g_object_unref (file); done: g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL); g_list_free (list); } /* * Callback from rows being inserted, to generate a thumbnail. */ static void on_row_inserted (GtkTreeModel *model, GtkTreePath *tree_path, GtkTreeIter *iter, gpointer user_data) { ThumbnailData *data; data = g_slice_new0 (ThumbnailData); gtk_tree_model_get (model, iter, COL_FILE, &data->file, -1); if (data->file == NULL) return; data->iter = *iter; thumbnailer_push (data); } /* * Callback to get the current URL list when dragging from the thumbnails */ static void on_drag_data_get (GtkWidget *widget, GdkDragContext *drag_context, GtkSelectionData *data, guint info, guint time, gpointer user_data) { GString *uri_list; gchar *uris; GList *selected, *l; selected = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (widget)); uri_list = g_string_new (NULL); for (l = selected; l; l = g_list_next (l)) { GtkTreePath *path; GtkTreeIter iter; GFile *file = NULL; char *uri; path = (GtkTreePath *) l->data; gtk_tree_model_get_iter (file_store, &iter, path); gtk_tree_model_get (file_store, &iter, COL_FILE, &file, -1); if (!file) continue; uri = g_file_get_uri (file); g_string_append (uri_list, uri); g_string_append_c (uri_list, '\n'); g_object_unref (file); g_free (uri); } uris = g_string_free (uri_list, FALSE); if (uris) { gtk_selection_data_set (data, data->target, 8, (guchar *)uris, strlen (uris)); } g_list_free (selected); } /* * Callback from the UI manager with the GtkMenu widget. Pack and add this to * the container. */ static void ui_add_widget (GtkUIManager *ui, GtkWidget *widget, GtkContainer *container) { gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); gtk_widget_show (widget); } static void on_open_location_action (GtkAction *action, gpointer user_data) { GtkWidget *dialog; dialog = gtk_file_chooser_dialog_new (_("Select Directory"), GTK_WINDOW (window), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { char *filename; filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); katachi_image_store_populate (KATACHI_IMAGE_STORE (file_store), g_file_new_for_path (filename)); g_free (filename); } gtk_widget_destroy (dialog); } static void on_delete_action (GtkAction *action, gpointer user_data) { GtkWidget *dialog; GList *iters, *l; int count; iters = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (file_list)); count = g_list_length (iters); dialog = gtk_message_dialog_new (GTK_WINDOW (window), 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, ngettext ("Are you sure you want to delete this file?", "Are you sure you want to delete these %d files?", count), count); gtk_dialog_add_buttons (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_DELETE, GTK_RESPONSE_ACCEPT, NULL); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { gtk_icon_view_unselect_all (GTK_ICON_VIEW (file_list)); for (l = iters; l; l = l->next) { GtkTreeIter iter; GError *error = NULL; GFile *file = NULL; gtk_tree_model_get_iter (file_store, &iter, l->data); gtk_tree_model_get (file_store, &iter, COL_FILE, &file, -1); if (!file) continue; if (g_file_delete (file, NULL, &error)) { /* TODO: remove from the model now instead of waiting for the notification */ } else { g_warning ("Cannot delete file: %s", error->message); g_error_free (error); error = NULL; } g_object_unref (file); } } gtk_widget_destroy (dialog); g_list_foreach (iters, (GFunc)gtk_tree_path_free, NULL); g_list_free (iters); } /* * Called when the fullscreen window is deleted, gets the currently viewed path, * and selects it in the main window icon view. */ static gboolean on_fullscreen_delete (GtkWidget *widget, GdkEvent *event, gpointer user_data) { GtkTreePath *path; path = katachi_fullscreen_get_path (KATACHI_FULLSCREEN (widget)); if (path) { gtk_icon_view_unselect_all (GTK_ICON_VIEW (file_list)); gtk_icon_view_select_path (GTK_ICON_VIEW (file_list), path); gtk_tree_path_free (path); } return FALSE; } static void on_fullscreen_action (GtkAction *action, gpointer user_data) { GtkWidget *fullscreen; GList *paths; paths = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (file_list)); if (g_list_length (paths) == 1) { fullscreen = katachi_fullscreen_new (); g_signal_connect (fullscreen, "delete-event", G_CALLBACK (on_fullscreen_delete), NULL); katachi_fullscreen_set_store (KATACHI_FULLSCREEN (fullscreen), KATACHI_IMAGE_STORE (file_store)); gtk_window_add_accel_group (GTK_WINDOW (fullscreen), gtk_ui_manager_get_accel_group (ui_manager)); katachi_fullscreen_set_path (KATACHI_FULLSCREEN (fullscreen), paths->data); gtk_widget_show (fullscreen); } else { g_warning ("Fullscreen action should be disabled"); } g_list_foreach (paths, (GFunc)gtk_tree_path_free, NULL); g_list_free (paths); } /* * Callback from the About action. */ static void on_about_action (GtkAction *action, gpointer user_data) { const char* authors[] = { "Ross Burton ", NULL, }; const char *license = { N_( "Tasks is free software; you can redistribute it and/or modify " "it under the terms of the GNU General Public License as published by " "the Free Software Foundation; either version 2 of the License, or " "(at your option) any later version.\n\n" "Tasks is distributed in the hope that it will be useful, " "but WITHOUT ANY WARRANTY; without even the implied warranty of " "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the " "GNU General Public License for more details.\n\n" "You should have received a copy of the GNU General Public License " "along with Tasks; if not, write to the Free Software Foundation, Inc., " "51 Franklin St, Fifth Floor, Boston, MA 0110-1301, USA" ) }; gtk_show_about_dialog (GTK_WINDOW (window), "name", _("Katachi Image Viewer"), "version", VERSION, "logo-icon-name", "katachi", "copyright", "Copyright \302\251 2007 Ross Burton", "authors", authors, "license", license, "wrap-license", TRUE, "website", "http://burtonini.com/", NULL); } static const GtkActionEntry actions[] = { /* Action name, stock ID, label, accelerator, tooltip, callback */ { "FileMenu", NULL, N_("_File") }, { "OpenLocation", NULL, N_("Open _Location..."), NULL, NULL, G_CALLBACK (on_open_location_action) }, { "Quit", GTK_STOCK_QUIT, NULL, NULL, NULL, G_CALLBACK (gtk_main_quit) }, { "EditMenu", NULL, N_("_Edit") }, { "ViewMenu", NULL, N_("_View") }, { "HelpMenu", NULL, N_("_Help") }, { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK (on_about_action) }, }; static const GtkActionEntry single_image_actions[] = { { "Fullscreen", GTK_STOCK_FULLSCREEN, NULL, "F11", NULL, G_CALLBACK (on_fullscreen_action) }, }; static const GtkActionEntry many_image_actions[] = { { "Delete", GTK_STOCK_DELETE, NULL, "Delete", NULL, G_CALLBACK (on_delete_action) }, }; int main (int argc, char **argv) { GError *error = NULL; GtkWidget *top_box, *box, *scrolled, *image_scroll; GtkActionGroup *action_group; const GtkTargetEntry drag_targets[] = { { "text/uri-list", 0, 0 } }; g_thread_init (NULL); g_set_application_name (_("Katachi")); gtk_init (&argc, &argv); load_depth = 0; busy_cursor = gdk_cursor_new_from_name (gdk_display_get_default (), "left_ptr_watch"); katachi_image_cache_init (); file_store = katachi_image_store_new (); thumbnailer_init (KATACHI_IMAGE_STORE (file_store)); g_signal_connect_after (file_store, "row-inserted", G_CALLBACK (on_row_inserted), NULL); /* This starts an async operation, and will unref the path when done */ if (argc > 1) { katachi_image_store_populate (KATACHI_IMAGE_STORE (file_store), g_file_new_for_commandline_arg (argv[1])); } else { katachi_image_store_populate (KATACHI_IMAGE_STORE (file_store), g_file_new_for_path ("/home/ross/Pictures/Photos/Incoming/")); } window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (window, "delete-event", G_CALLBACK (gtk_main_quit), NULL); gtk_window_set_default_size (GTK_WINDOW (window), 640, 480); top_box = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (window), top_box); ui_manager = gtk_ui_manager_new (); action_group = gtk_action_group_new ("Actions"); gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE); gtk_action_group_add_actions (action_group, actions, G_N_ELEMENTS (actions), NULL); gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); single_action_group = gtk_action_group_new ("SingleImageActions"); gtk_action_group_set_translation_domain (single_action_group, GETTEXT_PACKAGE); gtk_action_group_add_actions (single_action_group, single_image_actions, G_N_ELEMENTS (single_image_actions), NULL); gtk_ui_manager_insert_action_group (ui_manager, single_action_group, 0); many_action_group = gtk_action_group_new ("ManyImageActions"); gtk_action_group_set_translation_domain (many_action_group, GETTEXT_PACKAGE); gtk_action_group_add_actions (many_action_group, many_image_actions, G_N_ELEMENTS (many_image_actions), NULL); gtk_ui_manager_insert_action_group (ui_manager, many_action_group, 0); gtk_ui_manager_add_ui_from_file (ui_manager, PKGDATADIR "/katachi-ui.xml", &error); if (error) { g_warning ("Cannot load UI: %s", error->message); g_error_free (error); error = NULL; } /* Bind the accelerators */ gtk_window_add_accel_group (GTK_WINDOW (window), gtk_ui_manager_get_accel_group (ui_manager)); g_signal_connect (ui_manager, "add-widget", G_CALLBACK (ui_add_widget), top_box); /* Do this so that the menu is packed now instead of in the idle loop */ gtk_ui_manager_ensure_update (ui_manager); box = gtk_hbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (top_box), box); scrolled = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); gtk_box_pack_start (GTK_BOX (box), scrolled, FALSE, TRUE, 0); /* TODO: make a widget out of this */ file_list = gtk_icon_view_new_with_model (file_store); /* TODO: add my own renderers so that I can use icon names and magically handle theme changes? */ g_object_set (file_list, "selection-mode", GTK_SELECTION_MULTIPLE, "columns", 1, "pixbuf-column", COL_THUMB, NULL); /* TODO: get these numbers from the widget */ gtk_widget_set_size_request (file_list, 128 + 6 + 6, -1); gtk_icon_view_enable_model_drag_source (GTK_ICON_VIEW (file_list), GDK_BUTTON1_MASK, drag_targets, G_N_ELEMENTS (drag_targets), GDK_ACTION_COPY); g_signal_connect (file_list, "drag-data-get", G_CALLBACK (on_drag_data_get), NULL); g_signal_connect (file_list, "selection-changed", G_CALLBACK (on_selection_changed), NULL); gtk_container_add (GTK_CONTAINER (scrolled), file_list); preview = gtk_image_view_new (); image_scroll = gtk_image_scroll_win_new (GTK_IMAGE_VIEW (preview)); gtk_box_pack_start (GTK_BOX (box), image_scroll, TRUE, TRUE, 0); update_actions (0); gtk_widget_show_all (window); gtk_main (); return 0; }