Logo Search packages:      
Sourcecode: pan version File versions  Download package

grouplist.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Pan - A Newsreader for Gtk+
 * Copyright (C) 2002  Charles Kerr <charles@rebelbase.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <config.h>

#include <errno.h>
#include <string.h>

#include <glib.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>

#include <pan/base/argset.h>
#include <pan/base/debug.h>
#include <pan/base/file-grouplist.h>
#include <pan/base/pan-config.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/serverlist.h>

#include <pan/articlelist.h>
#include <pan/dialogs/dialogs.h>
#include <pan/globals.h>
#include <pan/grouplist.h>
#include <pan/group-action.h>
#include <pan/group-ui.h>
#include <pan/gui.h>
#include <pan/prefs.h>
#include <pan/queue.h>
#include <pan/task-grouplist.h>
#include <pan/util.h>

#include <pan/xpm/pan-pixbufs.h>

/***
****
****  PRIVATE VARIABLES
****
***/

static GtkItemFactoryEntry group_popup_entries[] =
{
      /* h */ {N_("/Get New _Headers"), NULL, group_action_selected_download_new, 0, NULL},
      /* b */ {N_("/Get New Headers and _Bodies"), NULL, group_action_selected_download_new_and_bodies, 0, NULL},
      /*   */ {N_("/Get New Headers in Subscribed Groups"), NULL, group_action_subscribed_download_new, 0, NULL},
      /* o */ {N_("/More Download _Options..."), NULL, group_action_selected_download_dialog, 0, NULL},
      /* c */ {N_("/Refresh Article _Counts"), NULL, group_action_selected_update_count_info, 0, "<StockItem>", GTK_STOCK_REFRESH},
      /*   */ {N_("/---"), NULL, NULL, 0, "<Separator>"},
      /* s */ {N_("/_Subscribe"), NULL, group_action_selected_subscribe, 0, "<ImageItem>", icon_newsgroup},
      /* u */ {N_("/_Unsubscribe"), NULL, group_action_selected_unsubscribe, 0, NULL},
      /* p */ {N_("/Group _Properties..."), NULL, group_action_selected_properties, 0, "<StockItem>", GTK_STOCK_PROPERTIES},
      /*   */ {N_("/---"), NULL, NULL, 0, "<Separator>"},
      /* r */ {N_("/Mark Group _Read"), NULL, group_action_selected_mark_read, 0, NULL},
      /* d */ {N_("/_Delete Group's Articles"), NULL, group_action_selected_empty, 0, "<StockItem>", GTK_STOCK_DELETE},
      /*   */ {N_("/---"), NULL, NULL, 0, "<Separator>"},
      /* t */ {N_("/Dele_te Group"), NULL, group_action_selected_destroy, 0, "<StockItem>", GTK_STOCK_DELETE}
};

extern GtkWidget   * ttips;
static GtkItemFactory * popup_factory        = NULL;
static GtkWidget * popup_menu                = NULL;

static int           group_mode              = GROUP_ALL;
static Server      * my_server               = NULL;
static GtkWidget   * grouplist_mode_menu     = NULL;
static GtkWidget   * groupname_filter_entry  = NULL;
static char       * groupname_filter_string = NULL;

PanCallback * grouplist_group_selection_changed = NULL;


/***
****
****  GROUP SELECTION
****
***/

static void
grouplist_set_selected_groups_nolock (const GPtrArray * a)
{
      GtkCList * clist;
      debug_enter ("grouplist_set_selected_groups_nolock");

      if (a != NULL)
      {
            guint i;

            clist = GTK_CLIST(Pan.group_tree);
            gtk_clist_freeze (clist);
            gtk_clist_unselect_all (clist);
            for (i=0; i!=a->len; ++i)
            {
                  const Group * g = GROUP(g_ptr_array_index(a,i));
                  const int row = gtk_clist_find_row_from_data (clist, (gpointer)g);
                  gtk_clist_select_row (clist, row, 0);
                  if (!gtk_clist_row_is_visible (clist, row))
                        gtk_clist_moveto (clist, row, 0, 0.5f, 0.0f);
            }
            gtk_clist_thaw (clist);
      }

      debug_exit ("grouplist_set_selected_groups_nolock");
}

Group*
grouplist_get_selected_group (void)
{
      GList * l;
      GtkCList * clist;
      Group * retval = NULL;
      debug_enter ("grouplist_get_selected_group");

      pan_lock ();
      clist = GTK_CLIST(Pan.group_tree);
      l = clist->selection;
      if (l != NULL)
            retval = GROUP(gtk_clist_get_row_data(clist, GPOINTER_TO_INT(l->data)));
      pan_unlock ();

      debug_exit ("grouplist_get_selected_group");
      return retval;
}

GPtrArray*
grouplist_get_selected_groups (void)
{
      GtkCList * clist = GTK_CLIST(Pan.group_tree);
      GPtrArray * retval = g_ptr_array_new ();
      GList * l = NULL;
      debug_enter ("grouplist_get_selected_groups");

      /* get the group selected items; otherwise, 
         use the articlelist's group */
      pan_lock ();
      for (l=clist->selection; l!=NULL; l=l->next)
            g_ptr_array_add (retval, gtk_clist_get_row_data (clist, GPOINTER_TO_INT (l->data)));
      pan_unlock ();

      if (!retval->len) {
            Group * group = articlelist_get_group ();
            if (group != NULL)
                  g_ptr_array_add (retval, group);
      }

      debug_exit ("grouplist_get_selected_groups");
      return retval;
}

