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

header-pane-renderer.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 <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

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

#include <pan/base/acache.h>
#include <pan/base/article.h>
#include <pan/base/debug.h>
#include <pan/base/group.h>
#include <pan/base/log.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/status-item.h>

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

#include <pan/filters/filter-score.h>

#include <pan/flagset.h>
#include <pan/header-pane-renderer.h>
#include <pan/prefs.h>
#include <pan/queue.h>
#include <pan/util.h>

typedef struct
{
      const guint8 * pixbuf_txt;
      GdkPixbuf * pixbuf;
      GdkPixmap * pixmap;
      GdkBitmap * mask;
}
Icon;

enum
{
      ICON_READ,
      ICON_UNREAD,
      ICON_NEW,
      ICON_COMPLETE,
      ICON_INCOMPLETE,
      ICON_CACHED,
      ICON_BINARY_DECODED,
      ICON_QUEUED,
      ICON_ERROR,
      ICON_FLAGGED_FOR_DL,
      ICON_NEWSGROUP,
      ICON_QTY
};

static Icon _icons[ICON_QTY] = {
      { icon_article_read, NULL, NULL, NULL },
      { icon_article_unread, NULL, NULL, NULL },
      { icon_article_new, NULL, NULL, NULL },
      { icon_binary_complete, NULL, NULL, NULL },
      { icon_binary_incomplete, NULL, NULL, NULL },
      { icon_disk, NULL, NULL, NULL },
      { icon_binary, NULL, NULL, NULL },
      { icon_bluecheck, NULL, NULL, NULL },
      { icon_x, NULL, NULL, NULL },
      { icon_blue_flag, NULL, NULL, NULL },
      { icon_newsgroup, NULL, NULL, NULL }
};

static GtkStyle * normal_style[2]                = { NULL, NULL };
static GtkStyle * read_style[2]                  = { NULL, NULL };
static GtkStyle * score_style[SCORE_COLOR_QTY][2] = {
      { NULL, NULL },
      { NULL, NULL },
      { NULL, NULL },
      { NULL, NULL },
      { NULL, NULL }
};

GdkColor read_color;
GdkColor unread_color;
GdkColor score_color[SCORE_COLOR_QTY][2];

static PangoFontDescription * normal_pfd = NULL;
static PangoFontDescription * new_replies_pfd = NULL;

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

struct _HeaderPaneRenderer
{
      GtkWidget * main_window;
      GtkCTree * ctree;
      gboolean rebuild_style;
      int score_column_index;
      int subject_column_index;
};

static void
header_pane_renderer_free (gpointer renderer_gpointer)
{
      g_free (renderer_gpointer);
}

HeaderPaneRenderer*
header_pane_renderer_new (GtkWidget * main_window, GtkCTree * ctree)
{
      GtkCList * clist = GTK_CLIST(ctree);
      HeaderPaneRenderer * retval = g_new0 (HeaderPaneRenderer, 1);

      retval->main_window = main_window;
      retval->ctree = ctree;
      retval->rebuild_style = TRUE;

      /* init the icons */
      {
            guint i;
            GdkColormap * cmap = gdk_colormap_get_system ();

            for (i=0; i<ICON_QTY; ++i) {
                  _icons[i].pixbuf = gdk_pixbuf_new_from_inline (-1, _icons[i].pixbuf_txt, FALSE, NULL);
                  gdk_pixbuf_render_pixmap_and_mask_for_colormap (_icons[i].pixbuf, cmap,
                                                      &_icons[i].pixmap,
                                                      &_icons[i].mask,
                                                      128);
            }
        }

      /* init the fonts */
        /* fonts */
        if (header_pane_custom_font_enabled) {
                normal_pfd = pango_font_description_from_string (header_pane_custom_font);
                new_replies_pfd = pango_font_description_copy (normal_pfd);
        } else {
                GtkStyle * style = gtk_widget_get_default_style ();
                normal_pfd = pango_font_description_copy (style->font_desc);
                new_replies_pfd = pango_font_description_copy (style->font_desc);
        }
        pango_font_description_set_weight (new_replies_pfd, PANGO_WEIGHT_BOLD);
        gtk_widget_modify_font (GTK_WIDGET(ctree), new_replies_pfd);

        /* ui settings */
        gtk_ctree_set_line_style (ctree, GTK_CTREE_LINES_DOTTED);
        gtk_clist_set_selection_mode (clist, GTK_SELECTION_MULTIPLE);

      /* columns */
      {
                int column_number;
                int column_type_widths[COLUMN_TYPE_QTY] = { 16, 16, 40, 220, 40, 120, 120 };
                for (column_number=0; column_number<articlelist_column_qty; ++column_number) {
                        const int column_type = articlelist_columns[column_number];
                        int column_width = column_type_widths[column_type];
                        if (column_type == COLUMN_SUBJECT)
                                column_width += 16; /* make room for the tree expander */
                        gtk_clist_set_column_width (clist, column_number, column_width);
                        if (column_type == COLUMN_LINES || column_type == COLUMN_SCORE)
                                gtk_clist_set_column_justification (clist, column_number, GTK_JUSTIFY_RIGHT);
                }
            gui_restore_column_widths (GTK_WIDGET(clist), "header_pane_2");
        }

      g_object_set_data_full (G_OBJECT(ctree), "pan_renderer", retval, header_pane_renderer_free);

      return retval;
}


