summaryrefslogtreecommitdiff
path: root/logview/src
diff options
context:
space:
mode:
authormonsta <[email protected]>2015-09-09 10:46:57 +0300
committermonsta <[email protected]>2015-09-09 10:46:57 +0300
commitc9cadcd53d03e73475dd266aad223a1ebc9229b5 (patch)
tree0166a7c33476e9d9a4a2a3a3b66bde69594dc242 /logview/src
parent09a33e1feefbf5bc06eb5692b03c894c3b48daa9 (diff)
downloadmate-utils-c9cadcd53d03e73475dd266aad223a1ebc9229b5.tar.bz2
mate-utils-c9cadcd53d03e73475dd266aad223a1ebc9229b5.tar.xz
logview: move sources to src/ subdir
Diffstat (limited to 'logview/src')
-rw-r--r--logview/src/Makefile.am73
-rw-r--r--logview/src/logview-about.h67
-rw-r--r--logview/src/logview-app.c420
-rw-r--r--logview/src/logview-app.h71
-rw-r--r--logview/src/logview-filter-manager.c575
-rw-r--r--logview/src/logview-filter-manager.h54
-rw-r--r--logview/src/logview-filter.c200
-rw-r--r--logview/src/logview-filter.h59
-rw-r--r--logview/src/logview-findbar.c352
-rw-r--r--logview/src/logview-findbar.h72
-rw-r--r--logview/src/logview-log.c936
-rw-r--r--logview/src/logview-log.h110
-rw-r--r--logview/src/logview-loglist.c500
-rw-r--r--logview/src/logview-loglist.h68
-rw-r--r--logview/src/logview-main.c120
-rw-r--r--logview/src/logview-manager.c432
-rw-r--r--logview/src/logview-manager.h90
-rw-r--r--logview/src/logview-marshal.list1
-rw-r--r--logview/src/logview-prefs.c546
-rw-r--r--logview/src/logview-prefs.h89
-rw-r--r--logview/src/logview-utils.c282
-rw-r--r--logview/src/logview-utils.h40
-rw-r--r--logview/src/logview-window.c1548
-rw-r--r--logview/src/logview-window.h56
-rw-r--r--logview/src/tests/Makefile.am9
-rw-r--r--logview/src/tests/test-reader.c60
26 files changed, 6830 insertions, 0 deletions
diff --git a/logview/src/Makefile.am b/logview/src/Makefile.am
new file mode 100644
index 00000000..70cbee21
--- /dev/null
+++ b/logview/src/Makefile.am
@@ -0,0 +1,73 @@
+SUBDIRS = tests
+
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"mate-system-log\" \
+ -DMATELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \
+ -DDATADIR=\""$(datadir)"\" \
+ -DLOGVIEWINSTALLPREFIX=\""$(prefix)/\"" \
+ -DLOGVIEW_DATADIR=\""$(pkgdatadir)"\" \
+ $(SUN_OS) \
+ $(DISABLE_DEPRECATED) \
+ $(NULL)
+
+bin_PROGRAMS = mate-system-log
+
+BUILT_SOURCES = \
+ logview-marshal.c \
+ logview-marshal.h
+
+mate_system_log_SOURCES = \
+ logview-app.c \
+ logview-app.h \
+ logview-main.c \
+ logview-about.h \
+ logview-manager.c \
+ logview-manager.h \
+ logview-utils.c \
+ logview-utils.h \
+ logview-loglist.c \
+ logview-loglist.h \
+ logview-window.c \
+ logview-window.h \
+ logview-log.h \
+ logview-log.c \
+ logview-findbar.h \
+ logview-findbar.c \
+ logview-prefs.c \
+ logview-prefs.h \
+ logview-filter.h \
+ logview-filter.c \
+ logview-filter-manager.h \
+ logview-filter-manager.c \
+ $(BUILT_SOURCES)
+
+mate_system_log_CFLAGS = \
+ $(GLIB_CFLAGS) \
+ $(GTHREAD_CFLAGS) \
+ $(GIO_CFLAGS) \
+ $(GTK_CFLAGS) \
+ $(NULL)
+
+mate_system_log_LDADD = \
+ $(GLIB_LIBS) \
+ $(GIO_LIBS) \
+ $(GTHREAD_LIBS) \
+ $(GTK_LIBS) \
+ $(Z_LIBS) \
+ -lm
+
+logview-marshal.h: logview-marshal.list $(GLIB_GENMARSHAL)
+ $(GLIB_GENMARSHAL) $< --header --prefix=logview_marshal >> $@
+
+logview-marshal.c: logview-marshal.list $(GLIB_GENMARSHAL)
+ echo "#include \"logview-marshal.h\"" > $@ && \
+ $(GLIB_GENMARSHAL) $< --body --prefix=logview_marshal >> $@
+
+EXTRA_DIST = logview-marshal.list
+
+CLEANFILES = \
+ $(BUILT_SOURCES)
+
+dist-hook:
+ cd $(distdir) ; rm -f $(CLEANFILES)
+
diff --git a/logview/src/logview-about.h b/logview/src/logview-about.h
new file mode 100644
index 00000000..9b4b0949
--- /dev/null
+++ b/logview/src/logview-about.h
@@ -0,0 +1,67 @@
+/* logview-about.h - authors and contributors information
+ *
+ * Copyright (C) 2004 Vincent Noel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 __LOGVIEW_ABOUT_H__
+#define __LOGVIEW_ABOUT_H__
+
+static const char *logview_about_authors[] = {
+ "Cesar Miquel <[email protected]>",
+ "Glynn Foster <[email protected]>",
+ "Fernando Herrera <[email protected]>",
+ "Shakti Sen <[email protected]>",
+ "Julio M Merino Vidal <[email protected]>",
+ "Jason Leach <[email protected]>",
+ "Christian Neumair <[email protected]>",
+ "Jan Arne Petersen <[email protected]>",
+ "Jason Long <[email protected]>",
+ "Kjartan Maraas <[email protected]>",
+ "Vincent Noel <[email protected]>",
+ "Cosimo Cecchi <[email protected]>",
+ NULL
+};
+
+static const char *logview_about_documenters[] = {
+ "Sun GNOME Documentation Team <[email protected]>",
+ "Vincent Noel <[email protected]>",
+ "Judith Samson <[email protected]>",
+ NULL
+};
+
+static const char * logview_about_license[] = {
+ N_("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."),
+ N_("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."),
+ N_("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")
+};
+
+/* translator credits */
+static const char *logview_about_translator_credits = N_("translator-credits");
+
+
+#endif /* __LOGVIEW_ABOUT_H__ */
+
diff --git a/logview/src/logview-app.c b/logview/src/logview-app.c
new file mode 100644
index 00000000..ffa37ec7
--- /dev/null
+++ b/logview/src/logview-app.c
@@ -0,0 +1,420 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-app.c - logview application singleton
+ *
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/* logview-app.c */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "logview-app.h"
+
+#include "logview-manager.h"
+#include "logview-window.h"
+#include "logview-prefs.h"
+
+#include <glib/gi18n.h>
+
+struct _LogviewAppPrivate {
+ LogviewPrefs *prefs;
+ LogviewManager *manager;
+ LogviewWindow *window;
+};
+
+enum {
+ APP_QUIT,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static LogviewApp *app_singleton = NULL;
+
+G_DEFINE_TYPE (LogviewApp, logview_app, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_APP, LogviewAppPrivate))
+
+static gboolean
+main_window_delete_cb (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ LogviewApp *app = user_data;
+
+ g_signal_emit (app, signals[APP_QUIT], 0, NULL);
+
+ return FALSE;
+}
+
+static gboolean
+logview_app_set_window (LogviewApp *app)
+{
+ LogviewWindow *window;
+ gboolean retval = FALSE;
+
+ window = LOGVIEW_WINDOW (logview_window_new ());
+
+ if (window) {
+ app->priv->window = window;
+ g_signal_connect (window, "delete-event",
+ G_CALLBACK (main_window_delete_cb), app);
+ retval = TRUE;
+ }
+
+ gtk_window_set_default_icon_name ("mate-system-log");
+
+ return retval;
+}
+
+typedef struct {
+ LogviewApp *app;
+ GSList *logs;
+} EnumerateJob;
+
+/* TODO: ideally we should parse configuration files in /etc/logrotate.conf
+ * and all the files in /etc/logrotate.d/ and group all the logs referring
+ * to the same entry under a category. Right now, we just do some
+ * parsing instead, and fill with quasi-sensible defaults.
+ */
+
+/* adapted from sysklogd sources */
+static GSList*
+parse_syslog ()
+{
+ char cbuf[BUFSIZ];
+ char *cline, *p;
+ FILE *cf;
+ GSList *logfiles = NULL;
+
+ if ((cf = fopen ("/etc/syslog.conf", "r")) == NULL) {
+ return NULL;
+ }
+
+ cline = cbuf;
+ while (fgets (cline, sizeof (cbuf) - (cline - cbuf), cf) != NULL) {
+ gchar **list;
+ gint i;
+
+ for (p = cline; g_ascii_isspace (*p); ++p);
+ if (*p == '\0' || *p == '#' || *p == '\n')
+ continue;
+
+ list = g_strsplit_set (p, ", -\t()\n", 0);
+
+ for (i = 0; list[i]; ++i) {
+ if (*list[i] == '/' &&
+ g_slist_find_custom (logfiles, list[i],
+ (GCompareFunc) g_ascii_strcasecmp) == NULL)
+ {
+ logfiles = g_slist_insert (logfiles,
+ g_strdup (list[i]), 0);
+ }
+ }
+
+ g_strfreev (list);
+ }
+
+ fclose (cf);
+
+ return logfiles;
+}
+
+static void
+enumerate_job_finish (EnumerateJob *job)
+{
+ GSList *files = job->logs;
+ LogviewApp *app = job->app;
+
+ logview_manager_add_logs_from_name_list (app->priv->manager, files, files->data);
+
+ g_slist_foreach (files, (GFunc) g_free, NULL);
+ g_slist_free (files);
+
+ g_object_unref (job->app);
+ g_slice_free (EnumerateJob, job);
+}
+
+static void
+enumerate_next_files_async_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ EnumerateJob *job = user_data;
+ GList *enumerated_files, *l;
+ GFileInfo *info;
+ GSList *logs;
+ const char *content_type, *name;
+ char *parse_string, *container_path;
+ GFileType type;
+ GFile *container;
+
+ enumerated_files = g_file_enumerator_next_files_finish (G_FILE_ENUMERATOR (source),
+ res, NULL);
+ if (!enumerated_files) {
+ enumerate_job_finish (job);
+ return;
+ }
+
+ logs = job->logs;
+ container = g_file_enumerator_get_container (G_FILE_ENUMERATOR (source));
+ container_path = g_file_get_path (container);
+
+ /* TODO: we don't support grouping rotated logs yet, skip gzipped files
+ * and those which name contains a formatted date.
+ */
+ for (l = enumerated_files; l; l = l->next) {
+ info = l->data;
+ type = g_file_info_get_file_type (info);
+ content_type = g_file_info_get_content_type (info);
+ name = g_file_info_get_name (info);
+
+ if (!g_file_info_get_attribute_boolean (info, "access::can-read")) {
+ g_object_unref (info);
+ continue;
+ }
+
+ if (type != (G_FILE_TYPE_REGULAR || G_FILE_TYPE_SYMBOLIC_LINK) ||
+ !g_content_type_is_a (content_type, "text/plain"))
+ {
+ g_object_unref (info);
+ continue;
+ }
+
+ if (g_content_type_is_a (content_type, "application/x-gzip")) {
+ g_object_unref (info);
+ continue;
+ }
+
+ if (g_regex_match_simple ("\\d{8}$", name, 0, 0)) {
+ g_object_unref (info);
+ continue;
+ }
+
+ parse_string = g_build_filename (container_path, name, NULL);
+
+ if (g_slist_find_custom (logs, parse_string, (GCompareFunc) g_ascii_strcasecmp) == NULL) {
+ logs = g_slist_append (logs, parse_string);
+ } else {
+ g_free (parse_string);
+ }
+
+ g_object_unref (info);
+ parse_string = NULL;
+ }
+
+ g_list_free (enumerated_files);
+ g_object_unref (container);
+ g_free (container_path);
+
+ job->logs = logs;
+
+ enumerate_job_finish (job);
+}
+
+static void
+enumerate_children_async_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ EnumerateJob *job = user_data;
+ GFileEnumerator *enumerator;
+
+ enumerator = g_file_enumerate_children_finish (G_FILE (source),
+ res, NULL);
+ if (!enumerator) {
+ enumerate_job_finish (job);
+ return;
+ }
+
+ g_file_enumerator_next_files_async (enumerator, G_MAXINT,
+ G_PRIORITY_DEFAULT,
+ NULL, enumerate_next_files_async_cb, job);
+}
+
+static void
+logview_app_first_time_initialize (LogviewApp *app)
+{
+ GSList *logs;
+ GFile *log_dir;
+ EnumerateJob *job;
+
+ /* let's add all accessible files in /var/log and those mentioned
+ * in /etc/syslog.conf.
+ */
+
+ logs = parse_syslog ();
+
+ job = g_slice_new0 (EnumerateJob);
+ job->app = g_object_ref (app);
+ job->logs = logs;
+
+ log_dir = g_file_new_for_path ("/var/log/");
+ g_file_enumerate_children_async (log_dir,
+ "standard::*,access::can-read", 0,
+ G_PRIORITY_DEFAULT, NULL,
+ enumerate_children_async_cb, job);
+
+ g_object_unref (log_dir);
+}
+
+static void
+do_finalize (GObject *obj)
+{
+ LogviewApp *app = LOGVIEW_APP (obj);
+
+ g_object_unref (app->priv->manager);
+ g_object_unref (app->priv->prefs);
+
+ G_OBJECT_CLASS (logview_app_parent_class)->finalize (obj);
+}
+
+static void
+logview_app_class_init (LogviewAppClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = do_finalize;
+
+ signals[APP_QUIT] =
+ g_signal_new ("app-quit",
+ G_OBJECT_CLASS_TYPE (oclass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewAppClass, app_quit),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (LogviewAppPrivate));
+}
+
+static void
+logview_app_init (LogviewApp *self)
+{
+ LogviewAppPrivate *priv = self->priv = GET_PRIVATE (self);
+
+ priv->prefs = logview_prefs_get ();
+ priv->manager = logview_manager_get ();
+}
+
+LogviewApp*
+logview_app_get (void)
+{
+ if (!app_singleton) {
+ app_singleton = g_object_new (LOGVIEW_TYPE_APP, NULL);
+
+ if (!logview_app_set_window (app_singleton)) {
+ g_object_unref (app_singleton);
+ app_singleton = NULL;
+ }
+ }
+
+ return app_singleton;
+}
+
+void
+logview_app_initialize (LogviewApp *app, char **log_files)
+{
+ LogviewAppPrivate *priv;
+
+ g_assert (LOGVIEW_IS_APP (app));
+
+ priv = app->priv;
+
+ /* open regular logs and add each log passed as a parameter */
+
+ if (log_files == NULL) {
+ char *active_log;
+ gchar **logs;
+
+ active_log = logview_prefs_get_active_logfile (priv->prefs);
+ logs = logview_prefs_get_stored_logfiles (priv->prefs);
+
+ if (!logs || !logs[0]) {
+ logview_app_first_time_initialize (app);
+ } else {
+ logview_manager_add_logs_from_names (priv->manager,
+ logs, active_log);
+
+ g_free (active_log);
+ g_strfreev (logs);
+ }
+ } else {
+ logview_manager_add_logs_from_names (priv->manager, log_files, NULL);
+ }
+
+ gtk_widget_show (GTK_WIDGET (priv->window));
+}
+
+void
+logview_app_add_error (LogviewApp *app,
+ const char *file_path,
+ const char *secondary)
+{
+ LogviewWindow *window;
+ char *primary;
+
+ g_assert (LOGVIEW_IS_APP (app));
+
+ window = app->priv->window;
+ primary = g_strdup_printf (_("Impossible to open the file %s"), file_path);
+
+ logview_window_add_error (window, primary, secondary);
+
+ g_free (primary);
+}
+
+static void
+check_error_prefs (gpointer data,
+ gpointer user_data)
+{
+ gchar **strings = data;
+ LogviewApp *app = user_data;
+ GFile *file = g_file_new_for_path (strings[0]);
+
+ logview_prefs_remove_stored_log (app->priv->prefs, file);
+ g_object_unref (file);
+}
+
+void
+logview_app_add_errors (LogviewApp *app,
+ GPtrArray *errors)
+{
+ LogviewWindow *window;
+
+ g_assert (LOGVIEW_IS_APP (app));
+
+ window = app->priv->window;
+
+ if (errors->len == 0) {
+ return;
+ }
+
+ g_ptr_array_foreach (errors, check_error_prefs, app);
+
+ if (errors->len == 1) {
+ char **err;
+
+ err = g_ptr_array_index (errors, 0);
+ logview_window_add_error (window, err[0], err[1]);
+ } else {
+ logview_window_add_errors (window, errors);
+ }
+}
diff --git a/logview/src/logview-app.h b/logview/src/logview-app.h
new file mode 100644
index 00000000..3ce7c059
--- /dev/null
+++ b/logview/src/logview-app.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-app.h - logview application singleton
+ *
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __LOGVIEW_APP_H__
+#define __LOGVIEW_APP_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define LOGVIEW_TYPE_APP logview_app_get_type()
+#define LOGVIEW_APP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), LOGVIEW_TYPE_APP, LogviewApp))
+#define LOGVIEW_APP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), LOGVIEW_TYPE_APP, LogviewAppClass))
+#define LOGVIEW_IS_APP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LOGVIEW_TYPE_APP))
+#define LOGVIEW_IS_APP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), LOGVIEW_TYPE_APP))
+#define LOGVIEW_APP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), LOGVIEW_TYPE_APP, LogviewAppClass))
+
+typedef struct _LogviewApp LogviewApp;
+typedef struct _LogviewAppClass LogviewAppClass;
+typedef struct _LogviewAppPrivate LogviewAppPrivate;
+
+struct _LogviewApp {
+ GObject parent;
+
+ LogviewAppPrivate *priv;
+};
+
+struct _LogviewAppClass {
+ GObjectClass parent_class;
+
+ void (* app_quit) (LogviewApp *app);
+};
+
+
+GType logview_app_get_type (void);
+
+/* public methods */
+LogviewApp * logview_app_get (void);
+void logview_app_initialize (LogviewApp *app,
+ char **log_files);
+void logview_app_add_error (LogviewApp *app,
+ const char *file_path,
+ const char *secondary);
+void logview_app_add_errors (LogviewApp *app,
+ GPtrArray *errors);
+
+G_END_DECLS
+
+#endif /* __LOGVIEW_APP_H__ */
diff --git a/logview/src/logview-filter-manager.c b/logview/src/logview-filter-manager.c
new file mode 100644
index 00000000..0905306f
--- /dev/null
+++ b/logview/src/logview-filter-manager.c
@@ -0,0 +1,575 @@
+/*-*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/*
+ * mate-utils
+ * Copyright (C) Johannes Schmid 2009 <[email protected]>
+ *
+ * mate-utils 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.
+ *
+ * mate-utils 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "logview-filter-manager.h"
+#include "logview-prefs.h"
+#include <gtk/gtk.h>
+#include <string.h>
+#include <glib/gi18n.h>
+
+#define UI_FILE LOGVIEW_DATADIR "/logview-filter.ui"
+
+struct _LogviewFilterManagerPrivate {
+ GtkWidget *tree;
+
+ GtkWidget *add_button;
+ GtkWidget *remove_button;
+ GtkWidget *edit_button;
+
+ GtkTreeModel *model;
+ GtkBuilder* builder;
+
+ LogviewPrefs* prefs;
+};
+
+enum {
+ COLUMN_NAME = 0,
+ COLUMN_FILTER,
+ N_COLUMNS
+};
+
+#define LOGVIEW_FILTER_MANAGER_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_FILTER_MANAGER, LogviewFilterManagerPrivate))
+
+G_DEFINE_TYPE (LogviewFilterManager, logview_filter_manager, GTK_TYPE_DIALOG);
+
+static void
+logview_filter_manager_update_model (LogviewFilterManager *manager)
+{
+ GList *filters;
+ GList *filter;
+ gchar *name;
+ GtkTreeIter iter;
+
+ gtk_list_store_clear (GTK_LIST_STORE (manager->priv->model));
+
+ filters = logview_prefs_get_filters (manager->priv->prefs);
+
+ for (filter = filters; filter != NULL; filter = g_list_next (filter)) {
+ g_object_get (filter->data, "name", &name, NULL);
+
+ gtk_list_store_append (GTK_LIST_STORE(manager->priv->model), &iter);
+ gtk_list_store_set (GTK_LIST_STORE (manager->priv->model),
+ &iter,
+ COLUMN_NAME, name,
+ COLUMN_FILTER, filter->data,
+ -1);
+
+ g_free (name);
+ }
+
+ g_list_free (filters);
+}
+
+static gboolean
+check_name (LogviewFilterManager *manager, const gchar *name)
+{
+ GtkWidget *dialog;
+
+ if (!strlen (name)) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (manager),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", _("Filter name is empty!"));
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ return FALSE;
+ }
+
+ if (strstr (name, ":") != NULL) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW(manager),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", _("Filter name may not contain the ':' character"));
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+check_regex (LogviewFilterManager *manager, const gchar *regex)
+{
+ GtkWidget *dialog;
+ GError *error = NULL;
+ GRegex *reg;
+
+ if (!strlen (regex)) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW(manager),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", _("Regular expression is empty!"));
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+ return FALSE;
+ }
+
+ reg = g_regex_new (regex,
+ 0, 0, &error);
+ if (error) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (manager),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("Regular expression is invalid: %s"),
+ error->message);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+ g_error_free (error);
+
+ return FALSE;
+ }
+
+ g_regex_unref (reg);
+
+ return TRUE;
+}
+
+static void
+on_check_toggled (GtkToggleButton *button, GtkWidget *widget)
+{
+ gtk_widget_set_sensitive (widget,
+ gtk_toggle_button_get_active (button));
+}
+
+static void
+on_dialog_add_edit_reponse (GtkWidget *dialog, int response_id,
+ LogviewFilterManager *manager)
+{
+ GtkWidget *entry_name, *entry_regex;
+ GtkWidget *radio_color, *radio_visible;
+ GtkWidget *check_foreground, *check_background;
+ GtkWidget *color_foreground, *color_background;
+ gchar *old_name;
+ const gchar *name;
+ const gchar *regex;
+ LogviewFilter *filter;
+ GtkTextTag *tag;
+ GtkBuilder *builder;
+
+ old_name = g_object_get_data (G_OBJECT (manager), "old_name");
+ builder = manager->priv->builder;
+
+ entry_name = GTK_WIDGET (gtk_builder_get_object (builder,
+ "entry_name"));
+ entry_regex = GTK_WIDGET (gtk_builder_get_object (builder,
+ "entry_regex"));
+ radio_color = GTK_WIDGET (gtk_builder_get_object (builder,
+ "radio_color"));
+ radio_visible = GTK_WIDGET (gtk_builder_get_object (builder,
+ "radio_visible"));
+ check_foreground = GTK_WIDGET (gtk_builder_get_object (builder,
+ "check_foreground"));
+ check_background = GTK_WIDGET (gtk_builder_get_object (builder,
+ "check_background"));
+ color_foreground = GTK_WIDGET (gtk_builder_get_object (builder,
+ "color_foreground"));
+ color_background = GTK_WIDGET (gtk_builder_get_object (builder,
+ "color_background"));
+
+ if (response_id == GTK_RESPONSE_APPLY) {
+ name = gtk_entry_get_text (GTK_ENTRY (entry_name));
+ regex = gtk_entry_get_text (GTK_ENTRY (entry_regex));
+
+ if (!check_name (manager, name) || !check_regex (manager, regex)) {
+ return;
+ }
+
+ filter = logview_filter_new (name, regex);
+ tag = gtk_text_tag_new (name);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio_color))) {
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_foreground))) {
+ GdkColor foreground_color;
+ gtk_color_button_get_color (GTK_COLOR_BUTTON (color_foreground),
+ &foreground_color);
+ g_object_set (G_OBJECT (tag),
+ "foreground-gdk", &foreground_color,
+ "foreground-set", TRUE, NULL);
+ }
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_background))) {
+ GdkColor background_color;
+ gtk_color_button_get_color (GTK_COLOR_BUTTON (color_background),
+ &background_color);
+ g_object_set (tag,
+ "paragraph-background-gdk", &background_color,
+ "paragraph-background-set", TRUE, NULL);
+ }
+
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_foreground))
+ && !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_background))) {
+ GtkWidget *error_dialog;
+
+ error_dialog = gtk_message_dialog_new (GTK_WINDOW (manager),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s",
+ _("Please specify either foreground or background color!"));
+ gtk_dialog_run (GTK_DIALOG (error_dialog));
+ gtk_widget_destroy (error_dialog);
+ g_object_unref (tag);
+ g_object_unref (filter);
+
+ return;
+ }
+ } else { /* !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio_color)) */
+ g_object_set (tag, "invisible", TRUE, NULL);
+ }
+
+ if (old_name && !g_str_equal (old_name, name)) {
+ logview_prefs_remove_filter (manager->priv->prefs, old_name);
+ }
+
+ g_object_set (G_OBJECT (filter), "texttag", tag, NULL);
+ g_object_unref (tag);
+
+ logview_prefs_add_filter (manager->priv->prefs, filter);
+ g_object_unref (filter);
+
+ logview_filter_manager_update_model (manager);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+run_add_edit_dialog (LogviewFilterManager *manager, LogviewFilter *filter)
+{
+ GError *error;
+ gchar *name, *regex;
+ const gchar *title;
+ GtkWidget *dialog, *entry_name, *entry_regex, *radio_color;
+ GtkWidget *radio_visible, *check_foreground, *check_background;
+ GtkWidget *color_foreground, *color_background, *vbox_color;
+ gboolean foreground_set, background_set, invisible;
+ GtkTextTag *tag;
+ GtkBuilder* builder;
+
+ builder = manager->priv->builder;
+
+ error = NULL;
+ name = NULL;
+
+ gtk_builder_add_from_file (builder, UI_FILE, &error);
+
+ if (error) {
+ g_warning ("Could not load filter ui: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ title = (filter != NULL ? _("Edit filter") : _("Add new filter"));
+
+ dialog = GTK_WIDGET (gtk_builder_get_object (builder,
+ "dialog_filter"));
+
+ gtk_window_set_title (GTK_WINDOW (dialog), title);
+
+ entry_name = GTK_WIDGET (gtk_builder_get_object (builder,
+ "entry_name"));
+ entry_regex = GTK_WIDGET (gtk_builder_get_object (builder,
+ "entry_regex"));
+ radio_color = GTK_WIDGET (gtk_builder_get_object (builder,
+ "radio_color"));
+ radio_visible = GTK_WIDGET (gtk_builder_get_object (builder,
+ "radio_visible"));
+
+ gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_color),
+ gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_visible)));
+
+ check_foreground = GTK_WIDGET (gtk_builder_get_object (builder,
+ "check_foreground"));
+ check_background = GTK_WIDGET (gtk_builder_get_object (builder,
+ "check_background"));
+ color_foreground = GTK_WIDGET (gtk_builder_get_object (builder,
+ "color_foreground"));
+ color_background = GTK_WIDGET (gtk_builder_get_object (builder,
+ "color_background"));
+ g_signal_connect (check_foreground, "toggled", G_CALLBACK (on_check_toggled),
+ color_foreground);
+ g_signal_connect (check_background, "toggled", G_CALLBACK (on_check_toggled),
+ color_background);
+
+ on_check_toggled (GTK_TOGGLE_BUTTON (check_foreground),
+ color_foreground);
+ on_check_toggled (GTK_TOGGLE_BUTTON (check_background),
+ color_background);
+
+ vbox_color = GTK_WIDGET (gtk_builder_get_object (builder, "vbox_color"));
+ g_signal_connect (radio_color, "toggled", G_CALLBACK (on_check_toggled),
+ vbox_color);
+ on_check_toggled (GTK_TOGGLE_BUTTON (radio_color),
+ vbox_color);
+
+ if (filter) {
+ g_object_get (filter,
+ "name", &name,
+ "regex", &regex,
+ "texttag", &tag,
+ NULL);
+ g_object_get (tag,
+ "foreground-set", &foreground_set,
+ "paragraph-background-set", &background_set,
+ "invisible", &invisible, NULL);
+ gtk_entry_set_text (GTK_ENTRY(entry_name), name);
+ gtk_entry_set_text (GTK_ENTRY(entry_regex), regex);
+
+ if (foreground_set) {
+ GdkColor *foreground;
+
+ g_object_get (tag, "foreground-gdk", &foreground, NULL);
+ gtk_color_button_set_color (GTK_COLOR_BUTTON (color_foreground),
+ foreground);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_foreground),
+ TRUE);
+
+ gdk_color_free (foreground);
+ }
+
+ if (background_set) {
+ GdkColor *background;
+
+ g_object_get (tag, "paragraph-background-gdk", &background, NULL);
+ gtk_color_button_set_color (GTK_COLOR_BUTTON (color_background),
+ background);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_background),
+ TRUE);
+
+ gdk_color_free (background);
+ }
+
+ if (background_set || foreground_set) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_color), TRUE);
+ } else if (invisible) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_visible), TRUE);
+ }
+
+ g_free (regex);
+ g_object_unref (tag);
+ }
+
+ g_object_set_data_full (G_OBJECT (manager), "old_name", name, g_free);
+
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (on_dialog_add_edit_reponse), manager);
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (manager));
+ gtk_window_set_modal (GTK_WINDOW (dialog),
+ TRUE);
+
+ gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+static void
+on_add_clicked (GtkWidget *button, LogviewFilterManager *manager)
+{
+ run_add_edit_dialog (manager, NULL);
+}
+
+static void
+on_edit_clicked (GtkWidget *button, LogviewFilterManager *manager)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ LogviewFilter *filter;
+ GtkTreeSelection *selection;
+
+ selection =
+ gtk_tree_view_get_selection (GTK_TREE_VIEW (manager->priv->tree));
+
+ gtk_tree_selection_get_selected (selection, &model, &iter);
+ gtk_tree_model_get (model, &iter, COLUMN_FILTER, &filter, -1);
+
+ run_add_edit_dialog (manager, filter);
+
+ g_object_unref (filter);
+}
+
+static void
+on_remove_clicked (GtkWidget *button, LogviewFilterManager *manager)
+{
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gchar *name;
+
+ selection =
+ gtk_tree_view_get_selection (GTK_TREE_VIEW (manager->priv->tree));
+
+ gtk_tree_selection_get_selected (selection, &model, &iter);
+ gtk_tree_model_get (model, &iter, COLUMN_NAME, &name, -1);
+
+ logview_prefs_remove_filter (manager->priv->prefs, name);
+ logview_filter_manager_update_model (manager);
+
+ g_free(name);
+}
+
+static void
+on_tree_selection_changed (GtkTreeSelection *selection, LogviewFilterManager *manager)
+{
+ gboolean status;
+
+ status = gtk_tree_selection_get_selected (selection, NULL, NULL);
+
+ gtk_widget_set_sensitive (manager->priv->edit_button, status);
+ gtk_widget_set_sensitive (manager->priv->remove_button, status);
+}
+
+static void
+logview_filter_manager_init (LogviewFilterManager *manager)
+{
+ GtkWidget *table;
+ GtkWidget *scrolled_window;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *text_renderer;
+ LogviewFilterManagerPrivate *priv;
+
+ manager->priv = LOGVIEW_FILTER_MANAGER_GET_PRIVATE (manager);
+ priv = manager->priv;
+
+ priv->builder = gtk_builder_new ();
+ g_object_ref (priv->builder);
+ priv->prefs = logview_prefs_get ();
+
+ gtk_dialog_add_button (GTK_DIALOG(manager),
+ GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CLOSE);
+ gtk_window_set_modal (GTK_WINDOW (manager),
+ TRUE);
+
+ priv->model = GTK_TREE_MODEL (gtk_list_store_new (N_COLUMNS,
+ G_TYPE_STRING,
+ G_TYPE_OBJECT));
+ logview_filter_manager_update_model (manager);
+
+ table = gtk_table_new (3, 2, FALSE);
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_ETCHED_IN);
+ priv->tree = gtk_tree_view_new_with_model (priv->model);
+ gtk_widget_set_size_request (priv->tree, 150, 200);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree), FALSE);
+ gtk_container_add (GTK_CONTAINER (scrolled_window), priv->tree);
+
+ text_renderer = gtk_cell_renderer_text_new ();
+
+ column = gtk_tree_view_column_new();
+ gtk_tree_view_column_pack_start (column, text_renderer, TRUE);
+ gtk_tree_view_column_set_attributes (column,
+ text_renderer,
+ "text", COLUMN_NAME,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree),
+ column);
+
+ priv->add_button = gtk_button_new_from_stock (GTK_STOCK_ADD);
+ priv->edit_button = gtk_button_new_from_stock (GTK_STOCK_PROPERTIES);
+ priv->remove_button = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
+
+ gtk_window_set_title (GTK_WINDOW (manager),
+ _("Filters"));
+
+ g_signal_connect (priv->add_button, "clicked",
+ G_CALLBACK (on_add_clicked), manager);
+ g_signal_connect (priv->edit_button, "clicked",
+ G_CALLBACK (on_edit_clicked), manager);
+ g_signal_connect (priv->remove_button, "clicked",
+ G_CALLBACK (on_remove_clicked), manager);
+
+ gtk_widget_set_sensitive (priv->edit_button, FALSE);
+ gtk_widget_set_sensitive (priv->remove_button, FALSE);
+
+ g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree)),
+ "changed", G_CALLBACK (on_tree_selection_changed),
+ manager);
+
+ gtk_table_attach_defaults (GTK_TABLE (table),
+ scrolled_window,
+ 0, 1, 0, 3);
+ gtk_table_attach (GTK_TABLE (table),
+ priv->add_button,
+ 1, 2, 0, 1, GTK_FILL, 0, 5, 5);
+ gtk_table_attach (GTK_TABLE (table),
+ priv->edit_button,
+ 1, 2, 1, 2, GTK_FILL, 0, 5, 5);
+ gtk_table_attach (GTK_TABLE (table),
+ priv->remove_button,
+ 1, 2, 2, 3, GTK_FILL, 0, 5, 5);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (manager))),
+ table, TRUE, TRUE, 5);
+ gtk_widget_show_all (GTK_WIDGET (manager));
+}
+
+static void
+logview_filter_manager_dispose (GObject *object)
+{
+ LogviewFilterManager* manager;
+
+ manager = LOGVIEW_FILTER_MANAGER (object);
+
+ g_object_unref (manager->priv->builder);
+
+ G_OBJECT_CLASS (logview_filter_manager_parent_class)->dispose (object);
+}
+
+static void
+logview_filter_manager_response (GtkDialog* dialog, gint response_id)
+{
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+logview_filter_manager_class_init (LogviewFilterManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkDialogClass *parent_class = GTK_DIALOG_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LogviewFilterManagerPrivate));
+
+ object_class->dispose = logview_filter_manager_dispose;
+ parent_class->response = logview_filter_manager_response;
+}
+
+GtkWidget *
+logview_filter_manager_new (void)
+{
+ return g_object_new (LOGVIEW_TYPE_FILTER_MANAGER, NULL);
+}
diff --git a/logview/src/logview-filter-manager.h b/logview/src/logview-filter-manager.h
new file mode 100644
index 00000000..c33ea1f7
--- /dev/null
+++ b/logview/src/logview-filter-manager.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/*
+ * mate-utils
+ * Copyright (C) Johannes Schmid 2009 <[email protected]>
+ *
+ * mate-utils 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.
+ *
+ * mate-utils 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LOGVIEW_FILTER_MANAGER_H_
+#define _LOGVIEW_FILTER_MANAGER_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define LOGVIEW_TYPE_FILTER_MANAGER (logview_filter_manager_get_type ())
+#define LOGVIEW_FILTER_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), LOGVIEW_TYPE_FILTER_MANAGER, LogviewFilterManager))
+#define LOGVIEW_FILTER_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), LOGVIEW_TYPE_FILTER_MANAGER, LogviewFilterManagerClass))
+#define LOGVIEW_IS_FILTER_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LOGVIEW_TYPE_FILTER_MANAGER))
+#define LOGVIEW_IS_FILTER_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LOGVIEW_TYPE_FILTER_MANAGER))
+#define LOGVIEW_FILTER_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), LOGVIEW_TYPE_FILTER_MANAGER, LogviewFilterManagerClass))
+
+typedef struct _LogviewFilterManagerClass LogviewFilterManagerClass;
+typedef struct _LogviewFilterManager LogviewFilterManager;
+typedef struct _LogviewFilterManagerPrivate LogviewFilterManagerPrivate;
+
+struct _LogviewFilterManagerClass {
+ GtkDialogClass parent_class;
+};
+
+struct _LogviewFilterManager {
+ GtkDialog parent_instance;
+
+ LogviewFilterManagerPrivate* priv;
+};
+
+GType logview_filter_manager_get_type (void) G_GNUC_CONST;
+GtkWidget * logview_filter_manager_new (void);
+
+G_END_DECLS
+
+#endif /* _LOGVIEW_FILTER_MANAGER_H_ */
diff --git a/logview/src/logview-filter.c b/logview/src/logview-filter.c
new file mode 100644
index 00000000..b5f9cfc6
--- /dev/null
+++ b/logview/src/logview-filter.c
@@ -0,0 +1,200 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/*
+ * mate-utils
+ * Copyright (C) Johannes Schmid 2009 <[email protected]>
+ *
+ * mate-utils 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.
+ *
+ * mate-utils 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "logview-filter.h"
+
+enum {
+ PROP_0,
+ PROP_REGEX,
+ PROP_NAME,
+ PROP_TEXTTAG
+};
+
+G_DEFINE_TYPE (LogviewFilter, logview_filter, G_TYPE_OBJECT);
+
+struct _LogviewFilterPrivate {
+ GRegex* regex;
+ gchar* name;
+ GtkTextTag* tag;
+};
+
+#define LOGVIEW_FILTER_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_FILTER, LogviewFilterPrivate))
+
+static void
+logview_filter_init (LogviewFilter *object)
+{
+ object->priv = LOGVIEW_FILTER_GET_PRIVATE(object);
+ object->priv->tag = NULL;
+}
+
+static void
+logview_filter_finalize (GObject *object)
+{
+ LogviewFilterPrivate *priv = LOGVIEW_FILTER (object)->priv;
+
+ if (priv->tag) {
+ g_object_unref (priv->tag);
+ }
+
+ g_regex_unref (priv->regex);
+ g_free (priv->name);
+
+ G_OBJECT_CLASS (logview_filter_parent_class)->finalize (object);
+}
+
+static void
+logview_filter_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ LogviewFilterPrivate* priv = LOGVIEW_FILTER (object)->priv;
+
+ switch (prop_id) {
+ case PROP_NAME:
+ priv->name = g_value_dup_string (value);
+ break;
+ case PROP_REGEX: {
+ GError* err;
+ const gchar* regex;
+
+ err = NULL;
+
+ regex = g_value_get_string (value);
+ priv->regex = g_regex_new (regex, 0, 0, &err);
+
+ if (err) {
+ g_regex_unref (priv->regex);
+ priv->regex = NULL;
+ g_warning ("Couldn't create GRegex object: %s", err->message);
+ g_error_free (err);
+ }
+
+ break;
+ }
+ case PROP_TEXTTAG: {
+ if (priv->tag) {
+ g_object_unref (priv->tag);
+ }
+
+ priv->tag = g_value_dup_object (value);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+logview_filter_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ LogviewFilterPrivate* priv = LOGVIEW_FILTER (object)->priv;
+
+ switch (prop_id) {
+ case PROP_REGEX:
+ g_value_set_string (value, g_regex_get_pattern (priv->regex));
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+ case PROP_TEXTTAG:
+ g_value_set_object (value, priv->tag);
+ break;
+ }
+}
+
+static void
+logview_filter_class_init (LogviewFilterClass *klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = logview_filter_finalize;
+ object_class->set_property = logview_filter_set_property;
+ object_class->get_property = logview_filter_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_REGEX,
+ g_param_spec_string ("regex",
+ "regular expression",
+ "regular expression",
+ "NULL",
+ G_PARAM_WRITABLE | G_PARAM_READABLE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "name",
+ "NULL",
+ G_PARAM_WRITABLE | G_PARAM_READABLE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_TEXTTAG,
+ g_param_spec_object ("texttag",
+ "texttag",
+ "The text tag to be set on matching lines",
+ GTK_TYPE_TEXT_TAG,
+ G_PARAM_READABLE | G_PARAM_WRITABLE));
+
+
+ g_type_class_add_private (klass, sizeof (LogviewFilterPrivate));
+}
+
+/* public methods */
+
+LogviewFilter*
+logview_filter_new (const gchar *name, const gchar *regex)
+{
+ return g_object_new (LOGVIEW_TYPE_FILTER,
+ "name", name,
+ "regex", regex,
+ NULL);
+}
+
+gboolean
+logview_filter_filter (LogviewFilter *filter, const gchar *line)
+{
+ GMatchInfo* match_info;
+ LogviewFilterPrivate* priv;
+ gboolean retval;
+
+ g_return_val_if_fail (LOGVIEW_IS_FILTER (filter), FALSE);
+ g_return_val_if_fail (line != NULL, FALSE);
+
+ priv = filter->priv;
+
+ g_regex_match (priv->regex, line, 0, &match_info);
+
+ retval = g_match_info_matches (match_info);
+
+ g_match_info_free (match_info);
+
+ return retval;
+}
+
+GtkTextTag *
+logview_filter_get_tag (LogviewFilter *filter)
+{
+ g_return_val_if_fail (LOGVIEW_IS_FILTER (filter), NULL);
+
+ return filter->priv->tag;
+}
diff --git a/logview/src/logview-filter.h b/logview/src/logview-filter.h
new file mode 100644
index 00000000..6eb827b8
--- /dev/null
+++ b/logview/src/logview-filter.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/*
+ * mate-utils
+ * Copyright (C) Johannes Schmid 2009 <[email protected]>
+ *
+ * mate-utils 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.
+ *
+ * mate-utils 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LOGVIEW_FILTER_H_
+#define _LOGVIEW_FILTER_H_
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define LOGVIEW_TYPE_FILTER (logview_filter_get_type ())
+#define LOGVIEW_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), LOGVIEW_TYPE_FILTER, LogviewFilter))
+#define LOGVIEW_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), LOGVIEW_TYPE_FILTER, LogviewFilterClass))
+#define LOGVIEW_IS_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LOGVIEW_TYPE_FILTER))
+#define LOGVIEW_IS_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LOGVIEW_TYPE_FILTER))
+#define LOGVIEW_FILTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), LOGVIEW_TYPE_FILTER, LogviewFilterClass))
+
+typedef struct _LogviewFilterClass LogviewFilterClass;
+typedef struct _LogviewFilter LogviewFilter;
+typedef struct _LogviewFilterPrivate LogviewFilterPrivate;
+
+struct _LogviewFilterClass {
+ GObjectClass parent_class;
+};
+
+struct _LogviewFilter {
+ GObject parent_instance;
+
+ LogviewFilterPrivate *priv;
+};
+
+GType logview_filter_get_type (void) G_GNUC_CONST;
+LogviewFilter * logview_filter_new (const gchar *name,
+ const gchar *regex);
+gboolean logview_filter_filter (LogviewFilter *filter,
+ const gchar *line);
+GtkTextTag * logview_filter_get_tag (LogviewFilter *filter);
+
+G_END_DECLS
+
+#endif /* _LOGVIEW_FILTER_H_ */
diff --git a/logview/src/logview-findbar.c b/logview/src/logview-findbar.c
new file mode 100644
index 00000000..3cb53f1d
--- /dev/null
+++ b/logview/src/logview-findbar.c
@@ -0,0 +1,352 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-findbar.c - find toolbar for logview
+ *
+ * Copyright (C) 2005 Vincent Noel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "logview-findbar.h"
+
+struct _LogviewFindbarPrivate {
+ GtkWidget *entry;
+ GtkWidget *message;
+
+ GtkToolItem *clear_button;
+ GtkToolItem *back_button;
+ GtkToolItem *forward_button;
+ GtkToolItem *status_item;
+ GtkToolItem *separator;
+
+ char *string;
+
+ guint status_bold_id;
+};
+
+enum {
+ PREVIOUS,
+ NEXT,
+ CLOSE,
+ TEXT_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (LogviewFindbar, logview_findbar, GTK_TYPE_TOOLBAR);
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_FINDBAR, LogviewFindbarPrivate))
+
+static void
+back_button_clicked_cb (GtkToolButton *button,
+ gpointer user_data)
+{
+ LogviewFindbar *findbar = user_data;
+
+ g_signal_emit (findbar, signals[PREVIOUS], 0);
+}
+
+static void
+forward_button_clicked_cb (GtkToolButton *button,
+ gpointer user_data)
+{
+ LogviewFindbar *findbar = user_data;
+
+ g_signal_emit (findbar, signals[NEXT], 0);
+}
+
+static void
+clear_button_clicked_cb (GtkToolButton *button,
+ gpointer user_data)
+{
+ LogviewFindbar *findbar = user_data;
+
+ logview_findbar_set_message (findbar, NULL);
+ gtk_entry_set_text (GTK_ENTRY (findbar->priv->entry), "");
+}
+
+static void
+entry_activate_cb (GtkWidget *entry,
+ gpointer user_data)
+{
+ LogviewFindbar *findbar = user_data;
+
+ g_signal_emit (findbar, signals[NEXT], 0);
+}
+
+static void
+entry_changed_cb (GtkEditable *editable,
+ gpointer user_data)
+{
+ LogviewFindbar *findbar = user_data;
+ const char *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (editable));
+
+ if (g_strcmp0 (text, "") == 0) {
+ return;
+ }
+
+ if (g_strcmp0 (findbar->priv->string, text) != 0) {
+ g_free (findbar->priv->string);
+ findbar->priv->string = g_strdup (text);
+
+ g_signal_emit (findbar, signals[TEXT_CHANGED], 0);
+ }
+}
+
+static gboolean
+entry_key_press_event_cb (GtkWidget *entry,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ LogviewFindbar *findbar = user_data;
+
+ if (event->keyval == GDK_KEY_Escape) {
+ g_signal_emit (findbar, signals[CLOSE], 0);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+unbold_timeout_cb (gpointer user_data)
+{
+ LogviewFindbar *findbar = user_data;
+ PangoFontDescription *desc;
+
+ desc = pango_font_description_new ();
+ gtk_widget_modify_font (findbar->priv->message, desc);
+ pango_font_description_free (desc);
+
+ findbar->priv->status_bold_id = 0;
+
+ return FALSE;
+}
+
+static void
+logview_findbar_init (LogviewFindbar *findbar)
+{
+ GtkWidget *label, *w, *box;
+ GtkToolbar *gtoolbar;
+ GtkToolItem *item;
+ LogviewFindbarPrivate *priv;
+
+ priv = findbar->priv = GET_PRIVATE (findbar);
+
+ gtoolbar = GTK_TOOLBAR (findbar);
+
+ gtk_toolbar_set_style (gtoolbar, GTK_TOOLBAR_BOTH_HORIZ);
+
+ priv->status_bold_id = 0;
+
+ /* Find: |_______| */
+ w = gtk_alignment_new (0.0, 0.5, 1.0, 1.0);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (w), 0, 0, 2, 2);
+
+ box = gtk_hbox_new (FALSE, 12);
+ gtk_container_add (GTK_CONTAINER (w), box);
+
+ label = gtk_label_new_with_mnemonic (_("_Find:"));
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+
+ priv->entry = gtk_entry_new ();
+ gtk_entry_set_width_chars (GTK_ENTRY (priv->entry), 32);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->entry);
+ gtk_box_pack_start (GTK_BOX (box), priv->entry, TRUE, TRUE, 0);
+
+ item = gtk_tool_item_new ();
+ gtk_container_add (GTK_CONTAINER (item), w);
+ gtk_toolbar_insert (gtoolbar, item, -1);
+ gtk_widget_show_all (GTK_WIDGET (item));
+
+ /* "Previous" and "Next" buttons */
+ w = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
+ priv->back_button = gtk_tool_button_new (w, _("Find Previous"));
+ gtk_tool_item_set_is_important (priv->back_button, TRUE);
+ gtk_tool_item_set_tooltip_text (priv->back_button,
+ _("Find previous occurrence of the search string"));
+ gtk_toolbar_insert (gtoolbar, priv->back_button, -1);
+ gtk_widget_show_all (GTK_WIDGET (priv->back_button));
+
+ w = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
+ priv->forward_button = gtk_tool_button_new (w, _("Find Next"));
+ gtk_tool_item_set_is_important (priv->forward_button, TRUE);
+ gtk_tool_item_set_tooltip_text (priv->forward_button,
+ _("Find next occurrence of the search string"));
+ gtk_toolbar_insert (gtoolbar, priv->forward_button, -1);
+ gtk_widget_show_all (GTK_WIDGET (priv->forward_button));
+
+ /* clear button */
+ priv->clear_button = gtk_tool_button_new_from_stock (GTK_STOCK_CLEAR);
+ gtk_tool_item_set_tooltip_text (priv->clear_button,
+ _("Clear the search string"));
+ gtk_toolbar_insert (gtoolbar, priv->clear_button, -1);
+ gtk_widget_show_all (GTK_WIDGET (priv->clear_button));
+
+ /* separator */
+ priv->separator = gtk_separator_tool_item_new ();
+ gtk_toolbar_insert (gtoolbar, priv->separator, -1);
+
+ /* message */
+ priv->status_item = gtk_tool_item_new ();
+ gtk_tool_item_set_expand (priv->status_item, TRUE);
+ priv->message = gtk_label_new ("");
+ gtk_label_set_use_markup (GTK_LABEL (priv->message), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (priv->message), 0.0, 0.5);
+ gtk_container_add (GTK_CONTAINER (priv->status_item), priv->message);
+ gtk_widget_show (priv->message);
+ gtk_toolbar_insert (gtoolbar, priv->status_item, -1);
+
+ priv->string = NULL;
+
+ /* signal handlers */
+ g_signal_connect (priv->back_button, "clicked",
+ G_CALLBACK (back_button_clicked_cb), findbar);
+ g_signal_connect (priv->forward_button, "clicked",
+ G_CALLBACK (forward_button_clicked_cb), findbar);
+ g_signal_connect (priv->clear_button, "clicked",
+ G_CALLBACK (clear_button_clicked_cb), findbar);
+ g_signal_connect (priv->entry, "activate",
+ G_CALLBACK (entry_activate_cb), findbar);
+ g_signal_connect (priv->entry, "changed",
+ G_CALLBACK (entry_changed_cb), findbar);
+ g_signal_connect (priv->entry, "key-press-event",
+ G_CALLBACK (entry_key_press_event_cb), findbar);
+}
+
+static void
+do_grab_focus (GtkWidget *widget)
+{
+ LogviewFindbar *findbar = LOGVIEW_FINDBAR (widget);
+
+ gtk_widget_grab_focus (findbar->priv->entry);
+}
+
+static void
+do_finalize (GObject *obj)
+{
+ LogviewFindbar *findbar = LOGVIEW_FINDBAR (obj);
+
+ g_free (findbar->priv->string);
+
+ G_OBJECT_CLASS (logview_findbar_parent_class)->finalize (obj);
+}
+
+static void
+logview_findbar_class_init (LogviewFindbarClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+
+ oclass->finalize = do_finalize;
+
+ wclass->grab_focus = do_grab_focus;
+
+ signals[PREVIOUS] = g_signal_new ("previous",
+ G_OBJECT_CLASS_TYPE (oclass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewFindbarClass, previous),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[NEXT] = g_signal_new ("next",
+ G_OBJECT_CLASS_TYPE (oclass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewFindbarClass, next),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CLOSE] = g_signal_new ("close",
+ G_OBJECT_CLASS_TYPE (oclass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewFindbarClass, close),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[TEXT_CHANGED] = g_signal_new ("text-changed",
+ G_OBJECT_CLASS_TYPE (oclass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewFindbarClass, text_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (LogviewFindbarPrivate));
+}
+
+/* public methods */
+
+GtkWidget *
+logview_findbar_new (void)
+{
+ GtkWidget *widget;
+ widget = g_object_new (LOGVIEW_TYPE_FINDBAR, NULL);
+ return widget;
+}
+
+void
+logview_findbar_open (LogviewFindbar *findbar)
+{
+ g_assert (LOGVIEW_IS_FINDBAR (findbar));
+
+ gtk_widget_show (GTK_WIDGET (findbar));
+ gtk_widget_grab_focus (GTK_WIDGET (findbar));
+}
+
+const char *
+logview_findbar_get_text (LogviewFindbar *findbar)
+{
+ g_assert (LOGVIEW_IS_FINDBAR (findbar));
+
+ return findbar->priv->string;
+}
+
+void
+logview_findbar_set_message (LogviewFindbar *findbar,
+ const char *text)
+{
+ PangoFontDescription *desc;
+
+ g_assert (LOGVIEW_IS_FINDBAR (findbar));
+
+ if (text) {
+ desc = pango_font_description_new ();
+ pango_font_description_set_weight (desc, PANGO_WEIGHT_BOLD);
+ gtk_widget_modify_font (findbar->priv->message, desc);
+ pango_font_description_free (desc);
+
+ findbar->priv->status_bold_id = g_timeout_add (600, unbold_timeout_cb, findbar);
+ }
+
+ gtk_label_set_text (GTK_LABEL (findbar->priv->message),
+ text != NULL ? text : "");
+ g_object_set (findbar->priv->separator, "visible", text != NULL, NULL);
+ g_object_set (findbar->priv->status_item, "visible", text != NULL, NULL);
+}
diff --git a/logview/src/logview-findbar.h b/logview/src/logview-findbar.h
new file mode 100644
index 00000000..89c65249
--- /dev/null
+++ b/logview/src/logview-findbar.h
@@ -0,0 +1,72 @@
+/* logview-findbar.h - find toolbar for logview
+ *
+ * Copyright (C) 2004 Vincent Noel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __LOGVIEW_FINDBAR_H__
+#define __LOGVIEW_FINDBAR_H__
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define LOGVIEW_TYPE_FINDBAR \
+ (logview_findbar_get_type ())
+#define LOGVIEW_FINDBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), LOGVIEW_TYPE_FINDBAR, LogviewFindbar))
+#define LOGVIEW_FINDBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), LOGVIEW_TYPE_FINDBAR, LogviewFindbarClass))
+#define LOGVIEW_IS_FINDBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LOGVIEW_TYPE_FINDBAR))
+#define LOGVIEW_IS_FINDBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((obj), LOGVIEW_TYPE_FINDBAR))
+#define LOGVIEW_FINDBAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), LOGVIEW_TYPE_FINDBAR, LogviewFindbarClass))
+
+typedef struct _LogviewFindbar LogviewFindbar;
+typedef struct _LogviewFindbarClass LogviewFindbarClass;
+typedef struct _LogviewFindbarPrivate LogviewFindbarPrivate;
+
+struct _LogviewFindbar {
+ GtkToolbar parent_instance;
+ LogviewFindbarPrivate *priv;
+};
+
+struct _LogviewFindbarClass {
+ GtkToolbarClass parent_class;
+
+ /* signals */
+ void (* previous) (LogviewFindbar *findbar);
+ void (* next) (LogviewFindbar *findbar);
+ void (* close) (LogviewFindbar *findbar);
+ void (* text_changed) (LogviewFindbar *findbar);
+};
+
+GType logview_findbar_get_type (void);
+
+/* public methods */
+GtkWidget * logview_findbar_new (void);
+void logview_findbar_open (LogviewFindbar *findbar);
+const char * logview_findbar_get_text (LogviewFindbar *findbar);
+void logview_findbar_set_message (LogviewFindbar *findbar,
+ const char *message);
+
+G_END_DECLS
+
+#endif /* __LOGVIEW_FINDBAR_H__ */
diff --git a/logview/src/logview-log.c b/logview/src/logview-log.c
new file mode 100644
index 00000000..407f49c3
--- /dev/null
+++ b/logview/src/logview-log.c
@@ -0,0 +1,936 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-log.c - object representation of a logfile
+ *
+ * Copyright (C) 1998 Cesar Miquel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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., 551 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#ifdef HAVE_ZLIB
+#include <zlib.h>
+#endif
+
+#include "logview-log.h"
+#include "logview-utils.h"
+
+G_DEFINE_TYPE (LogviewLog, logview_log, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_LOG, LogviewLogPrivate))
+
+enum {
+ LOG_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+struct _LogviewLogPrivate {
+ /* file and monitor */
+ GFile *file;
+ GFileMonitor *mon;
+
+ /* stats about the file */
+ time_t file_time;
+ goffset file_size;
+ char *display_name;
+ gboolean has_days;
+
+ /* lines and relative days */
+ GSList *days;
+ GPtrArray *lines;
+ guint lines_no;
+
+ /* stream poiting to the log */
+ GDataInputStream *stream;
+ gboolean has_new_lines;
+};
+
+typedef struct {
+ LogviewLog *log;
+ GError *err;
+ LogviewCreateCallback callback;
+ gpointer user_data;
+} LoadJob;
+
+typedef struct {
+ LogviewLog *log;
+ GError *err;
+ const char **lines;
+ GSList *new_days;
+ GCancellable *cancellable;
+ LogviewNewLinesCallback callback;
+ gpointer user_data;
+} NewLinesJob;
+
+typedef struct {
+ GInputStream *parent_str;
+ guchar * buffer;
+ GFile *file;
+
+ gboolean last_str_result;
+ int last_z_result;
+ z_stream zstream;
+} GZHandle;
+
+static void
+do_finalize (GObject *obj)
+{
+ LogviewLog *log = LOGVIEW_LOG (obj);
+ char ** lines;
+
+ if (log->priv->stream) {
+ g_object_unref (log->priv->stream);
+ log->priv->stream = NULL;
+ }
+
+ if (log->priv->file) {
+ g_object_unref (log->priv->file);
+ log->priv->file = NULL;
+ }
+
+ if (log->priv->mon) {
+ g_object_unref (log->priv->mon);
+ log->priv->mon = NULL;
+ }
+
+ if (log->priv->days) {
+ g_slist_foreach (log->priv->days,
+ (GFunc) logview_utils_day_free, NULL);
+ g_slist_free (log->priv->days);
+ log->priv->days = NULL;
+ }
+
+ if (log->priv->lines) {
+ lines = (char **) g_ptr_array_free (log->priv->lines, FALSE);
+ g_strfreev (lines);
+ log->priv->lines = NULL;
+ }
+
+ G_OBJECT_CLASS (logview_log_parent_class)->finalize (obj);
+}
+
+static void
+logview_log_class_init (LogviewLogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = do_finalize;
+
+ signals[LOG_CHANGED] = g_signal_new ("log-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewLogClass, log_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (LogviewLogPrivate));
+}
+
+static void
+logview_log_init (LogviewLog *self)
+{
+ self->priv = GET_PRIVATE (self);
+
+ self->priv->lines = NULL;
+ self->priv->lines_no = 0;
+ self->priv->days = NULL;
+ self->priv->file = NULL;
+ self->priv->mon = NULL;
+ self->priv->has_new_lines = FALSE;
+ self->priv->has_days = FALSE;
+}
+
+static void
+monitor_changed_cb (GFileMonitor *monitor,
+ GFile *file,
+ GFile *unused,
+ GFileMonitorEvent event,
+ gpointer user_data)
+{
+ LogviewLog *log = user_data;
+
+ if (event == G_FILE_MONITOR_EVENT_CHANGED) {
+ log->priv->has_new_lines = TRUE;
+ g_signal_emit (log, signals[LOG_CHANGED], 0, NULL);
+ }
+ /* TODO: handle the case where the log is deleted? */
+}
+
+static void
+setup_file_monitor (LogviewLog *log)
+{
+ GError *err = NULL;
+
+ log->priv->mon = g_file_monitor (log->priv->file,
+ 0, NULL, &err);
+ if (err) {
+ /* it'd be strange to get this error at this point but whatever */
+ g_warning ("Impossible to monitor the log file: the changes won't be notified");
+ g_error_free (err);
+ return;
+ }
+
+ /* set the rate to 1sec, as I guess it's not unusual to have more than
+ * one line written consequently or in a short time, being a log file.
+ */
+ g_file_monitor_set_rate_limit (log->priv->mon, 1000);
+ g_signal_connect (log->priv->mon, "changed",
+ G_CALLBACK (monitor_changed_cb), log);
+}
+
+static GSList *
+add_new_days_to_cache (LogviewLog *log, const char **new_lines, guint lines_offset)
+{
+ GSList *new_days, *l, *last_cached;
+ int res;
+ Day *day, *last;
+
+ new_days = log_read_dates (new_lines, log->priv->file_time);
+
+ /* the days are stored in chronological order, so we compare the last cached
+ * one with the new we got.
+ */
+ last_cached = g_slist_last (log->priv->days);
+
+ if (!last_cached) {
+ /* this means the day list is empty (i.e. we're on the first read */
+ log->priv->days = logview_utils_day_list_copy (new_days);
+ return new_days;
+ }
+
+ for (l = new_days; l; l = l->next) {
+ res = days_compare (l->data, last_cached->data);
+ day = l->data;
+
+ if (res > 0) {
+ /* this day in the list is newer than the last one, append to
+ * the cache.
+ */
+ day->first_line += lines_offset;
+ day->last_line += lines_offset;
+ log->priv->days = g_slist_append (log->priv->days, logview_utils_day_copy (day));
+ } else if (res == 0) {
+ last = last_cached->data;
+
+ /* update the lines number */
+ last->last_line += day->last_line;
+ }
+ }
+
+ return new_days;
+}
+
+static gboolean
+new_lines_job_done (gpointer data)
+{
+ NewLinesJob *job = data;
+
+ if (job->err) {
+ job->callback (job->log, NULL, NULL, job->err, job->user_data);
+ g_error_free (job->err);
+ } else {
+ job->callback (job->log, job->lines, job->new_days, job->err, job->user_data);
+ }
+
+ g_clear_object (&job->cancellable);
+
+ g_slist_foreach (job->new_days, (GFunc) logview_utils_day_free, NULL);
+ g_slist_free (job->new_days);
+
+ /* drop the reference we acquired before */
+ g_object_unref (job->log);
+
+ g_slice_free (NewLinesJob, job);
+
+ return FALSE;
+}
+
+static gboolean
+do_read_new_lines (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ /* this runs in a separate thread */
+ NewLinesJob *job = user_data;
+ LogviewLog *log = job->log;
+ char *line;
+ GError *err = NULL;
+ GPtrArray *lines;
+
+ g_assert (LOGVIEW_IS_LOG (log));
+ g_assert (log->priv->stream != NULL);
+
+ if (!log->priv->lines) {
+ log->priv->lines = g_ptr_array_new ();
+ g_ptr_array_add (log->priv->lines, NULL);
+ }
+
+ lines = log->priv->lines;
+
+ /* remove the NULL-terminator */
+ g_ptr_array_remove_index (lines, lines->len - 1);
+
+ while ((line = g_data_input_stream_read_line (log->priv->stream, NULL,
+ job->cancellable, &err)) != NULL)
+ {
+ g_ptr_array_add (lines, (gpointer) line);
+ }
+
+ /* NULL-terminate the array again */
+ g_ptr_array_add (lines, NULL);
+
+ if (err) {
+ job->err = err;
+ goto out;
+ }
+
+ log->priv->has_new_lines = FALSE;
+
+ /* we'll return only the new lines in the callback */
+ line = g_ptr_array_index (lines, log->priv->lines_no);
+ job->lines = (const char **) lines->pdata + log->priv->lines_no;
+
+ /* save the new number of days and lines */
+ job->new_days = add_new_days_to_cache (log, job->lines, log->priv->lines_no);
+ log->priv->lines_no = (lines->len - 1);
+
+out:
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ new_lines_job_done,
+ job, NULL);
+ return FALSE;
+}
+
+static gboolean
+log_load_done (gpointer user_data)
+{
+ LoadJob *job = user_data;
+
+ if (job->err) {
+ /* the callback will have NULL as log, and the error set */
+ g_object_unref (job->log);
+ job->callback (NULL, job->err, job->user_data);
+ g_error_free (job->err);
+ } else {
+ job->callback (job->log, NULL, job->user_data);
+ setup_file_monitor (job->log);
+ }
+
+ g_slice_free (LoadJob, job);
+
+ return FALSE;
+}
+
+#ifdef HAVE_ZLIB
+
+/* GZip functions adapted for GIO from mate-vfs/gzip-method.c */
+
+#define Z_BUFSIZE 16384
+
+#define GZIP_HEADER_SIZE 10
+#define GZIP_MAGIC_1 0x1f
+#define GZIP_MAGIC_2 0x8b
+#define GZIP_FLAG_ASCII 0x01 /* bit 0 set: file probably ascii text */
+#define GZIP_FLAG_HEAD_CRC 0x02 /* bit 1 set: header CRC present */
+#define GZIP_FLAG_EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
+#define GZIP_FLAG_ORIG_NAME 0x08 /* bit 3 set: original file name present */
+#define GZIP_FLAG_COMMENT 0x10 /* bit 4 set: file comment present */
+#define GZIP_FLAG_RESERVED 0xE0 /* bits 5..7: reserved */
+
+static gboolean
+skip_string (GInputStream *is)
+{
+ guchar c;
+ gssize bytes_read;
+
+ do {
+ bytes_read = g_input_stream_read (is, &c, 1, NULL, NULL);
+
+ if (bytes_read != 1) {
+ return FALSE;
+ }
+ } while (c != 0);
+
+ return TRUE;
+}
+
+static gboolean
+read_gzip_header (GInputStream *is,
+ time_t *modification_time)
+{
+ gboolean res;
+ guchar buffer[GZIP_HEADER_SIZE];
+ gssize bytes, to_skip;
+ guint mode;
+ guint flags;
+
+ bytes = g_input_stream_read (is, buffer, GZIP_HEADER_SIZE,
+ NULL, NULL);
+ if (bytes == -1) {
+ return FALSE;
+ }
+
+ if (bytes != GZIP_HEADER_SIZE)
+ return FALSE;
+
+ if (buffer[0] != GZIP_MAGIC_1 || buffer[1] != GZIP_MAGIC_2)
+ return FALSE;
+
+ mode = buffer[2];
+ if (mode != 8) /* Mode: deflate */
+ return FALSE;
+
+ flags = buffer[3];
+
+ if (flags & GZIP_FLAG_RESERVED)
+ return FALSE;
+
+ if (flags & GZIP_FLAG_EXTRA_FIELD) {
+ guchar tmp[2];
+
+ bytes = g_input_stream_read (is, tmp, 2, NULL, NULL);
+
+ if (bytes != 2) {
+ return FALSE;
+ }
+
+ to_skip = tmp[0] | (tmp[0] << 8);
+ bytes = g_input_stream_skip (is, to_skip, NULL, NULL);
+ if (bytes != to_skip) {
+ return FALSE;
+ }
+ }
+
+ if (flags & GZIP_FLAG_ORIG_NAME) {
+ if (!skip_string (is)) {
+ return FALSE;
+ }
+ }
+
+ if (flags & GZIP_FLAG_COMMENT) {
+ if (!skip_string (is)) {
+ return FALSE;
+ }
+ }
+
+ if (flags & GZIP_FLAG_HEAD_CRC) {
+ bytes = g_input_stream_skip (is, 2, NULL, NULL);
+ if (bytes != 2) {
+ return FALSE;
+ }
+ }
+
+ *modification_time = (buffer[4] | (buffer[5] << 8)
+ | (buffer[6] << 16) | (buffer[7] << 24));
+
+ return TRUE;
+}
+
+static GZHandle *
+gz_handle_new (GFile *file,
+ GInputStream *parent_stream)
+{
+ GZHandle *ret;
+
+ ret = g_new (GZHandle, 1);
+ ret->parent_str = g_object_ref (parent_stream);
+ ret->file = g_object_ref (file);
+ ret->buffer = NULL;
+
+ return ret;
+}
+
+static gboolean
+gz_handle_init (GZHandle *gz)
+{
+ gz->zstream.zalloc = NULL;
+ gz->zstream.zfree = NULL;
+ gz->zstream.opaque = NULL;
+
+ g_free (gz->buffer);
+ gz->buffer = g_malloc (Z_BUFSIZE);
+ gz->zstream.next_in = gz->buffer;
+ gz->zstream.avail_in = 0;
+
+ if (inflateInit2 (&gz->zstream, -MAX_WBITS) != Z_OK) {
+ return FALSE;
+ }
+
+ gz->last_z_result = Z_OK;
+ gz->last_str_result = TRUE;
+
+ return TRUE;
+}
+
+static void
+gz_handle_free (GZHandle *gz)
+{
+ g_object_unref (gz->parent_str);
+ g_object_unref (gz->file);
+ g_free (gz->buffer);
+ g_free (gz);
+}
+
+static gboolean
+fill_buffer (GZHandle *gz,
+ gsize num_bytes)
+{
+ gboolean res;
+ gsize count;
+
+ z_stream * zstream = &gz->zstream;
+
+ if (zstream->avail_in > 0) {
+ return TRUE;
+ }
+
+ count = g_input_stream_read (gz->parent_str,
+ gz->buffer,
+ Z_BUFSIZE,
+ NULL, NULL);
+ if (count == -1) {
+ if (zstream->avail_out == num_bytes) {
+ return FALSE;
+ }
+ gz->last_str_result = FALSE;
+ } else {
+ zstream->next_in = gz->buffer;
+ zstream->avail_in = count;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+result_from_z_result (int z_result)
+{
+ switch (z_result) {
+ case Z_OK:
+ case Z_STREAM_END:
+ return TRUE;
+ case Z_DATA_ERROR:
+ return FALSE;
+ default:
+ return FALSE;
+ }
+}
+
+static gboolean
+gz_handle_read (GZHandle *gz,
+ guchar *buffer,
+ gsize num_bytes,
+ gsize * bytes_read)
+{
+ z_stream *zstream;
+ gboolean res;
+ int z_result;
+
+ *bytes_read = 0;
+ zstream = &gz->zstream;
+
+ if (gz->last_z_result != Z_OK) {
+ if (gz->last_z_result == Z_STREAM_END) {
+ *bytes_read = 0;
+ return TRUE;
+ } else {
+ return result_from_z_result (gz->last_z_result);
+ }
+ } else if (gz->last_str_result == FALSE) {
+ return FALSE;
+ }
+
+ zstream->next_out = buffer;
+ zstream->avail_out = num_bytes;
+
+ while (zstream->avail_out != 0) {
+ res = fill_buffer (gz, num_bytes);
+
+ if (!res) {
+ return res;
+ }
+
+ z_result = inflate (zstream, Z_NO_FLUSH);
+ if (z_result == Z_STREAM_END) {
+ gz->last_z_result = z_result;
+ break;
+ } else if (z_result != Z_OK) {
+ gz->last_z_result = z_result;
+ }
+
+ if (gz->last_z_result != Z_OK && zstream->avail_out == num_bytes) {
+ return result_from_z_result (gz->last_z_result);
+ }
+ }
+
+ *bytes_read = num_bytes - zstream->avail_out;
+
+ return TRUE;
+}
+
+static GError *
+create_zlib_error (void)
+{
+ GError *err;
+
+ err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_ZLIB,
+ _("Error while uncompressing the GZipped log. The file "
+ "might be corrupt."));
+ return err;
+}
+
+#endif /* HAVE_ZLIB */
+
+static gboolean
+log_load (GIOSchedulerJob *io_job,
+ GCancellable *cancellable,
+ gpointer user_data)
+{
+ /* this runs in a separate i/o thread */
+ LoadJob *job = user_data;
+ LogviewLog *log = job->log;
+ GFile *f = log->priv->file;
+ GFileInfo *info;
+ GInputStream *is;
+ const char *peeked_buffer;
+ const char * parse_data[2];
+ GSList *days;
+ const char *content_type;
+ GFileType type;
+ GError *err = NULL;
+ GTimeVal timeval;
+ gboolean is_archive, can_read;
+
+ info = g_file_query_info (f,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+ G_FILE_ATTRIBUTE_TIME_MODIFIED ",",
+ 0, NULL, &err);
+ if (err) {
+ if (err->code == G_IO_ERROR_PERMISSION_DENIED) {
+ /* TODO: PolicyKit integration */
+ }
+ goto out;
+ }
+
+ can_read = g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_READ);
+ if (!can_read) {
+ /* TODO: PolicyKit integration */
+ err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_PERMISSION_DENIED,
+ _("You don't have enough permissions to read the file."));
+ g_object_unref (info);
+
+ goto out;
+ }
+
+ type = g_file_info_get_file_type (info);
+ content_type = g_file_info_get_content_type (info);
+
+ is_archive = g_content_type_equals (content_type, "application/x-gzip");
+
+ if (type != (G_FILE_TYPE_REGULAR || G_FILE_TYPE_SYMBOLIC_LINK) ||
+ (!g_content_type_is_a (content_type, "text/plain") && !is_archive))
+ {
+ err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_NOT_A_LOG,
+ _("The file is not a regular file or is not a text file."));
+ g_object_unref (info);
+
+ goto out;
+ }
+
+ log->priv->file_size = g_file_info_get_size (info);
+ g_file_info_get_modification_time (info, &timeval);
+ log->priv->file_time = timeval.tv_sec;
+ log->priv->display_name = g_strdup (g_file_info_get_display_name (info));
+
+ g_object_unref (info);
+
+ /* initialize the stream */
+ is = G_INPUT_STREAM (g_file_read (f, NULL, &err));
+
+ if (err) {
+ if (err->code == G_IO_ERROR_PERMISSION_DENIED) {
+ /* TODO: PolicyKit integration */
+ }
+
+ goto out;
+ }
+
+ if (is_archive) {
+#ifdef HAVE_ZLIB
+ GZHandle *gz;
+ gboolean res;
+ guchar * buffer;
+ gsize bytes_read;
+ GInputStream *real_is;
+ time_t mtime; /* seconds */
+
+ /* this also skips the header from |is| */
+ res = read_gzip_header (is, &mtime);
+
+ if (!res) {
+ g_object_unref (is);
+
+ err = create_zlib_error ();
+ goto out;
+ }
+
+ log->priv->file_time = mtime;
+
+ gz = gz_handle_new (f, is);
+ res = gz_handle_init (gz);
+
+ if (!res) {
+ g_object_unref (is);
+ gz_handle_free (gz);
+
+ err = create_zlib_error ();
+ goto out;
+ }
+
+ real_is = g_memory_input_stream_new ();
+
+ do {
+ buffer = g_malloc (1024);
+ res = gz_handle_read (gz, buffer, 1024, &bytes_read);
+ g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (real_is),
+ buffer, bytes_read, g_free);
+ } while (res == TRUE && bytes_read > 0);
+
+ if (!res) {
+ gz_handle_free (gz);
+ g_object_unref (real_is);
+ g_object_unref (is);
+
+ err = create_zlib_error ();
+ goto out;
+ }
+
+ g_object_unref (is);
+ is = real_is;
+
+ gz_handle_free (gz);
+#else /* HAVE_ZLIB */
+ g_object_unref (is);
+
+ err = g_error_new_literal (LOGVIEW_ERROR_QUARK, LOGVIEW_ERROR_NOT_SUPPORTED,
+ _("This version of System Log does not support GZipped logs."));
+ goto out;
+#endif /* HAVE_ZLIB */
+ }
+
+ log->priv->stream = g_data_input_stream_new (is);
+
+ /* sniff into the stream for a timestamped line */
+ g_buffered_input_stream_fill (G_BUFFERED_INPUT_STREAM (log->priv->stream),
+ (gssize) g_buffered_input_stream_get_buffer_size (G_BUFFERED_INPUT_STREAM (log->priv->stream)),
+ NULL, &err);
+ if (err == NULL) {
+ peeked_buffer = g_buffered_input_stream_peek_buffer
+ (G_BUFFERED_INPUT_STREAM (log->priv->stream), NULL);
+ parse_data[0] = peeked_buffer;
+ parse_data[1] = NULL;
+
+ if ((days = log_read_dates (parse_data, time (NULL))) != NULL) {
+ log->priv->has_days = TRUE;
+ g_slist_foreach (days, (GFunc) logview_utils_day_free, NULL);
+ g_slist_free (days);
+ } else {
+ log->priv->has_days = FALSE;
+ }
+ } else {
+ log->priv->has_days = FALSE;
+ g_clear_error (&err);
+ }
+
+ g_object_unref (is);
+
+out:
+ if (err) {
+ job->err = err;
+ }
+
+ g_io_scheduler_job_send_to_mainloop_async (io_job,
+ log_load_done,
+ job, NULL);
+ return FALSE;
+}
+
+static void
+log_setup_load (LogviewLog *log, LogviewCreateCallback callback,
+ gpointer user_data)
+{
+ LoadJob *job;
+
+ job = g_slice_new0 (LoadJob);
+ job->callback = callback;
+ job->user_data = user_data;
+ job->log = log;
+ job->err = NULL;
+
+ /* push the loading job into another thread */
+ g_io_scheduler_push_job (log_load,
+ job,
+ NULL, 0, NULL);
+}
+
+/* public methods */
+
+void
+logview_log_read_new_lines (LogviewLog *log,
+ GCancellable *cancellable,
+ LogviewNewLinesCallback callback,
+ gpointer user_data)
+{
+ NewLinesJob *job;
+
+ /* initialize the job struct with sensible values */
+ job = g_slice_new0 (NewLinesJob);
+ job->callback = callback;
+ job->user_data = user_data;
+ job->cancellable = (cancellable != NULL) ? g_object_ref (cancellable) : NULL;
+ job->log = g_object_ref (log);
+ job->err = NULL;
+ job->lines = NULL;
+ job->new_days = NULL;
+
+ /* push the fetching job into another thread */
+ g_io_scheduler_push_job (do_read_new_lines,
+ job,
+ NULL, 0,
+ job->cancellable);
+}
+
+void
+logview_log_create (const char *filename, LogviewCreateCallback callback,
+ gpointer user_data)
+{
+ LogviewLog *log = g_object_new (LOGVIEW_TYPE_LOG, NULL);
+
+ log->priv->file = g_file_new_for_path (filename);
+
+ log_setup_load (log, callback, user_data);
+}
+
+void
+logview_log_create_from_gfile (GFile *file, LogviewCreateCallback callback,
+ gpointer user_data)
+{
+ LogviewLog *log = g_object_new (LOGVIEW_TYPE_LOG, NULL);
+
+ log->priv->file = g_object_ref (file);
+
+ log_setup_load (log, callback, user_data);
+}
+
+const char *
+logview_log_get_display_name (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->display_name;
+}
+
+time_t
+logview_log_get_timestamp (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->file_time;
+}
+
+goffset
+logview_log_get_file_size (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->file_size;
+}
+
+guint
+logview_log_get_cached_lines_number (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->lines_no;
+}
+
+const char **
+logview_log_get_cached_lines (LogviewLog *log)
+{
+ const char ** lines = NULL;
+
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ if (log->priv->lines) {
+ lines = (const char **) log->priv->lines->pdata;
+ }
+
+ return lines;
+}
+
+GSList *
+logview_log_get_days_for_cached_lines (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->days;
+}
+
+gboolean
+logview_log_has_new_lines (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->has_new_lines;
+}
+
+char *
+logview_log_get_uri (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return g_file_get_uri (log->priv->file);
+}
+
+GFile *
+logview_log_get_gfile (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return g_object_ref (log->priv->file);
+}
+
+gboolean
+logview_log_get_has_days (LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ return log->priv->has_days;
+}
+
diff --git a/logview/src/logview-log.h b/logview/src/logview-log.h
new file mode 100644
index 00000000..4f81af14
--- /dev/null
+++ b/logview/src/logview-log.h
@@ -0,0 +1,110 @@
+/* logview-log.h - object representation of a logfile
+ *
+ * Copyright (C) 1998 Cesar Miquel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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., 551 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+/* logview-log.h */
+
+#ifndef __LOGVIEW_LOG_H__
+#define __LOGVIEW_LOG_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define LOGVIEW_TYPE_LOG logview_log_get_type()
+#define LOGVIEW_LOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), LOGVIEW_TYPE_LOG, LogviewLog))
+#define LOGVIEW_LOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), LOGVIEW_TYPE_LOG, LogviewLogClass))
+#define LOGVIEW_IS_LOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LOGVIEW_TYPE_LOG))
+#define LOGVIEW_IS_LOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), LOGVIEW_TYPE_LOG))
+#define LOGVIEW_LOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), LOGVIEW_TYPE_LOG, LogviewLogClass))
+
+typedef struct _LogviewLog LogviewLog;
+typedef struct _LogviewLogClass LogviewLogClass;
+typedef struct _LogviewLogPrivate LogviewLogPrivate;
+
+/* callback signatures for async I/O operations */
+
+typedef void (* LogviewCreateCallback) (LogviewLog *log,
+ GError *error,
+ gpointer user_data);
+typedef void (* LogviewNewLinesCallback) (LogviewLog *log,
+ const char **lines,
+ GSList *new_days,
+ GError *error,
+ gpointer user_data);
+
+#define LOGVIEW_ERROR_QUARK g_quark_from_static_string ("logview-error")
+
+typedef enum {
+ LOGVIEW_ERROR_FAILED,
+ LOGVIEW_ERROR_PERMISSION_DENIED,
+ LOGVIEW_ERROR_ZLIB,
+ LOGVIEW_ERROR_NOT_SUPPORTED,
+ LOGVIEW_ERROR_NOT_A_LOG
+} LogviewErrorEnum;
+
+struct _LogviewLog {
+ GObject parent;
+ LogviewLogPrivate *priv;
+};
+
+struct _LogviewLogClass {
+ GObjectClass parent_class;
+
+ /* signals */
+ void (* log_changed) (LogviewLog *log);
+};
+
+GType logview_log_get_type (void);
+
+/* public methods */
+
+/* these two do I/O, so they are wrapped async */
+void logview_log_create (const char *filename,
+ LogviewCreateCallback callback,
+ gpointer user_data);
+void logview_log_create_from_gfile (GFile *file,
+ LogviewCreateCallback callback,
+ gpointer user_data);
+void logview_log_read_new_lines (LogviewLog *log,
+ GCancellable *cancellable,
+ LogviewNewLinesCallback callback,
+ gpointer user_data);
+
+const char * logview_log_get_display_name (LogviewLog *log);
+time_t logview_log_get_timestamp (LogviewLog *log);
+goffset logview_log_get_file_size (LogviewLog *log);
+const char ** logview_log_get_cached_lines (LogviewLog *log);
+guint logview_log_get_cached_lines_number (LogviewLog *log);
+GSList * logview_log_get_days_for_cached_lines (LogviewLog *log);
+gboolean logview_log_has_new_lines (LogviewLog *log);
+char * logview_log_get_uri (LogviewLog *log);
+GFile * logview_log_get_gfile (LogviewLog *log);
+gboolean logview_log_get_has_days (LogviewLog *log);
+
+G_END_DECLS
+
+#endif /* __LOGVIEW_LOG_H__ */
diff --git a/logview/src/logview-loglist.c b/logview/src/logview-loglist.c
new file mode 100644
index 00000000..847626d9
--- /dev/null
+++ b/logview/src/logview-loglist.c
@@ -0,0 +1,500 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-loglist.c - displays a list of the opened logs
+ *
+ * Copyright (C) 2005 Vincent Noel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "logview-manager.h"
+#include "logview-log.h"
+#include "logview-utils.h"
+
+#include "logview-loglist.h"
+
+struct _LogviewLoglistPrivate {
+ GtkTreeStore *model;
+ LogviewManager *manager;
+ GtkTreePath *selection;
+ gboolean has_day_selection;
+};
+
+G_DEFINE_TYPE (LogviewLoglist, logview_loglist, GTK_TYPE_TREE_VIEW);
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_LOGLIST, LogviewLoglistPrivate))
+
+enum {
+ LOG_OBJECT = 0,
+ LOG_NAME,
+ LOG_WEIGHT,
+ LOG_WEIGHT_SET,
+ LOG_DAY
+};
+
+enum {
+ DAY_SELECTED,
+ DAY_CLEARED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+save_day_selection (LogviewLoglist *loglist, GtkTreeIter *iter)
+{
+ if (loglist->priv->selection) {
+ gtk_tree_path_free (loglist->priv->selection);
+ }
+
+ loglist->priv->selection = gtk_tree_model_get_path
+ (GTK_TREE_MODEL (loglist->priv->model), iter);
+}
+
+static void
+update_days_and_lines_for_log (LogviewLoglist *loglist,
+ GtkTreeIter *log, GSList *days)
+{
+ gboolean res;
+ GtkTreeIter iter, dummy;
+ GSList *l;
+ int i;
+ char date[200];
+ Day *day;
+
+ /* if we have some days, we can't remove all the items immediately, otherwise,
+ * if the row is expanded, it will be collapsed because there are no items,
+ * so we create a dummy entry, remove all the others and then remove the
+ * dummy one.
+ */
+ res = gtk_tree_model_iter_children (GTK_TREE_MODEL (loglist->priv->model),
+ &iter, log);
+ if (res) {
+ gtk_tree_store_insert_before (loglist->priv->model, &dummy, log, &iter);
+ gtk_tree_store_set (loglist->priv->model, &dummy,
+ LOG_NAME, "", -1);
+ do {
+ gtk_tree_store_remove (loglist->priv->model, &iter);
+ } while (gtk_tree_store_iter_is_valid (loglist->priv->model, &iter));
+ }
+
+ for (i = 1, l = days; l; l = l->next) {
+ /* now insert all the days */
+ day = l->data;
+
+ g_date_strftime (date, 200, "%A, %e %b", day->date);
+
+ gtk_tree_store_insert (GTK_TREE_STORE (loglist->priv->model),
+ &iter, log, i);
+ gtk_tree_store_set (GTK_TREE_STORE (loglist->priv->model),
+ &iter, LOG_NAME, date, LOG_DAY, day, -1);
+ i++;
+ }
+
+ if (res) {
+ gtk_tree_store_remove (loglist->priv->model, &dummy);
+ }
+}
+
+static GtkTreeIter *
+logview_loglist_find_log (LogviewLoglist *list, LogviewLog *log)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkTreeIter *retval = NULL;
+ LogviewLog *current;
+
+ model = GTK_TREE_MODEL (list->priv->model);
+
+ if (!gtk_tree_model_get_iter_first (model, &iter)) {
+ return NULL;
+ }
+
+ do {
+ gtk_tree_model_get (model, &iter, LOG_OBJECT, &current, -1);
+ if (current == log) {
+ retval = gtk_tree_iter_copy (&iter);
+ }
+ if (current)
+ g_object_unref (current);
+ } while (gtk_tree_model_iter_next (model, &iter) != FALSE && retval == NULL);
+
+ return retval;
+}
+
+static void
+log_changed_cb (LogviewLog *log,
+ gpointer user_data)
+{
+ LogviewLoglist *list = user_data;
+ LogviewLog *active;
+ GtkTreeIter *iter;
+
+ active = logview_manager_get_active_log (list->priv->manager);
+
+ if (log == active) {
+ g_object_unref (active);
+ return;
+ }
+
+ iter = logview_loglist_find_log (list, log);
+
+ if (!iter) {
+ return;
+ }
+
+ /* make the log bold in the list */
+ gtk_tree_store_set (list->priv->model, iter,
+ LOG_WEIGHT, PANGO_WEIGHT_BOLD,
+ LOG_WEIGHT_SET, TRUE, -1);
+
+ gtk_tree_iter_free (iter);
+}
+
+
+static void
+tree_selection_changed_cb (GtkTreeSelection *selection,
+ gpointer user_data)
+{
+ LogviewLoglist *list = user_data;
+ GtkTreeModel *model;
+ GtkTreeIter iter, parent;
+ LogviewLog *log;
+ gboolean is_bold, is_active;
+ Day *day;
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return;
+ }
+
+ gtk_tree_model_get (model, &iter, LOG_OBJECT, &log,
+ LOG_WEIGHT_SET, &is_bold,
+ LOG_DAY, &day, -1);
+ if (log) {
+ is_active = logview_manager_log_is_active (list->priv->manager, log);
+
+ if (is_active && list->priv->has_day_selection) {
+ list->priv->has_day_selection = FALSE;
+ g_signal_emit (list, signals[DAY_CLEARED], 0, NULL);
+ } else if (!is_active) {
+ logview_manager_set_active_log (list->priv->manager, log);
+ }
+ } else if (day) {
+ list->priv->has_day_selection = TRUE;
+ gtk_tree_model_iter_parent (model, &parent, &iter);
+ gtk_tree_model_get (model, &parent, LOG_OBJECT, &log, -1);
+
+ if (!logview_manager_log_is_active (list->priv->manager, log)) {
+ save_day_selection (list, &iter);
+ logview_manager_set_active_log (list->priv->manager, log);
+ } else {
+ g_signal_emit (list, signals[DAY_SELECTED], 0, day, NULL);
+ }
+ }
+
+ if (is_bold) {
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ LOG_WEIGHT_SET, FALSE, -1);
+ }
+
+ if (log) {
+ g_object_unref (log);
+ }
+}
+
+static void
+manager_active_changed_cb (LogviewManager *manager,
+ LogviewLog *log,
+ LogviewLog *old_log,
+ gpointer user_data)
+{
+ LogviewLoglist *list = user_data;
+ GtkTreeIter * iter, sel_iter;
+ GtkTreeSelection * selection;
+
+ if (list->priv->selection &&
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (list->priv->model),
+ &sel_iter, list->priv->selection))
+ {
+ Day *day;
+
+ iter = gtk_tree_iter_copy (&sel_iter);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (list->priv->model), iter,
+ LOG_DAY, &day, -1);
+
+ if (day) {
+ g_signal_emit (list, signals[DAY_SELECTED], 0, day, NULL);
+ }
+
+ gtk_tree_path_free (list->priv->selection);
+ list->priv->selection = NULL;
+ } else {
+ iter = logview_loglist_find_log (list, log);
+ }
+
+ if (!iter) {
+ return;
+ }
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ g_signal_handlers_block_by_func (selection, tree_selection_changed_cb, list);
+
+ gtk_tree_selection_select_iter (selection, iter);
+
+ g_signal_handlers_unblock_by_func (selection, tree_selection_changed_cb, list);
+ gtk_tree_iter_free (iter);
+}
+
+static void
+manager_log_closed_cb (LogviewManager *manager,
+ LogviewLog *log,
+ gpointer user_data)
+{
+ LogviewLoglist *list = user_data;
+ GtkTreeIter *iter;
+ gboolean res;
+
+ iter = logview_loglist_find_log (list, log);
+
+ if (!iter) {
+ return;
+ }
+
+ g_signal_handlers_disconnect_by_func (log, log_changed_cb, list);
+
+ res = gtk_tree_store_remove (list->priv->model, iter);
+ if (res) {
+ GtkTreeSelection *selection;
+
+ /* iter now points to the next valid row */
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ gtk_tree_selection_select_iter (selection, iter);
+ } else {
+ /* FIXME: what shall we do here? */
+ }
+
+ gtk_tree_iter_free (iter);
+}
+
+static void
+manager_log_added_cb (LogviewManager *manager,
+ LogviewLog *log,
+ gpointer user_data)
+{
+ LogviewLoglist *list = user_data;
+ GtkTreeIter iter, child;
+
+ gtk_tree_store_append (list->priv->model, &iter, NULL);
+ gtk_tree_store_set (list->priv->model, &iter,
+ LOG_OBJECT, g_object_ref (log),
+ LOG_NAME, logview_log_get_display_name (log), -1);
+ if (logview_log_get_has_days (log)) {
+ gtk_tree_store_insert (list->priv->model,
+ &child, &iter, 0);
+ gtk_tree_store_set (list->priv->model, &child,
+ LOG_NAME, _("Loading..."), -1);
+ }
+
+ g_signal_connect (log, "log-changed",
+ G_CALLBACK (log_changed_cb), list);
+}
+
+static void
+row_expanded_cb (GtkTreeView *view,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ LogviewLoglist *list = user_data;
+ LogviewLog *log;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (list->priv->model), iter,
+ LOG_OBJECT, &log, -1);
+ if (!logview_manager_log_is_active (list->priv->manager, log)) {
+ logview_manager_set_active_log (list->priv->manager, log);
+ }
+
+ g_object_unref (log);
+}
+
+static int
+loglist_sort_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data)
+{
+ char *name_a, *name_b;
+ Day *day_a, *day_b;
+ int retval = 0;
+
+ switch (gtk_tree_store_iter_depth (GTK_TREE_STORE (model), a)) {
+ case 0:
+ gtk_tree_model_get (model, a, LOG_NAME, &name_a, -1);
+ gtk_tree_model_get (model, b, LOG_NAME, &name_b, -1);
+ retval = g_utf8_collate (name_a, name_b);
+ g_free (name_a);
+ g_free (name_b);
+
+ break;
+ case 1:
+ gtk_tree_model_get (model, a, LOG_DAY, &day_a, -1);
+ gtk_tree_model_get (model, b, LOG_DAY, &day_b, -1);
+ if (day_a && day_b) {
+ retval = days_compare (day_a, day_b);
+ } else {
+ retval = 0;
+ }
+
+ break;
+ default:
+ g_assert_not_reached ();
+
+ break;
+ }
+
+ return retval;
+}
+
+static void
+do_finalize (GObject *obj)
+{
+ LogviewLoglist *list = LOGVIEW_LOGLIST (obj);
+
+ g_object_unref (list->priv->model);
+ list->priv->model = NULL;
+
+ if (list->priv->selection) {
+ gtk_tree_path_free (list->priv->selection);
+ list->priv->selection = NULL;
+ }
+
+ G_OBJECT_CLASS (logview_loglist_parent_class)->finalize (obj);
+}
+
+static void
+logview_loglist_init (LogviewLoglist *list)
+{
+ GtkTreeStore *model;
+ GtkTreeViewColumn *column;
+ GtkTreeSelection *selection;
+ GtkCellRenderer *cell;
+
+ list->priv = GET_PRIVATE (list);
+ list->priv->has_day_selection = FALSE;
+ list->priv->selection = NULL;
+
+ model = gtk_tree_store_new (5, LOGVIEW_TYPE_LOG, G_TYPE_STRING, G_TYPE_INT,
+ G_TYPE_BOOLEAN, G_TYPE_POINTER);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (list), GTK_TREE_MODEL (model));
+ list->priv->model = model;
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (tree_selection_changed_cb), list);
+
+ cell = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "text", LOG_NAME,
+ "weight-set", LOG_WEIGHT_SET,
+ "weight", LOG_WEIGHT,
+ NULL);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list->priv->model), LOG_NAME, GTK_SORT_ASCENDING);
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (list->priv->model),
+ LOG_NAME,
+ (GtkTreeIterCompareFunc) loglist_sort_func,
+ list, NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (list), -1);
+
+ list->priv->manager = logview_manager_get ();
+
+ g_signal_connect (list->priv->manager, "log-added",
+ G_CALLBACK (manager_log_added_cb), list);
+ g_signal_connect (list->priv->manager, "log-closed",
+ G_CALLBACK (manager_log_closed_cb), list);
+ g_signal_connect_after (list->priv->manager, "active-changed",
+ G_CALLBACK (manager_active_changed_cb), list);
+ g_signal_connect (list, "row-expanded",
+ G_CALLBACK (row_expanded_cb), list);
+}
+
+static void
+logview_loglist_class_init (LogviewLoglistClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ oclass->finalize = do_finalize;
+
+ signals[DAY_SELECTED] = g_signal_new ("day-selected",
+ G_OBJECT_CLASS_TYPE (oclass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewLoglistClass, day_selected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ signals[DAY_CLEARED] = g_signal_new ("day-cleared",
+ G_OBJECT_CLASS_TYPE (oclass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewLoglistClass, day_cleared),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (klass, sizeof (LogviewLoglistPrivate));
+}
+
+/* public methods */
+
+GtkWidget *
+logview_loglist_new (void)
+{
+ GtkWidget *widget;
+ widget = g_object_new (LOGVIEW_TYPE_LOGLIST, NULL);
+ return widget;
+}
+
+void
+logview_loglist_update_lines (LogviewLoglist *loglist, LogviewLog *log)
+{
+ GSList *days;
+ GtkTreeIter *parent;
+
+ g_assert (LOGVIEW_IS_LOGLIST (loglist));
+ g_assert (LOGVIEW_IS_LOG (log));
+
+ parent = logview_loglist_find_log (loglist, log);
+
+ if (parent) {
+ days = logview_log_get_days_for_cached_lines (log);
+ update_days_and_lines_for_log (loglist, parent, days);
+ gtk_tree_iter_free (parent);
+ }
+}
+
diff --git a/logview/src/logview-loglist.h b/logview/src/logview-loglist.h
new file mode 100644
index 00000000..ebfbf04f
--- /dev/null
+++ b/logview/src/logview-loglist.h
@@ -0,0 +1,68 @@
+/* logview-loglist.h - displays a list of the opened logs
+ *
+ * Copyright (C) 2005 Vincent Noel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __LOGVIEW_LOGLIST_H__
+#define __LOGVIEW_LOGLIST_H__
+
+#define LOGVIEW_TYPE_LOGLIST logview_loglist_get_type()
+#define LOGVIEW_LOGLIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), LOGVIEW_TYPE_LOGLIST, LogviewLoglist))
+#define LOGVIEW_LOGLIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), LOGVIEW_TYPE_LOGLIST, LogviewLogListClass))
+#define LOGVIEW_IS_LOGLIST(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LOGVIEW_TYPE_LOGLIST))
+#define LOGVIEW_IS_LOGLIST_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), LOGVIEW_TYPE_LOGLIST))
+#define LOGVIEW_LOGLIST_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), LOGVIEW_TYPE_LOGLIST, LogviewLoglistClass))
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+
+#include "logview-log.h"
+#include "logview-utils.h"
+
+typedef struct _LogviewLoglist LogviewLoglist;
+typedef struct _LogviewLoglistClass LogviewLoglistClass;
+typedef struct _LogviewLoglistPrivate LogviewLoglistPrivate;
+
+struct _LogviewLoglist {
+ GtkTreeView parent_instance;
+ LogviewLoglistPrivate *priv;
+};
+
+struct _LogviewLoglistClass {
+ GtkTreeViewClass parent_class;
+
+ void (* day_selected) (LogviewLoglist *loglist,
+ Day *day);
+ void (* day_cleared) (LogviewLoglist *loglist);
+};
+
+GType logview_loglist_get_type (void);
+
+/* public methods */
+GtkWidget * logview_loglist_new (void);
+void logview_loglist_update_lines (LogviewLoglist *loglist,
+ LogviewLog *log);
+GDate * logview_loglist_get_date_selection (LogviewLoglist *loglist);
+void logview_loglist_clear_date (LogviewLoglist *loglist);
+
+#endif /* __LOGVIEW_LOGLIST_H__ */
diff --git a/logview/src/logview-main.c b/logview/src/logview-main.c
new file mode 100644
index 00000000..74dcb44b
--- /dev/null
+++ b/logview/src/logview-main.c
@@ -0,0 +1,120 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-main.c - logview main
+ *
+ * Copyright (C) 2005 Vincent Noel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+#include <glib.h>
+
+#include <gtk/gtk.h>
+
+#include "logview-app.h"
+
+/* log files specified on the command line */
+static char **log_files = NULL;
+
+static void
+app_quit_cb (LogviewApp *app,
+ gpointer user_data)
+{
+ gtk_main_quit ();
+}
+
+static void
+logview_show_version_and_quit (void)
+{
+ g_print ("%s - Version %s\n"
+ "Copyright (C) 2004-2008 Vincent Noel, Cosimo Cecchi and others.\n",
+ g_get_application_name (),
+ VERSION);
+
+ exit (0);
+}
+
+static GOptionContext *
+create_option_context (void)
+{
+ GOptionContext *context;
+
+ const GOptionEntry entries[] = {
+ { "version", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
+ logview_show_version_and_quit, N_("Show the application's version"), NULL },
+ { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &log_files,
+ NULL, N_("[LOGFILE...]") },
+ { NULL },
+ };
+
+ context = g_option_context_new (_(" - Browse and monitor logs"));
+ g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
+ g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
+ g_option_context_set_ignore_unknown_options (context, TRUE);
+ g_option_context_add_group (context, gtk_get_option_group (TRUE));
+
+ return context;
+}
+
+int
+main (int argc, char *argv[])
+{
+ GError *error = NULL;
+ GOptionContext *context;
+ LogviewApp *app;
+
+ bindtextdomain (GETTEXT_PACKAGE, MATELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ context = create_option_context ();
+
+ g_option_context_parse (context, &argc, &argv, &error);
+
+ if (error) {
+ g_critical ("Unable to parse arguments: %s", error->message);
+ g_error_free (error);
+ g_option_context_free (context);
+
+ exit (1);
+ }
+
+ g_option_context_free (context);
+ g_set_application_name (_("Log Viewer"));
+
+ app = logview_app_get ();
+
+ if (!app) {
+ g_critical ("Unable to create the user interface.");
+
+ exit (1);
+ } else {
+ g_signal_connect (app, "app-quit",
+ G_CALLBACK (app_quit_cb), NULL);
+ }
+
+ logview_app_initialize (app, log_files);
+
+ gtk_main ();
+
+ g_object_unref (app);
+
+ return EXIT_SUCCESS;
+}
diff --git a/logview/src/logview-manager.c b/logview/src/logview-manager.c
new file mode 100644
index 00000000..54b5320b
--- /dev/null
+++ b/logview/src/logview-manager.c
@@ -0,0 +1,432 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-manager.c - manager for the opened log objects
+ *
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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., 551 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+/* logview-manager.c */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "logview-manager.h"
+
+#include <glib/gi18n.h>
+
+#include "logview-prefs.h"
+#include "logview-marshal.h"
+#include "logview-app.h"
+
+enum {
+ LOG_ADDED,
+ LOG_CLOSED,
+ ACTIVE_CHANGED,
+ LAST_SIGNAL
+};
+
+typedef struct {
+ LogviewManager *manager;
+ gboolean set_active;
+ gboolean is_multiple;
+ GFile *file;
+} CreateCBData;
+
+typedef struct {
+ int total;
+ int current;
+ GPtrArray *errors;
+} MultipleCreation;
+
+struct _LogviewManagerPrivate {
+ GHashTable *logs;
+ LogviewLog *active_log;
+};
+
+static LogviewManager *singleton = NULL;
+static MultipleCreation *op = NULL;
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (LogviewManager, logview_manager, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_MANAGER, LogviewManagerPrivate))
+
+static void
+logview_manager_finalize (GObject *object)
+{
+ LogviewManager *manager;
+
+ manager = LOGVIEW_MANAGER (object);
+
+ if (manager->priv->active_log) {
+ g_object_unref (manager->priv->active_log);
+ }
+
+ g_hash_table_destroy (manager->priv->logs);
+
+ G_OBJECT_CLASS (logview_manager_parent_class)->finalize (object);
+}
+
+static void
+logview_manager_class_init (LogviewManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = logview_manager_finalize;
+
+ signals[LOG_ADDED] = g_signal_new ("log-added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewManagerClass, log_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ LOGVIEW_TYPE_LOG);
+
+ signals[LOG_CLOSED] = g_signal_new ("log-closed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewManagerClass, log_closed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ LOGVIEW_TYPE_LOG);
+
+ signals[ACTIVE_CHANGED] = g_signal_new ("active-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewManagerClass, active_changed),
+ NULL, NULL,
+ logview_marshal_VOID__OBJECT_OBJECT,
+ G_TYPE_NONE, 2,
+ LOGVIEW_TYPE_LOG,
+ LOGVIEW_TYPE_LOG);
+
+ g_type_class_add_private (klass, sizeof (LogviewManagerPrivate));
+}
+
+static void
+logview_manager_init (LogviewManager *self)
+{
+ LogviewManagerPrivate *priv = self->priv = GET_PRIVATE (self);
+
+ priv->active_log = NULL;
+ priv->logs = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free, (GDestroyNotify) g_object_unref);
+}
+
+static MultipleCreation *
+multiple_creation_op_new (int total)
+{
+ MultipleCreation *retval;
+
+ retval = g_slice_new0 (MultipleCreation);
+ retval->total = total;
+ retval->current = 0;
+ retval->errors = g_ptr_array_new ();
+
+ return retval;
+}
+
+static void
+multiple_creation_op_free (MultipleCreation *mc)
+{
+ g_ptr_array_foreach (mc->errors, (GFunc) g_strfreev, NULL);
+ g_ptr_array_free (mc->errors, TRUE);
+
+ g_slice_free (MultipleCreation, mc);
+}
+
+static void
+create_log_cb (LogviewLog *log,
+ GError *error,
+ gpointer user_data)
+{
+ CreateCBData *data = user_data;
+
+ if (log) {
+ char *log_uri;
+ LogviewPrefs *prefs;
+ GFile *file;
+
+ log_uri = logview_log_get_uri (log);
+
+ /* creation went well, store the log and notify */
+ g_hash_table_insert (data->manager->priv->logs,
+ log_uri, log);
+
+ prefs = logview_prefs_get ();
+ file = logview_log_get_gfile (log);
+ logview_prefs_store_log (prefs, file);
+
+ g_object_unref (file);
+
+ g_signal_emit (data->manager, signals[LOG_ADDED], 0, log, NULL);
+
+ if (data->set_active) {
+ logview_manager_set_active_log (data->manager, log);
+ }
+ } else {
+ char *path;
+
+ /* notify the error */
+ path = g_file_get_path (data->file);
+
+ if (!data->is_multiple) {
+ logview_app_add_error (logview_app_get (),
+ path, error->message);
+ } else {
+ char **error_arr = g_new0 (char *, 3);
+
+ error_arr[0] = g_strdup (path);
+ error_arr[1] = g_strdup (error->message);
+ error_arr[2] = NULL;
+
+ g_ptr_array_add (op->errors, error_arr);
+ }
+
+ g_free (path);
+ }
+
+ if (data->is_multiple) {
+ op->current++;
+
+ if (op->total == op->current) {
+ logview_app_add_errors (logview_app_get (), op->errors);
+ multiple_creation_op_free (op);
+ op = NULL;
+ }
+ }
+
+ g_object_unref (data->file);
+ g_slice_free (CreateCBData, data);
+}
+
+static void
+add_log_from_gfile_internal (LogviewManager *manager,
+ GFile *file,
+ gboolean set_active,
+ gboolean is_multiple)
+{
+ char *file_uri;
+ LogviewLog *log;
+ CreateCBData *data;
+
+ file_uri = g_file_get_uri (file);
+
+ if (set_active == FALSE) {
+ /* if it's the first log being added, set it as active anyway */
+ set_active = (manager->priv->logs == NULL);
+ }
+
+ if ((log = g_hash_table_lookup (manager->priv->logs, file_uri)) != NULL) {
+ /* log already exists, don't load it */
+ if (set_active) {
+ logview_manager_set_active_log (manager, log);
+ }
+ } else {
+ data = g_slice_new0 (CreateCBData);
+ data->manager = manager;
+ data->set_active = set_active;
+ data->is_multiple = is_multiple;
+ data->file = g_object_ref (file);
+
+ logview_log_create_from_gfile (file, create_log_cb, data);
+ }
+
+ g_free (file_uri);
+}
+
+static void
+logview_manager_add_log_from_name (LogviewManager *manager,
+ const char *filename, gboolean set_active,
+ gboolean is_multiple)
+{
+ GFile *file;
+
+ file = g_file_new_for_path (filename);
+
+ add_log_from_gfile_internal (manager, file, set_active, is_multiple);
+
+ g_object_unref (file);
+}
+
+/* public methods */
+
+LogviewManager*
+logview_manager_get (void)
+{
+ if (!singleton) {
+ singleton = g_object_new (LOGVIEW_TYPE_MANAGER, NULL);
+ }
+
+ return singleton;
+}
+
+void
+logview_manager_set_active_log (LogviewManager *manager,
+ LogviewLog *log)
+{
+ LogviewLog *old_log = NULL;
+ GFile *file;
+ char *path;
+
+ g_assert (LOGVIEW_IS_MANAGER (manager));
+
+ if (manager->priv->active_log) {
+ old_log = manager->priv->active_log;
+ }
+
+ manager->priv->active_log = g_object_ref (log);
+
+ file = logview_log_get_gfile (log);
+ path = g_file_get_path (file);
+ logview_prefs_store_active_logfile (logview_prefs_get (), path);
+ g_free (path);
+ g_object_unref (file);
+
+ g_signal_emit (manager, signals[ACTIVE_CHANGED], 0, log, old_log, NULL);
+
+ if (old_log) {
+ g_object_unref (old_log);
+ }
+}
+
+LogviewLog *
+logview_manager_get_active_log (LogviewManager *manager)
+{
+ g_assert (LOGVIEW_IS_MANAGER (manager));
+
+ return (manager->priv->active_log != NULL) ?
+ g_object_ref (manager->priv->active_log) :
+ NULL;
+}
+
+void
+logview_manager_add_log_from_gfile (LogviewManager *manager,
+ GFile *file,
+ gboolean set_active)
+{
+ g_assert (LOGVIEW_IS_MANAGER (manager));
+
+ add_log_from_gfile_internal (manager, file, set_active, FALSE);
+}
+
+void
+logview_manager_add_logs_from_name_list (LogviewManager *manager,
+ GSList *names,
+ const char *active)
+{
+ GSList *l;
+
+ g_assert (LOGVIEW_IS_MANAGER (manager));
+ g_assert (op == NULL);
+
+ op = multiple_creation_op_new (g_slist_length (names));
+
+ for (l = names; l; l = l->next) {
+ logview_manager_add_log_from_name (manager, l->data,
+ (g_ascii_strcasecmp (active, l->data) == 0),
+ TRUE);
+ }
+}
+
+void
+logview_manager_add_logs_from_names (LogviewManager *manager,
+ char ** names,
+ const gchar *active)
+{
+ int i;
+ gboolean set_active;
+
+ g_assert (LOGVIEW_IS_MANAGER (manager));
+ g_assert (op == NULL);
+
+ op = multiple_creation_op_new (g_strv_length (names));
+
+ for (i = 0; names[i]; i++) {
+ set_active = (active != NULL) && (!g_ascii_strcasecmp (active, names[i]));
+ logview_manager_add_log_from_name (manager, names[i], set_active,
+ TRUE);
+ }
+}
+
+int
+logview_manager_get_log_count (LogviewManager *manager)
+{
+ g_assert (LOGVIEW_IS_MANAGER (manager));
+
+ return g_hash_table_size (manager->priv->logs);
+}
+
+LogviewLog *
+logview_manager_get_if_loaded (LogviewManager *manager, char *uri)
+{
+ LogviewLog *log;
+
+ g_assert (LOGVIEW_IS_MANAGER (manager));
+
+ log = g_hash_table_lookup (manager->priv->logs, uri);
+
+ if (log != NULL) {
+ return g_object_ref (log);
+ }
+
+ return NULL;
+}
+
+void
+logview_manager_close_active_log (LogviewManager *manager)
+{
+ LogviewLog *active_log;
+ char *log_uri;
+ GFile *file;
+
+ g_assert (LOGVIEW_IS_MANAGER (manager));
+
+ active_log = manager->priv->active_log;
+ if (active_log == NULL) {
+ return;
+ }
+
+ log_uri = logview_log_get_uri (active_log);
+ file = logview_log_get_gfile (active_log);
+
+ g_signal_emit (manager, signals[LOG_CLOSED], 0, active_log, NULL);
+
+ logview_prefs_remove_stored_log (logview_prefs_get (), file);
+
+ g_object_unref (file);
+
+ /* drop the hash table ref */
+ g_hash_table_remove (manager->priv->logs, log_uri);
+
+ g_free (log_uri);
+
+ /* someone else will take care of setting the next active log to us */
+}
+
+gboolean
+logview_manager_log_is_active (LogviewManager *manager,
+ LogviewLog *log)
+{
+ g_assert (LOGVIEW_IS_MANAGER (manager));
+
+ return (manager->priv->active_log == log);
+}
diff --git a/logview/src/logview-manager.h b/logview/src/logview-manager.h
new file mode 100644
index 00000000..b9444323
--- /dev/null
+++ b/logview/src/logview-manager.h
@@ -0,0 +1,90 @@
+/* logview-manager.h - manager for the opened log objects
+ *
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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., 551 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+/* logview-manager.h */
+
+#ifndef __LOGVIEW_MANAGER_H__
+#define __LOGVIEW_MANAGER_H__
+
+#include <glib-object.h>
+
+#include "logview-log.h"
+
+G_BEGIN_DECLS
+
+#define LOGVIEW_TYPE_MANAGER logview_manager_get_type()
+#define LOGVIEW_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), LOGVIEW_TYPE_MANAGER, LogviewManager))
+#define LOGVIEW_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), LOGVIEW_TYPE_MANAGER, LogviewManagerClass))
+#define LOGVIEW_IS_MANAGER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LOGVIEW_TYPE_MANAGER))
+#define LOGVIEW_IS_MANAGER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), LOGVIEW_TYPE_MANAGER))
+#define LOGVIEW_MANAGER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), LOGVIEW_TYPE_MANAGER, LogviewManagerClass))
+
+typedef struct _LogviewManager LogviewManager;
+typedef struct _LogviewManagerClass LogviewManagerClass;
+typedef struct _LogviewManagerPrivate LogviewManagerPrivate;
+
+struct _LogviewManager {
+ GObject parent;
+ LogviewManagerPrivate *priv;
+};
+
+struct _LogviewManagerClass {
+ GObjectClass parent_class;
+
+ void (* log_added) (LogviewManager *manager,
+ LogviewLog *log);
+ void (* log_closed) (LogviewManager *manager,
+ LogviewLog *log);
+ void (* active_changed) (LogviewManager *manager,
+ LogviewLog *log,
+ LogviewLog *old_log);
+};
+
+GType logview_manager_get_type (void);
+
+/* public methods */
+LogviewManager* logview_manager_get (void);
+void logview_manager_add_logs_from_name_list (LogviewManager *manager,
+ GSList *names,
+ const char *active);
+void logview_manager_add_log_from_gfile (LogviewManager *manager,
+ GFile *file,
+ gboolean set_active);
+void logview_manager_add_logs_from_names (LogviewManager *manager,
+ char ** names,
+ const gchar *active);
+void logview_manager_set_active_log (LogviewManager *manager,
+ LogviewLog *log);
+LogviewLog * logview_manager_get_active_log (LogviewManager *manager);
+int logview_manager_get_log_count (LogviewManager *manager);
+LogviewLog * logview_manager_get_if_loaded (LogviewManager *manager,
+ char *filename);
+gboolean logview_manager_log_is_active (LogviewManager *manager,
+ LogviewLog *log);
+void logview_manager_close_active_log (LogviewManager *manager);
+
+G_END_DECLS
+
+#endif /* __LOGVIEW_MANAGER_H__ */
diff --git a/logview/src/logview-marshal.list b/logview/src/logview-marshal.list
new file mode 100644
index 00000000..38076d6c
--- /dev/null
+++ b/logview/src/logview-marshal.list
@@ -0,0 +1 @@
+VOID:OBJECT,OBJECT
diff --git a/logview/src/logview-prefs.c b/logview/src/logview-prefs.c
new file mode 100644
index 00000000..0a2e0093
--- /dev/null
+++ b/logview/src/logview-prefs.c
@@ -0,0 +1,546 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-prefs.c - logview user preferences handling
+ *
+ * Copyright (C) 1998 Cesar Miquel <[email protected]>
+ * Copyright (C) 2004 Vincent Noel
+ * Copyright (C) 2006 Emmanuele Bassi
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/stat.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#include "logview-prefs.h"
+
+#define LOGVIEW_DEFAULT_HEIGHT 400
+#define LOGVIEW_DEFAULT_WIDTH 600
+
+/* logview settings */
+#define LOGVIEW_SCHEMA "org.mate.system-log"
+#define PREF_WIDTH "width"
+#define PREF_HEIGHT "height"
+#define PREF_LOGFILE "logfile"
+#define PREF_LOGFILES "logfiles"
+#define PREF_FONTSIZE "fontsize"
+#define PREF_FILTERS "filters"
+
+/* desktop-wide settings */
+#define MATE_MONOSPACE_FONT_NAME "monospace-font-name"
+#define MATE_MENUS_HAVE_TEAROFF "menus-have-tearoff"
+
+static LogviewPrefs *singleton = NULL;
+
+enum {
+ SYSTEM_FONT_CHANGED,
+ HAVE_TEAROFF_CHANGED,
+ LAST_SIGNAL
+};
+
+enum {
+ FILTER_NAME,
+ FILTER_INVISIBLE,
+ FILTER_FOREGROUND,
+ FILTER_BACKGROUND,
+ FILTER_REGEX,
+ MAX_TOKENS
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_PREFS, LogviewPrefsPrivate))
+
+struct _LogviewPrefsPrivate {
+ GSettings *logview_prefs;
+ GSettings *interface_prefs;
+
+ GHashTable *filters;
+};
+
+G_DEFINE_TYPE (LogviewPrefs, logview_prefs, G_TYPE_OBJECT);
+
+static void
+do_finalize (GObject *obj)
+{
+ LogviewPrefs *prefs = LOGVIEW_PREFS (obj);
+
+ g_hash_table_destroy (prefs->priv->filters);
+
+ g_object_unref (prefs->priv->logview_prefs);
+ g_object_unref (prefs->priv->interface_prefs);
+
+ G_OBJECT_CLASS (logview_prefs_parent_class)->finalize (obj);
+}
+
+static void
+logview_prefs_class_init (LogviewPrefsClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = do_finalize;
+
+ signals[SYSTEM_FONT_CHANGED] = g_signal_new ("system-font-changed",
+ G_OBJECT_CLASS_TYPE (oclass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewPrefsClass, system_font_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+ signals[HAVE_TEAROFF_CHANGED] = g_signal_new ("have-tearoff-changed",
+ G_OBJECT_CLASS_TYPE (oclass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LogviewPrefsClass, have_tearoff_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1,
+ G_TYPE_BOOLEAN);
+
+ g_type_class_add_private (klass, sizeof (LogviewPrefsPrivate));
+}
+
+static void
+have_tearoff_changed_cb (GSettings *settings,
+ gchar *key,
+ gpointer data)
+{
+ LogviewPrefs *prefs = data;
+ gboolean add_tearoffs;
+
+ add_tearoffs = g_settings_get_boolean (settings, key);
+ g_signal_emit (prefs, signals[HAVE_TEAROFF_CHANGED], 0, add_tearoffs, NULL);
+}
+
+static void
+monospace_font_changed_cb (GSettings *settings,
+ gchar *key,
+ gpointer data)
+{
+ LogviewPrefs *prefs = data;
+ gchar *monospace_font_name;
+
+ monospace_font_name = g_settings_get_string (settings, key);
+ g_signal_emit (prefs, signals[SYSTEM_FONT_CHANGED], 0, monospace_font_name, NULL);
+
+ g_free (monospace_font_name);
+}
+
+#define DELIMITER ":"
+
+static void
+load_filters (LogviewPrefs *prefs)
+{
+ gchar **filters;
+ gchar **tokens;
+ const gchar *str;
+ LogviewFilter *filter;
+ GtkTextTag *tag;
+ GdkColor color;
+ gint idx;
+
+ filters = g_settings_get_strv (prefs->priv->logview_prefs,
+ PREF_FILTERS);
+
+ prefs->priv->filters = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ for (idx = 0; filters[idx] != NULL; idx++) {
+ str = filters[idx];
+ tokens = g_strsplit (str, DELIMITER, MAX_TOKENS);
+ filter = logview_filter_new (tokens[FILTER_NAME], tokens[FILTER_REGEX]);
+ tag = gtk_text_tag_new (tokens[FILTER_NAME]);
+
+ g_object_set (tag, "invisible",
+ g_str_equal (tokens[FILTER_INVISIBLE], "1"), NULL);
+
+ if (strlen (tokens[FILTER_FOREGROUND])) {
+ gdk_color_parse (tokens[FILTER_FOREGROUND], &color);
+ g_object_set (tag, "foreground-gdk", &color,
+ "foreground-set", TRUE, NULL);
+ }
+
+ if (strlen (tokens[FILTER_BACKGROUND])) {
+ gdk_color_parse (tokens[FILTER_BACKGROUND], &color);
+ g_object_set (tag, "paragraph-background-gdk", &color,
+ "paragraph-background-set", TRUE, NULL);
+ }
+
+ g_object_set (filter, "texttag", tag, NULL);
+ g_hash_table_insert (prefs->priv->filters,
+ g_strdup(tokens[FILTER_NAME]),
+ filter);
+
+ g_object_ref (filter);
+ g_object_unref (tag);
+ g_strfreev (tokens);
+ }
+
+ g_strfreev (filters);
+}
+
+static void
+save_filter_foreach_func (gpointer key, gpointer value, gpointer user_data)
+{
+ GPtrArray *filters;
+ const gchar *name;
+ LogviewFilter *filter;
+ GdkColor *foreground;
+ gboolean foreground_set;
+ GdkColor *background;
+ gboolean background_set;
+ gchar *regex, *color;
+ gboolean invisible;
+ GtkTextTag *tag;
+ GString *prefs_string;
+
+ filters = user_data;
+ filter = LOGVIEW_FILTER (value);
+ name = key;
+ color = NULL;
+
+ prefs_string = g_string_new (name);
+ g_string_append (prefs_string, DELIMITER);
+
+ g_object_get (filter,
+ "regex", &regex,
+ "texttag", &tag,
+ NULL);
+ g_object_get (tag,
+ "foreground-gdk", &foreground,
+ "paragraph-background-gdk", &background,
+ "foreground-set", &foreground_set,
+ "paragraph-background-set", &background_set,
+ "invisible", &invisible, NULL);
+
+ if (invisible) {
+ g_string_append (prefs_string, "1" DELIMITER);
+ } else {
+ g_string_append (prefs_string, "0" DELIMITER);
+ }
+
+ if (foreground_set) {
+ color = gdk_color_to_string (foreground);
+ g_string_append (prefs_string, color);
+ g_free (color);
+ }
+
+ if (foreground) {
+ gdk_color_free (foreground);
+ }
+
+ g_string_append (prefs_string, DELIMITER);
+
+ if (background_set) {
+ color = gdk_color_to_string (background);
+ g_string_append (prefs_string, color);
+ g_free (color);
+ }
+
+ if (background) {
+ gdk_color_free (background);
+ }
+
+ g_string_append (prefs_string, DELIMITER);
+ g_string_append (prefs_string, regex);
+
+ g_free (regex);
+ g_object_unref (tag);
+
+ g_ptr_array_add (filters, g_string_free (prefs_string, FALSE));
+}
+
+static void
+save_filters (LogviewPrefs *prefs)
+{
+ GPtrArray *filters;
+ gchar **filters_strv;
+
+ filters = g_ptr_array_new ();
+ g_hash_table_foreach (prefs->priv->filters,
+ save_filter_foreach_func,
+ filters);
+ g_ptr_array_add (filters, NULL);
+
+ filters_strv = (gchar **) g_ptr_array_free (filters, FALSE);
+ g_settings_set_strv (prefs->priv->logview_prefs,
+ PREF_FILTERS,
+ (const gchar **) filters_strv);
+
+ g_strfreev (filters_strv);
+}
+
+static void
+get_filters_foreach (gpointer key, gpointer value, gpointer user_data)
+{
+ GList **list;
+ list = user_data;
+ *list = g_list_append (*list, value);
+}
+
+static void
+logview_prefs_init (LogviewPrefs *self)
+{
+ LogviewPrefsPrivate *priv;
+
+ priv = self->priv = GET_PRIVATE (self);
+
+ priv->logview_prefs = g_settings_new (LOGVIEW_SCHEMA);
+ priv->interface_prefs = g_settings_new ("org.mate.interface");
+
+ g_signal_connect (priv->interface_prefs, "changed::" MATE_MONOSPACE_FONT_NAME,
+ G_CALLBACK (monospace_font_changed_cb), self);
+ g_signal_connect (priv->interface_prefs, "changed::" MATE_MENUS_HAVE_TEAROFF,
+ G_CALLBACK (have_tearoff_changed_cb), self);
+
+ load_filters (self);
+}
+
+/* public methods */
+
+LogviewPrefs *
+logview_prefs_get ()
+{
+ if (!singleton)
+ singleton = g_object_new (LOGVIEW_TYPE_PREFS, NULL);
+
+ return singleton;
+}
+
+void
+logview_prefs_store_window_size (LogviewPrefs *prefs,
+ int width, int height)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ g_settings_set_int (prefs->priv->logview_prefs,
+ PREF_WIDTH, width);
+ g_settings_set_int (prefs->priv->logview_prefs,
+ PREF_HEIGHT, height);
+}
+
+void
+logview_prefs_get_stored_window_size (LogviewPrefs *prefs,
+ int *width, int *height)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ *width = g_settings_get_int (prefs->priv->logview_prefs,
+ PREF_WIDTH);
+ *height = g_settings_get_int (prefs->priv->logview_prefs,
+ PREF_HEIGHT);
+
+ if ((*width == 0) ^ (*height == 0)) {
+ /* if one of the two failed, return default for both */
+ *width = LOGVIEW_DEFAULT_WIDTH;
+ *height = LOGVIEW_DEFAULT_HEIGHT;
+ }
+}
+
+char *
+logview_prefs_get_monospace_font_name (LogviewPrefs *prefs)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ return (g_settings_get_string (prefs->priv->interface_prefs, MATE_MONOSPACE_FONT_NAME));
+}
+
+gboolean
+logview_prefs_get_have_tearoff (LogviewPrefs *prefs)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ return (g_settings_get_boolean (prefs->priv->interface_prefs, MATE_MENUS_HAVE_TEAROFF));
+}
+
+/* the elements should be freed with g_free () */
+
+gchar **
+logview_prefs_get_stored_logfiles (LogviewPrefs *prefs)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ return g_settings_get_strv (prefs->priv->logview_prefs,
+ PREF_LOGFILES);
+}
+
+void
+logview_prefs_store_log (LogviewPrefs *prefs, GFile *file)
+{
+ gchar **stored_logs;
+ GFile *stored;
+ gboolean found = FALSE;
+ gint idx, old_size;
+
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+ g_assert (G_IS_FILE (file));
+
+ stored_logs = logview_prefs_get_stored_logfiles (prefs);
+
+ for (idx = 0; stored_logs[idx] != NULL; idx++) {
+ stored = g_file_parse_name (stored_logs[idx]);
+ if (g_file_equal (file, stored)) {
+ found = TRUE;
+ }
+
+ g_object_unref (stored);
+
+ if (found) {
+ break;
+ }
+ }
+
+ if (!found) {
+ old_size = g_strv_length (stored_logs);
+ stored_logs = g_realloc (stored_logs, (old_size + 2) * sizeof (gchar *));
+ stored_logs[old_size] = g_file_get_parse_name (file);
+ stored_logs[old_size + 1] = NULL;
+
+ g_settings_set_strv (prefs->priv->logview_prefs,
+ PREF_LOGFILES,
+ (const gchar **) stored_logs);
+ }
+
+ g_strfreev (stored_logs);
+}
+
+void
+logview_prefs_remove_stored_log (LogviewPrefs *prefs, GFile *target)
+{
+ gchar **stored_logs;
+ GFile *stored;
+ GPtrArray *new_value;
+ gint idx;
+ gboolean removed = FALSE;
+
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+ g_assert (G_IS_FILE (target));
+
+ stored_logs = logview_prefs_get_stored_logfiles (prefs);
+ new_value = g_ptr_array_new ();
+
+ for (idx = 0; stored_logs[idx] != NULL; idx++) {
+ stored = g_file_parse_name (stored_logs[idx]);
+ if (!g_file_equal (stored, target)) {
+ g_ptr_array_add (new_value, g_strdup (stored_logs[idx]));
+ }
+
+ g_object_unref (stored);
+ }
+
+ g_ptr_array_add (new_value, NULL);
+ g_strfreev (stored_logs);
+ stored_logs = (gchar **) g_ptr_array_free (new_value, FALSE);
+
+ g_settings_set_strv (prefs->priv->logview_prefs,
+ PREF_LOGFILES,
+ (const gchar **) stored_logs);
+
+ g_strfreev (stored_logs);
+}
+
+void
+logview_prefs_store_fontsize (LogviewPrefs *prefs, int fontsize)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+ g_assert (fontsize > 0);
+
+ g_settings_set_int (prefs->priv->logview_prefs, PREF_FONTSIZE, fontsize);
+}
+
+int
+logview_prefs_get_stored_fontsize (LogviewPrefs *prefs)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ return g_settings_get_int (prefs->priv->logview_prefs, PREF_FONTSIZE);
+}
+
+void
+logview_prefs_store_active_logfile (LogviewPrefs *prefs,
+ const char *filename)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ g_settings_set_string (prefs->priv->logview_prefs,
+ PREF_LOGFILE, filename);
+}
+
+char *
+logview_prefs_get_active_logfile (LogviewPrefs *prefs)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ return g_settings_get_string (prefs->priv->logview_prefs,
+ PREF_LOGFILE);
+}
+
+GList *
+logview_prefs_get_filters (LogviewPrefs *prefs)
+{
+ GList *filters = NULL;
+
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ g_hash_table_foreach (prefs->priv->filters,
+ get_filters_foreach,
+ &filters);
+
+ return filters;
+}
+
+void
+logview_prefs_remove_filter (LogviewPrefs *prefs,
+ const gchar *name)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ g_hash_table_remove (prefs->priv->filters,
+ name);
+
+ save_filters (prefs);
+}
+
+void
+logview_prefs_add_filter (LogviewPrefs *prefs,
+ LogviewFilter *filter)
+{
+ gchar* name;
+
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+ g_assert (LOGVIEW_IS_FILTER (filter));
+
+ g_object_get (filter, "name", &name, NULL);
+ g_hash_table_insert (prefs->priv->filters, name, g_object_ref (filter));
+
+ save_filters (prefs);
+}
+
+LogviewFilter *
+logview_prefs_get_filter (LogviewPrefs *prefs,
+ const gchar *name)
+{
+ g_assert (LOGVIEW_IS_PREFS (prefs));
+
+ return g_hash_table_lookup (prefs->priv->filters, name);
+}
+
diff --git a/logview/src/logview-prefs.h b/logview/src/logview-prefs.h
new file mode 100644
index 00000000..bc9c6608
--- /dev/null
+++ b/logview/src/logview-prefs.h
@@ -0,0 +1,89 @@
+/* logview-prefs.h - logview user preferences handling
+ *
+ * Copyright (C) 2004 Vincent Noel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __LOGVIEW_PREFS_H__
+#define __LOGVIEW_PREFS_H__
+
+#include "logview-filter.h"
+
+#define LOGVIEW_TYPE_PREFS logview_prefs_get_type()
+#define LOGVIEW_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), LOGVIEW_TYPE_PREFS, LogviewPrefs))
+#define LOGVIEW_PREFS_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), LOGVIEW_TYPE_PREFS, LogviewPrefsClass))
+#define LOGVIEW_IS_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LOGVIEW_TYPE_PREFS))
+#define LOGVIEW_IS_PREFS_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), LOGVIEW_TYPE_PREFS))
+#define LOGVIEW_PREFS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), LOGVIEW_TYPE_PREFS, LogviewPrefsClass))
+
+typedef struct _LogviewPrefs LogviewPrefs;
+typedef struct _LogviewPrefsClass LogviewPrefsClass;
+typedef struct _LogviewPrefsPrivate LogviewPrefsPrivate;
+
+struct _LogviewPrefs {
+ GObject parent;
+ LogviewPrefsPrivate *priv;
+};
+
+struct _LogviewPrefsClass {
+ GObjectClass parent_class;
+
+ /* signals */
+ void (* system_font_changed) (LogviewPrefs *prefs,
+ const char *font_name);
+ void (* have_tearoff_changed) (LogviewPrefs *prefs,
+ gboolean have_tearoff);
+ void (* filters_changed) (LogviewPrefs *prefs);
+};
+
+GType logview_prefs_get_type (void);
+
+/* public methods */
+
+LogviewPrefs * logview_prefs_get (void);
+void logview_prefs_store_window_size (LogviewPrefs *prefs,
+ int width, int height);
+void logview_prefs_get_stored_window_size (LogviewPrefs *prefs,
+ int *width, int *height);
+char * logview_prefs_get_monospace_font_name (LogviewPrefs *prefs);
+gboolean logview_prefs_get_have_tearoff (LogviewPrefs *prefs);
+void logview_prefs_store_log (LogviewPrefs *prefs,
+ GFile *file);
+void logview_prefs_remove_stored_log (LogviewPrefs *prefs,
+ GFile *target);
+gchar ** logview_prefs_get_stored_logfiles (LogviewPrefs *prefs);
+void logview_prefs_store_fontsize (LogviewPrefs *prefs,
+ int fontsize);
+int logview_prefs_get_stored_fontsize (LogviewPrefs *prefs);
+void logview_prefs_store_active_logfile (LogviewPrefs *prefs,
+ const char *filename);
+char * logview_prefs_get_active_logfile (LogviewPrefs *prefs);
+
+GList * logview_prefs_get_filters (LogviewPrefs *prefs);
+void logview_prefs_remove_filter (LogviewPrefs *prefs,
+ const gchar* name);
+void logview_prefs_add_filter (LogviewPrefs *prefs,
+ LogviewFilter *filter);
+LogviewFilter * logview_prefs_get_filter (LogviewPrefs *prefs,
+ const gchar *name);
+
+#endif /* __LOG_PREFS_H__ */
diff --git a/logview/src/logview-utils.c b/logview/src/logview-utils.c
new file mode 100644
index 00000000..c8a61ed1
--- /dev/null
+++ b/logview/src/logview-utils.c
@@ -0,0 +1,282 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-utils.c - misc logview utilities
+ *
+ * Copyright (C) 1998 Cesar Miquel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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., 551 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED 1 /* strptime is XPG4v2 */
+#include <time.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <glib.h>
+
+#include "logview-utils.h"
+
+void
+logview_utils_day_free (Day *day)
+{
+ if (!day) {
+ return;
+ }
+
+ g_date_free (day->date);
+ g_slice_free (Day, day);
+}
+
+Day *
+logview_utils_day_copy (Day *day)
+{
+ Day *retval;
+
+ retval = g_slice_new0 (Day);
+ retval->date = g_date_new_julian (g_date_get_julian (day->date));
+ retval->first_line = day->first_line;
+ retval->last_line = day->last_line;
+ retval->timestamp_len = day->timestamp_len;
+
+ return retval;
+}
+
+GSList *
+logview_utils_day_list_copy (GSList *days)
+{
+ GSList *l, *retval = NULL;
+
+ for (l = days; l; l = l->next) {
+ retval = g_slist_prepend (retval, logview_utils_day_copy (l->data));
+ }
+
+ return g_slist_reverse (retval);
+}
+
+gint
+days_compare (gconstpointer a, gconstpointer b)
+{
+ const Day *day1 = a, *day2 = b;
+
+ return g_date_compare (day1->date, day2->date);
+}
+
+static GDate *
+string_get_date (const char *line, char **time_string, int *timestamp_len)
+{
+ GDate *date = NULL;
+ struct tm tp;
+ char *cp = NULL, *timestamp = NULL;
+
+ /* it's safe to assume that if strptime returns NULL, it's
+ * because of an error (format unmatched). being a log file, it's very
+ * unlikely that there aren't any more characters after the date.
+ */
+
+ if (line == NULL || line[0] == '\0') {
+ return NULL;
+ }
+
+ /* this parses the "MonthName DayNo" format */
+ cp = strptime (line, "%b %d", &tp);
+ if (cp) {
+ goto out;
+ }
+
+ /* this parses the YYYY-MM-DD format */
+ cp = strptime (line, "%F", &tp);
+ if (cp) {
+ goto out;
+ }
+
+out:
+ if (cp) {
+ /* the year doesn't matter to us now */
+ date = g_date_new_dmy (tp.tm_mday, tp.tm_mon + 1, 1);
+ *time_string = g_strndup (line, cp - line);
+
+ timestamp = strptime (cp, "%X", &tp);
+ if (timestamp) {
+ *timestamp_len = timestamp - line;
+ }
+ }
+
+ return date;
+}
+
+/**
+ * log_read_dates:
+ *
+ * @buffer_lines: an array of text lines.
+ * @current: the mtime of the file being parsed.
+ *
+ * Reads all the dates inside the text buffer.
+ * All dates are given with respect to the 1/1/1970
+ * and are then corrected to the correct year once we
+ * reach the end.
+ *
+ * Returns: a #GSList of #Day structures.
+ */
+
+GSList *
+log_read_dates (const char **buffer_lines, time_t current)
+{
+ int current_year, offsetyear, i, n, rangemin, rangemax, timestamp_len = 0;
+ GSList *days = NULL;
+ GDate *date = NULL;
+ struct tm *tmptm;
+ char *date_string = NULL;
+ Day *day;
+ gboolean done = FALSE;
+
+ g_return_val_if_fail (buffer_lines != NULL, NULL);
+
+ n = g_strv_length ((char **) buffer_lines);
+
+ tmptm = localtime (&current);
+ current_year = tmptm->tm_year + 1900;
+ offsetyear = 0;
+
+ /* find the first line with a date we're able to parse */
+ for (i = 0; buffer_lines[i]; i++) {
+ if ((date = string_get_date (buffer_lines[i], &date_string, &timestamp_len)) != NULL)
+ break;
+ }
+
+ if (!date) {
+ /* no valid dates in the array, return NULL */
+ return NULL;
+ }
+
+ if (!g_date_valid (date)) {
+ g_date_free (date);
+ g_free (date_string);
+ return NULL;
+ }
+
+ g_date_set_year (date, current_year);
+
+ day = g_slice_new0 (Day);
+ days = g_slist_append (days, day);
+
+ /* $i now contains the line number for the first good date */
+ day->date = date;
+ day->first_line = i;
+ day->last_line = -1;
+ day->timestamp_len = timestamp_len;
+
+ /* now scan the logfile to get the last line of the day */
+ rangemin = i;
+ rangemax = n - 1;
+
+ while (!done) {
+ /* find out the last line of the day we're currently building */
+
+ i = n - 1;
+
+ while (day->last_line < 0) {
+ if (strstr (buffer_lines[i], date_string)) {
+ /* if we find the same string on the last line of the log, we're done */
+ if (i == n - 1) {
+ done = TRUE;
+ day->last_line = i;
+ break;
+ }
+
+ /* we're still in a section of lines with the same date;
+ * - if the next one changes, then we're on the last.
+ * - else we keep searching in the following.
+ */
+
+ if (!strstr (buffer_lines[i + 1], date_string)) {
+ day->last_line = i;
+ break;
+ } else {
+ rangemin = i;
+ i = floor (((float) i + (float) rangemax) / 2.);
+ }
+ } else {
+ /* we can't find the same date here; go back to a safer range. */
+ rangemax = i;
+ i = floor (((float) rangemin + (float) i) / 2.);
+ }
+ }
+
+ g_free (date_string);
+ date_string = NULL;
+
+ if (!done) {
+ /* this means we finished the current day but we're not at the end
+ * of the buffer: reset the parameters for the next day.
+ */
+ GDate *newdate = NULL;
+
+ for (i = day->last_line + 1; buffer_lines[i]; i++) {
+ if ((newdate = string_get_date (buffer_lines[i], &date_string, &timestamp_len)) != NULL)
+ break;
+ }
+
+ if (date_string == NULL && i == n - 1) {
+ done = TRUE;
+ }
+
+ /* this will set the last line of the "old" log to either:
+ * - "n - 1" if we can't find another date
+ * - the line before the new date else.
+ */
+ day->last_line = i - 1;
+
+ if (newdate) {
+ /* append a new day to the list */
+
+ g_date_set_year (newdate, current_year + offsetyear);
+
+ if (g_date_compare (newdate, date) < 1) {
+ /* this isn't possible, as we're reading the log forward.
+ * so it means that newdate is the next year.
+ */
+ g_date_add_years (newdate, 1);
+ offsetyear++;
+ }
+
+ date = newdate;
+ day = g_slice_new0 (Day);
+ days = g_slist_prepend (days, day);
+
+ day->date = date;
+ day->first_line = i;
+ day->last_line = -1;
+ day->timestamp_len = timestamp_len;
+ rangemin = i;
+ rangemax = n - 1;
+ }
+ }
+ }
+
+ if (date_string) {
+ g_free (date_string);
+ }
+
+ /* sort the days in chronological order */
+ days = g_slist_sort (days, days_compare);
+
+ return days;
+}
diff --git a/logview/src/logview-utils.h b/logview/src/logview-utils.h
new file mode 100644
index 00000000..27242917
--- /dev/null
+++ b/logview/src/logview-utils.h
@@ -0,0 +1,40 @@
+/* logview-utils.h - misc logview utilities
+ *
+ * Copyright (C) 1998 Cesar Miquel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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., 551 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ */
+
+#ifndef __LOGVIEW_UTILS_H__
+#define __LOGVIEW_UTILS_H__
+
+#include <glib.h>
+
+typedef struct {
+ GDate *date;
+ int first_line;
+ int last_line;
+ int timestamp_len;
+} Day;
+
+GSList * log_read_dates (const char **buffer_lines, time_t current);
+gint days_compare (gconstpointer a, gconstpointer b);
+void logview_utils_day_free (Day *day);
+Day * logview_utils_day_copy (Day *day);
+GSList * logview_utils_day_list_copy (GSList *days);
+
+
+#endif /* __LOGVIEW_UTILS_H__ */ \ No newline at end of file
diff --git a/logview/src/logview-window.c b/logview/src/logview-window.c
new file mode 100644
index 00000000..d0728f92
--- /dev/null
+++ b/logview/src/logview-window.c
@@ -0,0 +1,1548 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* logview-window.c - main window of logview
+ *
+ * Copyright (C) 1998 Cesar Miquel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+
+#include "logview-window.h"
+
+#include "logview-loglist.h"
+#include "logview-findbar.h"
+#include "logview-about.h"
+#include "logview-prefs.h"
+#include "logview-manager.h"
+#include "logview-filter-manager.h"
+
+#define APP_NAME _("System Log Viewer")
+#define SEARCH_START_MARK "lw-search-start-mark"
+#define SEARCH_END_MARK "lw-search-end-mark"
+
+struct _LogviewWindowPrivate {
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+ GtkActionGroup *filter_action_group;
+
+ GtkWidget *find_bar;
+ GtkWidget *loglist;
+ GtkWidget *sidebar;
+ GtkWidget *version_bar;
+ GtkWidget *version_selector;
+ GtkWidget *hpaned;
+ GtkWidget *text_view;
+ GtkWidget *statusbar;
+
+ GtkWidget *message_area;
+ GtkWidget *message_primary;
+ GtkWidget *message_secondary;
+
+ GtkTextTagTable *tag_table;
+
+ int original_fontsize, fontsize;
+
+ LogviewPrefs *prefs;
+ LogviewManager *manager;
+
+ gulong monitor_id;
+ guint search_timeout_id;
+
+ GCancellable *read_cancellable;
+
+ guint filter_merge_id;
+ GList *active_filters;
+ gboolean matches_only;
+};
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), LOGVIEW_TYPE_WINDOW, LogviewWindowPrivate))
+
+G_DEFINE_TYPE (LogviewWindow, logview_window, GTK_TYPE_WINDOW);
+
+static void findbar_close_cb (LogviewFindbar *findbar,
+ gpointer user_data);
+static void read_new_lines_cb (LogviewLog *log,
+ const char **lines,
+ GSList *new_days,
+ GError *error,
+ gpointer user_data);
+
+/* private functions */
+
+static void
+logview_version_selector_changed (GtkComboBox *version_selector, gpointer user_data)
+{
+
+}
+#if 0
+ LogviewWindow *logview = user_data;
+ Log *log = logview->curlog;
+ int selected;
+
+ g_assert (LOGVIEW_IS_WINDOW (logview));
+
+ selected = gtk_combo_box_get_active (version_selector);
+
+ if (selected == log->current_version)
+ return;
+
+ /* select a new version */
+ if (selected == 0) {
+ logview_select_log (logview, log->parent_log);
+ } else {
+ Log *new;
+ if (log->parent_log) {
+ new = log->parent_log->older_logs[selected];
+ } else {
+ new = log->older_logs[selected];
+ }
+
+ logview_select_log (logview, new);
+ }
+}
+
+#endif
+
+/* private helpers */
+
+static void
+populate_tag_table (GtkTextTagTable *tag_table)
+{
+ GtkTextTag *tag;
+
+ tag = gtk_text_tag_new ("bold");
+ g_object_set (tag, "weight", PANGO_WEIGHT_BOLD,
+ "weight-set", TRUE, NULL);
+
+ gtk_text_tag_table_add (tag_table, tag);
+
+ tag = gtk_text_tag_new ("invisible");
+ g_object_set (tag, "invisible", TRUE, "invisible-set", TRUE, NULL);
+ gtk_text_tag_table_add (tag_table, tag);
+
+ tag = gtk_text_tag_new ("invisible-filter");
+ g_object_set (tag, "invisible", TRUE, "invisible-set", TRUE, NULL);
+ gtk_text_tag_table_add (tag_table, tag);
+}
+
+
+static void
+populate_style_tag_table (GtkStyle *style,
+ GtkTextTagTable *tag_table)
+{
+ GtkTextTag *tag;
+ GdkColor color;
+
+ tag = gtk_text_tag_table_lookup (tag_table, "gray");
+
+ if (tag) {
+ /* FIXME: do we need a way to update the buffer/view? */
+ gtk_text_tag_table_remove (tag_table, tag);
+ }
+
+ tag = gtk_text_tag_new ("gray");
+ color = style->text[GTK_STATE_INSENSITIVE];
+ g_object_set (tag, "foreground-gdk", &color, "foreground-set", TRUE, NULL);
+
+ gtk_text_tag_table_add (tag_table, tag);
+}
+
+static void
+_gtk_text_buffer_apply_tag_to_rectangle (GtkTextBuffer *buffer, int line_start, int line_end,
+ int offset_start, int offset_end, char *tag_name)
+{
+ GtkTextIter start, end;
+ int line_cur;
+
+ gtk_text_buffer_get_iter_at_line (buffer, &start, line_start);
+ gtk_text_buffer_get_iter_at_line (buffer, &end, line_start);
+
+ for (line_cur = line_start; line_cur < line_end + 1; line_cur++) {
+
+ if (offset_start > 0) {
+ gtk_text_iter_forward_chars (&start, offset_start);
+ }
+
+ gtk_text_iter_forward_chars (&end, offset_end);
+
+ gtk_text_buffer_apply_tag_by_name (buffer, tag_name, &start, &end);
+
+ gtk_text_iter_forward_line (&start);
+ gtk_text_iter_forward_line (&end);
+ }
+}
+
+static void
+logview_update_statusbar (LogviewWindow *logview, LogviewLog *active)
+{
+ char *statusbar_text;
+ char *size, *modified, *timestring_utf8;
+ time_t timestamp;
+ char timestring[255];
+
+ if (active == NULL) {
+ gtk_statusbar_pop (GTK_STATUSBAR (logview->priv->statusbar), 0);
+ return;
+ }
+
+ timestamp = logview_log_get_timestamp (active);
+ strftime (timestring, sizeof (timestring), "%a %b %e %T %Y", localtime (&timestamp));
+ timestring_utf8 = g_locale_to_utf8 (timestring, -1, NULL, NULL, NULL);
+
+ modified = g_strdup_printf (_("last update: %s"), timestring_utf8);
+
+ size = g_format_size (logview_log_get_file_size (active));
+
+ statusbar_text = g_strdup_printf (_("%d lines (%s) - %s"),
+ logview_log_get_cached_lines_number (active),
+ size, modified);
+
+ gtk_statusbar_pop (GTK_STATUSBAR (logview->priv->statusbar), 0);
+ gtk_statusbar_push (GTK_STATUSBAR (logview->priv->statusbar), 0, statusbar_text);
+
+ g_free (size);
+ g_free (timestring_utf8);
+ g_free (modified);
+ g_free (statusbar_text);
+}
+
+#define DEFAULT_LOGVIEW_FONT "Monospace 10"
+
+static void
+logview_set_font (LogviewWindow *logview,
+ const char *fontname)
+{
+ PangoFontDescription *font_desc;
+
+ if (fontname == NULL)
+ fontname = DEFAULT_LOGVIEW_FONT;
+
+ font_desc = pango_font_description_from_string (fontname);
+ if (font_desc) {
+ gtk_widget_modify_font (logview->priv->text_view, font_desc);
+ pango_font_description_free (font_desc);
+ }
+}
+
+static void
+logview_set_fontsize (LogviewWindow *logview, gboolean store)
+{
+ PangoFontDescription *fontdesc;
+ PangoContext *context;
+ LogviewWindowPrivate *priv = logview->priv;
+
+ context = gtk_widget_get_pango_context (priv->text_view);
+ fontdesc = pango_context_get_font_description (context);
+ pango_font_description_set_size (fontdesc, (priv->fontsize) * PANGO_SCALE);
+ gtk_widget_modify_font (priv->text_view, fontdesc);
+
+ if (store) {
+ logview_prefs_store_fontsize (logview->priv->prefs, priv->fontsize);
+ }
+}
+
+static void
+logview_set_window_title (LogviewWindow *logview, const char * log_name)
+{
+ char *window_title;
+
+ if (log_name) {
+ window_title = g_strdup_printf ("%s - %s", log_name, APP_NAME);
+ } else {
+ window_title = g_strdup_printf (APP_NAME);
+ }
+
+ gtk_window_set_title (GTK_WINDOW (logview), window_title);
+
+ g_free (window_title);
+}
+
+/* actions callbacks */
+
+static void
+open_file_selected_cb (GtkWidget *chooser, gint response, LogviewWindow *logview)
+{
+ GFile *f;
+ char *file_uri;
+ LogviewLog *log;
+
+ gtk_widget_hide (GTK_WIDGET (chooser));
+ if (response != GTK_RESPONSE_OK) {
+ return;
+ }
+
+ f = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (chooser));
+ file_uri = g_file_get_uri (f);
+
+ log = logview_manager_get_if_loaded (logview->priv->manager, file_uri);
+
+ g_free (file_uri);
+
+ if (log) {
+ logview_manager_set_active_log (logview->priv->manager, log);
+ g_object_unref (log);
+ goto out;
+ }
+
+ logview_manager_add_log_from_gfile (logview->priv->manager, f, TRUE);
+
+out:
+ g_object_unref (f);
+}
+
+static void
+logview_open_log (GtkAction *action, LogviewWindow *logview)
+{
+ static GtkWidget *chooser = NULL;
+ char *active;
+
+ if (chooser == NULL) {
+ chooser = gtk_file_chooser_dialog_new (_("Open Log"),
+ GTK_WINDOW (logview),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (chooser), GTK_RESPONSE_OK);
+ gtk_window_set_modal (GTK_WINDOW (chooser), TRUE);
+ g_signal_connect (chooser, "response",
+ G_CALLBACK (open_file_selected_cb), logview);
+ g_signal_connect (chooser, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &chooser);
+ active = logview_prefs_get_active_logfile (logview->priv->prefs);
+ if (active != NULL) {
+ gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (chooser), active);
+ g_free (active);
+ }
+ }
+
+ gtk_window_present (GTK_WINDOW (chooser));
+}
+
+static void
+logview_close_log (GtkAction *action, LogviewWindow *logview)
+{
+ findbar_close_cb (LOGVIEW_FINDBAR (logview->priv->find_bar), logview);
+ logview_manager_close_active_log (logview->priv->manager);
+}
+
+static void
+logview_help (GtkAction *action, GtkWidget *parent_window)
+{
+ GError *error = NULL;
+
+ gtk_show_uri (gtk_widget_get_screen (parent_window),
+ "help:mate-system-log", gtk_get_current_event_time (),
+ &error);
+
+ if (error) {
+ g_warning (_("There was an error displaying help: %s"), error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+logview_bigger_text (GtkAction *action, LogviewWindow *logview)
+{
+ logview->priv->fontsize = MIN (logview->priv->fontsize + 1, 24);
+ logview_set_fontsize (logview, TRUE);
+}
+
+static void
+logview_smaller_text (GtkAction *action, LogviewWindow *logview)
+{
+ logview->priv->fontsize = MAX (logview->priv->fontsize-1, 6);
+ logview_set_fontsize (logview, TRUE);
+}
+
+static void
+logview_normal_text (GtkAction *action, LogviewWindow *logview)
+{
+ logview->priv->fontsize = logview->priv->original_fontsize;
+ logview_set_fontsize (logview, TRUE);
+}
+
+static void
+logview_select_all (GtkAction *action, LogviewWindow *logview)
+{
+ GtkTextIter start, end;
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (logview->priv->text_view));
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ gtk_text_buffer_select_range (buffer, &start, &end);
+
+ gtk_widget_grab_focus (GTK_WIDGET (logview->priv->text_view));
+}
+
+static void
+logview_copy (GtkAction *action, LogviewWindow *logview)
+{
+ GtkTextBuffer *buffer;
+ GtkClipboard *clipboard;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (logview->priv->text_view));
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_copy_clipboard (buffer, clipboard);
+
+ gtk_widget_grab_focus (GTK_WIDGET (logview->priv->text_view));
+}
+
+static void
+findbar_close_cb (LogviewFindbar *findbar,
+ gpointer user_data)
+{
+ gtk_widget_hide (GTK_WIDGET (findbar));
+ logview_findbar_set_message (findbar, NULL);
+}
+
+static void
+logview_search_text (LogviewWindow *logview, gboolean forward)
+{
+ GtkTextBuffer *buffer;
+ GtkTextMark *search_start, *search_end;
+ GtkTextIter search, start_m, end_m;
+ const char *text;
+ gboolean res, wrapped;
+
+ wrapped = FALSE;
+
+ text = logview_findbar_get_text (LOGVIEW_FINDBAR (logview->priv->find_bar));
+
+ if (!text || g_strcmp0 (text, "") == 0) {
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (logview->priv->text_view));
+ search_start = gtk_text_buffer_get_mark (buffer, SEARCH_START_MARK);
+ search_end = gtk_text_buffer_get_mark (buffer, SEARCH_END_MARK);
+
+ if (!search_start) {
+ /* this is our first search on the buffer, create a new search mark */
+ gtk_text_buffer_get_start_iter (buffer, &search);
+ search_start = gtk_text_buffer_create_mark (buffer, SEARCH_START_MARK,
+ &search, TRUE);
+ search_end = gtk_text_buffer_create_mark (buffer, SEARCH_END_MARK,
+ &search, TRUE);
+ } else {
+ if (forward) {
+ gtk_text_buffer_get_iter_at_mark (buffer, &search, search_end);
+ } else {
+ gtk_text_buffer_get_iter_at_mark (buffer, &search, search_start);
+ }
+ }
+
+wrap:
+
+ if (forward) {
+ res = gtk_text_iter_forward_search (&search, text, GTK_TEXT_SEARCH_VISIBLE_ONLY, &start_m, &end_m, NULL);
+ } else {
+ res = gtk_text_iter_backward_search (&search, text, GTK_TEXT_SEARCH_VISIBLE_ONLY, &start_m, &end_m, NULL);
+ }
+
+ if (res) {
+ gtk_text_buffer_select_range (buffer, &start_m, &end_m);
+ gtk_text_buffer_move_mark (buffer, search_start, &start_m);
+ gtk_text_buffer_move_mark (buffer, search_end, &end_m);
+
+ gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (logview->priv->text_view), search_end);
+
+ if (wrapped) {
+ logview_findbar_set_message (LOGVIEW_FINDBAR (logview->priv->find_bar), _("Wrapped"));
+ }
+ } else {
+ if (wrapped) {
+
+ GtkTextMark *mark;
+ GtkTextIter iter;
+
+ if (gtk_text_buffer_get_has_selection (buffer)) {
+ /* unselect */
+ mark = gtk_text_buffer_get_mark (buffer, "insert");
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
+ gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter);
+ }
+
+ logview_findbar_set_message (LOGVIEW_FINDBAR (logview->priv->find_bar), _("Not found"));
+ } else {
+ if (forward) {
+ gtk_text_buffer_get_start_iter (buffer, &search);
+ } else {
+ gtk_text_buffer_get_end_iter (buffer, &search);
+ }
+
+ wrapped = TRUE;
+ goto wrap;
+ }
+ }
+}
+
+static void
+findbar_previous_cb (LogviewFindbar *findbar,
+ gpointer user_data)
+{
+ LogviewWindow *logview = user_data;
+
+ logview_search_text (logview, FALSE);
+}
+
+static void
+findbar_next_cb (LogviewFindbar *findbar,
+ gpointer user_data)
+{
+ LogviewWindow *logview = user_data;
+
+ logview_search_text (logview, TRUE);
+}
+
+static gboolean
+text_changed_timeout_cb (gpointer user_data)
+{
+ LogviewWindow *logview = user_data;
+ GtkTextMark *search_start, *search_end;
+ GtkTextIter start;
+ GtkTextBuffer *buffer;
+
+ logview->priv->search_timeout_id = 0;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (logview->priv->text_view));
+ search_start = gtk_text_buffer_get_mark (buffer, SEARCH_START_MARK);
+ search_end = gtk_text_buffer_get_mark (buffer, SEARCH_END_MARK);
+
+ if (search_start) {
+ /* reset the search mark to the start */
+ gtk_text_buffer_get_start_iter (buffer, &start);
+ gtk_text_buffer_move_mark (buffer, search_start, &start);
+ gtk_text_buffer_move_mark (buffer, search_end, &start);
+ }
+
+ logview_findbar_set_message (LOGVIEW_FINDBAR (logview->priv->find_bar), NULL);
+
+ logview_search_text (logview, TRUE);
+
+ return FALSE;
+}
+
+static void
+findbar_text_changed_cb (LogviewFindbar *findbar,
+ gpointer user_data)
+{
+ LogviewWindow *logview = user_data;
+
+ if (logview->priv->search_timeout_id != 0) {
+ g_source_remove (logview->priv->search_timeout_id);
+ }
+
+ logview->priv->search_timeout_id = g_timeout_add (300, text_changed_timeout_cb, logview);
+}
+
+static void
+logview_search (GtkAction *action, LogviewWindow *logview)
+{
+ logview_findbar_open (LOGVIEW_FINDBAR (logview->priv->find_bar));
+}
+
+static void
+filter_buffer (LogviewWindow *logview, gint start_line)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter start, *end;
+ gchar* text;
+ GList* cur_filter;
+ gboolean matched;
+ int lines, i;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (logview->priv->text_view));
+ lines = gtk_text_buffer_get_line_count (buffer);
+
+ for (i = start_line; i < lines; i++) {
+ matched = FALSE;
+
+ gtk_text_buffer_get_iter_at_line (buffer, &start, i);
+ end = gtk_text_iter_copy (&start);
+ gtk_text_iter_forward_line (end);
+
+ text = gtk_text_buffer_get_text (buffer, &start, end, TRUE);
+
+ for (cur_filter = logview->priv->active_filters; cur_filter != NULL;
+ cur_filter = g_list_next (cur_filter))
+ {
+ if (logview_filter_filter (LOGVIEW_FILTER (cur_filter->data), text)) {
+ gtk_text_buffer_apply_tag (buffer,
+ logview_filter_get_tag (LOGVIEW_FILTER (cur_filter->data)),
+ &start, end);
+ matched = TRUE;
+ }
+ }
+
+ g_free (text);
+
+ if (!matched && logview->priv->matches_only) {
+ gtk_text_buffer_apply_tag_by_name (buffer,
+ "invisible-filter",
+ &start, end);
+ } else {
+ gtk_text_buffer_remove_tag_by_name (buffer,
+ "invisible-filter",
+ &start, end);
+ }
+
+ gtk_text_iter_free (end);
+ }
+}
+
+static void
+filter_remove (LogviewWindow *logview, LogviewFilter *filter)
+{
+ GtkTextIter start, end;
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (logview->priv->text_view));
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+
+ gtk_text_buffer_remove_tag (buffer, logview_filter_get_tag (filter),
+ &start, &end);
+}
+
+static void
+on_filter_toggled (GtkToggleAction *action, LogviewWindow *logview)
+{
+ LogviewWindowPrivate *priv = GET_PRIVATE (logview);
+ const gchar* name;
+ LogviewFilter *filter;
+
+ name = gtk_action_get_name (GTK_ACTION (action));
+
+ if (gtk_toggle_action_get_active (action)) {
+ priv->active_filters = g_list_append (priv->active_filters,
+ logview_prefs_get_filter (priv->prefs,
+ name));
+ filter_buffer(logview, 0);
+ } else {
+ filter = logview_prefs_get_filter (priv->prefs, name);
+ priv->active_filters = g_list_remove (priv->active_filters,
+ filter);
+
+ filter_remove (logview, filter);
+ }
+}
+
+#define FILTER_PLACEHOLDER "/LogviewMenu/FilterMenu/PlaceholderFilters"
+static void
+update_filter_menu (LogviewWindow *window)
+{
+ LogviewWindowPrivate *priv;
+ GtkUIManager* ui;
+ GList *actions, *l;
+ guint id;
+ GList *filters;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+ GtkToggleAction *action;
+ gchar* name;
+
+ priv = GET_PRIVATE (window);
+ ui = priv->ui_manager;
+
+ g_return_if_fail (priv->filter_action_group != NULL);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
+ table = priv->tag_table;
+
+ if (priv->filter_merge_id != 0) {
+ gtk_ui_manager_remove_ui (ui,
+ priv->filter_merge_id);
+ }
+
+ actions = gtk_action_group_list_actions (priv->filter_action_group);
+
+ for (l = actions; l != NULL; l = g_list_next (l)) {
+ tag = gtk_text_tag_table_lookup (table, gtk_action_get_name (GTK_ACTION (l->data)));
+ gtk_text_tag_table_remove (table, tag);
+
+ g_signal_handlers_disconnect_by_func (GTK_ACTION (l->data),
+ G_CALLBACK (on_filter_toggled),
+ window);
+ gtk_action_group_remove_action (priv->filter_action_group,
+ GTK_ACTION (l->data));
+ }
+
+ g_list_free (actions);
+
+ filters = logview_prefs_get_filters (logview_prefs_get ());
+
+ id = (g_list_length (filters) > 0) ? gtk_ui_manager_new_merge_id (ui) : 0;
+
+ for (l = filters; l != NULL; l = g_list_next (l)) {
+ g_object_get (l->data, "name", &name, NULL);
+
+ action = gtk_toggle_action_new (name, name, NULL, NULL);
+ gtk_action_group_add_action (priv->filter_action_group,
+ GTK_ACTION (action));
+
+ g_signal_connect (action,
+ "toggled",
+ G_CALLBACK (on_filter_toggled),
+ window);
+
+ gtk_ui_manager_add_ui (ui, id, FILTER_PLACEHOLDER,
+ name, name, GTK_UI_MANAGER_MENUITEM, FALSE);
+ gtk_text_tag_table_add (table,
+ logview_filter_get_tag (LOGVIEW_FILTER (l->data)));
+
+ g_object_unref (action);
+ g_free(name);
+ }
+
+ g_list_free (filters);
+
+ priv->filter_merge_id = id;
+}
+
+static void
+on_logview_filter_manager_response (GtkDialog *dialog,
+ gint response,
+ LogviewWindow *logview)
+{
+ update_filter_menu (logview);
+
+ g_list_free (logview->priv->active_filters);
+ logview->priv->active_filters = NULL;
+}
+
+static void
+logview_manage_filters (GtkAction *action, LogviewWindow *logview)
+{
+ GtkWidget *manager;
+
+ manager = logview_filter_manager_new ();
+
+ g_signal_connect (manager, "response",
+ G_CALLBACK (on_logview_filter_manager_response),
+ logview);
+
+ gtk_window_set_transient_for (GTK_WINDOW (manager),
+ GTK_WINDOW (logview));
+ gtk_widget_show (GTK_WIDGET (manager));
+}
+
+static void
+logview_about (GtkWidget *widget, GtkWidget *window)
+{
+ g_return_if_fail (GTK_IS_WINDOW (window));
+
+ char *license_trans = g_strjoin ("\n\n", _(logview_about_license[0]),
+ _(logview_about_license[1]),
+ _(logview_about_license[2]), NULL);
+
+ gtk_show_about_dialog (GTK_WINDOW (window),
+ "name", _("System Log Viewer"),
+ "version", VERSION,
+ "copyright", "Copyright \xc2\xa9 1998-2008 Free Software Foundation, Inc.",
+ "license", license_trans,
+ "wrap-license", TRUE,
+ "comments", _("A system log viewer for MATE."),
+ "authors", logview_about_authors,
+ "documenters", logview_about_documenters,
+ "translator_credits", strcmp (logview_about_translator_credits,
+ "translator-credits") != 0 ?
+ logview_about_translator_credits : NULL,
+ "logo_icon_name", "mate-system-log",
+ NULL);
+ g_free (license_trans);
+
+ return;
+}
+
+static void
+logview_toggle_statusbar (GtkAction *action, LogviewWindow *logview)
+{
+ if (gtk_widget_get_visible (logview->priv->statusbar))
+ gtk_widget_hide (logview->priv->statusbar);
+ else
+ gtk_widget_show (logview->priv->statusbar);
+}
+
+static void
+logview_toggle_sidebar (GtkAction *action, LogviewWindow *logview)
+{
+ if (gtk_widget_get_visible (logview->priv->sidebar))
+ gtk_widget_hide (logview->priv->sidebar);
+ else
+ gtk_widget_show (logview->priv->sidebar);
+}
+
+static void
+logview_toggle_match_filters (GtkToggleAction *action, LogviewWindow *logview)
+{
+ logview->priv->matches_only = gtk_toggle_action_get_active (action);
+ filter_buffer (logview, 0);
+}
+
+/* GObject functions */
+
+/* Menus */
+
+static GtkActionEntry entries[] = {
+ { "FileMenu", NULL, N_("_File"), NULL, NULL, NULL },
+ { "EditMenu", NULL, N_("_Edit"), NULL, NULL, NULL },
+ { "ViewMenu", NULL, N_("_View"), NULL, NULL, NULL },
+ { "FilterMenu", NULL, N_("_Filters"), NULL, NULL, NULL },
+ { "HelpMenu", NULL, N_("_Help"), NULL, NULL, NULL },
+
+ { "OpenLog", GTK_STOCK_OPEN, N_("_Open..."), "<control>O", N_("Open a log from file"),
+ G_CALLBACK (logview_open_log) },
+ { "CloseLog", GTK_STOCK_CLOSE, N_("_Close"), "<control>W", N_("Close this log"),
+ G_CALLBACK (logview_close_log) },
+ { "Quit", GTK_STOCK_QUIT, N_("_Quit"), "<control>Q", N_("Quit the log viewer"),
+ G_CALLBACK (gtk_main_quit) },
+
+ { "Copy", GTK_STOCK_COPY, N_("_Copy"), "<control>C", N_("Copy the selection"),
+ G_CALLBACK (logview_copy) },
+ { "SelectAll", NULL, N_("Select _All"), "<Control>A", N_("Select the entire log"),
+ G_CALLBACK (logview_select_all) },
+ { "Search", GTK_STOCK_FIND, N_("_Find..."), "<control>F", N_("Find a word or phrase in the log"),
+ G_CALLBACK (logview_search) },
+
+ { "ViewZoomIn", GTK_STOCK_ZOOM_IN, NULL, "<control>plus", N_("Bigger text size"),
+ G_CALLBACK (logview_bigger_text)},
+ { "ViewZoomOut", GTK_STOCK_ZOOM_OUT, NULL, "<control>minus", N_("Smaller text size"),
+ G_CALLBACK (logview_smaller_text)},
+ { "ViewZoom100", GTK_STOCK_ZOOM_100, NULL, "<control>0", N_("Normal text size"),
+ G_CALLBACK (logview_normal_text)},
+
+ { "FilterManage", NULL, N_("Manage Filters"), NULL, N_("Manage filters"),
+ G_CALLBACK (logview_manage_filters)},
+
+ { "HelpContents", GTK_STOCK_HELP, N_("_Contents"), "F1", N_("Open the help contents for the log viewer"),
+ G_CALLBACK (logview_help) },
+ { "AboutAction", GTK_STOCK_ABOUT, N_("_About"), NULL, N_("Show the about dialog for the log viewer"),
+ G_CALLBACK (logview_about) },
+};
+
+static GtkToggleActionEntry toggle_entries[] = {
+ { "ShowStatusBar", NULL, N_("_Statusbar"), NULL, N_("Show Status Bar"),
+ G_CALLBACK (logview_toggle_statusbar), TRUE },
+ { "ShowSidebar", NULL, N_("Side _Pane"), "F9", N_("Show Side Pane"),
+ G_CALLBACK (logview_toggle_sidebar), TRUE },
+ { "FilterMatchOnly", NULL, N_("Show matches only"), NULL, N_("Only show lines that match one of the given filters"),
+ G_CALLBACK (logview_toggle_match_filters), FALSE}
+};
+
+static gboolean
+window_size_changed_cb (GtkWidget *widget, GdkEventConfigure *event,
+ gpointer data)
+{
+ LogviewWindow *window = data;
+
+ logview_prefs_store_window_size (window->priv->prefs,
+ event->width, event->height);
+
+ return FALSE;
+}
+
+static void
+real_select_day (LogviewWindow *logview,
+ GDate *date, int first_line, int last_line)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter start_iter, end_iter, start_vis, end_vis;
+ GdkRectangle visible_rect;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (logview->priv->text_view));
+
+ gtk_text_buffer_get_bounds (buffer, &start_iter, &end_iter);
+ gtk_text_buffer_get_iter_at_line (buffer, &start_vis, first_line);
+ gtk_text_buffer_get_iter_at_line (buffer, &end_vis, last_line + 1);
+
+ /* clear all previous invisible tags */
+ gtk_text_buffer_remove_tag_by_name (buffer, "invisible",
+ &start_iter, &end_iter);
+
+ gtk_text_buffer_apply_tag_by_name (buffer, "invisible",
+ &start_iter, &start_vis);
+ gtk_text_buffer_apply_tag_by_name (buffer, "invisible",
+ &end_vis, &end_iter);
+
+ /* FIXME: why is this needed to update the view when selecting a day back? */
+ gtk_text_view_get_visible_rect (GTK_TEXT_VIEW (logview->priv->text_view),
+ &visible_rect);
+ gdk_window_invalidate_rect (gtk_widget_get_window (logview->priv->text_view),
+ &visible_rect, TRUE);
+}
+
+static void
+loglist_day_selected_cb (LogviewLoglist *loglist,
+ Day *day,
+ gpointer user_data)
+{
+ LogviewWindow *logview = user_data;
+
+ real_select_day (logview, day->date, day->first_line, day->last_line);
+}
+
+static void
+loglist_day_cleared_cb (LogviewLoglist *loglist,
+ gpointer user_data)
+{
+ LogviewWindow *logview = user_data;
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (logview->priv->text_view));
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+
+ /* clear all previous invisible tags */
+ gtk_text_buffer_remove_tag_by_name (buffer, "invisible",
+ &start, &end);
+}
+
+static void
+logview_window_schedule_log_read (LogviewWindow *window,
+ LogviewLog *log)
+{
+ if (window->priv->read_cancellable != NULL) {
+ g_cancellable_cancel (window->priv->read_cancellable);
+ g_clear_object (&window->priv->read_cancellable);
+ }
+
+ window->priv->read_cancellable = g_cancellable_new ();
+ logview_log_read_new_lines (log,
+ window->priv->read_cancellable,
+ (LogviewNewLinesCallback) read_new_lines_cb,
+ window);
+}
+
+static void
+log_monitor_changed_cb (LogviewLog *log,
+ gpointer user_data)
+{
+ LogviewWindow *window = user_data;
+
+ /* reschedule a read */
+ logview_window_schedule_log_read (window, log);
+}
+
+static void
+paint_timestamps (GtkTextBuffer *buffer, int old_line_count,
+ GSList *days)
+{
+ GSList *l;
+
+ for (l = days; l; l = l->next) {
+ Day *day = l->data;
+
+ _gtk_text_buffer_apply_tag_to_rectangle (buffer,
+ old_line_count + day->first_line - 1,
+ old_line_count + day->last_line,
+ 0, day->timestamp_len, "gray");
+ }
+}
+
+static void
+read_new_lines_cb (LogviewLog *log,
+ const char **lines,
+ GSList *new_days,
+ GError *error,
+ gpointer user_data)
+{
+ LogviewWindow *window = user_data;
+ GtkTextBuffer *buffer;
+ gboolean boldify = FALSE;
+ int i, old_line_count, filter_start_line;
+ GtkTextIter iter, start;
+ GtkTextMark *mark;
+ char *converted, *primary;
+ gsize len;
+
+ if (error != NULL) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ primary = g_strdup_printf (_("Can't read from \"%s\""),
+ logview_log_get_display_name (log));
+ logview_window_add_error (window, primary, error->message);
+ g_free (primary);
+ }
+
+ return;
+ }
+
+ if (lines == NULL) {
+ /* there's no error, but no lines have been read */
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (window->priv->text_view));
+ old_line_count = gtk_text_buffer_get_line_count (buffer);
+ filter_start_line = old_line_count > 0 ? (old_line_count - 1) : 0;
+
+ if (gtk_text_buffer_get_char_count (buffer) != 0) {
+ boldify = TRUE;
+ }
+
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+
+ if (boldify) {
+ mark = gtk_text_buffer_create_mark (buffer, NULL, &iter, TRUE);
+ }
+
+ for (i = 0; lines[i]; i++) {
+ len = strlen (lines[i]);
+
+ if (!g_utf8_validate (lines[i], len, NULL)) {
+ converted = g_locale_to_utf8 (lines[i], (gssize) len, NULL, &len, NULL);
+ gtk_text_buffer_insert (buffer, &iter, converted, len);
+ g_free (converted);
+ } else {
+ gtk_text_buffer_insert (buffer, &iter, lines[i], strlen (lines[i]));
+ }
+
+ gtk_text_iter_forward_to_end (&iter);
+ gtk_text_buffer_insert (buffer, &iter, "\n", 1);
+ gtk_text_iter_forward_char (&iter);
+ }
+
+ if (boldify) {
+ gtk_text_buffer_get_iter_at_mark (buffer, &start, mark);
+ gtk_text_buffer_apply_tag_by_name (buffer, "bold", &start, &iter);
+ gtk_text_buffer_delete_mark (buffer, mark);
+ }
+ filter_buffer (window, filter_start_line);
+
+ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (window->priv->text_view),
+ &iter, 0.0, FALSE, 0.0, 0.0);
+
+ paint_timestamps (buffer, old_line_count, new_days);
+
+ if (window->priv->monitor_id == 0) {
+ window->priv->monitor_id = g_signal_connect (log, "log-changed",
+ G_CALLBACK (log_monitor_changed_cb), window);
+ }
+
+ logview_update_statusbar (window, log);
+ logview_loglist_update_lines (LOGVIEW_LOGLIST (window->priv->loglist), log);
+}
+
+static void
+active_log_changed_cb (LogviewManager *manager,
+ LogviewLog *log,
+ LogviewLog *old_log,
+ gpointer data)
+{
+ LogviewWindow *window = data;
+ const char **lines;
+ GtkTextBuffer *buffer;
+
+ findbar_close_cb (LOGVIEW_FINDBAR (window->priv->find_bar),
+ window);
+
+ logview_set_window_title (window, logview_log_get_display_name (log));
+
+ if (window->priv->monitor_id) {
+ g_signal_handler_disconnect (old_log, window->priv->monitor_id);
+ window->priv->monitor_id = 0;
+ }
+
+ lines = logview_log_get_cached_lines (log);
+ buffer = gtk_text_buffer_new (window->priv->tag_table);
+
+ if (lines != NULL) {
+ int i;
+ GtkTextIter iter;
+
+ /* update the text view to show the current lines */
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+
+ for (i = 0; lines[i]; i++) {
+ gtk_text_buffer_insert (buffer, &iter, lines[i], strlen (lines[i]));
+ gtk_text_iter_forward_to_end (&iter);
+ gtk_text_buffer_insert (buffer, &iter, "\n", 1);
+ gtk_text_iter_forward_char (&iter);
+ }
+
+ paint_timestamps (buffer, 1, logview_log_get_days_for_cached_lines (log));
+ }
+
+ if (lines == NULL || logview_log_has_new_lines (log)) {
+ /* read the new lines */
+ logview_window_schedule_log_read (window, log);
+ } else {
+ /* start now monitoring the log for changes */
+ window->priv->monitor_id = g_signal_connect (log, "log-changed",
+ G_CALLBACK (log_monitor_changed_cb), window);
+ }
+
+ /* we set the buffer to the view anyway;
+ * if there are no lines it will be empty for the duration of the thread
+ * and will help us to distinguish the two cases of the following if
+ * cause in the callback.
+ */
+ gtk_text_view_set_buffer (GTK_TEXT_VIEW (window->priv->text_view), buffer);
+ g_object_unref (buffer);
+}
+
+static void
+font_changed_cb (LogviewPrefs *prefs,
+ const char *font_name,
+ gpointer user_data)
+{
+ LogviewWindow *window = user_data;
+
+ logview_set_font (window, font_name);
+}
+
+static void
+tearoff_changed_cb (LogviewPrefs *prefs,
+ gboolean have_tearoffs,
+ gpointer user_data)
+{
+ LogviewWindow *window = user_data;
+
+ gtk_ui_manager_set_add_tearoffs (window->priv->ui_manager, have_tearoffs);
+}
+
+static void
+style_set_cb (GtkWidget *widget,
+ GtkStyle *prev,
+ gpointer user_data)
+{
+ LogviewWindow *logview = user_data;
+ GtkStyle *style = gtk_widget_get_style (widget);
+
+ populate_style_tag_table (style, logview->priv->tag_table);
+}
+
+static const struct {
+ guint keyval;
+ GdkModifierType modifier;
+ const gchar *action;
+} extra_keybindings [] = {
+ { GDK_KEY_KP_Add, GDK_CONTROL_MASK, "ViewZoomIn" },
+ { GDK_KEY_KP_Subtract, GDK_CONTROL_MASK, "ViewZoomOut" },
+ { GDK_KEY_KP_0, GDK_CONTROL_MASK, "ViewZoom100" }
+};
+
+static gboolean
+key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ LogviewWindow *window = user_data;
+ guint modifier = event->state & gtk_accelerator_get_default_mod_mask ();
+ GtkAction *action;
+ int i;
+
+ /* handle accelerators that we want bound, but aren't associated with
+ * an action */
+ for (i = 0; i < G_N_ELEMENTS (extra_keybindings); i++) {
+ if (event->keyval == extra_keybindings[i].keyval &&
+ modifier == extra_keybindings[i].modifier) {
+
+ action = gtk_action_group_get_action (window->priv->action_group,
+ extra_keybindings[i].action);
+ gtk_action_activate (action);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/* adapted from GEdit */
+
+static void
+message_area_create_error_box (LogviewWindow *window,
+ GtkWidget *message_area)
+{
+ GtkWidget *hbox_content;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+
+ hbox_content = gtk_hbox_new (FALSE, 8);
+ gtk_widget_show (hbox_content);
+
+ image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_ERROR,
+ GTK_ICON_SIZE_DIALOG);
+ gtk_widget_show (image);
+ gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_widget_show (vbox);
+ gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
+
+ primary_label = gtk_label_new (NULL);
+ gtk_widget_show (primary_label);
+ gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
+ gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
+ gtk_widget_set_can_focus (primary_label, TRUE);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+ window->priv->message_primary = primary_label;
+
+ secondary_label = gtk_label_new (NULL);
+ gtk_widget_show (secondary_label);
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
+ gtk_widget_set_can_focus (secondary_label, TRUE);
+ gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+
+ window->priv->message_secondary = secondary_label;
+
+ gtk_container_add
+ (GTK_CONTAINER (gtk_info_bar_get_content_area
+ (GTK_INFO_BAR (message_area))),
+ hbox_content);
+}
+
+static void
+message_area_set_labels (LogviewWindow *window,
+ const char *primary,
+ const char *secondary)
+{
+ char *primary_markup, *secondary_markup;
+
+ primary_markup = g_markup_printf_escaped ("<b>%s</b>", primary);
+ secondary_markup = g_markup_printf_escaped ("<small>%s</small>",
+ secondary);
+
+ gtk_label_set_markup (GTK_LABEL (window->priv->message_primary),
+ primary_markup);
+ gtk_label_set_markup (GTK_LABEL (window->priv->message_secondary),
+ secondary_markup);
+
+ g_free (primary_markup);
+ g_free (secondary_markup);
+}
+
+static void
+message_area_response_cb (GtkInfoBar *message_area,
+ int response_id, gpointer user_data)
+{
+ gtk_widget_hide (GTK_WIDGET (message_area));
+
+ g_signal_handlers_disconnect_by_func (message_area,
+ message_area_response_cb,
+ user_data);
+}
+
+static void
+logview_window_finalize (GObject *object)
+{
+ LogviewWindow *logview = LOGVIEW_WINDOW (object);
+
+ if (logview->priv->read_cancellable != NULL) {
+ g_cancellable_cancel (logview->priv->read_cancellable);
+ g_clear_object (&logview->priv->read_cancellable);
+ }
+
+ g_object_unref (logview->priv->ui_manager);
+ G_OBJECT_CLASS (logview_window_parent_class)->finalize (object);
+}
+
+static void
+logview_window_init (LogviewWindow *logview)
+{
+ GtkActionGroup *action_group;
+ GtkAccelGroup *accel_group;
+ GError *error = NULL;
+ GtkWidget *hpaned, *main_view, *vbox, *w;
+ PangoContext *context;
+ PangoFontDescription *fontdesc;
+ gchar *monospace_font_name;
+ LogviewWindowPrivate *priv;
+ int width, height;
+ gboolean res;
+
+ priv = logview->priv = GET_PRIVATE (logview);
+ priv->prefs = logview_prefs_get ();
+ priv->manager = logview_manager_get ();
+ priv->monitor_id = 0;
+
+ logview_prefs_get_stored_window_size (priv->prefs, &width, &height);
+ gtk_window_set_default_size (GTK_WINDOW (logview), width, height);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (logview), vbox);
+
+ /* create menus */
+ action_group = gtk_action_group_new ("LogviewMenuActions");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group, entries, G_N_ELEMENTS (entries), logview);
+ gtk_action_group_add_toggle_actions (action_group, toggle_entries, G_N_ELEMENTS (toggle_entries), logview);
+ priv->action_group = action_group;
+
+ priv->ui_manager = gtk_ui_manager_new ();
+
+ gtk_ui_manager_insert_action_group (priv->ui_manager, action_group, 0);
+ accel_group = gtk_ui_manager_get_accel_group (priv->ui_manager);
+ gtk_window_add_accel_group (GTK_WINDOW (logview), accel_group);
+
+ res = gtk_ui_manager_add_ui_from_file (priv->ui_manager,
+ LOGVIEW_DATADIR "/logview-toolbar.xml",
+ &error);
+
+ if (res == FALSE) {
+ priv->ui_manager = NULL;
+ g_critical ("Can't load the UI description: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ gtk_ui_manager_set_add_tearoffs (priv->ui_manager,
+ logview_prefs_get_have_tearoff (priv->prefs));
+
+ w = gtk_ui_manager_get_widget (priv->ui_manager, "/LogviewMenu");
+ gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 0);
+ gtk_widget_show (w);
+
+ /* panes */
+#if GTK_CHECK_VERSION (3, 0, 0)
+ hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
+#else
+ hpaned = gtk_hpaned_new ();
+#endif
+ gtk_box_pack_start (GTK_BOX (vbox), hpaned, TRUE, TRUE, 0);
+ priv->hpaned = hpaned;
+ gtk_widget_show (hpaned);
+
+ /* first pane : sidebar (list of logs) */
+ priv->sidebar = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (priv->sidebar);
+
+ /* first pane: log list */
+ w = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (w),
+ GTK_SHADOW_ETCHED_IN);
+
+ priv->loglist = logview_loglist_new ();
+ gtk_container_add (GTK_CONTAINER (w), priv->loglist);
+ gtk_box_pack_start (GTK_BOX (priv->sidebar), w, TRUE, TRUE, 0);
+ gtk_paned_pack1 (GTK_PANED (hpaned), priv->sidebar, FALSE, FALSE);
+ gtk_widget_show (w);
+ gtk_widget_show (priv->loglist);
+
+ g_signal_connect (priv->loglist, "day_selected",
+ G_CALLBACK (loglist_day_selected_cb), logview);
+ g_signal_connect (priv->loglist, "day_cleared",
+ G_CALLBACK (loglist_day_cleared_cb), logview);
+
+ /* second pane: log */
+ main_view = gtk_vbox_new (FALSE, 0);
+ gtk_paned_pack2 (GTK_PANED (hpaned), main_view, TRUE, TRUE);
+
+ /* second pane: error message area */
+ priv->message_area = gtk_info_bar_new ();
+ message_area_create_error_box (logview, priv->message_area);
+ gtk_info_bar_add_button (GTK_INFO_BAR (priv->message_area),
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
+ gtk_box_pack_start (GTK_BOX (main_view), priv->message_area, FALSE, FALSE, 0);
+
+ /* second pane: text view */
+ w = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (w), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (main_view), w, TRUE, TRUE, 0);
+ gtk_widget_show (w);
+
+ priv->tag_table = gtk_text_tag_table_new ();
+ populate_tag_table (priv->tag_table);
+ priv->text_view = gtk_text_view_new ();
+ g_object_set (priv->text_view, "editable", FALSE, NULL);
+
+ gtk_container_add (GTK_CONTAINER (w), priv->text_view);
+ gtk_widget_show (priv->text_view);
+
+ /* use the desktop monospace font */
+ monospace_font_name = logview_prefs_get_monospace_font_name (priv->prefs);
+ logview_set_font (logview, monospace_font_name);
+ g_free (monospace_font_name);
+
+ /* remember the original font size */
+ context = gtk_widget_get_pango_context (priv->text_view);
+ fontdesc = pango_context_get_font_description (context);
+ priv->original_fontsize = pango_font_description_get_size (fontdesc) / PANGO_SCALE;
+
+ /* restore saved zoom */
+ priv->fontsize = logview_prefs_get_stored_fontsize (priv->prefs);
+
+ if (priv->fontsize <= 0) {
+ /* restore the default */
+ logview_normal_text (NULL, logview);
+ } else {
+ logview_set_fontsize (logview, FALSE);
+ }
+
+ /* version selector */
+ priv->version_bar = gtk_hbox_new (FALSE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (priv->version_bar), 3);
+ priv->version_selector = gtk_combo_box_text_new ();
+ g_signal_connect (priv->version_selector, "changed",
+ G_CALLBACK (logview_version_selector_changed), logview);
+ w = gtk_label_new (_("Version: "));
+
+ gtk_box_pack_end (GTK_BOX (priv->version_bar), priv->version_selector, FALSE, FALSE, 0);
+ gtk_box_pack_end (GTK_BOX (priv->version_bar), w, FALSE, FALSE, 0);
+ gtk_box_pack_end (GTK_BOX (main_view), priv->version_bar, FALSE, FALSE, 0);
+
+ priv->find_bar = logview_findbar_new ();
+ gtk_box_pack_end (GTK_BOX (main_view), priv->find_bar, FALSE, FALSE, 0);
+
+ g_signal_connect (priv->find_bar, "previous",
+ G_CALLBACK (findbar_previous_cb), logview);
+ g_signal_connect (priv->find_bar, "next",
+ G_CALLBACK (findbar_next_cb), logview);
+ g_signal_connect (priv->find_bar, "text_changed",
+ G_CALLBACK (findbar_text_changed_cb), logview);
+ g_signal_connect (priv->find_bar, "close",
+ G_CALLBACK (findbar_close_cb), logview);
+
+ /* signal handlers
+ * - first is used to remember/restore the window size on quit.
+ */
+ g_signal_connect (logview, "configure_event",
+ G_CALLBACK (window_size_changed_cb), logview);
+ g_signal_connect (priv->prefs, "system-font-changed",
+ G_CALLBACK (font_changed_cb), logview);
+ g_signal_connect (priv->prefs, "have-tearoff-changed",
+ G_CALLBACK (tearoff_changed_cb), logview);
+ g_signal_connect (priv->manager, "active-changed",
+ G_CALLBACK (active_log_changed_cb), logview);
+ g_signal_connect (logview, "style-set",
+ G_CALLBACK (style_set_cb), logview);
+ g_signal_connect (logview, "key-press-event",
+ G_CALLBACK (key_press_event_cb), logview);
+
+ /* status area at bottom */
+ priv->statusbar = gtk_statusbar_new ();
+#if GTK_CHECK_VERSION (3, 0, 0)
+ gtk_widget_set_margin_top (GTK_WIDGET (logview->priv->statusbar), 0);
+ gtk_widget_set_margin_bottom (GTK_WIDGET (logview->priv->statusbar), 0);
+#endif
+ gtk_box_pack_start (GTK_BOX (vbox), priv->statusbar, FALSE, FALSE, 0);
+ gtk_widget_show (priv->statusbar);
+
+ /* Filter menu */
+ priv->filter_action_group = gtk_action_group_new ("ActionGroupFilter");
+ gtk_ui_manager_insert_action_group (priv->ui_manager, priv->filter_action_group,
+ 1);
+ priv->active_filters = NULL;
+ update_filter_menu (logview);
+
+ gtk_widget_show (vbox);
+ gtk_widget_show (main_view);
+}
+
+static void
+logview_window_class_init (LogviewWindowClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->finalize = logview_window_finalize;
+
+ g_type_class_add_private (klass, sizeof (LogviewWindowPrivate));
+}
+
+/* public methods */
+
+GtkWidget *
+logview_window_new ()
+{
+ LogviewWindow *logview;
+
+ logview = g_object_new (LOGVIEW_TYPE_WINDOW, NULL);
+
+ if (logview->priv->ui_manager == NULL) {
+ return NULL;
+ }
+
+ return GTK_WIDGET (logview);
+}
+
+void
+logview_window_add_error (LogviewWindow *window,
+ const char *primary,
+ const char *secondary)
+{
+ LogviewWindowPrivate *priv;
+
+ g_assert (LOGVIEW_IS_WINDOW (window));
+ priv = window->priv;
+
+ message_area_set_labels (window,
+ primary, secondary);
+
+ gtk_widget_show (priv->message_area);
+
+ g_signal_connect (priv->message_area, "response",
+ G_CALLBACK (message_area_response_cb), window);
+}
+
+void
+logview_window_add_errors (LogviewWindow *window,
+ GPtrArray *errors)
+{
+ char *primary, *secondary;
+ GString *str;
+ char **err;
+ int i;
+
+ g_assert (LOGVIEW_IS_WINDOW (window));
+ g_assert (errors->len > 1);
+
+ primary = g_strdup (_("Could not open the following files:"));
+ str = g_string_new (NULL);
+
+ for (i = 0; i < errors->len; i++) {
+ err = (char **) g_ptr_array_index (errors, i);
+ g_string_append (str, err[0]);
+ g_string_append (str, ": ");
+ g_string_append (str, err[1]);
+ g_string_append (str, "\n");
+ }
+
+ secondary = g_string_free (str, FALSE);
+
+ message_area_set_labels (window, primary, secondary);
+
+ gtk_widget_show (window->priv->message_area);
+
+ g_signal_connect (window->priv->message_area, "response",
+ G_CALLBACK (message_area_response_cb), window);
+
+ g_free (primary);
+ g_free (secondary);
+}
+
+
diff --git a/logview/src/logview-window.h b/logview/src/logview-window.h
new file mode 100644
index 00000000..f9fd5303
--- /dev/null
+++ b/logview/src/logview-window.h
@@ -0,0 +1,56 @@
+/* logview-window.h - main window of logview
+ *
+ * Copyright (C) 1998 Cesar Miquel <[email protected]>
+ * Copyright (C) 2008 Cosimo Cecchi <[email protected]>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __LOGVIEW_WINDOW_H__
+#define __LOGVIEW_WINDOW_H__
+
+#include <gtk/gtk.h>
+
+#define LOGVIEW_TYPE_WINDOW (logview_window_get_type ())
+#define LOGVIEW_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), LOGVIEW_TYPE_WINDOW, LogviewWindow))
+#define LOGVIEW_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), LOGVIEW_TYPE_WINDOW, LogviewWindowClass))
+#define LOGVIEW_IS_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), LOGVIEW_TYPE_WINDOW))
+#define LOGVIEW_IS_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), LOGVIEW_TYPE_WINDOW))
+#define LOGVIEW_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), LOGVIEW_TYPE_WINDOW, LogviewWindowClass))
+
+typedef struct _LogviewWindow LogviewWindow;
+typedef struct _LogviewWindowClass LogviewWindowClass;
+typedef struct _LogviewWindowPrivate LogviewWindowPrivate;
+
+struct _LogviewWindow {
+ GtkWindow parent_instance;
+ LogviewWindowPrivate *priv;
+};
+
+struct _LogviewWindowClass {
+ GtkWindowClass parent_class;
+};
+
+GType logview_window_get_type (void);
+
+/* public methods */
+GtkWidget * logview_window_new (void);
+void logview_window_add_error (LogviewWindow *window,
+ const char *primary,
+ const char *secondary);
+void logview_window_add_errors (LogviewWindow *window,
+ GPtrArray *errors);
+
+#endif /* __LOGVIEW_WINDOW_H__ */
diff --git a/logview/src/tests/Makefile.am b/logview/src/tests/Makefile.am
new file mode 100644
index 00000000..b7b668c3
--- /dev/null
+++ b/logview/src/tests/Makefile.am
@@ -0,0 +1,9 @@
+AM_CPPFLAGS = \
+ $(GLIB_CFLAGS) \
+ $(GIO_CFLAGS) \
+ -I../
+
+noinst_PROGRAMS = test-reader
+
+test_reader_SOURCES = test-reader.c ../logview-log.c ../logview-utils.c
+test_reader_LDADD = $(GLIB_LIBS) $(GIO_LIBS) $(Z_LIBS) -lm
diff --git a/logview/src/tests/test-reader.c b/logview/src/tests/test-reader.c
new file mode 100644
index 00000000..b9ef5d40
--- /dev/null
+++ b/logview/src/tests/test-reader.c
@@ -0,0 +1,60 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "../logview-log.h"
+#include "../logview-utils.h"
+
+#include <glib.h>
+#include <gio/gio.h>
+
+static GMainLoop *loop;
+
+static void
+new_lines_cb (LogviewLog *log,
+ const char **lines,
+ GSList *new_days,
+ GError *error,
+ gpointer user_data)
+{
+ int i;
+ guint8 day;
+ Day *day_s;
+ GSList *days, *l;
+
+ for (i = 0; lines[i]; i++) {
+ g_print ("line %d: %s\n", i, lines[i]);
+ }
+ g_print ("outside read, lines no %u\n", logview_log_get_cached_lines_number (log));
+
+ days = log_read_dates (lines, logview_log_get_timestamp (log));
+ g_print ("\ndays %p\n", days);
+
+ for (l = days; l; l = l->next) {
+ day_s = l->data;
+ g_print ("\nday %u month %u\n", g_date_get_day (day_s->date), g_date_get_month (day_s->date));
+ }
+
+ g_object_unref (log);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+callback (LogviewLog *log,
+ GError *error,
+ gpointer user_data)
+{
+ g_print ("callback! err %p, log %p\n", error, log);
+
+ logview_log_read_new_lines (log, NULL, new_lines_cb, NULL);
+}
+
+int main (int argc, char **argv)
+{
+ loop = g_main_loop_new (NULL, FALSE);
+ logview_log_create ("/var/log/dpkg.log.2.gz", callback, NULL);
+ g_main_loop_run (loop);
+
+ return 0;
+}