static int
grouplist_select_all_idle (gpointer unused) {
      pan_lock ();
      gtk_clist_select_all (GTK_CLIST (Pan.group_tree));
      pan_unlock ();
      return 0;
}
void
grouplist_select_all (void) {
      gui_queue_add (grouplist_select_all_idle, NULL);
}

static int
grouplist_deselect_all_idle (gpointer unused)
{
      pan_lock ();
      gtk_clist_unselect_all (GTK_CLIST(Pan.group_tree));
      pan_unlock ();
      return 0;
}
void
grouplist_deselect_all (void)
{
      gui_queue_add (grouplist_deselect_all_idle, NULL);
}


/***
****
****  REFRESH GROUPLIST
****
***/

static void
grouplist_get_group_name (const Group * group, char * buf, int buflen)
{
      const char * name = group_get_name (group);
      int bufleft;

      /**
      ***  fill the name
      **/

      if (!collapse_group_names)
            g_strlcpy (buf, name, buflen);
      else
            group_get_collapsed_name (group, buf, buflen);
      bufleft = buflen - strlen(buf);

      /**
      ***  add the parenthetical status messages
      **/

      if (group_is_moderated (group)) {
            strncat (buf, _(" (Moderated)"), bufleft);
            buf[buflen-1] = '\0';
            bufleft = buflen - strlen(buf);
      }

      if (group_is_read_only (group)) {
            strncat (buf, _(" (Read-Only)"), bufleft);
            buf[buflen-1] = '\0';
            bufleft = buflen - strlen(buf);
      }
}

static GdkPixmap * sub_pixmap = NULL;
static GdkBitmap * sub_bitmap = NULL;
static GdkPixmap * folder_pixmap = NULL;
static GdkBitmap * folder_bitmap = NULL;

static void
grouplist_add_group_nolock (GtkCList * clist, const Group * group)
{
      int row;
      char total_buf[16] = { '\0' };
      char unread_buf[16] = { '\0' };
      char name_buf[256] = { '\0' };
      char * freeme1 = NULL;
      char * freeme2 = NULL;
      const int total = group->article_qty;
      const int unread = MAX(0, total - group->article_read_qty);
      char * text[5];
      static gboolean inited = FALSE;

      /* inistialize the picture */
      if (!inited)
      {
            GdkPixbuf * pixbuf;

            pixbuf = gdk_pixbuf_new_from_inline (-1, icon_newsgroup, FALSE, NULL);
            gdk_pixbuf_render_pixmap_and_mask_for_colormap (pixbuf,
                                                            gtk_widget_get_colormap(Pan.group_tree),
                                                            &sub_pixmap,
                                                            &sub_bitmap, 128);
            g_object_unref (G_OBJECT(pixbuf));

            /* initialize the folder icon */
            pixbuf = gdk_pixbuf_new_from_inline (-1, icon_folder, FALSE, NULL);
            gdk_pixbuf_render_pixmap_and_mask_for_colormap (pixbuf,
                                                            gtk_widget_get_colormap(Pan.group_tree),
                                                            &folder_pixmap,
                                                            &folder_bitmap, 128);
            g_object_unref (G_OBJECT(pixbuf));

            inited = TRUE;
      }

      /* convert numbers to strings */
      g_snprintf (total_buf, sizeof(total_buf), "%d", total);
      g_snprintf (unread_buf, sizeof(unread_buf), "%d", unread);

      /* look for special qualities of the group */
      grouplist_get_group_name (group, name_buf, sizeof(name_buf));
     
      text[0] = "";
      text[1] = (char*) pan_utf8ize (name_buf, -1, &freeme1);
      text[2] = unread_buf;
      text[3] = total_buf;
      text[4] = (char*) pan_utf8ize (group->description, -1, &freeme2);

      /* add the row */
      row = gtk_clist_prepend (clist, (char**)text);
      gtk_clist_set_row_data (clist, row, (gpointer)group);

      /* add the pixmap, if any */
      if (group_is_folder (group))
            gtk_clist_set_pixmap (clist, row, 0, folder_pixmap, folder_bitmap);
      else if (group_is_subscribed (group))
            gtk_clist_set_pixmap (clist, row, 0, sub_pixmap, sub_bitmap);

      /* cleanup */
      g_free (freeme1);
      g_free (freeme2);
}

static void
grouplist_rebuild_nolock (GPtrArray * groups, GPtrArray * sel)
{
      int i;
      GtkCList * clist = GTK_CLIST (Pan.group_tree);
      GPatternSpec * pattern = NULL;
      const gboolean do_filter = is_nonempty_string (groupname_filter_string);

      gtk_clist_freeze (clist);
      gtk_clist_clear (clist);

      /* rebuild the clist */
      if (do_filter)
            pattern = g_pattern_spec_new (groupname_filter_string);
      for (i=groups->len-1; i>=0; --i) {
            Group * group = GROUP (g_ptr_array_index (groups, i));
            if (!do_filter || g_pattern_match (pattern, group->name.len, group->name.str, NULL))
                  grouplist_add_group_nolock (clist, group);
      }
      if (do_filter)
            g_pattern_spec_free (pattern);

      /* refresh the selection */
      if (sel!=NULL && sel->len!=0)
            grouplist_set_selected_groups_nolock (sel);

      gtk_clist_thaw (clist);
}