/***
****
****
****   SORTING
****
****
***/

int articlelist_columns[] = {
      COLUMN_ACTION_STATE,
      COLUMN_ARTICLE_STATE,
      COLUMN_SUBJECT,
      COLUMN_SCORE,
      COLUMN_LINES,
      COLUMN_AUTHOR,
      COLUMN_DATE
};
int articlelist_column_qty = G_N_ELEMENTS (articlelist_columns);

static int
get_column_number_from_column_type (int type)
{
      int i;
      for (i=0; i<articlelist_column_qty; ++i)
            if (articlelist_columns[i] == type)
                  return i;

      /* column is not visible */
      return -1;
}


/***
****
****
****  POPULATING THE TREE:  UTILITY FUNCTIONS
****
****
***/



static const Icon*
get_column_state_icon (const Article * a)
{
      const Icon * retval = NULL;
      const gboolean is_read = article_is_read (a);
      const gboolean is_new = a->is_new && !is_read;
      const int multipart_state = article_get_multipart_state (a);

      g_return_val_if_fail (a!=NULL, NULL);

      if (a->error_flag)
            retval = _icons + ICON_ERROR;
      else if (multipart_state == MULTIPART_STATE_ALL)
            retval = _icons + ICON_COMPLETE;
      else if (multipart_state == MULTIPART_STATE_SOME)
            retval = _icons + ICON_INCOMPLETE;
      else if (is_new)
            retval = _icons + ICON_NEW;
      else if (!is_new && !is_read)
            retval = _icons + ICON_UNREAD;
      else
            retval = _icons + ICON_READ;

      return retval;
}

static const Icon*
get_column_action_icon (const Article * a)
{
      const Icon * retval = NULL;

      g_return_val_if_fail (article_is_valid(a), NULL);

      if (flagset_has_article (a))
            retval = _icons + ICON_FLAGGED_FOR_DL;
      else if (article_get_decode_state (a) == DECODE_STATE_DECODED)
            retval = _icons + ICON_BINARY_DECODED;
      else if (queue_get_message_id_status (&a->message_id))
            retval = _icons + ICON_QUEUED;
      else if (acache_has_message (group_get_acache_key(a->group), &a->message_id))
            retval = _icons + ICON_CACHED;

      return retval;
}

#define NORMAL 0
#define NEW_REPLIES 1

