Logo Search packages:      
Sourcecode: beast version File versions

bstpatternview.c

/* BEAST - Bedevilled Audio System
 * Copyright (C) 2004 Tim Janik
 *
 * 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 "bstpatternview.h"
#include "bstskinconfig.h"
#include <string.h>


/* --- defines --- */
/* accessors */
#define     STYLE(self)       (GTK_WIDGET (self)->style)
#define     STATE(self)       (GTK_WIDGET (self)->state)
#define     SELECTED_STATE(self)    (GTK_WIDGET_IS_SENSITIVE (self) ? GTK_STATE_SELECTED : GTK_STATE_INSENSITIVE)
#define     ACTIVE_STATE(self)      (GTK_WIDGET_IS_SENSITIVE (self) ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE)
#define     XTHICKNESS(self)  (STYLE (self)->xthickness)
#define     YTHICKNESS(self)  (STYLE (self)->ythickness)
#define     ALLOCATION(self)  (&GTK_WIDGET (self)->allocation)
#define X_OFFSET(self)          (GXK_SCROLL_CANVAS (self)->x_offset)
#define Y_OFFSET(self)          (GXK_SCROLL_CANVAS (self)->y_offset)
#define PLAYOUT(self, i)        (gxk_scroll_canvas_get_pango_layout (GXK_SCROLL_CANVAS (self), i))
#define PLAYOUT_HPANEL(self)    (PLAYOUT (self, 0))
#define PLAYOUT_VPANEL(self)    (PLAYOUT (self, 1))
#define PLAYOUT_CANVAS(self)    (PLAYOUT (self, 2))
#define COLUMN_PLAYOUT_INDEX(i) (i + 3)
#define COLOR_GC(self, i)       (GXK_SCROLL_CANVAS (self)->color_gc[i])
#define COLOR_GC_SHADE1(self)   (COLOR_GC (self, CINDEX_SHADE1))
#define COLOR_GC_SHADE2(self)   (COLOR_GC (self, CINDEX_SHADE2))
#define COLOR_GC_TEXT0(self)    (COLOR_GC (self, CINDEX_TEXT0))
#define COLOR_GC_TEXT1(self)    (COLOR_GC (self, CINDEX_TEXT1))
#define COLOR_GC_VBAR(self)     (COLOR_GC (self, CINDEX_VBAR))
#define CANVAS(self)            (GXK_SCROLL_CANVAS (self)->canvas)
#define HPANEL(self)            (GXK_SCROLL_CANVAS (self)->top_panel)
#define VPANEL(self)            (GXK_SCROLL_CANVAS (self)->left_panel)
#define FOCUS_WIDTH(self)       (bst_pattern_view_get_focus_width (self))
/* layout (allocation) */
#define     CMARK_HEIGHT(self)      (YTHICKNESS (self) * 3 + 1)
#define     PMARK_HEIGHT(self)      (CMARK_HEIGHT (self) + 3 * YTHICKNESS (self))

#define CUSTOM_MARKER_OFFSET    (128)


/* --- prototypes --- */
static void     pattern_view_queue_expose       (BstPatternView *self,
                                                 guint           tick_start,
                                                 guint           tick_end);


/* --- static variables --- */
static guint      signal_drag = 0;
static guint      signal_clicked = 0;


/* --- functions --- */
G_DEFINE_TYPE (BstPatternView, bst_pattern_view, GXK_TYPE_SCROLL_CANVAS);

enum {
  CINDEX_SHADE1,
  CINDEX_SHADE2,
  CINDEX_TEXT0,
  CINDEX_TEXT1,
  CINDEX_VBAR,
  CINDEX_LAST
};

static void
pattern_view_class_setup_skin (BstPatternViewClass *class)
{
  static GdkColor colors[CINDEX_LAST];
  GxkScrollCanvasClass *scroll_canvas_class = GXK_SCROLL_CANVAS_CLASS (class);
  scroll_canvas_class->n_colors = G_N_ELEMENTS (colors);
  scroll_canvas_class->colors = colors;
  colors[CINDEX_SHADE1] = gdk_color_from_rgb (BST_SKIN_CONFIG (pattern_scolor1));
  colors[CINDEX_SHADE2] = gdk_color_from_rgb (BST_SKIN_CONFIG (pattern_scolor2));
  colors[CINDEX_TEXT0] = gdk_color_from_rgb (BST_SKIN_CONFIG (pattern_text0));
  colors[CINDEX_TEXT1] = gdk_color_from_rgb (BST_SKIN_CONFIG (pattern_text1));
  colors[CINDEX_VBAR] = gdk_color_from_rgb (BST_SKIN_CONFIG (pattern_vbar1));
  g_free (scroll_canvas_class->image_file_name);
  scroll_canvas_class->image_file_name = BST_SKIN_CONFIG_STRDUP_PATH (pattern_image);
  scroll_canvas_class->image_tint = gdk_color_from_rgb (BST_SKIN_CONFIG (pattern_color));
  scroll_canvas_class->image_saturation = BST_SKIN_CONFIG (pattern_shade) * 0.01;
  gxk_scroll_canvas_class_skin_changed (scroll_canvas_class);
}

static void
bst_pattern_view_init (BstPatternView *self)
{
  GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
  
  self->row_height = 1;
  self->tpt = 384 * 4;
  self->max_ticks = 1;
  self->vticks = 384;
  gxk_scroll_canvas_set_canvas_cursor (scc, GDK_LEFT_PTR);
  gxk_scroll_canvas_set_left_panel_cursor (scc, GDK_HAND2);
  gxk_scroll_canvas_set_top_panel_cursor (scc, GDK_LEFT_PTR);
  bst_pattern_view_vsetup (self, 384, 4, 800 * 384, 384);
  bst_pattern_view_set_layout (self, ("_ "
                                      "offset-0 : note-0 : velocity-0 : fine-tune-0 : length-0 | "
                                      "offset-1 : note-1 : velocity-1 : fine-tune-1 : length-1 | "
                                      "offset-2 : note-2 : velocity-2 : fine-tune-2 : length-2 | "
                                      "offset-3 : note-3 : velocity-3 : fine-tune-3 : length-3 | "
                                      "control-4 | control-7 | control-8"));
}