static void grouplist_server_groups_added_cb (gpointer, gpointer, gpointer);

static int
grouplist_update_ui_mainthread (gpointer unused)
{
      char * title;
      GtkCList * clist = GTK_CLIST(Pan.group_tree);
      GPtrArray * groups;
      GPtrArray * sel;
      debug_enter ("grouplist_update_ui_mainthread");

      /**
      ***  Get the list of groups that we're going to add.
      ***
      ***  Note that we temporarily turn off the groups_added
      ***  callback -- the groups may be loaded as a result of
      ***  our call, and we don't want to update the clist twice.
      ***  This is kind of an ugly hack.
      **/
      pan_callback_remove (server_get_groups_added_callback(),
                           grouplist_server_groups_added_cb, NULL);

      if (group_mode == GROUP_SUBSCRIBED)
      {
            groups = my_server==NULL
                  ? g_ptr_array_new ()
                  : server_get_groups (my_server, SERVER_GROUPS_SUBSCRIBED);
      }
      else if (group_mode == GROUP_ALL)
      {
            groups = my_server==NULL
                  ? g_ptr_array_new ()
                  : server_get_groups (my_server, SERVER_GROUPS_ALL);
      }
      else if (group_mode == GROUP_FOLDERS)
      {
            Server * s = serverlist_get_named_server(&INTERNAL_SERVER_NAME);
            groups = s==NULL
                  ? g_ptr_array_new()
                  : server_get_groups (s, SERVER_GROUPS_ALL);
      }
      else if (group_mode == GROUP_NEW)
      {
            groups = g_ptr_array_new ();
            if (my_server != NULL) {
                  guint i;
                  GPtrArray * a = server_get_groups (my_server, SERVER_GROUPS_ALL);
                  for (i=0; i<a->len; ++i) {
                        Group * g = GROUP(g_ptr_array_index(a,i));
                        if (group_is_new (g))
                              g_ptr_array_add (groups, g);
                  }
                  g_ptr_array_free (a, TRUE);
            }
      }
      else
      {
            pan_warn_if_reached ();
            groups = g_ptr_array_new ();
      }

      pan_callback_add (server_get_groups_added_callback(),
                        grouplist_server_groups_added_cb, NULL);

      /* get the list of selected groups */
            sel = grouplist_get_selected_groups ();

      /* set the groups column title */
      switch (group_mode) {
            case GROUP_SUBSCRIBED: title = _("Subscribed");         break;
            case GROUP_ALL:        title = _("All Groups");         break;
            case GROUP_NEW:        title = _("New Groups");         break;
            case GROUP_FOLDERS:    title = _("Folders");            break;
            default:               title = "Bug!";                  break;
      }

      /* update UI */
      pan_lock ();
      gtk_clist_set_column_title (clist, 1, title);
      grouplist_rebuild_nolock (groups, sel);
      pan_unlock ();

      /* cleanup */
      g_ptr_array_free (sel, TRUE);
      g_ptr_array_free (groups, TRUE);
      debug_exit ("grouplist_update_ui_mainthread");
      return 0;
}

static Server *
grouplist_get_visible_server (void)
{
      return group_mode==GROUP_FOLDERS
            ? serverlist_get_named_server(&INTERNAL_SERVER_NAME)
            : my_server;
}

void
grouplist_refresh_nolock (void)
{
      Server * server = grouplist_get_visible_server ();

      if (server!=NULL && server==grouplist_get_visible_server())
            gui_queue_add (grouplist_update_ui_mainthread, NULL);
}

static void
grouplist_server_groups_added_cb (gpointer server, gpointer groups, gpointer foo)
{
      Server * visible;
      debug_enter ("grouplist_server_groups_added_cb");

      visible = grouplist_get_visible_server ();
      if (visible!=NULL && visible==SERVER(server))
            grouplist_refresh_nolock ();

      debug_exit ("grouplist_server_groups_added_cb");
}

/***
****
****  KEYBOARD / MOUSE INTERACTION
****
***/

/*
 * This tries to reconcile * the desired behavior with GtkCList's quirks.
 * If the user selects a range, we don't want to activate the group
 * because the user is selecting the range to perform a batch operation.
 * However the GtkCList selection signal fires once per row, so there's
 * no way to tell if the first callback is part of a range or not.
 * The workaround is to push the work off to an idle function to be
 * invoked after all the selection callbacks are done.  The idle function
 * fires the grouplist seleciton pan_callback.
 */

static int _button_click_count = -1;
static int _mb = -1;
static gboolean _modifiers = FALSE;

static void
grouplist_selection_changed_cb (gpointer call_object,
                                gpointer call_arg,
                                gpointer user_data)
{
      GPtrArray * groups = (GPtrArray*)call_arg;
      const gboolean activate = !_modifiers && _mb==1 && _button_click_count>=(single_click_selects_groups ? 2 : 1);
      debug_enter ("grouplist_selection_changed_cb");

      /* sanity clause */
      g_return_if_fail (groups!=NULL);

      /* do we activate this group? */
      if (groups->len==1 && activate)
      {
            gui_page_set (HEADERS_PANE);
            articlelist_set_group (GROUP(g_ptr_array_index(groups,0)));
      }

      /* reset the click count for next time... */
      _button_click_count = -1;
      _modifiers = FALSE;
      _mb = -1;

      debug_exit ("grouplist_selection_changed_cb");
}

