[almanah/tagsupport: 1/2] Bug 684412 - Add support for tagging diary entries
- From: Ãlvaro PeÃa <alvaropg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [almanah/tagsupport: 1/2] Bug 684412 - Add support for tagging diary entries
- Date: Wed, 6 Feb 2013 11:14:19 +0000 (UTC)
commit 96b90bf60766d65f7e6c4735633a76722db4d55b
Author: Ãlvaro PeÃa <alvaropg gmail com>
Date: Thu Jan 24 16:58:40 2013 +0100
Bug 684412 - Add support for tagging diary entries
The main window now have an entry header, with the tag widget container
(AlmanahEntryTagsArea, base on a GtkGrid). This container is supposed to distribute the
tags (AlmanahTag) and the tag entry (AlmanahTagEntry) using the horizontal and vertical
space in a efficent way. It's responsible to load the tags for the selected entry.
The AlmanahTagEntry it's a GtkEntry with autocompletion capabilities with the tags available
in the diary storage.
AlmanahTag is a from scratch widget to draw the tag with some style using cairo.
The CSS has been updated to unify the visualization in the entry area.
data/almanah.css | 33 +++++-
data/almanah.ui | 211 ++++++++++++++++++-------------
src/Makefile.am | 8 +-
src/almanah-marshal.list | 1 +
src/main-window.c | 12 ++
src/storage-manager.c | 198 +++++++++++++++++++++++++++++
src/storage-manager.h | 5 +
src/widgets/entry-tags-area.c | 256 +++++++++++++++++++++++++++++++++++++
src/widgets/entry-tags-area.h | 53 ++++++++
src/widgets/tag-entry.c | 186 +++++++++++++++++++++++++++
src/widgets/tag-entry.h | 52 ++++++++
src/widgets/tag.c | 278 +++++++++++++++++++++++++++++++++++++++++
src/widgets/tag.h | 50 ++++++++
13 files changed, 1253 insertions(+), 90 deletions(-)
---
diff --git a/data/almanah.css b/data/almanah.css
index c0c3f42..fdf05ef 100644
--- a/data/almanah.css
+++ b/data/almanah.css
@@ -20,10 +20,39 @@ AlmanahCalendarWindow {
background-color: @menu_bg_color;
}
-AlmanahMainWindow GtkScrolledWindow {
+AlmanahMainWindow GtkToolbar {
border-style: solid;
border-radius: 0px;
border-width: 0px;
- border-top-width: 1px;
+ border-top-width: 0px;
border-bottom-width: 1px;
}
+
+AlmanahEntryTagsArea * {
+ background-color: #fff;
+}
+
+.almanah-mw-main-content GtkScrolledWindow {
+ border-style: solid;
+ border-radius: 0px;
+ border-width: 0px;
+ border-bottom-width: 1px;
+ background-color: #fff;
+}
+
+AlmanahTagEntry {
+ color: #aaa;
+ font-size: 8;
+ border-width: 0px;
+ border-style: none;
+ border-radius: 0px;
+ margin: 0px;
+ padding: 10px;
+ box-shadow: none;
+ background-color: #fff;
+ background-image: none;
+}
+
+AlmanahTagEntry:focused {
+ color: @theme_text_color;
+}
diff --git a/data/almanah.ui b/data/almanah.ui
index dbde58e..48c7a4a 100644
--- a/data/almanah.ui
+++ b/data/almanah.ui
@@ -127,11 +127,11 @@
<menuitem action="almanah_ui_insert_time"/>
<menuitem action="almanah_ui_hyperlink"/>
</popup>
- <popup name="almanah_mw_font_menu">
- <menuitem action="almanah_ui_bold"/>
- <menuitem action="almanah_ui_italic"/>
- <menuitem action="almanah_ui_underline"/>
- </popup>
+ <popup name="almanah_mw_font_menu">
+ <menuitem action="almanah_ui_bold"/>
+ <menuitem action="almanah_ui_italic"/>
+ <menuitem action="almanah_ui_underline"/>
+ </popup>
</ui>
</object>
@@ -212,16 +212,42 @@
</packing>
</child>
<child>
- <object class="GtkAlignment" id="mw_alignment">
+ <object class="GtkVBox" id="vbox3">
+ <property name="spacing">6</property>
<child>
- <object class="GtkVBox" id="vbox3">
- <property name="spacing">6</property>
+ <object class="GtkGrid" id="content_grid">
+ <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
+ <property name="row_spacing">0</property>
+ <style>
+ <class name="almanah-mw-main-content"/>
+ </style>
+ <child>
+ <object class="AlmanahEntryTagsArea" id="almanah_mw_entry_tags_area">
+ <property name="hexpand">True</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <property name="margin_top">0</property>
+ <property name="margin_left">0</property>
+ <property name="margin_right">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
<child>
- <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <object class="GtkScrolledWindow" id="almanah_mw_main_content_scrolled_window">
<property name="can-focus">True</property>
<property name="hscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow-type">GTK_SHADOW_IN</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <style>
+ <class name="almanah-main-content-scrolled"/>
+ </style>
<child>
<object class="GtkTextView" id="almanah_mw_entry_view">
<property name="width-request">300</property>
@@ -229,8 +255,9 @@
<property name="can-focus">True</property>
<property name="has-focus">True</property>
<property name="wrap-mode">GTK_WRAP_WORD</property>
- <property name="left-margin">3</property>
- <property name="right-margin">3</property>
+ <property name="margin_top">6</property>
+ <property name="left-margin">10</property>
+ <property name="right-margin">10</property>
<child internal-child="accessible">
<object class="AtkObject" id="a11y-almanah_mw_entry_view">
<property name="AtkObject::accessible-name" translatable="yes">Entry editing area</property>
@@ -240,104 +267,114 @@
</child>
</object>
<packing>
- <property name="expand">True</property>
- <property name="fill">True</property>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
</packing>
</child>
- <child>
- <object class="GtkExpander" id="almanah_mw_events_expander">
- <child type="label">
- <object class="GtkHBox" id="almanah_mw_events_expander_label_box">
- <property name="spacing">6</property>
- <child>
- <object class="GtkLabel" id="almanah_mw_events_label">
- <property name="label" translatable="yes">Past events</property>
- <accessibility>
- <relation target="almanah_mw_events_tree_view" type="label-for"/>
- </accessibility>
- <attributes>
- <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
- </attributes>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="almanah_mw_events_count_label">
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- </packing>
- </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkExpander" id="almanah_mw_events_expander">
+ <child type="label">
+ <object class="GtkHBox" id="almanah_mw_events_expander_label_box">
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="almanah_mw_events_label">
+ <property name="label" translatable="yes">Past events</property>
+ <accessibility>
+ <relation target="almanah_mw_events_tree_view" type="label-for"/>
+ </accessibility>
+ <attributes>
+ <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
+ </attributes>
</object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
</child>
<child>
- <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <object class="GtkLabel" id="almanah_mw_events_count_label">
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="can-focus">True</property>
+ <property name="hscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow-type">GTK_SHADOW_IN</property>
+ <child>
+ <object class="GtkTreeView" id="almanah_mw_events_tree_view">
+ <property name="model">almanah_mw_event_store</property>
<property name="can-focus">True</property>
- <property name="hscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
- <property name="vscrollbar-policy">GTK_POLICY_AUTOMATIC</property>
- <property name="shadow-type">GTK_SHADOW_IN</property>
+ <property name="headers-visible">False</property>
+ <signal name="row-activated" handler="mw_events_tree_view_row_activated_cb"/>
+ <accessibility>
+ <relation target="almanah_mw_events_label" type="labelled-by"/>
+ </accessibility>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-almanah_mw_events_tree_view">
+ <property name="AtkObject::accessible-name" translatable="yes">Past Event List</property>
+ </object>
+ </child>
<child>
- <object class="GtkTreeView" id="almanah_mw_events_tree_view">
- <property name="model">almanah_mw_event_store</property>
- <property name="can-focus">True</property>
- <property name="headers-visible">False</property>
- <signal name="row-activated" handler="mw_events_tree_view_row_activated_cb"/>
- <accessibility>
- <relation target="almanah_mw_events_label" type="labelled-by"/>
- </accessibility>
- <child internal-child="accessible">
- <object class="AtkObject" id="a11y-almanah_mw_events_tree_view">
- <property name="AtkObject::accessible-name" translatable="yes">Past Event List</property>
- </object>
- </child>
+ <object class="GtkTreeViewColumn" id="column3">
<child>
- <object class="GtkTreeViewColumn" id="column3">
- <child>
- <object class="GtkCellRendererPixbuf" id="renderer3"/>
- <attributes>
- <attribute name="icon-name">1</attribute>
- </attributes>
- </child>
- </object>
+ <object class="GtkCellRendererPixbuf" id="renderer3"/>
+ <attributes>
+ <attribute name="icon-name">1</attribute>
+ </attributes>
</child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="almanah_mw_event_value_column">
+ <property name="expand">True</property>
<child>
- <object class="GtkTreeViewColumn" id="almanah_mw_event_value_column">
- <property name="expand">True</property>
- <child>
- <object class="GtkCellRendererText" id="almanah_mw_event_value_renderer"/>
- <attributes>
- <attribute name="text">3</attribute>
- </attributes>
- </child>
- </object>
+ <object class="GtkCellRendererText" id="almanah_mw_event_value_renderer"/>
+ <attributes>
+ <attribute name="text">3</attribute>
+ </attributes>
</child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="almanah_mw_event_source_column">
<child>
- <object class="GtkTreeViewColumn" id="almanah_mw_event_source_column">
- <child>
- <object class="GtkCellRendererText" id="almanah_mw_event_source_renderer"/>
- <attributes>
- <attribute name="markup">4</attribute>
- </attributes>
- </child>
- </object>
+ <object class="GtkCellRendererText" id="almanah_mw_event_source_renderer"/>
+ <attributes>
+ <attribute name="markup">4</attribute>
+ </attributes>
</child>
</object>
</child>
</object>
</child>
</object>
- <packing>
- <property name="fill">False</property>
- <property name="expand">False</property>
- </packing>
</child>
</object>
+ <packing>
+ <property name="fill">False</property>
+ <property name="expand">False</property>
+ </packing>
</child>
</object>
+ <packing>
+ <property name="fill">True</property>
+ <property name="expand">True</property>
+ </packing>
</child>
</object>
</child>
diff --git a/src/Makefile.am b/src/Makefile.am
index 740c9f2..0c00ebf 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -40,8 +40,14 @@ almanah_SOURCES = \
widgets/calendar-button.c \
widgets/calendar-window.h \
widgets/calendar-window.c \
+ widgets/entry-tags-area.h \
+ widgets/entry-tags-area.c \
widgets/hyperlink-tag.c \
- widgets/hyperlink-tag.h
+ widgets/hyperlink-tag.h \
+ widgets/tag.c \
+ widgets/tag.h \
+ widgets/tag-entry.c \
+ widgets/tag-entry.h
if HAVE_EVO
almanah_SOURCES += \
diff --git a/src/almanah-marshal.list b/src/almanah-marshal.list
index 72f9937..a483b95 100644
--- a/src/almanah-marshal.list
+++ b/src/almanah-marshal.list
@@ -1 +1,2 @@
VOID:STRING,STRING
+VOID:OBJECT,STRING
diff --git a/src/main-window.c b/src/main-window.c
index fb931e1..6cb6b08 100644
--- a/src/main-window.c
+++ b/src/main-window.c
@@ -39,6 +39,7 @@
#include "widgets/calendar.h"
#include "widgets/calendar-button.h"
#include "widgets/hyperlink-tag.h"
+#include "widgets/entry-tags-area.h"
/* Interval for automatically saving the current entry. Currently an arbitrary 10 minutes. */
#define SAVE_ENTRY_INTERVAL 10 * 60 /* seconds */
@@ -85,6 +86,7 @@ static void mw_menu_button_popup_visible_cb (GtkWidget *menu, GParamSpec *pspec,
struct _AlmanahMainWindowPrivate {
GtkTextView *entry_view;
GtkTextBuffer *entry_buffer;
+ AlmanahEntryTagsArea *entry_tags_area;
AlmanahCalendarButton *calendar_button;
GtkListStore *event_store;
GtkWidget *events_expander;
@@ -170,6 +172,7 @@ almanah_main_window_new (AlmanahApplication *application)
AlmanahMainWindow *main_window;
AlmanahMainWindowPrivate *priv;
GError *error = NULL;
+ AlmanahStorageManager *storage_manager;
const gchar *interface_filename = almanah_get_interface_filename ();
const gchar *object_names[] = {
"almanah_main_window",
@@ -216,6 +219,7 @@ almanah_main_window_new (AlmanahApplication *application)
/* Grab our child widgets */
priv->entry_view = GTK_TEXT_VIEW (gtk_builder_get_object (builder, "almanah_mw_entry_view"));
priv->entry_buffer = gtk_text_view_get_buffer (priv->entry_view);
+ priv->entry_tags_area = ALMANAH_ENTRY_TAGS_AREA (gtk_builder_get_object (builder, "almanah_mw_entry_tags_area"));
priv->event_store = GTK_LIST_STORE (gtk_builder_get_object (builder, "almanah_mw_event_store"));
priv->events_expander = GTK_WIDGET (gtk_builder_get_object (builder, "almanah_mw_events_expander"));
priv->events_count_label = GTK_LABEL (gtk_builder_get_object (builder, "almanah_mw_events_count_label"));
@@ -257,6 +261,11 @@ almanah_main_window_new (AlmanahApplication *application)
/* Similarly, make sure we're notified when there's a selection so we can change the status of cut/copy/paste actions */
g_signal_connect (priv->entry_buffer, "notify::has-selection", G_CALLBACK (mw_entry_buffer_has_selection_cb), main_window);
+ /* Set the storage to the tags area */
+ storage_manager = almanah_application_dup_storage_manager (application);
+ almanah_entry_tags_area_set_storage_manager (priv->entry_tags_area, storage_manager);
+ g_object_unref (storage_manager);
+
/* Connect up the formatting actions */
g_signal_connect (priv->bold_action, "toggled", G_CALLBACK (mw_bold_toggled_cb), main_window);
g_signal_connect (priv->italic_action, "toggled", G_CALLBACK (mw_italic_toggled_cb), main_window);
@@ -1180,6 +1189,9 @@ mw_calendar_day_selected_cb (AlmanahCalendarButton *calendar_button, AlmanahMain
event_manager = almanah_application_dup_event_manager (application);
almanah_event_manager_query_events (event_manager, ALMANAH_EVENT_FACTORY_UNKNOWN, &calendar_date);
g_object_unref (event_manager);
+
+ /* Show the entry tags */
+ almanah_entry_tags_area_set_entry (priv->entry_tags_area, priv->current_entry);
}
void
diff --git a/src/storage-manager.c b/src/storage-manager.c
index 26ffe17..2708a5d 100644
--- a/src/storage-manager.c
+++ b/src/storage-manager.c
@@ -59,6 +59,8 @@ enum {
SIGNAL_ENTRY_ADDED,
SIGNAL_ENTRY_MODIFIED,
SIGNAL_ENTRY_REMOVED,
+ SIGNAL_ENTRY_TAG_ADDED,
+ SIGNAL_ENTRY_TAG_REMOVED,
LAST_SIGNAL
};
@@ -120,6 +122,18 @@ almanah_storage_manager_class_init (AlmanahStorageManagerClass *klass)
0, NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1, G_TYPE_DATE);
+ storage_manager_signals[SIGNAL_ENTRY_TAG_ADDED] = g_signal_new ("entry-tag-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ almanah_marshal_VOID__OBJECT_STRING,
+ G_TYPE_NONE, 2, ALMANAH_TYPE_ENTRY, G_TYPE_STRING);
+ storage_manager_signals[SIGNAL_ENTRY_TAG_REMOVED] = g_signal_new ("entry-tag-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ almanah_marshal_VOID__OBJECT_STRING,
+ G_TYPE_NONE, 2, ALMANAH_TYPE_ENTRY, G_TYPE_STRING);
}
static void
@@ -230,6 +244,8 @@ create_tables (AlmanahStorageManager *self)
"ALTER TABLE entries ADD COLUMN edited_month INTEGER", /* added in 0.8.0 */
"ALTER TABLE entries ADD COLUMN edited_day INTEGER", /* added in 0.8.0 */
"ALTER TABLE entries ADD COLUMN version INTEGER DEFAULT 1", /* added in 0.8.0 */
+ "CREATE TABLE IF NOT EXISTS entry_tag (year INTEGER, month INTEGER, day INTEGER, tag TEXT)", /* added in 0.10.0 */
+ "CREATE INDEX idx_tag ON entry_tag(tag)", /* added in 0.10.0, for information take a look at: http://www.sqlite.org/queryplanner.html */
NULL
};
@@ -1298,3 +1314,185 @@ almanah_storage_manager_get_filename (AlmanahStorageManager *self, gboolean plai
{
return (plain == TRUE) ? self->priv->plain_filename : self->priv->filename;
}
+
+/**
+ * almanah_storage_manager_entry_add_tag:
+ * @self: an #AlmanahStorageManager
+ * @entry: an #AlmanahEntry
+ * @tag: a string
+ *
+ * Append the string in @tag as a tag for the entry @entry. If the @tag is empty or the @entry don't be previuslly saved, returns %FALSE
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ */
+gboolean
+almanah_storage_manager_entry_add_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag)
+{
+ GDate entry_last_edited;
+ GDate entry_date;
+ sqlite3_stmt *statement;
+ gint result_error;
+
+ g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
+ g_return_val_if_fail (ALMANAH_IS_ENTRY (entry), FALSE);
+ g_return_val_if_fail (g_utf8_strlen (tag, 1) == 1, FALSE);
+
+ /* This validations are required for DB integrity. Only saved entry
+ must have tags */
+ almanah_entry_get_last_edited (entry, &entry_last_edited);
+ if (g_date_valid (&entry_last_edited) != TRUE) {
+ g_debug ("Entry don't saved into the storage");
+ return FALSE;
+ }
+
+ almanah_entry_get_date (entry, &entry_date);
+ if (g_date_valid (&entry_date) != TRUE) {
+ g_debug ("Invalid entry date");
+ return FALSE;
+ }
+
+ if ((result_error = sqlite3_prepare_v2 (self->priv->connection,
+ "INSERT INTO entry_tag (year, month, day, tag) VALUES (?, ?, ?, ?)",
+ -1, &statement, NULL)) != SQLITE_OK) {
+ g_debug ("Can't prepare statement. SQLite error code: %d", result_error);
+ return FALSE;
+ }
+
+ sqlite3_bind_int (statement, 1, g_date_get_year (&entry_date));
+ sqlite3_bind_int (statement, 2, g_date_get_month (&entry_date));
+ sqlite3_bind_int (statement, 3, g_date_get_day (&entry_date));
+ sqlite3_bind_text (statement, 4, tag, -1, SQLITE_STATIC); /* @TODO: STATIC or TRANSIENT */
+
+ if (sqlite3_step (statement) != SQLITE_DONE) {
+ sqlite3_finalize (statement);
+ g_debug ("Can't save tag");
+ return FALSE;
+ }
+
+ sqlite3_finalize (statement);
+
+ g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_TAG_ADDED], 0, entry, g_strdup (tag));
+
+ return TRUE;
+}
+
+/**
+ * almanah_storage_manager_entry_remove_tag:
+ * @self: an #AlmanahStorageManager
+ * @entry: an #AlmanahEntry
+ * @tag: a string with the tag to be removed
+ *
+ * Remove the tag with the given string in @tag as a tag for the entry @entry.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ */
+gboolean
+almanah_storage_manager_entry_remove_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag)
+{
+ GDate date;
+ gboolean result;
+
+ g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
+ g_return_val_if_fail (ALMANAH_IS_ENTRY (entry), FALSE);
+ g_return_val_if_fail (g_utf8_strlen (tag, 1) == 1, FALSE);
+
+ almanah_entry_get_date (entry, &date);
+
+ result = simple_query (self, "DELETE FROM entry_tag WHERE year = %u AND month = %u AND day = %u AND tag = '%s'", NULL,
+ g_date_get_year (&date),
+ g_date_get_month (&date),
+ g_date_get_day (&date),
+ tag);
+
+ if (result)
+ g_signal_emit (self, storage_manager_signals[SIGNAL_ENTRY_TAG_REMOVED], 0, entry, tag);
+
+ return result;
+}
+
+/**
+ * almanah_storage_manager_entry_get_tags:
+ * @self: an #AlmanahStorageManager
+ * @entry: an #AlmanahEntry
+ *
+ * Gets the tags added to an entry by the user from the database.
+ */
+GList *
+almanah_storage_manager_entry_get_tags (AlmanahStorageManager *self, AlmanahEntry *entry)
+{
+ GList *tags = NULL;
+ GDate date;
+ sqlite3_stmt *statement;
+ gint result;
+
+ g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
+ g_return_val_if_fail (ALMANAH_IS_ENTRY (entry), FALSE);
+
+ almanah_entry_get_date (entry, &date);
+ if (g_date_valid (&date) != TRUE) {
+ g_debug ("Invalid entry date.");
+ return NULL;
+ }
+
+ if (sqlite3_prepare_v2 (self->priv->connection,
+ "SELECT DISTINCT tag FROM entry_tag WHERE year = ? AND month = ? AND day = ?",
+ -1, &statement, NULL) != SQLITE_OK) {
+ g_debug ("Can't prepare statement");
+ return NULL;
+ }
+
+ sqlite3_bind_int (statement, 1, g_date_get_year (&date));
+ sqlite3_bind_int (statement, 2, g_date_get_month (&date));
+ sqlite3_bind_int (statement, 3, g_date_get_day (&date));
+
+ while ((result = sqlite3_step (statement)) == SQLITE_ROW) {
+ tags = g_list_append (tags, g_strdup (sqlite3_column_text (statement, 0)));
+ }
+
+ sqlite3_finalize (statement);
+
+ if (result != SQLITE_DONE) {
+ g_debug ("Error quering for tags from database: %s", sqlite3_errmsg (self->priv->connection));
+ g_free (tags);
+ tags = NULL;
+ }
+
+ return tags;
+}
+
+/**
+ * almanah_storage_manager_get_tags:
+ * @self: an #AlmanahStorageManager
+ *
+ * Gets all the tags added to entries by the user from the database.
+ *
+ * Return value: #GList with all the tags.
+ */
+GList *
+almanah_storage_manager_get_tags (AlmanahStorageManager *self)
+{
+ GList *tags = NULL;
+ sqlite3_stmt *statement;
+ gint result;
+
+ g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), FALSE);
+
+ if ((result = sqlite3_prepare_v2 (self->priv->connection, "SELECT DISTINCT tag FROM entry_tag", -1, &statement, NULL)) != SQLITE_OK) {
+ g_debug ("Can't prepare statement, error code: %d", result);
+ return NULL;
+ }
+
+ while ((result = sqlite3_step (statement)) == SQLITE_ROW) {
+ tags = g_list_append (tags, g_strdup (sqlite3_column_text (statement, 0)));
+ }
+
+ sqlite3_finalize (statement);
+
+ if (result != SQLITE_DONE) {
+ g_debug ("Error quering for tags from database: %s", sqlite3_errmsg (self->priv->connection));
+ g_free (tags);
+ tags = NULL;
+ }
+
+ return tags;
+}
diff --git a/src/storage-manager.h b/src/storage-manager.h
index 74e956a..0c47097 100644
--- a/src/storage-manager.h
+++ b/src/storage-manager.h
@@ -95,6 +95,11 @@ gboolean *almanah_storage_manager_get_month_important_days (AlmanahStorageManage
const gchar *almanah_storage_manager_get_filename (AlmanahStorageManager *self, gboolean plain);
+gboolean almanah_storage_manager_entry_add_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag);
+gboolean almanah_storage_manager_entry_remove_tag (AlmanahStorageManager *self, AlmanahEntry *entry, const gchar *tag);
+GList *almanah_storage_manager_entry_get_tags (AlmanahStorageManager *self, AlmanahEntry *entry);
+GList *almanah_storage_manager_get_tags (AlmanahStorageManager *self);
+
G_END_DECLS
#endif /* !ALMANAH_STORAGE_MANAGER_H */
diff --git a/src/widgets/entry-tags-area.c b/src/widgets/entry-tags-area.c
new file mode 100644
index 0000000..c73e39d
--- /dev/null
+++ b/src/widgets/entry-tags-area.c
@@ -0,0 +1,256 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Ãlvaro PeÃa 2013 <alvaropg gmail com>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "entry-tags-area.h"
+#include "tag.h"
+#include "tag-entry.h"
+#include "entry.h"
+#include "storage-manager.h"
+
+enum {
+ PROP_ENTRY = 1,
+ PROP_STORAGE_MANAGER
+};
+
+struct _AlmanahEntryTagsAreaPrivate {
+ AlmanahEntry *entry;
+ AlmanahStorageManager *storage_manager;
+ guint tags_number;
+ AlmanahTagEntry *tag_entry;
+};
+
+static void almanah_entry_tags_area_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void almanah_entry_tags_area_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void almanah_entry_tags_area_finalize (GObject *object);
+static void almanah_entry_tags_area_load_tags (AlmanahEntryTagsArea *self);
+static void almanah_entry_tags_area_update (AlmanahEntryTagsArea *self);
+static gint almanah_entry_tags_area_draw (GtkWidget *widget, cairo_t *cr);
+
+/* Signals */
+void tag_entry_activate_cb (GtkEntry *entry, AlmanahEntryTagsArea *self);
+void entry_tags_area_remove_foreach_cb (GtkWidget *tag_widget, AlmanahEntryTagsArea *self);
+void storage_manager_entry_tag_added_cb (AlmanahEntry *entry, gchar *tag, AlmanahEntryTagsArea *self);
+
+G_DEFINE_TYPE (AlmanahEntryTagsArea, almanah_entry_tags_area, GTK_TYPE_GRID)
+
+static void
+almanah_entry_tags_area_class_init (AlmanahEntryTagsAreaClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (AlmanahEntryTagsAreaPrivate));
+
+ gobject_class->get_property = almanah_entry_tags_area_get_property;
+ gobject_class->set_property = almanah_entry_tags_area_set_property;
+ gobject_class->finalize = almanah_entry_tags_area_finalize;
+
+ widget_class->draw = almanah_entry_tags_area_draw;
+
+ g_object_class_install_property (gobject_class, PROP_ENTRY,
+ g_param_spec_object ("entry",
+ "Entry", "The entry from which show the tag list",
+ ALMANAH_TYPE_ENTRY,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class, PROP_STORAGE_MANAGER,
+ g_param_spec_object ("storage-manager",
+ "Storage manager", "The storage manager whose entries should be listed.",
+ ALMANAH_TYPE_STORAGE_MANAGER,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+almanah_entry_tags_area_init (AlmanahEntryTagsArea *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_ENTRY_TAGS_AREA, AlmanahEntryTagsAreaPrivate);
+
+ /* There is no tags showed right now. */
+ self->priv->tags_number = 0;
+
+ /* The tag entry widget */
+ self->priv->tag_entry = g_object_new (ALMANAH_TYPE_TAG_ENTRY, NULL);
+ gtk_entry_set_text (GTK_ENTRY (self->priv->tag_entry), "add tag");
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (self->priv->tag_entry));
+ g_signal_connect (self->priv->tag_entry, "activate", G_CALLBACK (tag_entry_activate_cb), self);
+}
+
+static void
+almanah_entry_tags_area_finalize (GObject *object)
+{
+ AlmanahEntryTagsAreaPrivate *priv = ALMANAH_ENTRY_TAGS_AREA (object)->priv;
+
+ g_clear_object (&priv->entry);
+ g_clear_object (&priv->storage_manager);
+
+ G_OBJECT_CLASS (almanah_entry_tags_area_parent_class)->finalize (object);
+}
+
+static void
+almanah_entry_tags_area_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ AlmanahEntryTagsAreaPrivate *priv = ALMANAH_ENTRY_TAGS_AREA (object)->priv;
+
+ switch (property_id) {
+ case PROP_ENTRY:
+ g_value_set_object (value, priv->entry);
+ break;
+ case PROP_STORAGE_MANAGER:
+ g_value_set_object (value, priv->storage_manager);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+almanah_entry_tags_area_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ AlmanahEntryTagsAreaPrivate *priv = ALMANAH_ENTRY_TAGS_AREA (object)->priv;
+
+ switch (property_id) {
+ case PROP_ENTRY:
+ g_clear_object (&priv->entry);
+ priv->entry = ALMANAH_ENTRY (g_value_get_object (value));
+ g_object_ref (priv->entry);
+ almanah_entry_tags_area_update (ALMANAH_ENTRY_TAGS_AREA (object));
+ break;
+ case PROP_STORAGE_MANAGER:
+ g_return_if_fail (ALMANAH_IS_STORAGE_MANAGER (g_value_get_object (value)));
+ g_clear_object (&priv->storage_manager);
+ priv->storage_manager = ALMANAH_STORAGE_MANAGER (g_value_get_object (value));
+ g_object_ref (priv->storage_manager);
+ almanah_tag_entry_set_storage_manager (priv->tag_entry, priv->storage_manager);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+almanah_entry_tags_area_load_tags (AlmanahEntryTagsArea *self)
+{
+ GList *tags;
+
+ tags = almanah_storage_manager_entry_get_tags (self->priv->storage_manager, self->priv->entry);
+ while (tags) {
+ GtkWidget *tag_widget = almanah_tag_new (tags->data);
+ gtk_container_add (GTK_CONTAINER (self), tag_widget);
+ self->priv->tags_number++;
+
+ g_free (tags->data);
+ tags = g_list_next (tags);
+ }
+
+ g_free (tags);
+ gtk_widget_show_all (GTK_WIDGET (self));
+}
+
+static void
+almanah_entry_tags_area_update (AlmanahEntryTagsArea *self)
+{
+ gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback) entry_tags_area_remove_foreach_cb, self);
+}
+
+static gint
+almanah_entry_tags_area_draw (GtkWidget *widget, cairo_t *cr)
+{
+ gint width, height;
+
+ width = gtk_widget_get_allocated_width (widget);
+ height = gtk_widget_get_allocated_height (widget);
+
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_rectangle (cr, 0, 0, width, height);
+ cairo_fill (cr);
+
+ return GTK_WIDGET_CLASS (almanah_entry_tags_area_parent_class)->draw (widget, cr);
+}
+
+void
+tag_entry_activate_cb (GtkEntry *entry, AlmanahEntryTagsArea *self)
+{
+ gboolean result;
+ gchar *tag;
+
+ tag = g_strdup (gtk_entry_get_text (entry));
+ gtk_entry_set_text (entry, "");
+ if (almanah_storage_manager_entry_add_tag (self->priv->storage_manager, self->priv->entry, tag)) {
+ GtkWidget *tag_widget = almanah_tag_new (tag);
+ gtk_container_add (GTK_CONTAINER (self), tag_widget);
+ self->priv->tags_number++;
+ gtk_widget_show (tag_widget);
+ }
+ g_free (tag);
+
+ /* @TODO: Return the focus to the GtkTextView */
+}
+
+void
+entry_tags_area_remove_foreach_cb (GtkWidget *tag_widget, AlmanahEntryTagsArea *self)
+{
+ if (ALMANAH_IS_TAG (tag_widget)) {
+ gtk_widget_destroy (tag_widget);
+ self->priv->tags_number--;
+ }
+
+ /* Show the tags for the entry */
+ if (self->priv->tags_number == 0) {
+ almanah_entry_tags_area_load_tags (self);
+ }
+}
+
+void
+almanah_entry_tags_area_set_entry (AlmanahEntryTagsArea *entry_tags_area, AlmanahEntry *entry)
+{
+ GValue entry_value = G_VALUE_INIT;
+
+ g_return_if_fail (ALMANAH_IS_ENTRY_TAGS_AREA (entry_tags_area));
+ g_return_if_fail (ALMANAH_IS_ENTRY (entry));
+
+ g_value_init (&entry_value, G_TYPE_OBJECT);
+ g_value_set_object (&entry_value, entry);
+ g_object_set_property (G_OBJECT (entry_tags_area), "entry", &entry_value);
+ g_value_unset (&entry_value);
+}
+
+void
+storage_manager_entry_tag_added_cb (AlmanahEntry *entry, gchar *tag, AlmanahEntryTagsArea *self)
+{
+ GtkWidget *tag_widget;
+
+ /* TODO: test if the priv->entry == entry */
+
+}
+
+void
+almanah_entry_tags_area_set_storage_manager (AlmanahEntryTagsArea *entry_tags_area, AlmanahStorageManager *storage_manager)
+{
+ GValue storage_value = G_VALUE_INIT;
+
+ g_return_if_fail (ALMANAH_IS_ENTRY_TAGS_AREA (entry_tags_area));
+ g_return_if_fail (ALMANAH_IS_STORAGE_MANAGER (storage_manager));
+
+ g_value_init (&storage_value, G_TYPE_OBJECT);
+ g_value_set_object (&storage_value, storage_manager);
+ g_object_set_property (G_OBJECT (entry_tags_area), "storage-manager", &storage_value);
+ g_value_unset (&storage_value);
+}
diff --git a/src/widgets/entry-tags-area.h b/src/widgets/entry-tags-area.h
new file mode 100644
index 0000000..3ba006f
--- /dev/null
+++ b/src/widgets/entry-tags-area.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Ãlvaro PeÃa 2013 <alvaropg gmail com>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ALMANAH_ENTRY_TAGS_AREA_H
+#define ALMANAH_ENTRY_TAGS_AREA_H
+
+#include <gtk/gtk.h>
+#include "entry.h"
+#include "storage-manager.h"
+
+G_BEGIN_DECLS
+
+#define ALMANAH_TYPE_ENTRY_TAGS_AREA (almanah_entry_tags_area_get_type ())
+#define ALMANAH_ENTRY_TAGS_AREA(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_ENTRY_TAGS_AREA, AlmanahEntryTagsArea))
+#define ALMANAH_ENTRY_TAGS_AREA_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ALMANAH_TYPE_ENTRY_TAGS_AREA, AlmanahEntryTagsAreaClass))
+#define ALMANAH_IS_ENTRY_TAGS_AREA(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALMANAH_TYPE_ENTRY_TAGS_AREA))
+#define ALMANAH_IS_ENTRY_TAGS_AREA_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ALMANAH_TYPE_ENTRY_TAGS_AREA))
+#define ALMANAH_ENTRY_TAGS_AREA_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ALMANAH_TYPE_ENTRY_TAGS_AREA, AlmanahEntryTagsAreaClass))
+
+typedef struct _AlmanahEntryTagsAreaPrivate AlmanahEntryTagsAreaPrivate;
+
+typedef struct {
+ GtkGrid parent;
+ AlmanahEntryTagsAreaPrivate *priv;
+} AlmanahEntryTagsArea;
+
+typedef struct {
+ GtkGridClass parent;
+} AlmanahEntryTagsAreaClass;
+
+GType almanah_entry_tags_area_get_type (void) G_GNUC_CONST;
+void almanah_entry_tags_area_set_entry (AlmanahEntryTagsArea *entry_tags_area, AlmanahEntry *entry);
+void almanah_entry_tags_area_set_storage_manager (AlmanahEntryTagsArea *entry_tags_area, AlmanahStorageManager *storage_manager);
+
+G_END_DECLS
+
+#endif /* !ALMANAH_ENTRY_TAGS_AREA_H */
diff --git a/src/widgets/tag-entry.c b/src/widgets/tag-entry.c
new file mode 100644
index 0000000..6bcc5ee
--- /dev/null
+++ b/src/widgets/tag-entry.c
@@ -0,0 +1,186 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Ãlvaro PeÃa 2013 <alvaropg gmail com>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtk/gtk.h>
+
+#include "tag-entry.h"
+#include "storage-manager.h"
+
+enum {
+ PROP_STORAGE_MANAGER = 1
+};
+
+struct _AlmanahTagEntryPrivate {
+ GtkListStore *tags_store;
+ AlmanahStorageManager *storage_manager;
+};
+
+static void almanah_tag_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void almanah_tag_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static void almanah_tag_entry_finalize (GObject *object);
+static void almanah_tag_entry_update_tags (AlmanahTagEntry *tag_entry);
+static void almanah_tag_entry_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural);
+gboolean almanah_tag_entry_focus_out_event (GtkWidget *self, GdkEventFocus *event);
+gboolean almanah_tag_entry_focus_in_event (GtkWidget *self, GdkEventFocus *event);
+
+G_DEFINE_TYPE (AlmanahTagEntry, almanah_tag_entry, GTK_TYPE_ENTRY)
+
+static void
+almanah_tag_entry_class_init (AlmanahTagEntryClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (AlmanahTagEntryPrivate));
+
+ gobject_class->get_property = almanah_tag_entry_get_property;
+ gobject_class->set_property = almanah_tag_entry_set_property;
+ gobject_class->finalize = almanah_tag_entry_finalize;
+
+ gtkwidget_class->focus_out_event = almanah_tag_entry_focus_out_event;
+ gtkwidget_class->focus_in_event = almanah_tag_entry_focus_in_event;
+ gtkwidget_class->get_preferred_width = almanah_tag_entry_get_preferred_width;
+
+ g_object_class_install_property (gobject_class, PROP_STORAGE_MANAGER,
+ g_param_spec_object ("storage-manager",
+ "Storage manager", "The storage manager whose entries should be listed.",
+ ALMANAH_TYPE_STORAGE_MANAGER,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+almanah_tag_entry_init (AlmanahTagEntry *self)
+{
+ GtkEntryCompletion *completion;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_TAG_ENTRY, AlmanahTagEntryPrivate);
+
+ self->priv->tags_store = gtk_list_store_new (1, G_TYPE_STRING);
+ completion = gtk_entry_completion_new ();
+ gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (self->priv->tags_store));
+ gtk_entry_completion_set_text_column (completion, 0);
+ gtk_entry_set_completion (GTK_ENTRY (self), completion);
+
+ gtk_entry_set_has_frame (GTK_ENTRY (self), FALSE);
+}
+
+static void
+almanah_tag_entry_finalize (GObject *object)
+{
+ AlmanahTagEntryPrivate *priv = ALMANAH_TAG_ENTRY (object)->priv;
+
+ g_clear_object (&priv->storage_manager);
+
+ G_OBJECT_CLASS (almanah_tag_entry_parent_class)->finalize (object);
+}
+
+static void
+almanah_tag_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ AlmanahTagEntryPrivate *priv = ALMANAH_TAG_ENTRY (object)->priv;
+
+ switch (property_id) {
+ case PROP_STORAGE_MANAGER:
+ g_value_set_object (value, priv->storage_manager);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+almanah_tag_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ AlmanahTagEntryPrivate *priv = ALMANAH_TAG_ENTRY (object)->priv;
+
+ switch (property_id) {
+ case PROP_STORAGE_MANAGER:
+ g_clear_object (&priv->storage_manager);
+ priv->storage_manager = ALMANAH_STORAGE_MANAGER (g_value_get_object (value));
+ g_object_ref (priv->storage_manager);
+ almanah_tag_entry_update_tags (ALMANAH_TAG_ENTRY (object));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+almanah_tag_entry_update_tags (AlmanahTagEntry *tag_entry)
+{
+ GList *tags;
+ GtkTreeIter iter;
+ AlmanahTagEntryPrivate *priv = tag_entry->priv;
+
+ gtk_list_store_clear (priv->tags_store);
+ tags = almanah_storage_manager_get_tags (priv->storage_manager);
+ while (tags) {
+ gtk_list_store_append (priv->tags_store, &iter);
+ gtk_list_store_set (priv->tags_store, &iter, 0, tags->data, -1);
+
+ tags = g_list_next (tags);
+ }
+
+ if (tags)
+ g_list_free (tags);
+}
+
+static void
+almanah_tag_entry_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural)
+{
+ gint m_width, n_width;
+
+ GTK_WIDGET_CLASS (almanah_tag_entry_parent_class)->get_preferred_width (widget, &m_width, &n_width);
+
+ *minimum = m_width - 100;
+ *natural = n_width - 100;
+}
+
+gboolean
+almanah_tag_entry_focus_out_event (GtkWidget *self, GdkEventFocus *event)
+{
+ gtk_entry_set_text (GTK_ENTRY (self), "add tag");
+
+ return FALSE;
+}
+
+gboolean
+almanah_tag_entry_focus_in_event (GtkWidget *self, GdkEventFocus *event)
+{
+ gtk_entry_set_text (GTK_ENTRY (self), "");
+
+ return FALSE;
+}
+
+/* @TODO: Remove? use g_object_set */
+void
+almanah_tag_entry_set_storage_manager (AlmanahTagEntry *tag_entry, AlmanahStorageManager *storage_manager)
+{
+ GValue storage_value = G_VALUE_INIT;
+
+ g_return_if_fail (ALMANAH_IS_TAG_ENTRY (tag_entry));
+ g_return_if_fail (ALMANAH_IS_STORAGE_MANAGER (storage_manager));
+
+ g_value_init (&storage_value, G_TYPE_OBJECT);
+ g_value_set_object (&storage_value, storage_manager);
+ g_object_set_property (G_OBJECT (tag_entry), "storage-manager", &storage_value);
+ g_value_unset (&storage_value);
+}
diff --git a/src/widgets/tag-entry.h b/src/widgets/tag-entry.h
new file mode 100644
index 0000000..7e5bacc
--- /dev/null
+++ b/src/widgets/tag-entry.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Ãlvaro PeÃa 2013 <alvaropg gmail com>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ALMANAH_TAG_ENTRY_H
+#define ALMANAH_TAG_ENTRY_H
+
+#include <gtk/gtk.h>
+
+#include "storage-manager.h"
+
+G_BEGIN_DECLS
+
+#define ALMANAH_TYPE_TAG_ENTRY (almanah_tag_entry_get_type ())
+#define ALMANAH_TAG_ENTRY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_TAG_ENTRY, AlmanahTagEntry))
+#define ALMANAH_TAG_ENTRY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ALMANAH_TYPE_TAG_ENTRY, AlmanahTagEntryClass))
+#define ALMANAH_IS_TAG_ENTRY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALMANAH_TYPE_TAG_ENTRY))
+#define ALMANAH_IS_TAG_ENTRY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ALMANAH_TYPE_TAG_ENTRY))
+#define ALMANAH_TAG_ENTRY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ALMANAH_TYPE_TAG_ENTRY, AlmanahTagEntryClass))
+
+typedef struct _AlmanahTagEntryPrivate AlmanahTagEntryPrivate;
+
+typedef struct {
+ GtkEntry parent;
+ AlmanahTagEntryPrivate *priv;
+} AlmanahTagEntry;
+
+typedef struct {
+ GtkEntryClass parent;
+} AlmanahTagEntryClass;
+
+GType almanah_tag_entry_get_type (void) G_GNUC_CONST;
+void almanah_tag_entry_set_storage_manager (AlmanahTagEntry *tag_entry, AlmanahStorageManager *storage_manager);
+
+G_END_DECLS
+
+#endif /* !ALMANAH_TAG_ENTRY_H */
diff --git a/src/widgets/tag.c b/src/widgets/tag.c
new file mode 100644
index 0000000..8340f1e
--- /dev/null
+++ b/src/widgets/tag.c
@@ -0,0 +1,278 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Ãlvaro PeÃa 2013 <alvaropg gmail com>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pango/pangocairo.h>
+#include <math.h>
+
+#include "tag.h"
+
+#define PADDING_TOP 1
+#define PADDING_BOTTOM 1
+#define PADDING_LEFT 10
+#define PADDING_RIGHT 5
+#define SHADOW_RIGHT 1
+#define SHADOW_BOTTOM 2
+#define CLOSE_BUTTON 5
+#define CLOSE_BUTTON_SPACING 5
+
+enum {
+ PROP_TAG = 1
+};
+
+struct _AlmanahTagPrivate {
+ gchar *tag;
+ PangoLayout *layout;
+ GdkRGBA text_color;
+ GdkRGBA strock_color;
+ GdkRGBA fill_a_color;
+ GdkRGBA fill_b_color;
+};
+
+static void almanah_tag_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void almanah_tag_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+void almanah_tag_ensure_layout (AlmanahTag *self);
+void almanah_tag_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width);
+void almanah_tag_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height);
+gboolean almanah_tag_draw (GtkWidget *widget, cairo_t *cr, gpointer data);
+
+G_DEFINE_TYPE (AlmanahTag, almanah_tag, GTK_TYPE_DRAWING_AREA)
+
+static void
+almanah_tag_class_init (AlmanahTagClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (AlmanahTagPrivate));
+
+ gobject_class->get_property = almanah_tag_get_property;
+ gobject_class->set_property = almanah_tag_set_property;
+
+ widget_class->get_preferred_width = almanah_tag_get_preferred_width;
+ widget_class->get_preferred_height = almanah_tag_get_preferred_height;
+
+ g_object_class_install_property (gobject_class, PROP_TAG,
+ g_param_spec_string ("tag",
+ "Tag", "The tag name.",
+ NULL, G_PARAM_READWRITE));
+}
+
+static void
+almanah_tag_init (AlmanahTag *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_TAG, AlmanahTagPrivate);
+ g_signal_connect (G_OBJECT (self), "draw", G_CALLBACK (almanah_tag_draw), NULL);
+
+ gdk_rgba_parse (&self->priv->text_color, "#936835");
+ gdk_rgba_parse (&self->priv->strock_color, "#ECB447");
+ gdk_rgba_parse (&self->priv->fill_a_color, "#FFDB73");
+ gdk_rgba_parse (&self->priv->fill_b_color, "#FCBC4E");
+}
+
+static void
+almanah_tag_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ AlmanahTagPrivate *priv = ALMANAH_TAG (object)->priv;
+
+ switch (property_id) {
+ case PROP_TAG:
+ g_value_set_string (value, priv->tag);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+almanah_tag_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ AlmanahTagPrivate *priv = ALMANAH_TAG (object)->priv;
+
+ switch (property_id) {
+ case PROP_TAG:
+ if (priv->tag)
+ g_free (priv->tag);
+ priv->tag = g_strdup (g_value_get_string (value));
+ if (PANGO_IS_LAYOUT (priv->layout)) {
+ pango_layout_set_text (priv->layout, priv->tag, -1);
+ gtk_widget_queue_resize (GTK_WIDGET (object));
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+void
+almanah_tag_ensure_layout (AlmanahTag *self)
+{
+ if (!self->priv->layout) {
+ GtkStyleContext *style_context;
+ PangoFontDescription *font_desc;
+
+ self->priv->layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), self->priv->tag);
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+ font_desc = (PangoFontDescription *) gtk_style_context_get_font (style_context, GTK_STATE_FLAG_NORMAL);
+ pango_font_description_set_size (font_desc, (pango_font_description_get_size (font_desc) * 0.8));
+ pango_font_description_set_weight (font_desc, PANGO_WEIGHT_BOLD);
+ pango_layout_set_font_description (self->priv->layout, font_desc);
+ }
+}
+
+void
+almanah_tag_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height)
+{
+ AlmanahTagPrivate *priv = ALMANAH_TAG (widget)->priv;
+ gint height;
+
+ almanah_tag_ensure_layout (ALMANAH_TAG (widget));
+
+ pango_layout_get_size (priv->layout, NULL, &height);
+ *minimum_height = (height / PANGO_SCALE) + PADDING_TOP + PADDING_BOTTOM + SHADOW_BOTTOM;
+ *natural_height = *minimum_height;
+}
+
+void
+almanah_tag_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width)
+{
+ AlmanahTagPrivate *priv = ALMANAH_TAG (widget)->priv;
+ gint width;
+
+ almanah_tag_ensure_layout (ALMANAH_TAG (widget));
+
+ pango_layout_get_size (priv->layout, &width, NULL);
+ *minimum_width = (width / PANGO_SCALE) + CLOSE_BUTTON_SPACING + CLOSE_BUTTON + PADDING_LEFT + PADDING_RIGHT + SHADOW_RIGHT;
+ *natural_width = *minimum_width;
+}
+
+gboolean
+almanah_tag_draw (GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+ AlmanahTagPrivate *priv = ALMANAH_TAG (widget)->priv;
+ gint y_origin, allocated_height, width, height, middle_height, middle_padding_left, text_height, text_width;
+ cairo_pattern_t *fill_pattrn;
+
+ almanah_tag_ensure_layout (ALMANAH_TAG (widget));
+
+ /* Get the tag dimensions */
+ gtk_widget_get_preferred_width (widget, &width, NULL);
+ width = width - SHADOW_RIGHT;
+ gtk_widget_get_preferred_height (widget, &height, NULL);
+ height = height - SHADOW_BOTTOM;
+
+ /* Some coordinates */
+ middle_height = height / 2;
+ middle_padding_left = PADDING_LEFT / 2;
+
+ /* The tag must the vertical centered */
+ allocated_height = gtk_widget_get_allocated_height (widget);
+ y_origin = (allocated_height / 2) - middle_height;
+
+ /* Tag border */
+ cairo_set_line_width (cr, 1);
+ cairo_move_to (cr, width - 2, y_origin);
+ cairo_line_to (cr, middle_padding_left, y_origin);
+ cairo_line_to (cr, 0, y_origin + middle_height);
+ cairo_line_to (cr, middle_padding_left, y_origin + height);
+ cairo_line_to (cr, width - 2, y_origin + height);
+ cairo_line_to (cr, width, y_origin + height - 2);
+ cairo_line_to (cr, width, y_origin + 2);
+ cairo_close_path (cr);
+ /* gradient background */
+ fill_pattrn = cairo_pattern_create_linear (1, y_origin + 1, 2, y_origin + height - 1);
+ cairo_pattern_add_color_stop_rgb (fill_pattrn, 0,
+ priv->fill_a_color.red,
+ priv->fill_a_color.green,
+ priv->fill_a_color.blue);
+ cairo_pattern_add_color_stop_rgb (fill_pattrn, 1,
+ priv->fill_b_color.red,
+ priv->fill_b_color.green,
+ priv->fill_b_color.blue);
+ cairo_set_source (cr, fill_pattrn);
+ cairo_fill_preserve (cr);
+ /* paint the border */
+ gdk_cairo_set_source_rgba (cr, &priv->strock_color);
+ cairo_stroke (cr);
+ /* a little white line (biselado ?) */
+ cairo_set_line_width (cr, 0.5);
+ cairo_move_to (cr, middle_padding_left, y_origin + 1);
+ cairo_line_to (cr, width - 1, y_origin + 1);
+ cairo_set_source_rgba (cr, 1, 1, 1, 0.6);
+ cairo_stroke (cr);
+
+ /* Little circle */
+ cairo_set_line_width (cr, 1);
+ cairo_arc (cr, middle_padding_left, y_origin + middle_height, 2, 0, 2 * M_PI);
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_fill_preserve (cr);
+ gdk_cairo_set_source_rgba (cr, &priv->strock_color);
+ cairo_stroke (cr);
+
+ /* Tag text */
+ pango_layout_get_size (priv->layout, &text_width, &text_height);
+ text_height = text_height / PANGO_SCALE;
+ text_width = text_width / PANGO_SCALE;
+ /* A white text shadow */
+ cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
+ cairo_move_to (cr, PADDING_LEFT, y_origin + middle_height - (text_height /2));
+ pango_cairo_show_layout (cr, priv->layout);
+ /* The text */
+ gdk_cairo_set_source_rgba (cr, &priv->text_color);
+ cairo_move_to (cr, PADDING_LEFT, y_origin + middle_height - (text_height /2) - 1);
+ pango_cairo_show_layout (cr, priv->layout);
+
+ /* Remove button, it's a "x" */
+
+ /* First the shadow */
+ cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
+ /* Line from left up */
+ cairo_move_to (cr, PADDING_LEFT + text_width + CLOSE_BUTTON_SPACING, y_origin + middle_height - (CLOSE_BUTTON / 2) + 1);
+ /* To right down */
+ cairo_line_to (cr, PADDING_LEFT + text_width + CLOSE_BUTTON_SPACING + CLOSE_BUTTON, y_origin + middle_height + (CLOSE_BUTTON / 2) + 1);
+ cairo_stroke (cr);
+ /* From right left */
+ cairo_move_to (cr, PADDING_LEFT + text_width + CLOSE_BUTTON_SPACING + CLOSE_BUTTON, y_origin + middle_height - (CLOSE_BUTTON / 2) + 1);
+ /* To left down */
+ cairo_line_to (cr, PADDING_LEFT + text_width + CLOSE_BUTTON_SPACING, y_origin + middle_height + (CLOSE_BUTTON / 2) + 1);
+ cairo_stroke (cr);
+
+ gdk_cairo_set_source_rgba (cr, &priv->text_color);
+ /* Line from left up */
+ cairo_move_to (cr, PADDING_LEFT + text_width + CLOSE_BUTTON_SPACING, y_origin + middle_height - (CLOSE_BUTTON / 2));
+ /* To right down */
+ cairo_line_to (cr, PADDING_LEFT + text_width + CLOSE_BUTTON_SPACING + CLOSE_BUTTON, y_origin + middle_height + (CLOSE_BUTTON / 2));
+ cairo_stroke (cr);
+ /* From right left */
+ cairo_move_to (cr, PADDING_LEFT + text_width + CLOSE_BUTTON_SPACING + CLOSE_BUTTON, y_origin + middle_height - (CLOSE_BUTTON / 2));
+ /* To left down */
+ cairo_line_to (cr, PADDING_LEFT + text_width + CLOSE_BUTTON_SPACING, y_origin + middle_height + (CLOSE_BUTTON / 2));
+ cairo_stroke (cr);
+
+ return FALSE;
+}
+
+GtkWidget *
+almanah_tag_new (const gchar *tag)
+{
+ return GTK_WIDGET (g_object_new (ALMANAH_TYPE_TAG,
+ "tag", tag,
+ NULL));
+}
diff --git a/src/widgets/tag.h b/src/widgets/tag.h
new file mode 100644
index 0000000..8908f99
--- /dev/null
+++ b/src/widgets/tag.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Ãlvaro PeÃa 2013 <alvaropg gmail com>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ALMANAH_TAG_H
+#define ALMANAH_TAG_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define ALMANAH_TYPE_TAG (almanah_tag_get_type ())
+#define ALMANAH_TAG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_TAG, AlmanahTag))
+#define ALMANAH_TAG_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ALMANAH_TYPE_TAG, AlmanahTagClass))
+#define ALMANAH_IS_TAG(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALMANAH_TYPE_TAG))
+#define ALMANAH_IS_TAG_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ALMANAH_TYPE_TAG))
+#define ALMANAH_TAG_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ALMANAH_TYPE_TAG, AlmanahTagClass))
+
+typedef struct _AlmanahTagPrivate AlmanahTagPrivate;
+
+typedef struct {
+ GtkDrawingArea parent;
+ AlmanahTagPrivate *priv;
+} AlmanahTag;
+
+typedef struct {
+ GtkDrawingAreaClass parent;
+} AlmanahTagClass;
+
+GType almanah_tag_get_type (void) G_GNUC_CONST;
+GtkWidget *almanah_tag_new (const gchar *tag);
+
+G_END_DECLS
+
+#endif /* !ALMANAH_TAG_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]