static void
bst_pattern_view_dispose (GObject *object)
{
  BstPatternView *self = BST_PATTERN_VIEW (object);
  
  bst_pattern_view_set_proxy (self, 0);
  
  G_OBJECT_CLASS (bst_pattern_view_parent_class)->dispose (object);
}

static void
bst_pattern_view_destroy_columns (BstPatternView *self)
{
  bst_pattern_view_set_pixmarker (self, 1, BST_PATTERN_VIEW_MARKER_FOCUS, -1, -1, 1, 1);
  bst_pattern_view_set_pixmarker (self, 2, BST_PATTERN_VIEW_MARKER_FOCUS, -1, -1, 1, 1);
  bst_pattern_view_set_pixmarker (self, 3, BST_PATTERN_VIEW_MARKER_FOCUS, -1, -1, 1, 1);
  bst_pattern_view_set_pixmarker (self, 4, BST_PATTERN_VIEW_MARKER_FOCUS, -1, -1, 1, 1);
  bst_pattern_view_set_pixmarker (self, 5, BST_PATTERN_VIEW_MARKER_FOCUS, -1, -1, 1, 1);
  while (self->n_cols)
    {
      self->n_cols--;
      self->cols[self->n_cols]->klass->finalize (self->cols[self->n_cols]);
    }
  self->n_cols = 0;
  g_free (self->cols);
  self->cols = NULL;
  self->n_focus_cols = 0;
  g_free (self->focus_cols);
  self->focus_cols = NULL;
  gtk_widget_queue_resize (GTK_WIDGET (self));
  g_free (self->layout_string);
  self->layout_string = NULL;
  gxk_scroll_canvas_reset_pango_layouts (GXK_SCROLL_CANVAS (self));
}

static void
bst_pattern_view_finalize (GObject *object)
{
  BstPatternView *self = BST_PATTERN_VIEW (object);

  bst_pattern_view_set_proxy (self, 0);
  bst_pattern_view_destroy_columns (self);
  
  G_OBJECT_CLASS (bst_pattern_view_parent_class)->finalize (object);
}

static void
pattern_view_update (BstPatternView *self,
                     guint           tick,
                     guint           duration,
                     gint            min_note,
                     gint            max_note)
{
  duration = MAX (duration, 1);
  if (GTK_WIDGET_DRAWABLE (self))
    pattern_view_queue_expose (self, tick, tick + duration - 1);
}

static void
pattern_view_release_proxy (BstPatternView *self)
{
  gxk_toplevel_delete (GTK_WIDGET (self));
  bst_pattern_view_set_proxy (self, 0);
}

static void
pattern_view_range_changed (BstPatternView *self)
{
  guint max_ticks;
  bse_proxy_get (self->proxy, "last-tick", &max_ticks, NULL);
  bst_pattern_view_vsetup (self, 384, 4, MAX (max_ticks, 1), self->vticks);
}

void
bst_pattern_view_set_proxy (BstPatternView *self,
                            SfiProxy        proxy)
{
  g_return_if_fail (BST_PATTERN_VIEW (self));
  if (proxy)
    {
      g_return_if_fail (BSE_IS_PART (proxy));
      g_return_if_fail (bse_item_get_project (proxy) != 0);
    }
  
  if (self->proxy)
    {
      bse_proxy_disconnect (self->proxy,
                            "any_signal", pattern_view_release_proxy, self,
                            "any_signal", pattern_view_range_changed, self,
                            "any_signal", pattern_view_update, self,
                            NULL);
      bse_item_unuse (self->proxy);
    }
  self->proxy = proxy;
  if (self->proxy)
    {
      bse_item_use (self->proxy);
      bse_proxy_connect (self->proxy,
                         "swapped_signal::release", pattern_view_release_proxy, self,
                         "swapped_signal::property-notify::last-tick", pattern_view_range_changed, self,
                         "swapped_signal::range-changed", pattern_view_update, self,
                         NULL);
      pattern_view_range_changed (self);
    }
  gtk_widget_queue_resize (GTK_WIDGET (self));
}

static PangoLayout*
pattern_view_column_pango_layout (BstPatternView   *self,
                                  BstPatternColumn *col)
{
  GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
  gint i;
  for (i = 0; i < self->n_cols; i++)
    if (self->cols[i] == col)
      break;
  return gxk_scroll_canvas_peek_pango_layout (scc, COLUMN_PLAYOUT_INDEX (i));
}

static void
pattern_view_get_layout (GxkScrollCanvas        *scc,
                         GxkScrollCanvasLayout  *layout)
{
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  PangoFontDescription *fdesc;
  PangoRectangle rect = { 0 };
  gchar buffer[64];
  guint i, accu = 0;

  /* hpanel writings */
  pango_layout_get_pixel_extents (PLAYOUT_HPANEL (self), NULL, &rect);
  layout->top_panel_height = rect.height;

  /* vpanel numbers */
  fdesc = pango_font_description_new ();
  pango_font_description_set_family_static (fdesc, "monospace");
  // pango_font_description_set_weight (fdesc, PANGO_WEIGHT_BOLD);
  pango_font_description_merge (fdesc, STYLE (self)->font_desc, FALSE);
  pango_layout_set_font_description (PLAYOUT_VPANEL (self), fdesc);
  pango_font_description_free (fdesc);
  g_snprintf (buffer, 64, "%05x", self->max_ticks);
  pango_layout_set_text (PLAYOUT_VPANEL (self), buffer, -1);
  pango_layout_get_pixel_extents (PLAYOUT_VPANEL (self), NULL, &rect);
  layout->left_panel_width = rect.width + 2 * XTHICKNESS (self);

  layout->right_panel_width = 0;
  layout->bottom_panel_height = 0;

  /* canvas size */
  pango_layout_get_pixel_extents (PLAYOUT_CANVAS (self), NULL, &rect);
  self->row_height = rect.height + FOCUS_WIDTH (self);
  layout->canvas_height = self->row_height;
  for (i = 0; i < self->n_cols; i++)
    {
      BstPatternColumn *col = self->cols[i];
      PangoLayout *playout = gxk_scroll_canvas_peek_pango_layout (scc, COLUMN_PLAYOUT_INDEX (i));
      if (!playout && col->klass->create_font_desc)
        {
          PangoFontDescription *fdesc = col->klass->create_font_desc (col);
          pango_font_description_merge (fdesc, STYLE (self)->font_desc, FALSE);
          playout = gxk_scroll_canvas_get_pango_layout (scc, COLUMN_PLAYOUT_INDEX (i));
          pango_layout_set_font_description (playout, fdesc);
          pango_font_description_free (fdesc);
        }
      accu += self->cols[i]->width = self->cols[i]->klass->width_request (self->cols[i], self, CANVAS (self), playout, self->vticks);
    }
  layout->canvas_width = accu;
}