static gboolean
grouplist_button_press (GtkWidget *widget, GdkEventButton * event)
{
      gboolean retval = FALSE;
      GdkModifierType modifiers = 0;
      GtkItemFactory * gif;
      Group * group;
      gboolean have_group;
      debug_enter ("grouplist_button_press");

      gdk_event_get_state ((GdkEvent*)event, &modifiers);
      _modifiers = modifiers & (GDK_SHIFT_MASK || GDK_CONTROL_MASK || GDK_MOD1_MASK || GDK_MOD2_MASK || GDK_MOD3_MASK || GDK_MOD4_MASK || GDK_MOD5_MASK);

      switch (event->button)
      {
            case 1:
            case 2:
                  _mb = event->button;
                  _button_click_count = event->type==GDK_2BUTTON_PRESS ? 2 : 1;

                  retval = FALSE;
                  break;

            case 3:
                  gif = popup_factory;
                  group = grouplist_get_selected_group ();
                  have_group = group!=NULL;

                  menu_set_sensitive (gif, "/Mark Group Read", have_group);
                  menu_set_sensitive (gif, "/Delete Group's Articles", have_group);
                  menu_set_sensitive (gif, "/Get New Headers", have_group);
                  menu_set_sensitive (gif, "/Get New Headers and Bodies", have_group);
                  menu_set_sensitive (gif, "/Get New Headers in Subscribed Groups", TRUE);
                  menu_set_sensitive (gif, "/More Download Options...", have_group);
                  menu_set_sensitive (gif, "/Refresh Article Counts", have_group);
                  menu_set_sensitive (gif, "/Subscribe", have_group);
                  menu_set_sensitive (gif, "/Unsubscribe", have_group);
                  menu_set_sensitive (gif, "/Group Properties...", have_group);
                  menu_set_sensitive (gif, "/Delete Group", have_group);
                  gtk_menu_popup (GTK_MENU(popup_menu), NULL, NULL, NULL, NULL, event->button, event->time);

                  retval = TRUE;
                  break;
      }

      debug_exit ("grouplist_button_press");
      return retval;
}

static gboolean select_callback_pending = FALSE;

static int
select_row_cb_mainthread (gpointer data)
{
      GPtrArray * groups;
      debug_enter ("select_row_cb_mainthread");

      /* get the selected groups */
            groups = grouplist_get_selected_groups ();

      /* make the callbacks */
      pan_callback_call (grouplist_group_selection_changed,
                         Pan.group_tree,
                     groups);
      select_callback_pending = FALSE;

      /* if the header pane's group is no longer selected, hide it.
       * http://bugzilla.gnome.org/show_bug.cgi?id=102244 */
      if (1) {
            Group * group = articlelist_get_group ();
            if (group != NULL) {
                  guint i;
                  gboolean match;
                  for (match=FALSE, i=0; !match && i<groups->len; ++i)
                        if (g_ptr_array_index(groups,i) == group)
                              match = TRUE;
                  if (!match)
                        articlelist_set_group (NULL);
            }
      }

      /* cleanup */
      g_ptr_array_free (groups, TRUE);
      debug_exit ("select_row_cb_mainthread");
      return FALSE;
}
static void
select_row_cb (GtkWidget  * widget,
               int          row,
               int          col,
               GdkEvent   * event)
{
      debug_enter ("select_row_cb");

      if (!select_callback_pending)
      {
            select_callback_pending = TRUE;
            gui_queue_add (select_row_cb_mainthread, NULL);
      }

      debug_exit ("select_row_cb");
}
/*****
****** ccc
*****/

void
grouplist_get_all (void)
{
      if (my_server == NULL)
            pan_error_dialog ("No server selected.");
      else
            queue_add (TASK(task_grouplist_new (my_server, GROUPLIST_ALL)));
}

void
grouplist_get_new (void)
{
      if (my_server == NULL)
            pan_error_dialog ("No server selected.");
      else
            queue_add (TASK(task_grouplist_new (my_server, GROUPLIST_NEW)));
}

/*****
******
*****/

void
grouplist_add_subscribed_to_selection_nolock (void)
{
      GtkCList * clist = GTK_CLIST (Pan.group_tree);
      const int row_qty = clist->rows;
      const gboolean do_freeze = row_qty > 3;
      int row;

      if (do_freeze)
            gtk_clist_freeze (clist);
      for (row=0; row<row_qty; ++row)
            if (group_is_subscribed (GROUP(gtk_clist_get_row_data(clist,row))))
                  gtk_clist_select_row (clist, row, -1);
      if (do_freeze)
            gtk_clist_thaw (clist);
}

/***
****
****  GROUPS CHANGED
****
***/

