view mcabber/src/hbuf.c @ 147:7571de4aed73

[/trunk] Changeset 159 by mikael * Fix a bug in buddylist_build() * We now lock the current buddy even not when being in chat mode. For example, if we're writing to s.o. and he leaves just before we press enter, we won't write to the wrong buddy... If the current_buddy is a group, we lock it too. * Remove MCABBER_TESTUNIT ifdef in roster.h (test program isn't up-to-date anymore...)
author mikael
date Fri, 29 Apr 2005 19:56:28 +0000
parents a95e2fc9ea6b
children 5647381a7dfb
line wrap: on
line source

/*
 * hbuf.c       -- History buffer implementation
 * 
 * Copyright (C) 2005 Mikael Berthe <bmikael@lists.lilotux.net>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include <string.h>

#include "hbuf.h"


/* This is a private structure type */

#define PREFIX_LENGTH 32
typedef struct {
  char *ptr;
  char *ptr_end;
  guchar flags;

  // XXX This should certainly be a pointer, and be allocated only when needed
  // (for ex. when HBB_FLAG_PERSISTENT is set).
  struct { // hbuf_line_info
    char *ptr_end_alloc;
    char prefix[PREFIX_LENGTH];
  } persist;
} hbuf_block;


//  hbuf_add_line(p_hbuf, text, prefix, width)
// Add a line to the given buffer.  If width is not null, then lines are
// wrapped at this length.
//
// Note 1: Splitting according to width won't work if there are tabs; they
//         should be expanded before.
// Note 2: width does not include the ending \0.
void hbuf_add_line(GList **p_hbuf, const char *text, const char *prefix,
        unsigned int width)
{
  GList *hbuf = *p_hbuf;
  char *line, *cr, *end;
  hbuf_block *hbuf_block_elt;

  if (!text) return;

  hbuf_block_elt = g_new0(hbuf_block, 1);
  if (prefix)
    strncpy(hbuf_block_elt->persist.prefix, prefix, PREFIX_LENGTH-1);
  if (!hbuf) {
    hbuf_block_elt->ptr    = g_new(char, HBB_BLOCKSIZE);
    hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
    hbuf_block_elt->persist.ptr_end_alloc = hbuf_block_elt->ptr + HBB_BLOCKSIZE;
    *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt);
  } else {
    hbuf_block *hbuf_b_prev = g_list_last(hbuf)->data;
    hbuf_block_elt->ptr    = hbuf_b_prev->ptr_end;
    hbuf_block_elt->flags  = HBB_FLAG_PERSISTENT;
    hbuf_block_elt->persist.ptr_end_alloc = hbuf_b_prev->persist.ptr_end_alloc;
    *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt);
  }

  if (strlen(text) >= HBB_BLOCKSIZE) {
    // Too long
    text = "[ERR:LINE_TOO_LONG]";
  }
  if (hbuf_block_elt->ptr + strlen(text) >= hbuf_block_elt->persist.ptr_end_alloc) {
    // Too long for the current allocated bloc, we need another one
    hbuf_block_elt->ptr    = g_new0(char, HBB_BLOCKSIZE);
    hbuf_block_elt->flags  = HBB_FLAG_ALLOC | HBB_FLAG_PERSISTENT;
    hbuf_block_elt->persist.ptr_end_alloc = hbuf_block_elt->ptr + HBB_BLOCKSIZE;
  }

  line = hbuf_block_elt->ptr;
  // Ok, now we can copy the text..
  strcpy(line, text);
  hbuf_block_elt->ptr_end = line + strlen(line) + 1;
  end = hbuf_block_elt->ptr_end;

  // Let's add non-persistent blocs if necessary
  // - If there are '\n' in the string
  // - If length > width (and width != 0)
  cr = strchr(line, '\n');
  while (cr || (width && strlen(line) > width)) {
    hbuf_block *hbuf_b_prev = hbuf_block_elt;

    if (!width || (cr && (cr - line <= (int)width))) {
      // Carriage return
      *cr = 0;
      hbuf_block_elt->ptr_end = cr;
      // Create another persistent block
      hbuf_block_elt = g_new0(hbuf_block, 1);
      hbuf_block_elt->ptr      = hbuf_b_prev->ptr_end + 1; // == cr+1
      hbuf_block_elt->ptr_end  = end;
      hbuf_block_elt->flags    = HBB_FLAG_PERSISTENT;
      hbuf_block_elt->persist.ptr_end_alloc = hbuf_b_prev->persist.ptr_end_alloc;
      *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt);
      line = hbuf_block_elt->ptr;
    } else {
      // We need to break where we can find a space char
      char *br; // break pointer
      for (br = line + width; br > line && *br != 32 && *br != 9; br--)
        ;
      if (br <= line)
        br = line + width;
      else
        br++;
      hbuf_block_elt->ptr_end = br;
      // Create another block, non-persistent
      hbuf_block_elt = g_new0(hbuf_block, 1);
      hbuf_block_elt->ptr      = hbuf_b_prev->ptr_end; // == br
      hbuf_block_elt->ptr_end  = end;
      hbuf_block_elt->flags    = 0;
      hbuf_block_elt->persist.ptr_end_alloc = hbuf_b_prev->persist.ptr_end_alloc;
      *p_hbuf = g_list_append(*p_hbuf, hbuf_block_elt);
      line = hbuf_block_elt->ptr;
    }
    cr = strchr(line, '\n');
  }
}

