#include #include #include #include "window_main.h" #include "walls_ui.h" #include "db.h" #include "browse_model.h" #include "wallpapers.h" #include "thumbnails.h" #include "walls_conf.h" #include "window_tag.h" struct wallpaper_t *cur_wall = NULL; GdkPixbuf *orig_pixbuf = NULL; gint last_width = 0, last_height = 0, hpane_last_pos = 0; guint hpane_source_id = 0; GtkWindow *window; GtkImage *image = NULL; GtkWidget *layout = NULL; GtkStatusbar *statusbar; GtkIconView *thumbview; GtkWidget *window_hpane, *window_vpane, *foldtree, *tagview; GThread *add_thread = NULL; gchar *cur_wall_msg = NULL; guint image_context; void on_foldtree_selection_changed(GtkTreeSelection *treeselection, gpointer user_data); void on_main_tagview_cell_toggled(GtkCellRendererToggle *cell_renderer, gchar *path_string, gpointer user_data); inline static void foldtree_create_model(GtkTreeView *foldtree) { GtkTreeModel *tree_model; tree_model = GTK_TREE_MODEL(browse_model_new()); gtk_tree_view_set_model(foldtree, GTK_TREE_MODEL(tree_model)); } inline static void foldtree_init(GtkTreeView *foldtree) { GtkTreeViewColumn *col1; GtkCellRenderer *renderer; GtkTreeSelection *selection; col1 = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col1, "Name"); gtk_tree_view_append_column(foldtree, col1); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(col1, renderer, TRUE); gtk_tree_view_column_add_attribute(col1, renderer, "text", 0); foldtree_create_model(foldtree); selection = gtk_tree_view_get_selection(foldtree); g_signal_connect(selection, "changed", G_CALLBACK(on_foldtree_selection_changed), foldtree); } inline static void tagview_create_model(GtkTreeView *tagview) { GtkListStore *model; GArray *array; struct tag_t *tag; GtkTreeIter iter; model = gtk_list_store_new(3, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_UINT64); if(db_get_tags_all(&array)) { for(int i = 0; i < array->len; i++) { tag = &g_array_index(array, struct tag_t, i); gtk_list_store_append(model, &iter); gtk_list_store_set(model, &iter, 0, FALSE, 1, g_strdup(tag->name), 2, tag->id, -1); g_free(tag->name); } g_array_free(array, TRUE); } gtk_tree_view_set_model(tagview, GTK_TREE_MODEL(model)); } inline static void tagview_init(GtkTreeView *tagview) { GtkTreeViewColumn *col1, *col2; GtkCellRenderer *renderer; GtkTreeSelection *selection; col1 = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col1, "Show"); gtk_tree_view_append_column(tagview, col1); col2 = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(col2, "Name"); gtk_tree_view_append_column(tagview, col2); renderer = gtk_cell_renderer_toggle_new(); gtk_tree_view_column_pack_start(col1, renderer, FALSE); gtk_tree_view_column_add_attribute(col1, renderer, "active", 0); g_signal_connect(renderer, "toggled", G_CALLBACK(on_main_tagview_cell_toggled), tagview); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_end(col2, renderer, TRUE); gtk_tree_view_column_add_attribute(col2, renderer, "text", 1); tagview_create_model(tagview); } static void set_resize_msg(gdouble scale) { gchar *msg; gtk_statusbar_pop(statusbar, image_context); msg = g_strdup_printf("%s (%d %%)", cur_wall_msg, (gint)(scale * 100.0)); gtk_statusbar_push(statusbar, image_context, msg); g_free(msg); } static void resize_pixbuf() { GdkPixbuf *pb; GdkWindow *window; gint win_width, win_height, img_width, img_height, width, height; gdouble scalex, scaley, width_ratio, height_ratio, max_ratio; /* Skip if no pixbuf is loaded yet. */ if(!orig_pixbuf) return; window = gtk_widget_get_window(layout); gdk_drawable_get_size(window, &win_width, &win_height); gtk_layout_set_size(GTK_LAYOUT(layout), win_width, win_height); img_width = gdk_pixbuf_get_width(orig_pixbuf); img_height = gdk_pixbuf_get_height(orig_pixbuf); /* do we need to resize? */ if(img_width > win_width || img_height > win_height) { /* resize by width */ width_ratio = (gdouble)img_width / (gdouble)win_width; height_ratio = (gdouble)img_height / (gdouble)win_height; if(width_ratio > height_ratio) { width = win_width; height = (gint)(1.0 / width_ratio * img_height); scalex = (gdouble)width / (gdouble)img_width; scaley = (gdouble)height / (gdouble)img_height; } else { /* resize by height */ height = win_height; width = (gint)(1.0 / height_ratio * img_width); scalex = (gdouble)width / (gdouble)img_width; scaley = (gdouble)height / (gdouble)img_height; } if(width == 0 || height == 0) return; pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height); gdk_pixbuf_scale(orig_pixbuf, pb, 0, 0, width, height, 0, 0, scalex, scaley, GDK_INTERP_BILINEAR); } else { width = img_width; height = img_height; pb = gdk_pixbuf_copy(orig_pixbuf); } gtk_widget_set_size_request(GTK_WIDGET(image), width, height); gtk_widget_set_uposition(GTK_WIDGET(image), (width < win_width ? win_width / 2 - width / 2 : 0), (height < win_height ? win_height / 2 - height / 2 : 0)); gtk_image_set_from_pixbuf(image, pb); g_object_unref(pb); set_resize_msg((gdouble)width / (gdouble)img_width); } void on_window_size_allocate(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data) { if(orig_pixbuf && (allocation->width != last_width || allocation->height != last_height)) { resize_pixbuf(); last_width = allocation->width; last_height = allocation->height; } } static gboolean hpane_resize_idle_func(gpointer data) { gint pos; pos = gtk_paned_get_position(GTK_PANED(data)); if(pos != hpane_last_pos) { resize_pixbuf(); hpane_last_pos = pos; return TRUE; /* Ensure that we'll keep checking until we're done resizing. */ } hpane_source_id = 0; return FALSE; } void on_window_hpane_resized(GObject *gobject, GParamSpec *pspec, gpointer user_data) { GSource *src; /* Don't let hpane_resize_idle_func run until the user has stopped moving the hpane (no movement for 100 ms). */ if(hpane_source_id > 0) { src = g_main_context_find_source_by_id(NULL, hpane_source_id); if(src) g_source_destroy(src); } hpane_source_id = g_timeout_add(100, hpane_resize_idle_func, (gpointer)gobject); } static void walls_set_window_title() { gchar *msg; msg = g_strdup_printf("walls - %s", cur_wall_msg); gtk_window_set_title(GTK_WINDOW(window), msg); g_free(msg); } static void load_pixbuf(const gchar *filepath) { GdkPixbuf *pb; GError *error = NULL; GdkWindow *window; sqlite_uint64 wallid; struct wallpaper_t *wall; gchar *base; if(orig_pixbuf) g_object_unref(orig_pixbuf); orig_pixbuf = gdk_pixbuf_new_from_file(filepath, &error); if(!orig_pixbuf) { g_warning("%s", error->message); g_error_free(error); return; } if(cur_wall) { g_free(cur_wall->filepath); g_free(cur_wall); cur_wall = NULL; } wallid = db_get_wallpaper(filepath); if(wallid) { wall = g_malloc(sizeof(struct wallpaper_t)); if(db_get_wallpaper_data(wallid, wall)) { cur_wall = wall; } else { g_free(wall->filepath); g_free(wall); } } if(cur_wall) { base = g_path_get_basename(cur_wall->filepath); if(cur_wall_msg) g_free(cur_wall_msg); cur_wall_msg = g_strdup_printf("%s: %dx%d", base, cur_wall->width, cur_wall->height); g_free(base); walls_set_window_title(); } resize_pixbuf(); } /** * Load wallpapers from array into liststore. */ static void fill_wall_list(GtkListStore *liststore, GArray *array) { GtkTreeIter iter; for(int i = 0; i < array->len; i++) { struct wallpaper_t *wall; wall = &g_array_index(array, struct wallpaper_t, i); gtk_list_store_append(liststore, &iter); gtk_list_store_set(liststore, &iter, 0, NULL, 1, wall->filepath, 2, wall->id,-1); g_free(wall->filepath); } g_array_free(array, TRUE); } static void start_thumb_thread(GtkListStore *liststore) { GError *error = NULL; if(thumb_thread) { thumb_thread_exit = TRUE; g_thread_join(thumb_thread); } thumb_thread = g_thread_create(add_thumbs_thread, liststore, TRUE, &error); if(!thumb_thread) { g_warning("Failed to start the thumbnail thread: %s", error->message); g_error_free(error); } } /** * Load wallpapers from the currently selected folder. */ static void display_from_foldtree(GtkTreeSelection *treeselection) { GtkTreeModel *model; GtkTreeIter iter; struct directory_t *dir; GArray *array; GtkListStore *liststore; if(treeselection == NULL) treeselection = gtk_tree_view_get_selection(GTK_TREE_VIEW(foldtree)); if(gtk_tree_selection_get_selected(treeselection, &model, &iter)) { browse_model_get_dir_record(model, &iter, &dir); if(!db_get_wallpapers(dir->dirid, &array)) return; liststore = gtk_list_store_new(3, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_UINT64); fill_wall_list(liststore, array); gtk_icon_view_set_model(thumbview, GTK_TREE_MODEL(liststore)); start_thumb_thread(liststore); } } void on_foldtree_selection_changed(GtkTreeSelection *treeselection, gpointer user_data) { display_from_foldtree(treeselection); } void on_thumbview_selection_changed(GtkIconView *iconview, gpointer user_data) { GList *list; GValue value; const gchar *filename; GtkTreePath *path; GtkTreeModel *model; GtkTreeIter iter; list = gtk_icon_view_get_selected_items(iconview); if(list) { if(g_list_length(list) == 1) { path = g_list_nth_data(list, 0); model = gtk_icon_view_get_model(iconview); gtk_tree_model_get_iter(model, &iter, path); memset(&value, 0, sizeof(GValue)); gtk_tree_model_get_value(model, &iter, 1, &value); filename = g_value_get_string(&value); load_pixbuf(filename); g_value_unset(&value); } g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL); g_list_free(list); } } /** * Load wallpapers from the currently selected tags. */ void display_from_tagview() { GtkTreeIter iter; GtkTreeModel *model; GtkListStore *liststore; GValue value = {0}; gboolean active; GArray *tagarray, *wallarray; guint64 id; model = gtk_tree_view_get_model(GTK_TREE_VIEW(tagview)); if(!gtk_tree_model_get_iter_first(model, &iter)) return; tagarray = g_array_new(FALSE, FALSE, sizeof(guint64)); do { gtk_tree_model_get_value(model, &iter, 0, &value); active = g_value_get_boolean(&value); g_value_unset(&value); if(!active) continue; gtk_tree_model_get_value(model, &iter, 2, &value); id = g_value_get_uint64(&value); g_value_unset(&value); g_array_append_val(tagarray, id); } while(gtk_tree_model_iter_next(model, &iter)); if(!db_get_walls_by_tags(tagarray, &wallarray)) { g_array_free(tagarray, TRUE); g_warning("Could not fetch walls by tags\n"); return; } liststore = gtk_list_store_new(3, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_UINT64); fill_wall_list(liststore, wallarray); gtk_icon_view_set_model(thumbview, GTK_TREE_MODEL(liststore)); start_thumb_thread(liststore); } void on_main_tagview_cell_toggled(GtkCellRendererToggle *cell_renderer, gchar *path_string, gpointer user_data) { GtkTreeIter iter; GtkTreeModel *model; GValue value = {0}; gboolean active; model = gtk_tree_view_get_model(GTK_TREE_VIEW(tagview)); gtk_tree_model_get_iter_from_string(model, &iter, path_string); gtk_tree_model_get_value(model, &iter, 0, &value); active = g_value_get_boolean(&value); g_value_unset(&value); gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, !active, -1); display_from_tagview(); } /** * Thread function for recursive directory adding. */ gpointer add_dir_thread(gpointer data) { gchar *directory; directory = (gchar*)data; add_dir_recursive(directory, 0, statusbar); g_free(directory); return NULL; } void on_add_dir_action_activate(GtkAction *action, gpointer user_data) { GtkWidget *dialog; GError *error; if(add_thread) return; dialog = gtk_file_chooser_dialog_new("Choose Directory", NULL, 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 *directory; directory = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); add_thread = g_thread_create(add_dir_thread, strdup(directory), FALSE, &error); if(!add_thread) { g_warning("%s", error->message); g_error_free(error); } } gtk_widget_destroy(dialog); } /** * Callback for adding selected wallpapers to an array. */ static void thumbview_selected_foreach(GtkIconView *icon_view, GtkTreePath *path, gpointer user_data) { GtkTreeIter iter; GtkTreeModel *model; GArray *array; GValue value = {0}; guint64 id; model = gtk_icon_view_get_model(icon_view); gtk_tree_model_get_iter(model, &iter, path); gtk_tree_model_get_value(model, &iter, 2, &value); array = user_data; id = g_value_get_uint64(&value); g_array_append_val(array, id); } /** * Add or remove tags for wallpapers. */ inline static void update_wall_tags(GArray *wallarray, guint64 tagid, gboolean active) { guint64 wallid; for(int i = 0; i < wallarray->len; i++) { wallid = g_array_index(wallarray, guint64, i); if(active) { db_add_wall_tag(wallid, tagid); } else { db_remove_wall_tag(wallid, tagid); } } } /** * Update tags for wallpapers. */ static void update_tags(GArray *wallarray, GtkListStore *liststore) { GtkTreeIter iter; GValue value = {0}; gboolean active, inconsistent; guint64 tagid; /* No tags available. */ if(!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter)) { return; } do { gtk_tree_model_get_value(GTK_TREE_MODEL(liststore), &iter, 1, &value); inconsistent = g_value_get_boolean(&value); g_value_unset(&value); /* Don't update inconsistent tags. */ if(inconsistent) continue; gtk_tree_model_get_value(GTK_TREE_MODEL(liststore), &iter, 0, &value); active = g_value_get_boolean(&value); g_value_unset(&value); gtk_tree_model_get_value(GTK_TREE_MODEL(liststore), &iter, 3, &value); tagid = g_value_get_uint64(&value); g_value_unset(&value); update_wall_tags(wallarray, tagid, active); } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(liststore), &iter)); } void on_tags_activate(GtkMenuItem *menuitem, gpointer user_data) { struct tagdialog_data_t *data; GArray *array; array = g_array_new(FALSE, TRUE, sizeof(guint64)); gtk_icon_view_selected_foreach(thumbview, thumbview_selected_foreach, array); data = window_tagview_new(GTK_WIDGET(window), array); if(gtk_dialog_run(GTK_DIALOG(data->dialog)) == GTK_RESPONSE_APPLY) { update_tags(array, GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(data->tagview)))); } gtk_widget_destroy(GTK_WIDGET(data->dialog)); g_array_free(array, TRUE); } inline static void thumbview_popup_add_items(GtkWidget *menu) { GtkWidget *tags; tags = gtk_menu_item_new_with_label("Tags"); gtk_menu_shell_append(GTK_MENU_SHELL(menu), tags); g_signal_connect(G_OBJECT(tags), "activate", G_CALLBACK(on_tags_activate), NULL); gtk_widget_show(tags); } static void do_thumbview_popup(GtkWidget *widget, GdkEventButton *event) { GtkWidget *menu; int button, event_time; menu = gtk_menu_new(); g_signal_connect(menu, "selection-done", G_CALLBACK(gtk_widget_destroy), NULL); thumbview_popup_add_items(menu); if(event) { button = event->button; event_time = event->time; } else { button = 0; event_time = gtk_get_current_event_time(); } gtk_menu_attach_to_widget(GTK_MENU(menu), widget, NULL); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button, event_time); } gboolean on_thumbview_popup_menu(GtkWidget *widget, gpointer user_data) { do_thumbview_popup(widget, NULL); } gboolean on_thumbview_button_press_event(GtkWidget *widget, GdkEventButton *event) { if(event->button == 3 && event->type == GDK_BUTTON_PRESS) { do_thumbview_popup(widget, event); return TRUE; } return FALSE; } gboolean on_left_pages_switch_page(GtkNotebook *notebook, GtkNotebookTab page, guint page_num, gpointer user_data) { switch(page_num) { case 0: display_from_foldtree(NULL); break; case 1: display_from_tagview(); break; } } static void save_window() { gint width, height; gtk_window_get_size(window, &width, &height); g_key_file_set_integer(keyfile, "window", "width", width); g_key_file_set_integer(keyfile, "window", "height", height); g_key_file_set_integer(keyfile, "window", "hpane_pos", gtk_paned_get_position(GTK_PANED(window_hpane))); g_key_file_set_integer(keyfile, "window", "vpane_pos", gtk_paned_get_position(GTK_PANED(window_vpane))); } void on_file_quit_activate(GtkMenuItem *menuitem, gpointer user_data) { save_window(); gtk_main_quit(); } static void set_sizes() { gint width, height, hpane_pos, vpane_pos; width = conf_get_int("window", "width", 800); height = conf_get_int("window", "height", 600); gtk_window_set_default_size(window, width, height); hpane_pos = conf_get_int("window", "hpane_pos", 190); vpane_pos = conf_get_int("window", "vpane_pos", 160); gtk_paned_set_position(GTK_PANED(window_hpane), hpane_pos); gtk_paned_set_position(GTK_PANED(window_vpane), vpane_pos); } int gui_main(int argc, char **argv) { GtkBuilder *builder ; GError *error = NULL; GdkColor color; g_thread_init(NULL); gdk_threads_init(); if(!db_open()) return 1; conf_open(); gtk_init(&argc, &argv); builder = gtk_builder_new(); if(!gtk_builder_add_from_string(builder, walls_ui_string, -1, &error)) { g_warning("%s", error->message); g_error_free(error); return 1; } window = GTK_WINDOW(gtk_builder_get_object(builder, "window")); foldtree = GTK_WIDGET(gtk_builder_get_object(builder, "foldtree")); foldtree_init(GTK_TREE_VIEW(foldtree)); tagview = GTK_WIDGET(gtk_builder_get_object(builder, "tagview")); tagview_init(GTK_TREE_VIEW(tagview)); thumbview = GTK_ICON_VIEW(gtk_builder_get_object(builder, "thumbview")); gtk_icon_view_set_pixbuf_column(thumbview, 0); image = GTK_IMAGE(gtk_builder_get_object(builder, "image")); layout = GTK_WIDGET(gtk_builder_get_object(builder, "layout")); if(gdk_color_parse("#000", &color)) gtk_widget_modify_bg(GTK_WIDGET(layout), GTK_STATE_NORMAL, &color); window_hpane = GTK_WIDGET(gtk_builder_get_object(builder, "window_hpane")); g_signal_connect(window_hpane, "notify::position", G_CALLBACK(on_window_hpane_resized), window_hpane); window_vpane = GTK_WIDGET(gtk_builder_get_object(builder, "window_vpane")); statusbar = GTK_STATUSBAR(gtk_builder_get_object(builder, "statusbar")); image_context = gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), "Image display"); gtk_builder_connect_signals(builder, NULL); g_object_unref(G_OBJECT(builder)); set_sizes(); gtk_widget_show_all(GTK_WIDGET(window)); gtk_main(); db_close(); conf_close(); return 0; }