static void
ensure_styles_inited (HeaderPaneRenderer  * renderer)
{
      int i;
      GtkStyle * s;

      if (!renderer->rebuild_style)
            return;
      renderer->rebuild_style = FALSE;

      renderer->score_column_index = get_column_number_from_column_type (COLUMN_SCORE);
      renderer->subject_column_index = get_column_number_from_column_type (COLUMN_SUBJECT);

      /* build the styles */
      s = gtk_widget_get_style (renderer->main_window);
      for (i=0; i<2; ++i)
      {
            int j;
            PangoFontDescription * pfd;

            /* find the font description */
            if (i==NORMAL)
                  pfd = normal_pfd;
            else if (i==NEW_REPLIES)
                  pfd = new_replies_pfd;
            else
                  g_assert (0);

            /* build the normal style */
            if (normal_style[i] != NULL)
                  g_object_unref (G_OBJECT(normal_style[i]));
            normal_style[i] = gtk_style_copy (s);
            normal_style[i]->fg[0] = unread_color;
            if (pfd != NULL) {
                  PangoFontDescription * tmp = normal_style[i]->font_desc;
                  normal_style[i]->font_desc = pango_font_description_copy (pfd);
                  pango_font_description_free (tmp);
            }

            /* build the read style */
            if (read_style[i] != NULL)
                  g_object_unref (G_OBJECT(read_style[i]));
            read_style[i] = gtk_style_copy (s);
            read_style[i]->fg[0] = read_color;
            if (pfd != NULL) {
                  PangoFontDescription * tmp = read_style[i]->font_desc;
                  read_style[i]->font_desc = pango_font_description_copy (pfd);
                  pango_font_description_free (tmp);
            }

            for (j=0; j<SCORE_COLOR_QTY; ++j) {
                  if (score_style[j][i] != NULL)
                        g_object_unref (G_OBJECT(score_style[j][i]));
                  score_style[j][i] = gtk_style_copy (s);
                  score_style[j][i]->base[0] = score_color[j][0];
                  score_style[j][i]->fg[0] = score_color[j][1];
                  if (pfd != NULL) {
                        PangoFontDescription * tmp = score_style[j][i]->font_desc;
                        score_style[j][i]->font_desc = pango_font_description_copy (pfd);
                        pango_font_description_free (tmp);
                  }
            }
      }
}

static void
set_node_style (HeaderPaneRenderer  * renderer,
                GtkCTreeNode        * node,
            const Article       * article)
{
      int score;

      GtkStyle * style = NULL;
      const gboolean is_unread = article==NULL || !article_is_read (article);
      const gboolean is_new = article!=NULL &&  article->is_new && is_unread;
      const gboolean new_children = article!=NULL && article->new_children!=0;
      const gboolean unread_children = article!=NULL && article->unread_children!=0;
      const gboolean collapsed_subthread = node!=NULL && !GTK_CTREE_ROW(node)->is_leaf && !GTK_CTREE_ROW(node)->expanded;
      const gboolean do_mark_new = is_new || (collapsed_subthread && new_children);
      const gboolean do_mark_unread = is_unread || (collapsed_subthread && unread_children);
      GtkCTree * tree = renderer->ctree;

      ensure_styles_inited (renderer);

      if (do_mark_new)
            style = normal_style[NEW_REPLIES];
      else if (do_mark_unread)
            style = normal_style[NORMAL];
      else
            style = read_style[NORMAL];

      gtk_ctree_node_set_row_style (tree, node, style);

      /* find the score to use */
      if (article == NULL)
            score = 0;
      else if (collapsed_subthread)
            score = article->subthread_score;
      else 
            score = article->score;

      /* if article has a score, then change the style */
      if (score != 0)
      {
            int color_index;

            /* get the color index from the score mode */
            switch (filter_score_get_score_mode (score)) {
                  case SCORE_WATCHED:  color_index = SCORE_COLOR_WATCHED; break;
                  case SCORE_HIGH:     color_index = SCORE_COLOR_HIGH; break;
                  case SCORE_MEDIUM:   color_index = SCORE_COLOR_MEDIUM; break;
                  case SCORE_LOW:      color_index = SCORE_COLOR_LOW; break;
                  case SCORE_IGNORED:  color_index = SCORE_COLOR_IGNORED; break;
                  default:             color_index = SCORE_COLOR_WATCHED; pan_warn_if_reached (); break;
            }

            style = score_style[color_index][do_mark_new ? NEW_REPLIES : NORMAL];
      }

      if (header_pane_color_score_column)
            gtk_ctree_node_set_cell_style (tree, node, renderer->score_column_index, style);

      if (header_pane_color_subject_column)
            gtk_ctree_node_set_cell_style (tree, node, renderer->subject_column_index, style);
}


