summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SConstruct3
-rw-r--r--db.c160
-rw-r--r--db.h10
-rw-r--r--tags.ui157
-rw-r--r--text_input_dialog.c35
-rw-r--r--text_input_dialog.h10
-rw-r--r--thumbnails.c14
-rw-r--r--walls.ui4
-rw-r--r--window_main.c133
-rw-r--r--window_tag.c204
-rw-r--r--window_tag.h15
11 files changed, 725 insertions, 20 deletions
diff --git a/SConstruct b/SConstruct
index 1b0395b..f41d190 100644
--- a/SConstruct
+++ b/SConstruct
@@ -23,7 +23,7 @@ def build_ui(target, source, env):
f.write('#ifndef _WALLS_UI_H_\n')
f.write('#define _WALLS_UI_H_\n')
f.write('#include <gtk/gtk.h>\n')
- f.write('gchar *ui_string = \n')
+ f.write('gchar *%s_string = \n' % str(target[0]).rsplit('.', 1)[0])
for line in open(str(source[0]), 'r'):
f.write('"%s"\n' % line.replace('"', '\\"').strip())
f.write(';\n')
@@ -33,6 +33,7 @@ ui_builder = Builder(action = build_ui)
env['BUILDERS']['walls_ui'] = ui_builder
env.walls_ui('walls_ui.h', 'walls.ui')
+env.walls_ui('tags_ui.h', 'tags.ui')
walls = env.Program('walls', Glob('*.c'))
destdir = ARGUMENTS.get('DESTDIR', '')
diff --git a/db.c b/db.c
index 4446d1e..b78ed54 100644
--- a/db.c
+++ b/db.c
@@ -323,6 +323,44 @@ int db_get_wallpaper_data(sqlite_uint64 id, struct wallpaper_t *wall) {
return 1;
}
+int db_get_wall_tags(sqlite_uint64 wallid, GArray **array) {
+ struct tag_t temp, *temp2;
+ sqlite3_stmt *stmt;
+ int rc;
+
+ rc = sqlite3_prepare_v2(db, "SELECT t.id, t.name FROM tag t JOIN walltags w ON (w.tagid = t.id AND w.wallid = ?)", -1, &stmt, NULL);
+
+ if(rc != SQLITE_OK) {
+ return 0;
+ }
+
+ rc = sqlite3_bind_int64(stmt, 1, wallid);
+ if(rc != SQLITE_OK) {
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+
+ *array = g_array_new(FALSE, FALSE, sizeof(struct tag_t));
+ while((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
+ temp.id = sqlite3_column_int64(stmt, 0);
+ temp.name = g_strdup(sqlite3_column_text(stmt, 1));
+ g_array_append_val(*array, temp);
+ }
+
+ sqlite3_finalize(stmt);
+
+ if(rc != SQLITE_DONE) {
+ for(int i = 0; i < (*array)->len; i++) {
+ temp2 = &g_array_index(*array, struct tag_t, i);
+ g_free(temp2->name);
+ }
+ g_array_free(*array, TRUE);
+ return 0;
+ }
+
+ return 1;
+}
+
int db_get_wallpapers(sqlite_uint64 dirid, GArray **array) {
struct wallpaper_t temp, *temp2;
sqlite3_stmt *stmt;
@@ -357,8 +395,130 @@ int db_get_wallpapers(sqlite_uint64 dirid, GArray **array) {
temp2 = &g_array_index(*array, struct wallpaper_t, i);
g_free(temp2->filepath);
}
+ g_array_free(*array, TRUE);
+ return 0;
+ }
+
+ return 1;
+}
+
+sqlite_uint64 db_add_tag(const char *name) {
+ sqlite3_stmt *stmt;
+ int rc;
+
+ rc = sqlite3_prepare_v2(db, "INSERT INTO tag (name) VALUES (?)", -1, &stmt, NULL);
+
+ if(rc != SQLITE_OK) {
+ return 0;
+ }
+
+ rc = sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
+ if(rc != SQLITE_OK) {
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+
+ rc = sqlite3_step(stmt);
+
+ sqlite3_finalize(stmt);
+ if(rc == SQLITE_DONE) {
+ return sqlite3_last_insert_rowid(db);
+ } else {
+ return 0;
+ }
+}
+
+int db_get_tags_all(GArray **array) {
+ struct tag_t temp, *temp2;
+ sqlite3_stmt *stmt;
+ int rc;
+
+ rc = sqlite3_prepare_v2(db, "SELECT id, name FROM tag ORDER BY name", -1, &stmt, NULL);
+ if(rc != SQLITE_OK) {
+ return 0;
+ }
+
+ *array = g_array_new(FALSE, FALSE, sizeof(struct tag_t));
+ while((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
+ temp.name = g_strdup(sqlite3_column_text(stmt, 1));
+ temp.id = sqlite3_column_int64(stmt, 0);
+ g_array_append_val(*array, temp);
+ }
+
+ sqlite3_finalize(stmt);
+
+ if(rc != SQLITE_DONE) {
+ for(int i = 0; i < (*array)->len; i++) {
+ temp2 = &g_array_index(*array, struct tag_t, i);
+ g_free(temp2->name);
+ }
+ g_array_free(*array, TRUE);
return 0;
}
return 1;
}
+
+int db_add_wall_tag(sqlite_uint64 wallid, sqlite_uint64 tagid) {
+ sqlite3_stmt *stmt;
+ int rc;
+
+ rc = sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO walltags (wallid, tagid) VALUES (?, ?)", -1, &stmt, NULL);
+
+ if(rc != SQLITE_OK) {
+ return 0;
+ }
+
+ rc = sqlite3_bind_int64(stmt, 1, wallid);
+ if(rc != SQLITE_OK) {
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+
+ rc = sqlite3_bind_int64(stmt, 2, tagid);
+ if(rc != SQLITE_OK) {
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+
+ rc = sqlite3_step(stmt);
+
+ sqlite3_finalize(stmt);
+ if(rc == SQLITE_DONE) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int db_remove_wall_tag(sqlite_uint64 wallid, sqlite_uint64 tagid) {
+ sqlite3_stmt *stmt;
+ int rc;
+
+ rc = sqlite3_prepare_v2(db, "DELETE FROM walltags WHERE wallid = ? AND tagid = ?", -1, &stmt, NULL);
+
+ if(rc != SQLITE_OK) {
+ return 0;
+ }
+
+ rc = sqlite3_bind_int64(stmt, 1, wallid);
+ if(rc != SQLITE_OK) {
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+
+ rc = sqlite3_bind_int64(stmt, 2, tagid);
+ if(rc != SQLITE_OK) {
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+
+ rc = sqlite3_step(stmt);
+
+ sqlite3_finalize(stmt);
+ if(rc == SQLITE_DONE) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
diff --git a/db.h b/db.h
index e655f7c..16f4cac 100644
--- a/db.h
+++ b/db.h
@@ -17,6 +17,11 @@ struct wallpaper_t {
int height;
};
+struct tag_t {
+ gchar *name;
+ sqlite_uint64 id;
+};
+
int db_open();
void db_close();
sqlite_uint64 db_add_directory(const char*, sqlite_uint64);
@@ -26,6 +31,11 @@ int db_get_directories(sqlite_uint64, GArray**);
sqlite_uint64 db_add_wallpaper(const char*, sqlite_uint64, int, int, int);
sqlite_uint64 db_get_wallpaper(const char*);
int db_get_wallpaper_data(sqlite_uint64, struct wallpaper_t*);
+int db_get_wall_tags(sqlite_uint64, GArray**);
int db_get_wallpapers(sqlite_uint64, GArray**);
+sqlite_uint64 db_add_tag(const char*);
+int db_get_tags_all(GArray**);
+int db_add_wall_tag(sqlite_uint64, sqlite_uint64);
+int db_remove_wall_tag(sqlite_uint64, sqlite_uint64);
#endif
diff --git a/tags.ui b/tags.ui
new file mode 100644
index 0000000..9d4d802
--- /dev/null
+++ b/tags.ui
@@ -0,0 +1,157 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="tagsdialog">
+ <property name="border_width">5</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="type_hint">normal</property>
+ <property name="has_separator">False</property>
+ <signal name="destroy" handler="on_tagsdialog_destroy"/>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkTreeView" id="tagview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVButtonBox" id="vbuttonbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="layout_style">spread</property>
+ <child>
+ <object class="GtkButton" id="addbtn">
+ <property name="label">gtk-add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_tags_addbtn_clicked"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="rembtn">
+ <property name="label">gtk-remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_tags_rembtn_clicked"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="selallbtn">
+ <property name="label">gtk-select-all</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_tags_selallbtn_clicked"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="selnonebtn">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_tags_selnonebtn_clicked"/>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label">gtk-cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="gtk_false"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label">gtk-apply</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="gtk_true"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button2</action-widget>
+ <action-widget response="-10">button1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/text_input_dialog.c b/text_input_dialog.c
new file mode 100644
index 0000000..da18b24
--- /dev/null
+++ b/text_input_dialog.c
@@ -0,0 +1,35 @@
+#include "text_input_dialog.h"
+
+GtkDialog *text_input_dialog_new(GtkWidget *parent, const gchar *msg) {
+ GtkWidget *dialog;
+ GtkWidget *entry;
+ GtkWidget *vbox;
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(parent), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, msg);
+ entry = gtk_entry_new();
+
+ vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ gtk_box_pack_end(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
+
+ gtk_widget_show(entry);
+
+ return GTK_DIALOG(dialog);
+}
+
+gchar *text_input_dialog_get_text(GtkWidget *dialog) {
+ GtkWidget *vbox;
+ GtkWidget *entry;
+ GList *list;
+ GtkEntryBuffer *buf;
+
+ vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ list = gtk_container_get_children(GTK_CONTAINER(vbox));
+ entry = NULL;
+ for(int i = 0; i < g_list_length(list); i++)
+ if(GTK_IS_ENTRY(g_list_nth_data(list, i)))
+ entry = GTK_WIDGET(g_list_nth_data(list, i));
+ g_assert(entry != NULL);
+ buf = gtk_entry_get_buffer(GTK_ENTRY(entry));
+ return g_strdup(gtk_entry_buffer_get_text(buf));
+}
diff --git a/text_input_dialog.h b/text_input_dialog.h
new file mode 100644
index 0000000..6edf6ac
--- /dev/null
+++ b/text_input_dialog.h
@@ -0,0 +1,10 @@
+#ifndef _TEXT_INPUT_DIALOG_H_
+#define _TEXT_INPUT_DIALOG_H_
+
+#include <gtk/gtk.h>
+#include <glib.h>
+
+GtkDialog *text_input_dialog_new(GtkWidget*, const gchar*);
+gchar *text_input_dialog_get_text(GtkWidget*);
+
+#endif
diff --git a/thumbnails.c b/thumbnails.c
index e88c19d..33b0573 100644
--- a/thumbnails.c
+++ b/thumbnails.c
@@ -98,20 +98,6 @@ GdkPixbuf *get_thumbnail(const gchar *filepath) {
return pb2;
}
-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, -1);
- g_free(wall->filepath);
- }
- g_array_free(array, TRUE);
-}
-
gpointer add_thumbs_thread(gpointer data) {
GtkListStore *liststore;
GtkTreeIter iter;
diff --git a/walls.ui b/walls.ui
index 52e5b64..242a5b1 100644
--- a/walls.ui
+++ b/walls.ui
@@ -134,9 +134,11 @@
<object class="GtkIconView" id="thumbview">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="selection_mode">browse</property>
+ <property name="selection_mode">multiple</property>
<property name="orientation">horizontal</property>
+ <signal name="button_press_event" handler="on_thumbview_button_press_event"/>
<signal name="selection_changed" handler="on_thumbview_selection_changed"/>
+ <signal name="popup_menu" handler="on_thumbview_popup_menu"/>
</object>
</child>
</object>
diff --git a/window_main.c b/window_main.c
index 38e1899..a1832e4 100644
--- a/window_main.c
+++ b/window_main.c
@@ -1,4 +1,3 @@
-//#include <unistd.h>
#include <string.h>
#include <glib/gstdio.h>
@@ -12,6 +11,7 @@
#include "wallpapers.h"
#include "thumbnails.h"
#include "walls_conf.h"
+#include "window_tag.h"
struct wallpaper_t *cur_wall = NULL;
GdkPixbuf *orig_pixbuf = NULL;
@@ -217,7 +217,7 @@ static void fill_wall_list(GtkListStore *liststore, GArray *array) {
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, -1);
+ gtk_list_store_set(liststore, &iter, 0, NULL, 1, wall->filepath, 2, wall->id,-1);
g_free(wall->filepath);
}
g_array_free(array, TRUE);
@@ -237,7 +237,7 @@ void on_foldtree_selection_changed(GtkTreeSelection *treeselection, gpointer use
if(!db_get_wallpapers(dir->dirid, &array))
return;
- liststore = gtk_list_store_new(2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
+ 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));
@@ -308,6 +308,131 @@ void on_add_dir_action_activate(GtkAction *action, gpointer user_data) {
gtk_widget_destroy(dialog);
}
+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);
+}
+
+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);
+ }
+ }
+}
+
+static void update_tags(GArray *wallarray, GtkListStore *liststore) {
+ GtkTreeIter iter;
+ GValue value = {0};
+ gboolean active, inconsistent;
+ guint64 tagid;
+
+ if(!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter)) {
+ g_error("Failed to get iter\n");
+ 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;
+}
+
static void save_window() {
gint width, height;
@@ -356,7 +481,7 @@ int gui_main(int argc, char **argv) {
gtk_init(&argc, &argv);
builder = gtk_builder_new();
- if(!gtk_builder_add_from_string(builder, ui_string, -1, &error)) {
+ if(!gtk_builder_add_from_string(builder, walls_ui_string, -1, &error)) {
g_warning("%s", error->message);
g_error_free(error);
return 1;
diff --git a/window_tag.c b/window_tag.c
new file mode 100644
index 0000000..2209572
--- /dev/null
+++ b/window_tag.c
@@ -0,0 +1,204 @@
+#include "window_tag.h"
+#include "tags_ui.h"
+#include "db.h"
+#include "text_input_dialog.h"
+
+void on_tagview_cell_toggled(GtkCellRendererToggle *cell_renderer, gchar *path_string, gpointer user_data) {
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ struct tagdialog_data_t *data;
+ GValue value = {0};
+ gboolean active, inconsistent;
+
+ data = user_data;
+
+ model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->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_tree_model_get_value(model, &iter, 1, &value);
+ inconsistent = g_value_get_boolean(&value);
+ g_value_unset(&value);
+
+ if(inconsistent) {
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 1, FALSE, -1);
+ } else {
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, !active, -1);
+ }
+}
+
+/* TODO: Find a better way to do this. */
+inline static gboolean is_tag_inconsistent(struct tag_t *tag, GArray *walltags, gboolean *exists) {
+ gboolean res;
+ gint n_y, n_n;
+ GArray *walltag_single;
+ gboolean found;
+ struct tag_t *wall_tag;
+
+ n_y = n_n = 0;
+
+ for(int i = 0; i < walltags->len; i++) {
+ walltag_single = g_array_index(walltags, GArray*, i);
+ found = FALSE;
+ for(int j = 0; j < walltag_single->len; j++) {
+ wall_tag = &g_array_index(walltag_single, struct tag_t, j);
+ if(wall_tag->id == tag->id) {
+ n_y++;
+ found = TRUE;
+ break;
+ }
+ }
+ if(!found)
+ n_n++;
+ }
+ if(n_y == 0)
+ *exists = FALSE;
+ else
+ *exists = TRUE;
+ return (n_n == 0 || n_y == 0 ? FALSE : TRUE);
+}
+
+static void tagview_create_model(GtkTreeView *tagview, gpointer user_data) {
+ GtkListStore *model;
+ GArray *array, *walltags, *walltag_single, *wallarray;
+ GtkTreeIter iter;
+ struct tag_t *tag;
+ gboolean inconsistent, exists;
+ struct tagdialog_data_t *data;
+
+ data = user_data;
+ wallarray = data->wallarray;
+
+ walltags = g_array_new(FALSE, FALSE, sizeof(GArray*));
+ for(int i = 0; i < wallarray->len; i++) {
+ if(db_get_wall_tags(g_array_index(wallarray, guint64, i), &walltag_single)) {
+ g_array_append_val(walltags, walltag_single);
+ } else {
+ g_error("db_get_wall_tags failed\n");
+ return;
+ }
+ }
+
+ model = gtk_list_store_new(4, G_TYPE_BOOLEAN, 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);
+ inconsistent = is_tag_inconsistent(tag, walltags, &exists);
+ gtk_list_store_set(model, &iter, 0, exists, 1, inconsistent, 2, g_strdup(tag->name), 3, tag->id, -1);
+ g_free(tag->name);
+ }
+ g_array_free(array, TRUE);
+ } else {
+ g_warning("Could not fetch tags\n");
+ }
+
+ for(int i = 0; i < walltags->len; i++) {
+ walltag_single = g_array_index(walltags, GArray*, i);
+ g_array_free(walltag_single, TRUE);
+ }
+ g_array_free(walltags, TRUE);
+
+ gtk_tree_view_set_model(tagview, GTK_TREE_MODEL(model));
+}
+
+static void tagview_init(GtkTreeView *tagview, gpointer user_data) {
+ GtkTreeViewColumn *col1, *col2;
+ GtkCellRenderer *renderer;
+
+ col1 = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(col1, "Select");
+ 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);
+ gtk_tree_view_column_add_attribute(col1, renderer, "inconsistent", 1);
+ g_signal_connect(renderer, "toggled", G_CALLBACK(on_tagview_cell_toggled), user_data);
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_end(col2, renderer, TRUE);
+ gtk_tree_view_column_add_attribute(col2, renderer, "text", 2);
+
+ tagview_create_model(tagview, user_data);
+}
+
+void on_tags_addbtn_clicked(GtkButton *button, gpointer user_data) {
+ struct tagdialog_data_t *data;
+ GtkDialog *dialog;
+ gchar *s;
+
+ data = user_data;
+ dialog = text_input_dialog_new(data->dialog, "Enter the name of the new tag:");
+ if(gtk_dialog_run(dialog) == GTK_RESPONSE_OK) {
+ s = text_input_dialog_get_text(GTK_WIDGET(dialog));
+ if(db_add_tag(s))
+ tagview_create_model(GTK_TREE_VIEW(data->tagview), user_data);
+ else
+ g_warning("Failed to add tag \"%s\"\n", s);
+ g_free(s);
+ }
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+void on_tags_rembtn_clicked(GtkButton *button, gpointer user_data) {
+}
+
+void on_tags_selallbtn_clicked(GtkButton *button, gpointer user_data) {
+}
+
+void on_tags_selnonebtn_clicked(GtkButton *button, gpointer user_data) {
+}
+
+void on_tagsdialog_destroy(GtkObject *object, gpointer user_data) {
+ g_free(user_data);
+}
+
+struct tagdialog_data_t *window_tagview_new(GtkWidget *parent, GArray* array) {
+ GtkBuilder *builder;
+ GtkDialog *dialog;
+ GtkTreeView *tagview;
+ GtkWidget *content;
+ GtkButton *selnonebtn;
+ GtkImage *image;
+ GError *error = NULL;
+ struct tagdialog_data_t *data;
+
+ builder = gtk_builder_new();
+ if(!gtk_builder_add_from_string(builder, tags_ui_string, -1, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ dialog = GTK_DIALOG(gtk_builder_get_object(builder, "tagsdialog"));
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent));
+ gtk_window_set_default_size(GTK_WINDOW(dialog), 300, 300);
+
+ tagview = GTK_TREE_VIEW(gtk_builder_get_object(builder, "tagview"));
+
+ selnonebtn = GTK_BUTTON(gtk_builder_get_object(builder, "selnonebtn"));
+ gtk_button_set_label(selnonebtn, "Select None");
+
+ image = GTK_IMAGE(gtk_image_new_from_stock(GTK_STOCK_NEW, GTK_ICON_SIZE_BUTTON));
+ gtk_button_set_image(selnonebtn, GTK_WIDGET(image));
+
+ data = g_malloc(sizeof(struct tagdialog_data_t));
+ data->dialog = GTK_WIDGET(dialog);
+ data->tagview = GTK_WIDGET(tagview);
+ data->wallarray = array;
+
+ tagview_init(tagview, data);
+
+ gtk_builder_connect_signals(builder, data);
+
+ return data;
+}
diff --git a/window_tag.h b/window_tag.h
new file mode 100644
index 0000000..96fceac
--- /dev/null
+++ b/window_tag.h
@@ -0,0 +1,15 @@
+#ifndef _WINDOW_TAG_H_
+#define _WINDOW_TAG_H_
+
+#include <gtk/gtk.h>
+#include <glib.h>
+
+struct tagdialog_data_t {
+ GtkWidget *dialog;
+ GtkWidget *tagview;
+ GArray *wallarray;
+};
+
+struct tagdialog_data_t *window_tagview_new(GtkWidget*, GArray*);
+
+#endif