diff options
author | monsta <[email protected]> | 2015-09-09 10:46:57 +0300 |
---|---|---|
committer | monsta <[email protected]> | 2015-09-09 10:46:57 +0300 |
commit | c9cadcd53d03e73475dd266aad223a1ebc9229b5 (patch) | |
tree | 0166a7c33476e9d9a4a2a3a3b66bde69594dc242 /logview/src | |
parent | 09a33e1feefbf5bc06eb5692b03c894c3b48daa9 (diff) | |
download | mate-utils-c9cadcd53d03e73475dd266aad223a1ebc9229b5.tar.bz2 mate-utils-c9cadcd53d03e73475dd266aad223a1ebc9229b5.tar.xz |
logview: move sources to src/ subdir
Diffstat (limited to 'logview/src')
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", ®ex, + "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, ¤t, -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", ®ex, + "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 (¤t); + 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, ×tamp_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, ×tamp_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 (×tamp)); + 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; +} |