static const char*
get_formatted_subject (char            * buf,
                       gulong            buflen,
                       const Article   * article,
                       gboolean          expanded)
{
      const char * retval;

      g_return_val_if_fail (buf!=NULL, NULL);
      g_return_val_if_fail (buflen!=0ul, NULL);
      g_return_val_if_fail (article!=NULL, NULL);

      if (expanded || !article->threads || !article->new_children)
            retval = article->subject.str;
      else {
            retval = buf;
            g_snprintf (buf, buflen, "%s (%u)", article->subject.str, article->new_children);
      }

      return retval;
}

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

gboolean
header_pane_renderer_populate_node (GtkCTree            * ctree,
                                    guint                 depth,
                                    GNode               * node,
                                    GtkCTreeNode        * cnode,
                                    gpointer              renderer_gpointer)
{
      HeaderPaneRenderer * renderer = (HeaderPaneRenderer*) renderer_gpointer;
      const Article * article;
      const gboolean expanded = depth>2;
      const char * cpch;
      gulong lcountsum;
      int action_icon_col = -1;
      int state_icon_col = -1;
      int column;
      char buf[512];

      /* sanity clause */
      g_return_val_if_fail (renderer != NULL, FALSE);

      if (node->parent == NULL) /* top-level, group node */
      {
            const Group * group = GROUP (node->data);

            g_assert (group_is_valid (group));

              gtk_ctree_set_node_info (ctree, cnode, group->name.str,
                                         GUI_PAD_SMALL,
                                         _icons[ICON_NEWSGROUP].pixmap,
                                         _icons[ICON_NEWSGROUP].mask,
                                         _icons[ICON_NEWSGROUP].pixmap,
                                         _icons[ICON_NEWSGROUP].mask,
                                         node->children==NULL, FALSE);

            return TRUE;
      }

      article = (const Article*) node->data;
      for (column=0; column<articlelist_column_qty; ++column)
      {
            char * freeme = NULL;
            cpch = buf;
            *buf = '\0';

            switch (articlelist_columns[column])
            {
                  case COLUMN_ACTION_STATE:
                        action_icon_col = column;
                        break;

                  case COLUMN_ARTICLE_STATE:
                        state_icon_col = column;
                        break;

                  case COLUMN_SCORE:
                        if (1) {
                              const int score = expanded ? article->score : article->subthread_score;
                              if (score != 0)
                                    g_snprintf (buf, sizeof(buf), "%d", score);
                        }           
                        break;

                  case COLUMN_SUBJECT:
                        /* we'll get this later with gtk_ctree_set_node_info */
                        break;

                  case COLUMN_LINES:
                        lcountsum = article->linecount;
                        if (!expanded && article_get_multipart_state (article)==MULTIPART_STATE_ALL) {
                              GSList * l;
                              for (l=article->threads; l!=NULL; l=l->next)
                                    lcountsum += ARTICLE(l->data)->linecount;
                        }
                        g_snprintf (buf, sizeof(buf), "%lu", lcountsum);
                        break;

                  case COLUMN_AUTHOR:
                        article_get_short_author_str (article, buf, sizeof(buf));
                        break;

                  case COLUMN_DATE:
                        freeme = get_date_display_string (article->date, header_pane_date_format);
                        cpch = (const char *) freeme;
                        break;

                  default:
                        g_message ("unhandled type: %d", articlelist_columns[column]);
                        break;
            }

            if (is_nonempty_string (cpch))
                  gtk_ctree_node_set_text (ctree, cnode, column, cpch);

            if (freeme != NULL)
                  g_free (freeme);
      }

      if (state_icon_col != -1) {
            const Icon * col = get_column_state_icon (article);
            if (col != NULL)
                  gtk_ctree_node_set_pixmap (ctree, cnode, state_icon_col, col->pixmap, col->mask);
      }

      if (action_icon_col != -1) {
            const Icon * col = get_column_action_icon (article);
            if (col != NULL)
                  gtk_ctree_node_set_pixmap (ctree, cnode, action_icon_col, col->pixmap, col->mask);
      }

      gtk_ctree_node_set_row_data (ctree, cnode, (gpointer)article);

      cpch = get_formatted_subject (buf, sizeof(buf), article, expanded);
      gtk_ctree_set_node_info (ctree, cnode, cpch, GUI_PAD_SMALL,
                               NULL, NULL, NULL, NULL,
                               node->children==NULL,
                               expanded);

      set_node_style (renderer, cnode, article);

      return TRUE;
}