static void
grouplist_update_group_nolock (GtkCList * clist, const Group * group, guint change_type)
{
      int row;

      /* sanity check */
      g_return_if_fail (group != NULL);

            row = gtk_clist_find_row_from_data (clist, (gpointer)group);
      if (row != -1)
      {
            if (change_type & GROUP_CHANGED_SUBSCRIPTION_STATE)
            {
                  if (group_is_subscribed (group))
                        gtk_clist_set_pixmap (clist, row, 0, sub_pixmap, sub_bitmap);
                  else if (!group_is_folder(group))
                        gtk_clist_set_text (clist, row, 0, "");
            }

            if (change_type & GROUP_CHANGED_ARTICLE_QTY)
            {
                  char buf[256] = { '\0' };
                  g_snprintf (buf, sizeof(buf), "%d", group->article_qty);
                  gtk_clist_set_text (clist, row, 3, buf);
            }

            if ((change_type & GROUP_CHANGED_ARTICLE_QTY)
                || (change_type & GROUP_CHANGED_ARTICLE_READ_QTY))
            {
                  char buf[256] = { '\0' };
                  const int unread = group->article_qty - group->article_read_qty;
                  g_snprintf (buf, sizeof(buf), "%d", MAX(0,unread));
                  gtk_clist_set_text (clist, row, 2, buf);
            }
      }
}

static int
grouplist_groups_changed_idle (gpointer data)
{
      guint i;
      struct GroupChangeEvent * e = (struct GroupChangeEvent*) data;
      const gboolean do_freeze = e->group_qty > 3u;
      Server * server = grouplist_get_visible_server ();
      GtkCList * clist = GTK_CLIST (Pan.group_tree);
      debug_enter ("grouplist_groups_changed_idle");

      pan_lock ();

      if (do_freeze)
            gtk_clist_freeze (clist);
      for (i=0; i!=e->group_qty; ++i)
            if (group_is_valid(e->groups[i]) && e->groups[i]->server==server)
                  grouplist_update_group_nolock (clist, e->groups[i], e->change_type);
      if (do_freeze)
            gtk_clist_thaw (clist);

      pan_unlock ();

      /* cleanup */
      g_free (e->groups);
      g_free (e);
      debug_exit ("grouplist_groups_changed_idle");
      return 0;
}

static void
grouplist_groups_changed_cb (gpointer change_event_gpointer, gpointer unused, gpointer unused2)
{
      struct GroupChangeEvent * e = (struct GroupChangeEvent*) change_event_gpointer;
      struct GroupChangeEvent * my_e;
      debug_enter ("grouplist_groups_changed_cb");

      my_e = g_new0 (struct GroupChangeEvent, 1);
      my_e->groups = g_memdup (e->groups, sizeof(Group*) * e->group_qty);
      my_e->group_qty = e->group_qty;
      my_e->change_type = e->change_type;
      gui_queue_add (grouplist_groups_changed_idle, my_e);

      debug_exit ("grouplist_groups_changed_cb");
}

/***
****  GROUPS REMOVED
***/

static int
grouplist_server_groups_removed_idle (gpointer data)
{
      GPtrArray * groups;
      debug_enter ("grouplist_server_groups_removed_idle");

      /* if the group's server matches the visible server... */
            groups = (GPtrArray*) data;
      if (groups->len && (GROUP(g_ptr_array_index(groups,0))->server == grouplist_get_visible_server()))
      {
            guint i;
            GtkCList * clist = GTK_CLIST(Pan.group_tree);
            const gboolean do_freeze = groups->len > 3;

            /* remove the rows that match the groups that were removed. */
            pan_lock();
            if (do_freeze)
                  gtk_clist_freeze (clist);
            for (i=0; i<groups->len; ++i) {
                  const int row = gtk_clist_find_row_from_data (clist, g_ptr_array_index(groups,i));
                  if (row != -1)
                        gtk_clist_remove (clist, row);
            }
            if (do_freeze)
                  gtk_clist_thaw (clist);
            pan_unlock();
      }

      /* cleanup */
      g_ptr_array_free (groups, TRUE);
      debug_exit ("grouplist_server_groups_removed_idle");
      return 0;
}
static void
grouplist_server_groups_removed_cb (gpointer server, gpointer groups, gpointer foo)
{
      gui_queue_add (grouplist_server_groups_removed_idle, pan_g_ptr_array_dup((GPtrArray*)groups));
}

/***
****
****  FILTERING
****
***/

static void
grouplist_refresh_filter_nolock (void)
{
      char * new_filter;
        char * pch;

      pch  = gtk_editable_get_chars (GTK_EDITABLE(groupname_filter_entry), 0, -1);

      /* build the new filter string */
      new_filter = NULL;
      g_strstrip (pch);
      if (is_nonempty_string (pch))
            new_filter = strchr (pch,'*') ? g_strdup(pch) : g_strdup_printf ("*%s*", pch);

      /* if it differs from the old filter string, refresh */
      if (pan_strcmp (new_filter, groupname_filter_string)) {
            replace_gstr (&groupname_filter_string, g_strdup(new_filter));
            grouplist_refresh_nolock ();
      }

      /* cleanup */
      g_free (pch);
      g_free (new_filter);
}

static gboolean
grab_focus_idle (gpointer w)
{
      pan_lock ();
      gtk_widget_grab_focus (GTK_WIDGET(w));
      pan_unlock ();
      return FALSE;
}

static gboolean
grouplist_filter_changed_cb_nolock (void)
{
      grouplist_refresh_filter_nolock ();

      gui_queue_add (grab_focus_idle, Pan.group_tree);

      return FALSE;
}

/***
****
****  VIEW MODE
****
***/