static gint
tick_to_row (BstPatternView *self,
             gint            tick)
{
  double row = tick / (double) self->vticks;
  return MIN (row, G_MAXINT);
}

static gint
last_visible_row (BstPatternView *self)
{
  GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
  gint model_row = tick_to_row (self, self->max_ticks - 1);
  gint view_row = (-1 + (gint) scc->vadjustment->upper) / self->row_height;
  return MAX (model_row, view_row);
}

static void
row_to_ticks (BstPatternView *self,
              gint            row,
              gint           *tick_p,
              gint           *duration_p)
{
  gint tick = MIN (G_MAXINT - self->vticks, row * (double) self->vticks);
  if (tick_p)
    *tick_p = tick;
  if (duration_p)
    *duration_p = self->vticks;
}

static gint
pixels_to_row_unscrolled (BstPatternView *self,
                          gint            y)
{
  double row = y / (double) self->row_height;
  return MIN (G_MAXINT, row);
}

static gint
coord_to_row (BstPatternView *self,
              gint            y,
              gboolean       *is_valid)
{
  gint row = pixels_to_row_unscrolled (self, y + Y_OFFSET (self));
  if (is_valid)
    *is_valid = row >= 0 && row <= last_visible_row (self);
  return row;
}

static void
row_to_pixels_unscrolled (BstPatternView *self,
                          gint            row,
                          gint           *y_p,
                          gint           *height_p)
{
  if (y_p)
    *y_p = MIN (G_MAXINT, row * (double) self->row_height);
  if (height_p)
    *height_p = self->row_height;
}

static gboolean
row_to_coords (BstPatternView *self,
             gint            row,
             gint           *y_p,
             gint           *height_p)
{
  row_to_pixels_unscrolled (self, row, y_p, height_p);
  if (y_p)
    *y_p -= Y_OFFSET (self);
  if (height_p)
    *height_p = self->row_height;
  return row >= 0 && row <= last_visible_row (self);
}

static gint
coord_to_focus_col (BstPatternView *self,
                    gint            x,
                    gboolean       *is_valid)
{
  gint i, vwidth = 0, valid = 0, focus_col = G_MAXINT, dist = G_MAXINT;
  for (i = 0; i < self->n_focus_cols; i++)
    {
      gint ldist = x - self->focus_cols[i]->x;
      gint rdist = x - self->focus_cols[i]->x - self->focus_cols[i]->width;
      if (ABS (ldist) < dist || ABS (rdist) < dist)
        {
          focus_col = i;
          dist = MIN (ABS (ldist), ABS (rdist));
        }
      vwidth += self->focus_cols[i]->width;
    }
  if (focus_col < self->n_focus_cols)
    valid = x >= self->focus_cols[focus_col]->x && x < self->focus_cols[focus_col]->x + self->focus_cols[focus_col]->width;
  else if (x < 0 && self->n_focus_cols)
    focus_col = x / (vwidth / self->n_focus_cols);
  if (focus_col >= 0 && focus_col < self->n_focus_cols &&
      self->focus_cols[focus_col]->n_focus_positions > 1)
    {
      BstPatternColumn *col = self->focus_cols[focus_col];
      GdkRectangle rect;
      gint tick, duration, fx, fw, pos = 0, cx = x - col->x;
      row_to_ticks (self, self->focus_row, &tick, &duration);
      row_to_coords (self, self->focus_row, &rect.y, &rect.height);
      rect.x = col->x;
      rect.width = col->width;
      dist = G_MAXINT;
      for (i = 0; i < col->n_focus_positions; i++)
        {
          col->klass->get_focus_pos (col, self, CANVAS (self),
                                     pattern_view_column_pango_layout (self, col),
                                     tick, duration, &rect, i, &fx, &fw);
          fx += fw / 2;
          if (ABS (fx - cx) < dist)
            {
              dist = ABS (fx - cx);
              pos = i;
            }
        }
      focus_col += pos;
    }
  if (is_valid)
    *is_valid = valid;
  return focus_col;
}

#if 0
static gint
ticks_to_pixels (BstPatternView *self,
             gint          ticks)
{
  gdouble rppt = self->row_height / (double) self->vticks;      /* row-pixels per tick */
  gint pixels = ticks * rppt;
  if (ticks)
    pixels = MAX (pixels, 1);
  return pixels;
}

static gint
pixels_to_ticks (BstPatternView *self,
             gint          pixels)
{
  gdouble rtpp = self->vticks / (double) self->row_height;      /* row-ticks per pixel */
  gint ticks = pixels * rtpp;
  if (pixels > 0)
    ticks = MAX (ticks, 1);
  else
    ticks = 0;
  return ticks;
}

static gint
coord_to_tick (BstPatternView *self,
             gint        y,
             gboolean          extended_bound)
{
  guint tick;
  
  y += Y_OFFSET (self);
  tick = pixels_to_ticks (self, y);
  if (extended_bound)
    {
      guint tick2 = pixels_to_ticks (self, y + 1);
      if (tick2 > tick)
      tick = tick2 - 1;
    }
  return tick;
}

#define     CROSSING_TACT4          (1)
#define     CROSSING_TACT           (2)

static guint
coord_check_crossing (BstPatternView *self,
                  gint        y,
                  guint       crossing)
{
  guint ntick = coord_to_tick (self, y, FALSE);
  guint xtick = coord_to_tick (self, y, TRUE);
  guint nq = 0, xq = 0;
  
  /* catch _at_ tick boundary as well */
  xtick += 1;
  
  switch (crossing)
    {
    case CROSSING_TACT4:
      nq = ntick / (self->tpt * 4);
      xq = xtick / (self->tpt * 4);
      break;
    case CROSSING_TACT:
      nq = ntick / self->tpt;
      xq = xtick / self->tpt;
      break;
    }
  
  return nq != xq;
}
#endif