void
header_pane_renderer_refresh_node  (HeaderPaneRenderer  * renderer,
                                    GtkCTreeNode        * node,
                                    const Article       * article)
{
      int column_number;
      GtkCTree * ctree = renderer->ctree;

      /* update the subject column */
      column_number = get_column_number_from_column_type (COLUMN_SUBJECT);
      if (column_number != -1) {
            char * cur_text = NULL;
            const gboolean collapsed  = !GTK_CTREE_ROW(node)->is_leaf && !GTK_CTREE_ROW(node)->expanded;
            char buf[512];
            char * text = (char*) get_formatted_subject (buf, sizeof(buf), article, !collapsed);
            gtk_ctree_node_get_text (ctree, node, column_number, &cur_text);
            if (pan_strcmp (cur_text, buf))
                  gtk_ctree_node_set_text (ctree, node, column_number, text);
      }

      /* update the score column */
      column_number = get_column_number_from_column_type (COLUMN_SCORE);
      if (column_number != -1) {
            char * cur_text = NULL;
            char buf[512];
            const gboolean collapsed  = !GTK_CTREE_ROW(node)->is_leaf && !GTK_CTREE_ROW(node)->expanded;
            const int score = collapsed ? article->subthread_score : article->score;
            if (score == 0)
                  *buf = '\0';
            else
                  g_snprintf (buf, sizeof(buf), "%d", score);
            gtk_ctree_node_get_text (ctree, node, column_number, &cur_text);
            if (pan_strcmp (cur_text, buf))
                  gtk_ctree_node_set_text (ctree, node, column_number, buf);
      }

      /* update the article state column */
      column_number = get_column_number_from_column_type (COLUMN_ARTICLE_STATE);
      if (column_number != -1) {
            const Icon * icon = get_column_state_icon (article);
            if (icon != NULL)
                  gtk_ctree_node_set_pixmap (ctree, node, column_number, icon->pixmap, icon->mask);
            else
                  gtk_ctree_node_set_text (ctree, node, column_number, "");
      }

      /* update the action state column */
      column_number = get_column_number_from_column_type (COLUMN_ACTION_STATE);
      if (column_number != -1) {
            const Icon * icon = get_column_action_icon (article);
            if (icon != NULL)
                  gtk_ctree_node_set_pixmap (ctree, node, column_number, icon->pixmap, icon->mask);
            else
                  gtk_ctree_node_set_text (ctree, node, column_number, "");
      }

      /* update the line count column */
      column_number = get_column_number_from_column_type (COLUMN_LINES);
      if (column_number != -1) {
            char buf[512];
            const gboolean expanded = GTK_CTREE_ROW(node)->expanded;
            gulong lines = article->linecount;
            if (!expanded && article_get_multipart_state (article)==MULTIPART_STATE_ALL) {
                  GSList * l;
                  for (l=article->threads; l!=NULL; l=l->next)
                        lines += ARTICLE(l->data)->linecount;
            }
            g_snprintf (buf, sizeof(buf), "%lu", lines);
            gtk_ctree_node_set_text (ctree, node, column_number, buf);
      }

      set_node_style (renderer, node, article);
}

void
header_pane_renderer_reset_style (HeaderPaneRenderer * renderer)
{
        PangoFontDescription * old_normal_pfd = normal_pfd;
        PangoFontDescription * old_new_replies_pfd = new_replies_pfd;
                                                                                                                           
        pan_lock ();
        if (header_pane_custom_font_enabled) {
                normal_pfd = pango_font_description_from_string (header_pane_custom_font);
                new_replies_pfd = pango_font_description_copy (normal_pfd);
        } else {
                GtkStyle * style = gtk_widget_get_default_style ();
                normal_pfd = pango_font_description_copy (style->font_desc);
                new_replies_pfd = pango_font_description_copy (style->font_desc);
        }
        pango_font_description_set_weight (new_replies_pfd, PANGO_WEIGHT_BOLD);
        gtk_widget_modify_font (GTK_WIDGET(renderer->ctree), new_replies_pfd);
                                                                                                                           
        pango_font_description_free (old_normal_pfd);
        pango_font_description_free (old_new_replies_pfd);
        pan_unlock ();

        renderer->rebuild_style = TRUE;
}

Generated by  Doxygen 1.6.0   Back to index