static void
grouplist_set_view_mode_nolock_impl (int view_mode)
{
      GtkWidget *menu = NULL;
      debug_enter ("grouplist_set_view_mode_nolock_impl");

        menu = gtk_option_menu_get_menu (GTK_OPTION_MENU(grouplist_mode_menu));
        g_object_ref (G_OBJECT(menu));
        gtk_option_menu_remove_menu (GTK_OPTION_MENU (grouplist_mode_menu));
      switch (group_mode = view_mode) {
            case GROUP_ALL:
                  gtk_menu_set_active (GTK_MENU (menu), 0);
                  break;
            case GROUP_SUBSCRIBED:
                  gtk_menu_set_active (GTK_MENU (menu), 1);
                  break;
            case GROUP_NEW:
                  gtk_menu_set_active (GTK_MENU (menu), 2);
                  break;
            case GROUP_FOLDERS:
                  gtk_menu_set_active (GTK_MENU (menu), 3);
                  break;
            default: break;
      }
        gtk_option_menu_set_menu (GTK_OPTION_MENU(grouplist_mode_menu), menu);
        g_object_unref (G_OBJECT(menu));

      debug_exit ("grouplist_set_view_mode_nolock_impl");
}

static void
grouplist_set_view_mode_nolock (GtkWidget *widget, gpointer data)
{
      grouplist_set_view_mode_nolock_impl (GPOINTER_TO_INT(data));
      grouplist_refresh_nolock ();
}


/***
****
****  SERVER CHANGED / ACTIVATED
****
***/

static void
grouplist_dialog_response_cb (GtkDialog * dialog, int response, gpointer user_data)
{
      if (response == GTK_RESPONSE_YES)
            queue_add (TASK(task_grouplist_new (SERVER(user_data), GROUPLIST_ALL)));
      gtk_widget_destroy (GTK_WIDGET(dialog));
}

static void
grouplist_set_server_nolock (Server * server)
{
      Server * old_server;
      debug_enter ("grouplist_set_server_nolock");

      /* find out what server we have, and what kind of groups it has */
      old_server = my_server;
      my_server = server;

      if (my_server==NULL)
      {
            if (grouplist_get_visible_server()==old_server)
                  gtk_clist_clear (GTK_CLIST(Pan.group_tree));
      }
      else
      {
            int view_mode;
            GPtrArray * groups = NULL;
            const ServerGroupsType have_type = file_grouplist_exists (my_server);

            /* if we have subbed, always load those, otherwise load all */
            if (have_type & SERVER_GROUPS_SUBSCRIBED)
            {
                  view_mode = GROUP_SUBSCRIBED;

                  groups = my_server==NULL
                        ? g_ptr_array_new ()
                        : server_get_groups (my_server, SERVER_GROUPS_SUBSCRIBED);
            }
            else if (have_type)
            {
                  view_mode = GROUP_ALL;

                  groups = my_server==NULL
                        ? g_ptr_array_new ()
                        : server_get_groups (my_server, SERVER_GROUPS_ALL);
            }
            else
            {
                  GtkWidget * w;

                        view_mode = GROUP_ALL;
                  groups = NULL;

                  if (grouplist_get_visible_server()==old_server)
                        grouplist_rebuild_nolock (NULL, NULL);

                  w = gtk_message_dialog_new (GTK_WINDOW(Pan.window),
                                              GTK_DIALOG_DESTROY_WITH_PARENT,
                                              GTK_MESSAGE_QUESTION,
                                              GTK_BUTTONS_YES_NO,
                                              _("We don't have a list of groups for \"%*.*s\".\nShall we download one?"),
                                              server->name.len, server->name.len, server->name.str);
                  gtk_dialog_set_default_response (GTK_DIALOG(w), GTK_RESPONSE_YES);
                  g_signal_connect (w, "response", G_CALLBACK(grouplist_dialog_response_cb), server);
                  gtk_widget_show_all (w);
            }

            /* update the UI */
            grouplist_set_view_mode_nolock_impl (view_mode);
            if (groups != NULL)
                  grouplist_rebuild_nolock (groups, NULL);

            /* cleanup */
            if (groups != NULL)
                  g_ptr_array_free (groups, TRUE);
      }

      debug_exit ("grouplist_set_server_nolock");
}

static int
grouplist_server_activated_idle (gpointer server) {
      pan_lock ();
      grouplist_set_server_nolock (SERVER(server));
      pan_unlock ();
      return 0;
}
static void
grouplist_server_activated_cb (gpointer server, gpointer unused1, gpointer unused2) {
      gui_queue_add (grouplist_server_activated_idle, server);
}

/***
****
****  LIFE CYCLE - SHUTDOWN
****
***/

void
grouplist_shutdown_module (void)
{
      /* stop listening... */
      pan_callback_remove (serverlist_get_server_activated_callback(),
                           grouplist_server_activated_cb, NULL);
      pan_callback_remove (group_get_groups_changed_callback(),
                           grouplist_groups_changed_cb, NULL);
      pan_callback_remove (grouplist_group_selection_changed,
                           grouplist_selection_changed_cb, NULL);
      pan_callback_remove (server_get_groups_added_callback(),
                           grouplist_server_groups_added_cb, NULL);
      pan_callback_remove (server_get_groups_removed_callback(),
                           grouplist_server_groups_removed_cb, NULL);
}

/***
****
****  LIFE CYCLE - START UP
****
***/