static void
pattern_view_allocate_2markers (BstPatternView    *self,
                                GxkScrollMarker *marker)
{
  GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
  gint pw = 10, cw = 10, ry, rh, row = tick_to_row (self, marker[0].coords.y);
  if (CANVAS (self))
    gdk_window_get_size (CANVAS (self), &cw, NULL);
  if (VPANEL (self))
    gdk_window_get_size (HPANEL (self), &pw, NULL);
  row_to_coords (self, row, &ry, &rh);
  gxk_scroll_canvas_setup_marker (scc, &marker[0], &scc->canvas,
                                  0, ry + rh / 2 - CMARK_HEIGHT (self) / 2,
                                  cw, CMARK_HEIGHT (self));
  gxk_scroll_canvas_setup_marker (scc, &marker[1], &scc->left_panel,
                                  0, ry + rh / 2 - PMARK_HEIGHT (self) / 2,
                                  pw, PMARK_HEIGHT (self));
}

static void
pattern_view_move_2markers (BstPatternView    *self,
                            GxkScrollMarker *marker)
{
  GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
  gint ry, rh, row = tick_to_row (self, marker[0].coords.y);
  row_to_coords (self, row, &ry, &rh);
  gxk_scroll_canvas_move_marker (scc, &marker[0], 0, ry + rh / 2 - CMARK_HEIGHT (self) / 2);
  gxk_scroll_canvas_move_marker (scc, &marker[1], 0, ry + rh / 2 - PMARK_HEIGHT (self) / 2);
}

static void
pattern_view_reallocate_contents (GxkScrollCanvas *scc,
                                  gint             xdiff,
                                  gint             ydiff)
{
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  guint i;
  if (!xdiff && !ydiff)         /* real size-allocate */
    {
      guint accu = 0;
      for (i = 0; i < self->n_cols; i++)
        {
          self->cols[i]->x = scc->x_offset + accu;
          accu += self->cols[i]->width;
        }
    }
  if (xdiff)
    for (i = 0; i < self->n_cols; i++)
      self->cols[i]->x += xdiff;
  for (i = 0; i < scc->n_markers; i++)
    {
      GxkScrollMarker *marker1 = scc->markers + i;
      GxkScrollMarker *marker2 = i + 1 < scc->n_markers ? scc->markers + i + 1 : NULL;
      if (marker2 && marker1->index == marker2->index)
        {
          if (xdiff || ydiff)
            pattern_view_move_2markers (self, marker1);
          else
            pattern_view_allocate_2markers (self, marker1);
          i += 1;
        }
    }
}

static void
pattern_view_realize (GtkWidget *widget)
{
  BstPatternView *self = BST_PATTERN_VIEW (widget);
  GTK_WIDGET_CLASS (bst_pattern_view_parent_class)->realize (widget);
  bst_pattern_view_set_focus (self, 0, 0);
}

static void
draw_row_shading (BstPatternView *self,
                  GdkWindow      *drawable,
                  gint            row,
                  gint            drawable_width,
                  gint            y,
                  gint            height)
{
  if (self->srow1 && row % self->srow1 == 0)
    gdk_draw_rectangle (drawable, COLOR_GC_SHADE1 (self), TRUE,
                        0, y, drawable_width, height);
  else if (self->srow2 && row % self->srow2 == 0)
    gdk_draw_rectangle (drawable, COLOR_GC_SHADE2 (self), TRUE,
                        0, y, drawable_width, height);
}

static void
bst_pattern_view_draw_canvas (GxkScrollCanvas *scc,
                              GdkWindow       *drawable,
                              GdkRectangle    *area)
{
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  gint row = coord_to_row (self, area->y, NULL);
  gint validrow, width, height;
  GdkRectangle rect;
  // gint line_width = 0; /* line widths != 0 interfere with dash-settings on some X servers */
  GXK_SCROLL_CANVAS_CLASS (bst_pattern_view_parent_class)->draw_canvas (scc, drawable, area);
  gdk_window_get_size (CANVAS (self), &width, &height);
  
  GdkGC *gcs[BST_PATTERN_COLUMN_GC_LAST] = { NULL, };
  gcs[BST_PATTERN_COLUMN_GC_TEXT0] = COLOR_GC_TEXT0 (self);
  gcs[BST_PATTERN_COLUMN_GC_TEXT1] = COLOR_GC_TEXT1 (self);
  gcs[BST_PATTERN_COLUMN_GC_VBAR] = COLOR_GC_VBAR (self);

  validrow = row_to_coords (self, row, &rect.y, &rect.height);
  while (validrow && rect.y < area->y + area->height)
    {
      gint tick, duration;
      guint i;
      draw_row_shading (self, drawable, row, width, rect.y, rect.height);
      row_to_ticks (self, row, &tick, &duration);
      for (i = 0; i < self->n_cols; i++)
        {
          rect.x = self->cols[i]->x;
          rect.width = self->cols[i]->width;
          self->cols[i]->klass->draw_cell (self->cols[i], self, drawable,
                                           gxk_scroll_canvas_peek_pango_layout (scc, COLUMN_PLAYOUT_INDEX (i)),
                                           tick, duration, &rect, area, gcs);
        }
      validrow = row_to_coords (self, ++row, &rect.y, &rect.height);
    }
}

#if 0   // FIXME
static void
bst_pattern_view_overlap_grow_hpanel_area (BstPatternView *self,
                                           GdkRectangle *area)
{
  gint i, x = area->x, xbound = x + area->width;
  
  /* grow hpanel exposes by surrounding tacts */
  i = coord_to_tick (self, x, FALSE);
  i /= self->tpt;
  if (i > 0)
    i -= 1;       /* fudge 1 tact to the left */
  i *= self->tpt;
  x = tick_to_coord (self, i);
  i = coord_to_tick (self, xbound + 1, TRUE);
  i /= self->tpt;
  i += 2;               /* fudge 1 tact to the right (+1 for round-off) */
  i *= self->tpt;
  xbound = tick_to_coord (self, i);
  
  area->x = x;
  area->width = xbound - area->x;
}
#endif