//  hbuf_free()
// Destroys all hbuf list.
void hbuf_free(GList **p_hbuf)
{
  hbuf_block *hbuf_b_elt;
  GList *hbuf_elt;
  GList *first_elt = g_list_first(*p_hbuf);

  for (hbuf_elt = first_elt; hbuf_elt; hbuf_elt = g_list_next(hbuf_elt)) {
    hbuf_b_elt = (hbuf_block*)(hbuf_elt->data);
    if (hbuf_b_elt->flags & HBB_FLAG_ALLOC) {
      g_free(hbuf_b_elt->ptr);
    }
  }

  g_list_free(*p_hbuf);
  *p_hbuf = NULL;
}

//  hbuf_rebuild()
// Rebuild all hbuf list, with the new width.
// If width == 0, lines are not wrapped.
void hbuf_rebuild(GList **p_hbuf, unsigned int width)
{
  GList *first_elt, *curr_elt, *next_elt;
  hbuf_block *hbuf_b_curr, *hbuf_b_next;

  first_elt = g_list_first(*p_hbuf);

  // #1 Remove non-persistent blocks (ptr_end should be updated!)
  curr_elt = first_elt;
  while (curr_elt) {
    next_elt = g_list_next(curr_elt);
    // Last element?
    if (!next_elt)
      break;
    hbuf_b_curr = (hbuf_block*)(curr_elt->data);
    hbuf_b_next = (hbuf_block*)(next_elt->data);
    // Is next line not-persistent?
    if (!(hbuf_b_next->flags & HBB_FLAG_PERSISTENT)) {
      hbuf_b_curr->ptr_end = hbuf_b_next->ptr_end;
      g_list_delete_link(curr_elt, next_elt);
      next_elt = g_list_next(curr_elt);
    } else 
      curr_elt = next_elt;
  }
  // #2 Go back to head and create non-persistent blocks when needed
  if (width) {
    char *line, *end;
    curr_elt = first_elt;

    while (curr_elt) {
      hbuf_b_curr = (hbuf_block*)(curr_elt->data);
      line = hbuf_b_curr->ptr;
      if (strlen(line) > width) {
        hbuf_block *hbuf_b_prev = hbuf_b_curr;

        // We need to break where we can find a space char
        char *br; // break pointer
        for (br = line + width; br > line && *br != 32 && *br != 9; br--)
          ;
        if (br <= line)
          br = line + width;
        else
          br++;
        end = hbuf_b_curr->ptr_end;
        hbuf_b_curr->ptr_end = br;
        // Create another block, non-persistent
        hbuf_b_curr = g_new0(hbuf_block, 1);
        hbuf_b_curr->ptr      = hbuf_b_prev->ptr_end; // == br
        hbuf_b_curr->ptr_end  = end;
        hbuf_b_curr->flags    = 0;
        hbuf_b_curr->persist.ptr_end_alloc = hbuf_b_prev->persist.ptr_end_alloc;
        /*
        // Is there a better way?
        if (g_list_next(curr_elt))
          g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr);
        else
          *p_hbuf = g_list_append(*p_hbuf, hbuf_b_curr);
        */
        // This is OK because insert_before(NULL) <==> append()
        g_list_insert_before(*p_hbuf, curr_elt->next, hbuf_b_curr);
      }
      curr_elt = g_list_next(curr_elt);
    }
  }
}

//  hbuf_get_lines(hbuf, n, where)
// Returns an array of 2*n pointers (for n prefixes + n lines from hbuf)
// (prefix line 1, line 1, prefix line 2, line 2, etc.)
// (The first line will be the line currently pointed by hbuf)
// Note:The caller should free the array after use.
char **hbuf_get_lines(GList *hbuf, unsigned int n)
{
  unsigned int i;

  char **array = g_new0(char*, n*2);
  char **array_elt = array;

  for (i=0 ; i < n ; i++) {
    if (hbuf) {
      hbuf_block *blk = (hbuf_block*)(hbuf->data);
      int maxlen;
      maxlen = blk->ptr_end - blk->ptr;
      *array_elt++ = blk->persist.prefix;
      *array_elt++ = g_strndup(blk->ptr, maxlen);
      hbuf = g_list_next(hbuf);
    } else
      *array_elt++ = NULL;
  }

  return array;
}