extern GtkAccelGroup * _main_accel_group;

static gboolean
entry_focus_in_cb (GtkWidget * w, GdkEventKey * event, gpointer user_data) {
      g_object_ref (_main_accel_group);
      gtk_window_remove_accel_group (GTK_WINDOW(Pan.window), _main_accel_group);
      return FALSE;
}
static gboolean
entry_focus_out_cb (GtkWidget * w, GdkEventKey * event, gpointer user_data) {
      gtk_window_add_accel_group (GTK_WINDOW(Pan.window), _main_accel_group);
      g_object_unref (_main_accel_group);
      return FALSE;
}

typedef struct
{
      char * name;
      int mode;
}
GrouplistModeMenuStruct;

static GtkWidget*
grouplist_mode_menu_create (void)
{
      GtkWidget *option_menu = gtk_option_menu_new();
      GtkWidget *menu = gtk_menu_new ();
      int index = 0;
      int i;
      GrouplistModeMenuStruct foo[] = {
            {NULL, GROUP_ALL},
            {NULL, GROUP_SUBSCRIBED},
            {NULL, GROUP_NEW},
            {NULL, GROUP_FOLDERS}
      };
      const int row_qty = sizeof(foo) / sizeof(foo[0]);
      foo[0].name = _("All Groups");
      foo[1].name = _("Subscribed");
      foo[2].name = _("New Groups");
      foo[3].name = _("Folders");

      for (i=0; i<row_qty; ++i) {
            GtkWidget *item = gtk_menu_item_new_with_label (foo[i].name);
            gtk_widget_show (item);
            gtk_menu_append (GTK_MENU (menu), item);
            g_signal_connect (item, "activate",
                          G_CALLBACK(grouplist_set_view_mode_nolock), GINT_TO_POINTER(foo[i].mode));
            if (group_mode == foo[i].mode)
                  index = i;
      }

      gtk_menu_set_active (GTK_MENU(menu), index);
        gtk_option_menu_set_menu (GTK_OPTION_MENU (option_menu), menu);
      gtk_widget_show_all (GTK_WIDGET(option_menu));

      return option_menu;
}


gpointer
grouplist_create (void)
{
      GtkWidget * w;
        GtkWidget * vbox;
        GtkWidget * hbox;
      GtkWidget * toolbar;
      GtkCList * clist;
      char * titles[5];
      debug_enter ("grouplist_create");

      titles[0] = "";
      titles[1] = _("Groups");
      titles[2] = _("Unread");
      titles[3] = _("Total");
      titles[4] = _("Description");

      /* create callbacks */
      grouplist_group_selection_changed = pan_callback_new ();

      /* register callbacks */
      pan_callback_add (serverlist_get_server_activated_callback(),
                        grouplist_server_activated_cb, NULL);
      pan_callback_add (group_get_groups_changed_callback(),
                        grouplist_groups_changed_cb, NULL);
      pan_callback_add (grouplist_group_selection_changed,
                        grouplist_selection_changed_cb, NULL);
      pan_callback_add (server_get_groups_added_callback(),
                        grouplist_server_groups_added_cb, NULL);
      pan_callback_add (server_get_groups_removed_callback(),
                        grouplist_server_groups_removed_cb, NULL);

      toolbar = gtk_toolbar_new ();
      gtk_toolbar_set_orientation (GTK_TOOLBAR(toolbar), GTK_ORIENTATION_HORIZONTAL);
      gtk_toolbar_set_style (GTK_TOOLBAR(toolbar), GTK_TOOLBAR_TEXT);

      vbox = gtk_vbox_new (FALSE, GUI_PAD_SMALL);
      gtk_container_set_border_width (GTK_CONTAINER(vbox), GUI_PAD_SMALL); 
      hbox = gtk_hbox_new (FALSE, GUI_PAD);

      Pan.group_tree = gtk_clist_new_with_titles (5, titles);
      clist = GTK_CLIST(Pan.group_tree);
      clist->button_actions[1] = clist->button_actions[0];
      gtk_clist_set_column_justification (clist, 2, GTK_JUSTIFY_RIGHT);
      gtk_clist_set_column_justification (clist, 3, GTK_JUSTIFY_RIGHT);
      grouplist_update_font ();

      w = gtk_label_new (_("Groups"));
      gtk_toolbar_append_widget(GTK_TOOLBAR(toolbar), w, NULL, NULL);
      gtk_toolbar_append_space (GTK_TOOLBAR(toolbar));

      grouplist_mode_menu = grouplist_mode_menu_create();
      gtk_box_pack_start (GTK_BOX(hbox), grouplist_mode_menu, FALSE, FALSE, 0);

      w = gtk_label_new_with_mnemonic (_("F_ind:"));
      gtk_box_pack_start (GTK_BOX(hbox), w, FALSE, FALSE, 0);

      groupname_filter_entry = gtk_entry_new ();
      gtk_label_set_mnemonic_widget (GTK_LABEL(w), groupname_filter_entry);
      w = groupname_filter_entry;
      g_signal_connect (w, "focus_in_event", G_CALLBACK(entry_focus_in_cb), w);
      g_signal_connect (w, "focus_out_event", G_CALLBACK(entry_focus_out_cb), w);
      g_signal_connect (w, "focus_out_event", G_CALLBACK(grouplist_filter_changed_cb_nolock), w);
      g_signal_connect (w, "activate", G_CALLBACK(grouplist_filter_changed_cb_nolock), w);
      gtk_tooltips_set_tip (GTK_TOOLTIPS(ttips), w,
            _("Type in a group search string and press ENTER.  "
              "Wildcards are allowed."), NULL);
      gtk_widget_show_all (toolbar);
      gtk_box_pack_start (GTK_BOX(hbox), w, TRUE, TRUE, 0);
      gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

      w = gtk_scrolled_window_new (NULL, NULL);

      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w),
                              GTK_POLICY_AUTOMATIC,
                              GTK_POLICY_AUTOMATIC);
      gtk_container_add (GTK_CONTAINER(w), Pan.group_tree);
        gtk_box_pack_start (GTK_BOX (vbox), w, TRUE, TRUE, 0);

      g_signal_connect (Pan.group_tree, "select_row",
                        G_CALLBACK(select_row_cb), NULL);
      g_signal_connect (Pan.group_tree, "unselect_row",
                        G_CALLBACK(select_row_cb), NULL);
      g_signal_connect (Pan.group_tree, "button_press_event",
                        G_CALLBACK(grouplist_button_press), NULL);


      gtk_clist_set_column_width     (clist, 0, 20);
      gtk_clist_set_column_min_width (clist, 0, 20);
      gtk_clist_set_column_max_width (clist, 0, 20);

      gtk_clist_set_column_width (clist, 1, 200);
      gtk_clist_set_column_width (clist, 2, 50);
      gtk_clist_set_column_width (clist, 3, 50);
      gtk_clist_set_column_width (clist, 4, 400);

      gtk_clist_set_selection_mode (clist, GTK_SELECTION_EXTENDED);

      /* Create the Popup Menu while we are here. */
      popup_menu = menu_create_items (group_popup_entries,
                                      G_N_ELEMENTS (group_popup_entries),
                                      "<GroupView>",
                                      &popup_factory,
                                      NULL);


      /* bootstrap */
      grouplist_set_server_nolock (serverlist_get_active_server());

      gtk_widget_show_all (vbox);
      debug_exit ("grouplist_create");
      return vbox;
}