static void
bst_pattern_view_draw_hpanel (GxkScrollCanvas *scc,
                              GdkWindow       *drawable,
                              GdkRectangle    *area)
{
#if 0   // FIXME
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  GdkGC *draw_gc = STYLE (self)->fg_gc[STATE (self)];
  PangoRectangle rect = { 0 };
  gchar buffer[64];
  gint i, width, height;
  gdk_window_get_size (drawable, &width, &height);
  bst_pattern_view_overlap_grow_hpanel_area (self, area);
  
  /* tact numbers */
  for (i = area->x; i < area->x + area->width; i++)
    {
      /* drawing tact numbers is not of much use if we can't even draw
       * the tact grid, so we special case draw_tact_grid here
       */
      if (coord_check_crossing (self, i, CROSSING_TACT4))
      {
        guint next_pixel, tact4 = coord_to_tick (self, i, TRUE) + 1;
          
        tact4 /= (self->tpt * 4);
        next_pixel = tick_to_coord (self, (tact4 + 1) * (self->tpt * 4));
          
        g_snprintf (buffer, 64, "%u", tact4 + 1);
          pango_layout_set_text (PLAYOUT_HPANEL (self), buffer, -1);
          pango_layout_get_pixel_extents (PLAYOUT_HPANEL (self), NULL, &rect);
          
        /* draw this tact if there's enough space */
        if (i + rect.width / 2 < (i + next_pixel) / 2)
            gdk_draw_layout (drawable, draw_gc,
                             i - rect.width / 2, (height - rect.height) / 2,
                             PLAYOUT_HPANEL (self));
      }
      else if (self->draw_tact_grid && coord_check_crossing (self, i, CROSSING_TACT))
      {
          guint next_pixel, tact = coord_to_tick (self, i, TRUE) + 1;
          
        tact /= self->tpt;
        next_pixel = tick_to_coord (self, (tact + 1) * self->tpt);
          tact = tact % 4 + 1;
          if (tact == 1444)
            continue;   /* would draw on top of tact4 number */
          
        g_snprintf (buffer, 64, ":%u", tact % 4 + 1);
          pango_layout_set_text (PLAYOUT_HPANEL (self), buffer, -1);
          pango_layout_get_pixel_extents (PLAYOUT_HPANEL (self), NULL, &rect);
          
        /* draw this tact if there's enough space */
        if (i + rect.width < (i + next_pixel) / 2)          /* don't half width, leave some more space */
            gdk_draw_layout (drawable, draw_gc,
                             i - rect.width / 2, (height - rect.height) / 2,
                             PLAYOUT_HPANEL (self));
      }
    }
#endif
}

static void
bst_pattern_view_draw_vpanel (GxkScrollCanvas *scc,
                              GdkWindow       *drawable,
                              GdkRectangle    *area)
{
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  GdkGC *draw_gc = STYLE (self)->fg_gc[STATE (self)];
  gint row = coord_to_row (self, area->y, NULL);
  gint validrow, width, height;
  gchar buffer[64];
  GdkRectangle rect;
  gdk_window_get_size (VPANEL (self), &width, &height);

  validrow = row_to_coords (self, row, &rect.y, &rect.height);
  while (validrow && rect.y < area->y + area->height)
    {
      PangoRectangle prect = { 0 };
      gint tick;
      row_to_ticks (self, row, &tick, NULL);
      g_snprintf (buffer, 64, "%05x", tick);
      pango_layout_set_text (PLAYOUT_VPANEL (self), buffer, -1);
      pango_layout_get_pixel_extents (PLAYOUT_VPANEL (self), NULL, &prect);
      gdk_draw_layout (drawable, draw_gc,
                       width - prect.width - XTHICKNESS (self),
                       rect.y + (rect.height - prect.height) / 2,
                       PLAYOUT_VPANEL (self));
      validrow = row_to_coords (self, ++row, &rect.y, &rect.height);
    }
}

static void
bst_pattern_view_draw_marker (GxkScrollCanvas *scc,
                              GdkWindow       *drawable,
                              GdkRectangle    *area,
                              GxkScrollMarker *marker)
{
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  GdkGC *dark_gc = STYLE (self)->dark_gc[STATE (self)];
  GdkGC *slct_gc = STYLE (self)->bg_gc[GTK_STATE_SELECTED];
  GdkGC *draw_gc = GTK_WIDGET_HAS_FOCUS (self) ? slct_gc : dark_gc;
  switch (marker->mtype)
    {
    case BST_PATTERN_VIEW_MARKER_FOCUS:
      gdk_draw_rectangle (drawable, draw_gc, TRUE,
                          marker->extends.x, marker->extends.y,
                          marker->extends.width, marker->extends.height);
      break;
    }
#if 0   // FIXME
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  BstPatternViewMarkerType mtype = marker->mtype;
  gint x = marker->extends.x, y = marker->extends.y, width = marker->extends.width, height = marker->extends.height;
  GdkGC *draw_gc;
  switch (mtype)
    {
    default:                            draw_gc = STYLE (self)->bg_gc[STATE (self)]; break;
    }
  gdk_draw_rectangle (drawable, draw_gc, TRUE,
                      x + XTHICKNESS (self), y + YTHICKNESS (self),
                      width - 2 * XTHICKNESS (self),
                      height - 2 * YTHICKNESS (self));
  if (width == PMARK_WIDTH (self))
    gtk_paint_shadow (STYLE (self), drawable, STATE (self), GTK_SHADOW_OUT, NULL, NULL, NULL,
                      x + XTHICKNESS (self), y + YTHICKNESS (self),
                      width - 2 * XTHICKNESS (self), height - 2 * YTHICKNESS (self));
  gtk_paint_shadow (STYLE (self), drawable, STATE (self),
                    width == PMARK_WIDTH (self) ? GTK_SHADOW_IN : GTK_SHADOW_OUT, NULL, NULL, NULL,
                    x, y, width, height);
#endif
}

static void
pattern_view_queue_expose (BstPatternView *self,
                           guint           tick_start,
                           guint           tick_end)
{
  GdkRectangle area;
  gint y1, y2, height;
  gint row1 = tick_to_row (self, tick_start);
  gint row2 = tick_to_row (self, tick_end);
  gint row_last = bst_pattern_view_get_last_row (self);
  row1 = MAX (row1, 0);
  row2 = MIN (row2, row_last);
  row_to_coords (self, row1, &y1, NULL);
  row_to_coords (self, row2, &y2, &height);
  area.y = y1;
  area.height = y2 + height - y1;
  area.x = 0;
  gdk_window_get_size (VPANEL (self), &area.width, NULL);
  gdk_window_invalidate_rect (VPANEL (self), &area, TRUE);
  gdk_window_get_size (CANVAS (self), &area.width, NULL);
  gdk_window_invalidate_rect (CANVAS (self), &area, TRUE);
}

