[almanah/tagsupport: 1/2] Bug 684412 - Add support for tagging diary entries



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]