/***
****
****  ccc
****
***/

void
grouplist_update_font (void)
{
      pan_lock ();
      if (!group_pane_custom_font_enabled)
            gtk_widget_modify_font (Pan.group_tree, gtk_widget_get_default_style()->font_desc);
      else {
            PangoFontDescription * pfd = pango_font_description_from_string (group_pane_custom_font);
            gtk_widget_modify_font (Pan.group_tree, pfd);
            pango_font_description_free (pfd);
      }
      pan_unlock ();
}

/***
****
****  GROUP WALKING
****
***/

static void
grouplist_internal_select_row (int       row,
                               gboolean  activate)
{
        GtkCList* list = GTK_CLIST(Pan.group_tree);

      _mb = activate ? 1 : -1;
      _modifiers = activate ? FALSE : TRUE;
      _button_click_count = activate ? 2 : -1;

      gtk_clist_freeze (list);
      gtk_clist_unselect_all (list);
      gtk_clist_select_row (list,row,-1);
      gtk_clist_thaw (list);
}

static gboolean
grouplist_select_row_if_unread (int row,
                                gboolean activate)
{
        GtkCList * list = GTK_CLIST(Pan.group_tree);
      Group * g = gtk_clist_get_row_data(list,row);
      const gulong total = g->article_qty;
      const gulong read = g->article_read_qty;
      gboolean retval = FALSE;

      if (read < total) {
            grouplist_internal_select_row (row, activate);
            retval = TRUE;
      }

      return retval;
}
static void
grouplist_next_unread_group (gboolean activate)
{
        GtkCList* list;
      int row_qty, i, row=0;

      pan_lock ();
      list = GTK_CLIST(Pan.group_tree);
        row_qty = list->rows;

      if (row_qty != 0)
      {
            /* get the first row to check */
            row = 0;
            if (list->selection) {
                  int sel = GPOINTER_TO_INT(list->selection->data);
                  row = (sel + 1) % row_qty;
            }

            /* walk through */
            for ( i=0; i<row_qty; ++i, row=(row+1)%row_qty)
                  if (grouplist_select_row_if_unread (row, activate))
                        break;
      }

      pan_unlock ();
}
void
grouplist_select_next_unread_group (void)
{
      grouplist_next_unread_group (FALSE);
}
void
grouplist_activate_next_unread_group (void)
{
      grouplist_next_unread_group (TRUE);
}
static void
grouplist_next_group (gboolean activate)
{
        GtkCList* list;
      int row_qty;
      int row = 0;

      pan_lock ();
      list = GTK_CLIST(Pan.group_tree);
        row_qty = list->rows;

      if (row_qty != 0)
      {
            /* get the first row to check */
            row = 0;
            if (list->selection) {
                  int sel = GPOINTER_TO_INT(list->selection->data);
                  row = (sel + 1) % row_qty;
            }

            grouplist_internal_select_row (row, activate);
      }

      pan_unlock ();
}
void
grouplist_select_next_group (void)
{
      grouplist_next_group (FALSE);
}
void
grouplist_activate_next_group (void)
{
      grouplist_next_group (TRUE);
}

Generated by  Doxygen 1.6.0   Back to index