static void
pattern_view_adjustment_changed (GxkScrollCanvas *scc,
                                 GtkAdjustment   *adj)
{
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  if (adj == scc->vadjustment)
    {
      gint ry, rh, tick, duration, row = tick_to_row (self, self->max_ticks);
      row_to_pixels_unscrolled (self, row, &ry, &rh);
      double umin = ry + rh;                                            /* lower bound for adj->upper based on max_ticks */
      row = pixels_to_row_unscrolled (self, 1e+9);
      row_to_ticks (self, row, &tick, &duration);
      double umax = MIN (tick + duration, 1e+9);                        /* confine to possible tick range */
      row = tick_to_row (self, umax);
      row_to_pixels_unscrolled (self, row, &ry, &rh);
      umax = MIN (ry + rh, 1e+9);                                       /* upper bound for adj->upper based on pixels */
      umin = MIN (umin, umax * 1.5), umax = MAX (umin, umax);           /* properly confine boundaries */
      /* quantize to row height */
      gint n = CLAMP (adj->upper, umin, umax);
      n = (n + self->row_height - 1) / self->row_height;
      gdouble new_upper = MAX (n, 1) * self->row_height;
      new_upper = CLAMP (new_upper, umin, umax);
      /* guard against invalid changes */
      if (adj->lower != 0 || adj->upper != new_upper)
        {
          scc->vadjustment->lower = 0;
          scc->vadjustment->upper = new_upper;
          gtk_adjustment_changed (adj);
        }
      gtk_widget_queue_draw (GTK_WIDGET (self));
      bst_pattern_view_set_focus (self, self->focus_col, self->focus_row);
    }
}

static void
pattern_view_update_adjustments (GxkScrollCanvas *scc,
                                 gboolean         hadj,
                                 gboolean         vadj)
{
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  
  if (hadj)
    {
      scc->hadjustment->upper = scc->layout.canvas_width;
      scc->hadjustment->step_increment = self->cols[0]->width;
    }
  if (vadj)
    {
      gint ry, rh, tick, duration, row = tick_to_row (self, self->max_ticks);
      row_to_pixels_unscrolled (self, row, &ry, &rh);
      double umin = ry + rh;                                            /* lower bound for adj->upper based on max_ticks */
      row = pixels_to_row_unscrolled (self, 1e+9);
      row_to_ticks (self, row, &tick, &duration);
      double umax = MIN (tick + duration, 1e+9);                        /* confine to possible tick range */
      row = tick_to_row (self, umax);
      row_to_pixels_unscrolled (self, row, &ry, &rh);
      umax = MIN (ry + rh, 1e+9);                                       /* upper bound for adj->upper based on pixels */
      umin = MIN (umin, umax * 1.5), umax = MAX (umin, umax);           /* properly confine boundaries */
      /* quantize to row height */
      gint n = CLAMP (scc->vadjustment->upper, umin, umax);
      n = (n + self->row_height - 1) / self->row_height;
      gdouble new_upper = MAX (n, 1) * self->row_height;
      new_upper = CLAMP (new_upper, umin, umax);
      scc->vadjustment->lower = 0;
      scc->vadjustment->upper = new_upper;
      scc->hadjustment->step_increment = 4 * rh;        // FIXME rows-per-tact
    }
  GXK_SCROLL_CANVAS_CLASS (bst_pattern_view_parent_class)->update_adjustments (scc, hadj, vadj);

  bst_pattern_view_set_focus (self, self->focus_col, self->focus_row);
}

void
bst_pattern_view_vsetup (BstPatternView           *self,
                         guint                     tpqn,
                         guint                     qnpt,
                         guint                     max_ticks,
                         guint                     vticks)
{
  self->tpqn = MAX (tpqn, 1);
  self->tpt = MAX (qnpt, 1) * self->tpqn;
  self->vticks = CLAMP (vticks, 1, self->tpt);
  self->max_ticks = MAX (max_ticks, self->tpt);

  gtk_widget_queue_draw (GTK_WIDGET (self));
  Y_OFFSET (self) = GXK_SCROLL_CANVAS (self)->vadjustment->value;
  gxk_scroll_canvas_update_adjustments (GXK_SCROLL_CANVAS (self), FALSE, TRUE);
}

static void
pattern_view_handle_drag (GxkScrollCanvas     *scc,
                          GxkScrollCanvasDrag *scc_drag,
                          GdkEvent            *event)
{
  BstPatternView *self = BST_PATTERN_VIEW (scc);
  BstPatternViewDrag drag_mem = { 0 }, *drag = &drag_mem;
  gint hvalid = 1, hdrag = scc_drag->canvas_drag || scc_drag->top_panel_drag;
  gint vvalid = 1, vdrag = scc_drag->canvas_drag || scc_drag->left_panel_drag;
  /* copy over drag setup */
  memcpy (drag, scc_drag, sizeof (*scc_drag));  /* sizeof (*scc_drag) < sizeof (*drag) */
  drag->pview = self;
  /* calculate widget specific drag data */
  if (hdrag)
    drag->current_col = coord_to_focus_col (self, drag->current_x, &hvalid);
  if (vdrag)
    {
      drag->current_row = coord_to_row (self, drag->current_y, &vvalid);
      row_to_ticks (self, drag->current_row, &drag->current_tick, &drag->current_duration);
    }
  drag->current_valid = hvalid && vvalid;
  /* sync start-position fields and select row */
  if (drag->type == GXK_DRAG_START)
    {
      drag->start_col = self->start_col = drag->current_col;
      drag->start_row = self->start_row = drag->current_row;
      drag->start_tick = self->start_tick = drag->current_tick;
      drag->start_duration = self->start_duration = drag->current_duration;
      drag->start_valid = self->start_valid = drag->current_valid;
    }
  else
    {
      drag->start_col = self->start_col;
      drag->start_row = self->start_row;
      drag->start_tick = self->start_tick;
      drag->start_duration = self->start_duration;
      drag->start_valid = self->start_valid;
    }
  bst_pattern_view_set_focus (self, drag->current_col, drag->current_row);
  /* handle drag */
  g_signal_emit (self, signal_drag, 0, drag);
  /* copy over drag reply */
  scc_drag->state = drag->state;
  /* resort to clicks for unhandled button presses */
  if (drag->type == GXK_DRAG_START && drag->state == GXK_DRAG_UNHANDLED &&
      event && event->type == GDK_BUTTON_PRESS)
    {
      drag->state = GXK_DRAG_HANDLED;
      g_signal_emit (self, signal_clicked, 0, drag->button, drag->start_row, drag->start_tick, event);
    }
}

