Commit 1ee4d5fa authored by Jente Hidskes's avatar Jente Hidskes

Introduce Gcolor3ColorStore to centrally manage colors

This enables one to concurrently open several instances of Gcolor3 where all the colors (both added
and removed) are synced between the instances
parent 3d1a0703
......@@ -22,6 +22,9 @@ LT_INIT(disable-static)
# Resources
AC_PATH_PROG(GLIB_COMPILE_RESOURCES, glib-compile-resources)
# Marshals
AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal)
# Internationalization
IT_PROG_INTLTOOL(0.50.1)
AM_GLIB_GNU_GETTEXT
......
......@@ -14,12 +14,23 @@ gcolor3_resource_deps = $(call GRESDEPS,gcolor3.gresource.xml)
gcolor3-resources.c: $(gcolor3_resource_deps)
$(GRESGEN)
BUILT_SOURCES = gcolor3-resources.c
# Marshals
gcolor3-marshalers.c: gcolor3-marshalers.list
$(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=_gcolor3_marshal $< --body > $@
gcolor3-marshalers.h: gcolor3-marshalers.list
$(AM_V_GEN) $(GLIB_GENMARSHAL) --prefix=_gcolor3_marshal $< --header > $@
BUILT_SOURCES = \
gcolor3-resources.c \
gcolor3-marshalers.c \
gcolor3-marshalers.h
EXTRA_DIST = \
$(desktop_in_files) \
gcolor3.gresource.xml \
$(gcolor3_resource_deps)
$(gcolor3_resource_deps) \
gcolor3-marshalers.list
CLEANFILES = \
$(BUILT_SOURCES)
......
# see glib-genmarshal(1) for a detailed description of the file format,
# possible parameter types are:
# VOID indicates no return type, or no extra
# parameters. if VOID is used as the parameter
# list, no additional parameters may be present.
# BOOLEAN for boolean types (gboolean)
# CHAR for signed char types (gchar)
# UCHAR for unsigned char types (guchar)
# INT for signed integer types (gint)
# UINT for unsigned integer types (guint)
# LONG for signed long integer types (glong)
# ULONG for unsigned long integer types (gulong)
# ENUM for enumeration types (gint)
# FLAGS for flag enumeration types (guint)
# FLOAT for single-precision float types (gfloat)
# DOUBLE for double-precision float types (gdouble)
# STRING for string types (gchar*)
# BOXED for boxed (anonymous but reference counted) types (GBoxed*)
# POINTER for anonymous pointer types (gpointer)
# OBJECT for GObject or derived types (GObject*)
# NONE deprecated alias for VOID
# BOOL deprecated alias for BOOLEAN
VOID:STRING
VOID:STRING,STRING
......@@ -6,4 +6,5 @@ data/gcolor3.desktop.in.in
[type: gettext/glade]data/window.ui
src/main.c
src/gcolor3-application.c
src/gcolor3-color-store.c
src/gcolor3-window.c
......@@ -4,19 +4,25 @@
bin_PROGRAMS = \
gcolor3
# FIXME: make dist fails on resources if Gcolor3 hasn't been make'd yet.
# FIXME: make dist fails on data files if Gcolor3 hasn't been make'd yet.
gcolor3_SOURCES = \
../data/gcolor3-resources.c \
../data/gcolor3-marshalers.c \
main.c \
gcolor3-application.c \
gcolor3-color-store.c \
gcolor3-window.c \
$(gcolor3_NOINST_H_FILES)
# FIXME: make dist fails on data files if Gcolor3 hasn't been make'd yet.
gcolor3_NOINST_H_FILES = \
../data/gcolor3-marshalers.h \
gcolor3-application.h \
gcolor3-color-store.h \
gcolor3-window.h
gcolor3_CPPFLAGS = \
-I$(top_srcdir)/data \
-DLOCALE_DIR=\"$(localedir)\"
gcolor3_CFLAGS = \
......
......@@ -27,20 +27,15 @@
#include <glib/gi18n.h>
#include "gcolor3-application.h"
#include "gcolor3-color-store.h"
#include "gcolor3-window.h"
struct _Gcolor3ApplicationPrivate {
GKeyFile *colors;
Gcolor3ColorStore *colors;
};
G_DEFINE_TYPE_WITH_PRIVATE (Gcolor3Application, gcolor3_application, GTK_TYPE_APPLICATION);
static gchar *
get_user_file (void)
{
return g_build_filename (g_get_home_dir (), ".rgb.ini", NULL);
}
static void
gcolor3_application_action_about (UNUSED GSimpleAction *action,
UNUSED GVariant *parameter,
......@@ -95,27 +90,6 @@ gcolor3_application_init_accelerators (GtkApplication *application)
}
}
static void
gcolor3_application_load_colors (Gcolor3Application *app) {
Gcolor3ApplicationPrivate *priv;
gchar *file;
GError *error = NULL;
priv = gcolor3_application_get_instance_private (app);
priv->colors = g_key_file_new ();
file = get_user_file ();
if (!(g_key_file_load_from_file (priv->colors,
file,
G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
&error))) {
g_warning ("%s%s\n", _("Error opening file: "), error->message);
g_clear_error (&error);
}
g_free (file);
}
static void
gcolor3_application_startup (GApplication *application)
{
......@@ -131,79 +105,60 @@ gcolor3_application_startup (GApplication *application)
application);
gcolor3_application_init_accelerators (GTK_APPLICATION (app));
gcolor3_application_load_colors (app);
}
static void
gcolor3_application_activate (GApplication *application)
{
Gcolor3ApplicationPrivate *priv;
Gcolor3Window *window;
window = gcolor3_window_new (GCOLOR3_APPLICATION (application));
gcolor3_window_add_colors (window);
priv = gcolor3_application_get_instance_private (GCOLOR3_APPLICATION (application));
window = gcolor3_window_new (GCOLOR3_APPLICATION (application), priv->colors);
gtk_window_present_with_time (GTK_WINDOW (window), GDK_CURRENT_TIME);
}
static void
gcolor3_application_shutdown (GApplication *application)
gcolor3_application_dispose (GObject *object)
{
Gcolor3ApplicationPrivate *priv;
GError *error = NULL;
gchar *file;
priv = gcolor3_application_get_instance_private (GCOLOR3_APPLICATION (application));
priv = gcolor3_application_get_instance_private (GCOLOR3_APPLICATION (object));
if (priv->colors != NULL) {
// TODO: possibly only write to disk if contents changed?
file = get_user_file ();
if (!(g_key_file_save_to_file (priv->colors, file, &error))) {
g_warning ("%s%s", _("Error writing file: "), error->message);
g_clear_error (&error);
}
g_free (file);
g_key_file_free (priv->colors);
}
g_clear_object (&priv->colors);
G_APPLICATION_CLASS (gcolor3_application_parent_class)->shutdown (application);
G_OBJECT_CLASS (gcolor3_application_parent_class)->dispose (object);
}
static void
gcolor3_application_class_init (Gcolor3ApplicationClass *gcolor3_application_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (gcolor3_application_class);
GApplicationClass *application_class = G_APPLICATION_CLASS (gcolor3_application_class);
object_class->dispose = gcolor3_application_dispose;
application_class->startup = gcolor3_application_startup;
application_class->activate = gcolor3_application_activate;
application_class->shutdown = gcolor3_application_shutdown;
}
static void
gcolor3_application_init (UNUSED Gcolor3Application *application)
gcolor3_application_init (Gcolor3Application *application)
{
}
Gcolor3Application *
gcolor3_application_new (void)
{
GObject *application;
Gcolor3ApplicationPrivate *priv;
application = g_object_new (GCOLOR3_TYPE_APPLICATION,
"application-id", "org.unia.gcolor3",
"flags", G_APPLICATION_FLAGS_NONE,
NULL);
priv = gcolor3_application_get_instance_private (application);
return GCOLOR3_APPLICATION (application);
priv->colors = gcolor3_color_store_new ();
}
GKeyFile *
gcolor3_application_get_colors (Gcolor3Application *application)
Gcolor3Application *
gcolor3_application_new (void)
{
Gcolor3ApplicationPrivate *priv;
g_return_val_if_fail (GCOLOR3_IS_APPLICATION (application), NULL);
priv = gcolor3_application_get_instance_private (application);
return priv->colors;
return g_object_new (GCOLOR3_TYPE_APPLICATION,
"application-id", "org.unia.gcolor3",
"flags", G_APPLICATION_FLAGS_NONE,
NULL);
}
/* Gcolor3Application
*
* Copyright (C) 2015 Jente Hidskes
* Copyright (C) 2015-2016 Jente Hidskes
*
* Author: Jente Hidskes <hjdskes@gmail.com>
*
......@@ -50,8 +50,6 @@ GType gcolor3_application_get_type (void) G_GNUC_CONST;
Gcolor3Application *gcolor3_application_new (void);
GKeyFile *gcolor3_application_get_colors (Gcolor3Application *application);
G_END_DECLS
#endif /* __GCOLOR3_APPLICATION_H__ */
......
/* Gcolor3ColorStore
*
* Copyright (C) 2016 Jente Hidskes
*
* Author: Jente Hidskes <hjdskes@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#include <glib/gi18n.h>
#include "gcolor3-color-store.h"
#include "gcolor3-marshalers.h"
enum {
SIGNAL_COLOR_ADDED,
SIGNAL_COLOR_REMOVED,
SIGNAL_LAST,
};
static guint signals[SIGNAL_LAST];
struct _Gcolor3ColorStorePrivate {
GKeyFile *colors;
};
G_DEFINE_TYPE_WITH_PRIVATE (Gcolor3ColorStore, gcolor3_color_store, G_TYPE_OBJECT)
static inline gchar *
get_user_file (void)
{
return g_build_filename (g_get_home_dir (), ".rgb.ini", NULL);
}
static void
gcolor3_color_store_dispose (GObject *object)
{
Gcolor3ColorStorePrivate *priv;
GError *error = NULL;
gchar *file;
priv = gcolor3_color_store_get_instance_private (GCOLOR3_COLOR_STORE (object));
// TODO: possibly only write to disk if contents changed?
file = get_user_file ();
if (!(g_key_file_save_to_file (priv->colors, file, &error))) {
g_warning (_("Error writing file: %s\n"), error->message);
g_clear_error (&error);
}
g_free (file);
g_key_file_free (priv->colors);
G_OBJECT_CLASS (gcolor3_color_store_parent_class)->dispose (object);
}
static void
gcolor3_color_store_class_init (Gcolor3ColorStoreClass *gcolor3_color_store_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (gcolor3_color_store_class);
object_class->dispose = gcolor3_color_store_dispose;
signals[SIGNAL_COLOR_ADDED] =
g_signal_new ("color-added", // FIXME: wrap in I_()?
GCOLOR3_TYPE_COLOR_STORE,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
_gcolor3_marshal_VOID__STRING_STRING,
G_TYPE_NONE, 2,
G_TYPE_STRING, G_TYPE_STRING);
signals[SIGNAL_COLOR_REMOVED] =
g_signal_new ("color-removed", // FIXME: wrap in I_()?
GCOLOR3_TYPE_COLOR_STORE,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
_gcolor3_marshal_VOID__STRING,
G_TYPE_NONE, 1,
G_TYPE_STRING);
}
static void
gcolor3_color_store_init (Gcolor3ColorStore *store)
{
Gcolor3ColorStorePrivate *priv;
gchar *file;
GError *error = NULL;
priv = gcolor3_color_store_get_instance_private (store);
priv->colors = g_key_file_new ();
file = get_user_file ();
if (!(g_key_file_load_from_file (priv->colors,
file,
G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
&error))) {
g_warning (_("Error opening file: %s. Colors likely won't be saved.\n"),
error->message);
g_clear_error (&error);
}
g_free (file);
}
Gcolor3ColorStore *
gcolor3_color_store_new ()
{
return g_object_new (GCOLOR3_TYPE_COLOR_STORE, NULL);
}
gboolean
gcolor3_color_store_add_color (Gcolor3ColorStore *store, const gchar *key, const gchar *hex)
{
Gcolor3ColorStorePrivate *priv;
g_return_val_if_fail (GCOLOR3_IS_COLOR_STORE (store), FALSE);
g_return_val_if_fail (key != NULL && hex != NULL, FALSE);
priv = gcolor3_color_store_get_instance_private (store);
if (g_key_file_has_key (priv->colors, "Colors", key, NULL)) {
g_warning (_("There is already a color name: %s\n"), key);
return FALSE;
}
g_key_file_set_string (priv->colors, "Colors", key, hex);
g_signal_emit (store, signals[SIGNAL_COLOR_ADDED], 0, key, hex);
return TRUE;
}
gboolean
gcolor3_color_store_remove_color (Gcolor3ColorStore *store, const gchar *key)
{
Gcolor3ColorStorePrivate *priv;
GError *error = NULL;
g_return_val_if_fail (GCOLOR3_IS_COLOR_STORE (store), FALSE);
g_return_val_if_fail (key != NULL, FALSE);
priv = gcolor3_color_store_get_instance_private (store);
if (!(g_key_file_remove_key (priv->colors, "Colors", key, &error))) {
g_warning (_("Error deleting key: %s\n"), error->message);
g_clear_error (&error);
return FALSE;
}
g_signal_emit (store, signals[SIGNAL_COLOR_REMOVED], 0, key);
return TRUE;
}
void
gcolor3_color_store_foreach (Gcolor3ColorStore *store,
Gcolor3ColorStoreForeachFunc func,
gpointer user_data)
{
Gcolor3ColorStorePrivate *priv;
GError *error = NULL;
gchar **keys = NULL;
gsize length;
g_return_if_fail (GCOLOR3_IS_COLOR_STORE (store));
priv = gcolor3_color_store_get_instance_private (store);
if (!(keys = g_key_file_get_keys (priv->colors, "Colors", &length, &error))) {
g_warning (_("Error reading keys: %s\n"), error->message);
g_clear_error (&error);
return;
}
for (guint i = 0; i < length; i++) {
gchar *value;
if (!(value = g_key_file_get_value (priv->colors, "Colors", keys[i], NULL))) {
continue;
}
func (keys[i], value, user_data);
}
g_strfreev (keys);
}
/* Gcolor3ColorStore
*
* Copyright (C) 2016 Jente Hidskes
*
* Author: Jente Hidskes <hjdskes@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __GCOLOR3_COLOR_STORE_H__
#define __GCOLOR3_COLOR_STORE_H__
#include <gtk/gtk.h>
#include <glib.h>
G_BEGIN_DECLS
typedef struct _Gcolor3ColorStore Gcolor3ColorStore;
typedef struct _Gcolor3ColorStoreClass Gcolor3ColorStoreClass;
typedef struct _Gcolor3ColorStorePrivate Gcolor3ColorStorePrivate;
typedef void (*Gcolor3ColorStoreForeachFunc) (const gchar *key,
const gchar *hex,
gpointer user_data);
#define GCOLOR3_TYPE_COLOR_STORE (gcolor3_color_store_get_type ())
#define GCOLOR3_COLOR_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GCOLOR3_TYPE_COLOR_STORE, Gcolor3ColorStore))
#define GCOLOR3_COLOR_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GCOLOR3_TYPE_COLOR_STORE, Gcolor3ColorStoreClass))
#define GCOLOR3_IS_COLOR_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GCOLOR3_TYPE_COLOR_STORE))
#define GCOLOR3_IS_COLOR_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GCOLOR3_TYPE_COLOR_STORE))
#define GCOLOR3_COLOR_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GCOLOR3_TYPE_COLOR_STORE, Gcolor3ColorStoreClass))
struct _Gcolor3ColorStore {
GObject base_instance;
};
struct _Gcolor3ColorStoreClass {
GObjectClass parent_class;
};
GType gcolor3_color_store_get_type (void);
Gcolor3ColorStore *gcolor3_color_store_new (void);
gboolean gcolor3_color_store_add_color (Gcolor3ColorStore *store,
const gchar *key,
const gchar *hex);
gboolean gcolor3_color_store_remove_color (Gcolor3ColorStore *store,
const gchar *key);
void gcolor3_color_store_foreach (Gcolor3ColorStore *store,
Gcolor3ColorStoreForeachFunc func,
gpointer user_data);
G_END_DECLS
#endif /* __GCOLOR3_COLOR_STORE_H__ */
......@@ -27,6 +27,7 @@
#include <glib/gprintf.h>
#include <glib/gi18n.h>
#include "gcolor3-color-store.h"
#include "gcolor3-window.h"
/* I know GtkColorSelection is deprecated... */
......@@ -42,6 +43,11 @@ enum {
N_COLUMNS
};
enum {
PROP_0,
PROP_STORE,
};
struct _Gcolor3WindowPrivate {
GtkWidget *headerbar;
GtkWidget *button;
......@@ -52,18 +58,19 @@ struct _Gcolor3WindowPrivate {
GtkWidget *tree;
GtkTreeSelection *selection;
GKeyFile *colors; /* Do not free: owned by Gcolor3Application! */
Gcolor3ColorStore *store;
GdkColor current;
};
G_DEFINE_TYPE_WITH_PRIVATE (Gcolor3Window, gcolor3_window, GTK_TYPE_APPLICATION_WINDOW);
static void gcolor3_window_color_added (UNUSED Gcolor3ColorStore *store,
const gchar *key,
const gchar *hex,
gpointer user_data);
static void gcolor3_window_add_color_to_list (Gcolor3Window *window,
const gchar *name,
const gchar *value,
gboolean new);
G_DEFINE_TYPE_WITH_PRIVATE (Gcolor3Window, gcolor3_window, GTK_TYPE_APPLICATION_WINDOW)
static gchar *
static inline gchar *
hex_value (GdkColor *color) {
return g_strdup_printf ("#%.2X%.2X%.2X",
color->red / 256,
......@@ -71,6 +78,34 @@ hex_value (GdkColor *color) {
color->blue / 256);
}
static GdkPixbuf *
create_pixbuf_from_xpm (const gchar *hex)
{
gchar colorline[] = ". c #FFFFFF";
const gchar *xpm[] = {
"16 14 1 1",
". c #FFFFFF",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................"
};
g_sprintf (colorline, ". c %s", hex);
xpm[1] = colorline;
return gdk_pixbuf_new_from_xpm_data ((gchar const **) xpm);
}
static void
gcolor3_window_action_save (UNUSED GSimpleAction *action,
UNUSED GVariant *parameter,
......@@ -81,7 +116,6 @@ gcolor3_window_action_save (UNUSED GSimpleAction *action,
gchar *hex;
priv = gcolor3_window_get_instance_private (GCOLOR3_WINDOW (user_data));
g_return_if_fail (priv->colors != NULL);
key = gtk_entry_get_text (GTK_ENTRY (priv->entry));
hex = hex_value (&priv->current);
......@@ -91,11 +125,9 @@ gcolor3_window_action_save (UNUSED GSimpleAction *action,
key = hex + 1;
}
if (!g_key_file_has_key (priv->colors, "Colors", key, NULL)) {
g_key_file_set_string (priv->colors, "Colors", key, hex);
gcolor3_window_add_color_to_list (GCOLOR3_WINDOW (user_data), key, hex, TRUE);
}
/* This will add the color to the store (if the key isn't already used), which in turn
* shall emit the signal which will trigger the color_added callback below. */
gcolor3_color_store_add_color (priv->store, key, hex);
g_free (hex);
}
......@@ -107,24 +139,18 @@ gcolor3_window_action_delete (UNUSED GSimpleAction *action,
Gcolor3WindowPrivate *priv;
GtkTreeIter iter;
GtkTreeModel *model;
GError *error = NULL;
gchar *key;
priv = gcolor3_window_get_instance_private (GCOLOR3_WINDOW (user_data));
g_return_if_fail (priv->colors != NULL);
if (!gtk_tree_selection_get_selected (priv->selection, &model, &iter)) {
return;
}
gtk_tree_model_get (model, &iter, COLOR_NAME, &key, -1);
if (!(g_key_file_remove_key (priv->colors, "Colors", key, &error))) {
g_warning ("%s%s", _("Error deleting key: "), error->message);
g_clear_error (&error);
} else {
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
}
/* This will remove the color from the store, which in turn shall emit the signal which
* will trigger the color_removed callback below. */
gcolor3_color_store_remove_color (priv->store, key);
g_free (key);
}
......@@ -153,58 +179,14 @@ static const GActionEntry window_actions[] = {
};
static void
gcolor3_window_add_color_to_list (Gcolor3Window *window,
const gchar *name,
const gchar *value,
gboolean new)
gcolor3_window_add_existing_color (const gchar *key, const gchar *hex, gpointer user_data)
{
Gcolor3WindowPrivate *priv;
GtkTreeModel *model;
GtkTreeIter iter;
GdkPixbuf *pixbuf;
gchar colorline[] = ". c #FFFFFF";
const gchar *xpm[] = {
"16 14 1 1",
". c #FFFFFF",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................",
"................"
};