void
bst_pattern_view_add_column (BstPatternView   *self,
                             BstPatternLType   ltype,
                             gint              num,
                             BstPatternLFlags  lflags)
{
  BstPatternColumn *col;
  guint i;
  g_return_if_fail (BST_PATTERN_VIEW (self));

  self->cols = g_renew (BstPatternColumn*, self->cols, self->n_cols + 1);
  col = bst_pattern_column_create (ltype, num, lflags);
  self->cols[self->n_cols++] = col;
  col->focus_base = self->n_focus_cols;
  for (i = 0; i < col->n_focus_positions; i++)
    {
      self->focus_cols = g_renew (BstPatternColumn*, self->focus_cols, self->n_focus_cols + 1);
      self->focus_cols[self->n_focus_cols++] = col;
    }
  gtk_widget_queue_resize (GTK_WIDGET (self));
}

gint
bst_pattern_view_get_focus_width (BstPatternView           *self)
{
  g_return_val_if_fail (BST_PATTERN_VIEW (self), 0);
  return (XTHICKNESS (self) + YTHICKNESS (self)) / 2;
}

gint
bst_pattern_view_get_last_row (BstPatternView *self)
{
  g_return_val_if_fail (BST_PATTERN_VIEW (self), 0);
  return last_visible_row (self);
}

BstPatternColumn*
bst_pattern_view_get_focus_cell (BstPatternView            *self,
                                 guint                     *tick_p,
                                 guint                     *duration_p)
{
  guint focus_col = self->focus_col;
  guint focus_row = self->focus_row;
  if (focus_col < self->n_focus_cols)
    {
      row_to_ticks (self, focus_row, tick_p, duration_p);
      return self->focus_cols[focus_col];
    }
  return NULL;
}

gboolean
bst_pattern_view_dispatch_key (BstPatternView            *self,
                               guint                      keyval,
                               GdkModifierType            modifier,
                               BstPatternFunction         action,
                               gdouble                    param,
                               BstPatternFunction        *movement)
{
  GdkRectangle rect;
  guint focus_col = self->focus_col;
  guint focus_row = self->focus_row;
  if (focus_col < self->n_focus_cols && row_to_coords (self, focus_row, &rect.y, &rect.height))
    {
      BstPatternColumn *col = self->focus_cols[focus_col];
      gint tick, duration;
      rect.x = col->x;
      rect.width = col->width;
      row_to_ticks (self, focus_row, &tick, &duration);
      if (col->klass->key_event &&
          col->klass->key_event (col, self, CANVAS (self),
                                 pattern_view_column_pango_layout (self, col),
                                 tick, duration, &rect, focus_col - col->focus_base,
                                 keyval, modifier, action, param, movement))
        return TRUE;
    }
  return FALSE;
}

void
bst_pattern_view_set_focus (BstPatternView           *self,
                            guint                     focus_col,
                            guint                     focus_row)
{
  GdkRectangle rect;
  gint last_row;
  g_return_if_fail (BST_PATTERN_VIEW (self));

  focus_col = MIN (focus_col + 1, self->n_focus_cols) - 1;
  last_row = bst_pattern_view_get_last_row (self);
  focus_row = MIN (focus_row, last_row);
  if (focus_col < self->n_focus_cols && row_to_coords (self, focus_row, &rect.y, &rect.height))
    {
      GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
      BstPatternColumn *col = self->focus_cols[focus_col];
      gint fwidth = FOCUS_WIDTH (self);
      rect.x = col->x;
      rect.width = col->width;
      self->focus_row = focus_row;
      self->focus_col = focus_col;
      bst_pattern_view_set_pixmarker (self, 1, BST_PATTERN_VIEW_MARKER_FOCUS,
                                      rect.x, rect.y, rect.width, fwidth);
      bst_pattern_view_set_pixmarker (self, 2, BST_PATTERN_VIEW_MARKER_FOCUS,
                                      rect.x, rect.y + fwidth, fwidth, rect.height - 2 * fwidth);
      bst_pattern_view_set_pixmarker (self, 3, BST_PATTERN_VIEW_MARKER_FOCUS,
                                      rect.x + rect.width - fwidth, rect.y + fwidth, fwidth, rect.height - 2 * fwidth);
      bst_pattern_view_set_pixmarker (self, 4, BST_PATTERN_VIEW_MARKER_FOCUS,
                                      rect.x, rect.y + rect.height - fwidth, rect.width, fwidth);
      if (col->n_focus_positions > 1)
        {
          gint tick, duration, fx, fw;
          row_to_ticks (self, focus_row, &tick, &duration);
          col->klass->get_focus_pos (col, self, CANVAS (self),
                                     pattern_view_column_pango_layout (self, col),
                                     tick, duration, &rect, focus_col - col->focus_base, &fx, &fw);
          bst_pattern_view_set_pixmarker (self, 5, BST_PATTERN_VIEW_MARKER_FOCUS,
                                          rect.x + fx,
                                          rect.y + rect.height - fwidth - fwidth,
                                          fw,
                                          fwidth);
        }
      else
        bst_pattern_view_set_pixmarker (self, 5, BST_PATTERN_VIEW_MARKER_FOCUS, -1, -1, 1, 1);
      gxk_scroll_canvas_make_visible (scc,
                                      X_OFFSET (self) + rect.x,
                                      Y_OFFSET (self) + rect.y,
                                      rect.width, rect.height);
    }
}

void
bst_pattern_view_set_marker (BstPatternView          *self,
                             guint                    mark_index,
                             guint                    position,
                             BstPatternViewMarkerType mtype)
{
  GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
  GxkScrollMarker *marker;
  guint count;
  g_return_if_fail (mark_index > 0);
  mark_index += CUSTOM_MARKER_OFFSET;
  
  marker = gxk_scroll_canvas_lookup_marker (scc, mark_index, &count);
  if (!marker && !mtype)
    return;
  else if (!marker && mtype)
    {
      gxk_scroll_canvas_add_marker (scc, mark_index);
      gxk_scroll_canvas_add_marker (scc, mark_index);
      marker = gxk_scroll_canvas_lookup_marker (scc, mark_index, &count);
    }
  else if (marker && !mtype)
    {
      while (marker)
        {
          gxk_scroll_canvas_remove_marker (scc, marker);
          marker = gxk_scroll_canvas_lookup_marker (scc, mark_index, NULL);
        }
      return;
    }
  
  g_return_if_fail (count == 2);
  
  marker[0].coords.y = position;
  marker[1].coords.y = position;
  if (marker[0].mtype != mtype || !marker[0].pixmap)
    {
      marker[0].mtype = mtype;
      marker[1].mtype = mtype;
      pattern_view_allocate_2markers (self, marker);
    }
  else
    pattern_view_move_2markers (self, marker);
}

void
bst_pattern_view_set_pixmarker (BstPatternView           *self,
                                guint                     mark_index,
                                BstPatternViewMarkerType  mtype,
                                gint                      x,
                                gint                      y,
                                gint                      width,
                                gint                      height)
{
  GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
  GxkScrollMarker *marker;
  guint count;
  g_return_if_fail (mark_index > 0 && mark_index < CUSTOM_MARKER_OFFSET);
  
  marker = gxk_scroll_canvas_lookup_marker (scc, mark_index, &count);
  if (!marker && !mtype)
    return;
  else if (!marker && mtype)
    {
      gxk_scroll_canvas_add_marker (scc, mark_index);
      marker = gxk_scroll_canvas_lookup_marker (scc, mark_index, &count);
    }
  else if (marker && !mtype)
    {
      while (marker)
        {
          gxk_scroll_canvas_remove_marker (scc, marker);
          marker = gxk_scroll_canvas_lookup_marker (scc, mark_index, NULL);
        }
      return;
    }
  
  g_return_if_fail (count == 1);

  if (marker->mtype == mtype && marker->windowp == &CANVAS (self) &&
      marker->extends.width == width && marker->extends.height == height)
    gxk_scroll_canvas_move_marker (scc, marker, x, y);
  else
    {
      marker->mtype = mtype;
      gxk_scroll_canvas_setup_marker (scc, marker, &CANVAS (self), x, y, width, height);
    }
}

const gchar*
bst_pattern_view_get_layout (BstPatternView *self)
{
  return self->layout_string;
}

guint
bst_pattern_view_set_layout (BstPatternView *self,
                             const gchar    *layout)
{
  const gchar *p2, *p = layout;
  struct {
    BstPatternLType  ltype;
    BstPatternLFlags flags;
    gint             num;
  } *column = NULL;
  guint n = 0;
  while (*p == ' ')
    p++;
  column = g_realloc (column, sizeof (column[0]) * (n + 1));
  p2 = bst_pattern_layout_parse_column (p, &column[n].ltype, &column[n].num, &column[n].flags);
  while (p2 > p)
    {
      n++;
      p = p2;
      while (*p == ' ')
        p++;
      column = g_realloc (column, sizeof (column[0]) * (n + 1));
      p2 = bst_pattern_layout_parse_column (p, &column[n].ltype, &column[n].num, &column[n].flags);
    }
  if (!p2[0] && n)
    {
      guint i;
      bst_pattern_view_destroy_columns (self);
      self->layout_string = g_strndup (layout, p2 - layout);
      for (i = 0; i < n; i++)
        bst_pattern_view_add_column (self, column[i].ltype, column[i].num, column[i].flags);
      self->focus_col = self->n_focus_cols ? self->focus_col % self->n_focus_cols : 0;
      bst_pattern_view_set_focus (self, self->focus_col, self->focus_row);
    }
  g_free (column);
  return p2 - layout;
}

void
bst_pattern_view_set_shading (BstPatternView            *self,
                              guint                      row1,
                              guint                      row2,
                              guint                      row3,
                              guint                      row4)
{
  self->srow1 = row1;
  self->srow2 = row2;
  gtk_widget_queue_draw (GTK_WIDGET (self));
}

static void
bst_pattern_view_class_init (BstPatternViewClass *class)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
  GxkScrollCanvasClass *scroll_canvas_class = GXK_SCROLL_CANVAS_CLASS (class);
  
  gobject_class->finalize = bst_pattern_view_finalize;
  gobject_class->dispose = bst_pattern_view_dispose;

  widget_class->realize = pattern_view_realize;

  scroll_canvas_class->hscrollable = TRUE;
  scroll_canvas_class->vscrollable = TRUE;
  scroll_canvas_class->get_layout = pattern_view_get_layout;
  scroll_canvas_class->update_adjustments = pattern_view_update_adjustments;
  scroll_canvas_class->adjustment_changed = pattern_view_adjustment_changed;
  scroll_canvas_class->reallocate_contents = pattern_view_reallocate_contents;
  scroll_canvas_class->draw_canvas = bst_pattern_view_draw_canvas;
  scroll_canvas_class->draw_top_panel = bst_pattern_view_draw_hpanel;
  scroll_canvas_class->draw_left_panel = bst_pattern_view_draw_vpanel;
  scroll_canvas_class->draw_marker = bst_pattern_view_draw_marker;
  scroll_canvas_class->handle_drag = pattern_view_handle_drag;
  scroll_canvas_class->image_tint = gdk_color_from_rgb (0x00ffffff);
  scroll_canvas_class->image_saturation = 0;

  class->drag = NULL;
  class->clicked = NULL;
  
  signal_drag = g_signal_new ("drag", G_OBJECT_CLASS_TYPE (class),
                        G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (BstPatternViewClass, drag),
                        NULL, NULL,
                        bst_marshal_NONE__POINTER,
                        G_TYPE_NONE, 1, G_TYPE_POINTER);
  signal_clicked = g_signal_new ("clicked", G_OBJECT_CLASS_TYPE (class),
                         G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (BstPatternViewClass, clicked),
                         NULL, NULL,
                         bst_marshal_NONE__UINT_UINT_INT_BOXED,
                         G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INT,
                         GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

  bst_skin_config_add_notify ((BstSkinConfigNotify) pattern_view_class_setup_skin, class);
  pattern_view_class_setup_skin (class);
}

Generated by  Doxygen 1.6.0   Back to index