Custom Cell Renderers in a TreeView that requires scrolling broken in gtk+-2.8.18 and 2.8.19



Hi all.

I've just noticed a very strange bug in my TreeViews since updating gtk.

I have some apps that use a custom cell renderer to provide a pop-up
GtkCalender. When I insert a row in the model, the custom cell renderer
pops up ( begins editing ) as it's the 1st column. After I select a date
and accept changes AND IF THERE ARE MORE ROWS THAN WILL FIT IN THE
TREEVIEW WITHOUT SCROLLING, the new row DISAPPEARS from the TreeView. I
scroll right from top to bottom - the new row certainly *looks* like
it's gone. However if I insert *another* row, my previous missing row
appears ( momentarily ) at the bottom again ... until I accept changes
from the custom cell renderer on the newest row, and then they both ( or
ALL, if I've inserted a number of rows ) disappear.

For completeness ( partial anyway - it's a pretty complicated beast, but
I can post an entire working example if required - please reply and
request this if necessary, but note that it will be in Perl ), I'm
pasting the entire CellRendererDate.pm module that provides the custom
cell renderer at the bottom, however I note again that everything has
worked properly for every version of gtk+ prior to 2.8.18.

I initially updated from 2.8.17 to 2.8.19, and then when I found the
bug, reverted to 2.8.17 ... tested some more ( no such bug here ), and
then updated to 2.8.18 and did some more testing. It definitely arrived
in 2.8.18.

Has anyone noticed this behaviour with custom cell renderers? I have
tested many other TreeViews that *don't* have a custom cell renderer,
and have also tried moving the custom cell renderer to another position
( column ). I am almost positive that something is broken with
displaying rows with a custom cell renderer when scrolling is required.
I would greatly appreciate some help / suggests / confirmations / etc.

Dan

---

use strict;
use Gtk2 -init;

package Gtk2::Ex::Datasheet::DBI::CellRendererDate;

use Glib::Object::Subclass
  "Gtk2::CellRenderer",
  signals => {
    edited => {
      flags => [qw(run-last)],
      param_types => [qw(Glib::String Glib::Scalar)],
    },
  },
  properties => [
    Glib::ParamSpec -> boolean("editable", "Editable", "Can I change
that?", 0, [qw(readable writable)]),
    Glib::ParamSpec -> string("date", "Date", "What's the date again?",
"", [qw(readable writable)]),
  ]
;

use constant x_padding => 2;
use constant y_padding => 3;

use constant arrow_width => 15;
use constant arrow_height => 15;

sub hide_popup {
  my ($cell) = @_;

  Gtk2 -> grab_remove($cell -> { _popup });
  $cell -> { _popup } -> hide();
}

sub get_today {
  my ($cell) = @_;

  my ($day, $month, $year) = (localtime())[3, 4, 5];
  $year += 1900;
  $month += 1;

  return ($year, $month, $day);
}

sub get_date {
  my ($cell) = @_;

  my $text = $cell -> get("date");
  my ($year, $month, $day) = $text
    ? split(/[\/-]/, $text)
    : $cell -> get_today();

  return ($year, $month, $day);
}

sub add_padding {
  my ($cell, $year, $month, $day) = @_;
  return ($year, sprintf("%02d", $month), sprintf("%02d", $day));
}

sub INIT_INSTANCE {
  my ($cell) = @_;

  my $popup = Gtk2::Window -> new ('popup');
  my $vbox = Gtk2::VBox -> new(0, 0);

  my $calendar = Gtk2::Calendar -> new();

  my $hbox = Gtk2::HBox -> new(0, 0);

  my $today = Gtk2::Button -> new('Today');
  my $none = Gtk2::Button -> new('None');

  $cell -> {_arrow} = Gtk2::Arrow -> new("down", "none");

  # We can't just provide the callbacks now because they might need
access to
  # cell-specific variables.  And we can't just connect the signals in
  # START_EDITING because we'd be connecting many signal handlers to the
same
  # widgets.
  $today -> signal_connect(clicked => sub {
    $cell -> { _today_clicked_callback } -> (@_)
      if (exists($cell -> { _today_clicked_callback }));
  });

  $none -> signal_connect(clicked => sub {
    $cell -> { _none_clicked_callback } -> (@_)
      if (exists($cell -> { _none_clicked_callback }));
  });

  $calendar -> signal_connect(day_selected_double_click => sub {
    $cell -> { _day_selected_double_click_callback } -> (@_)
      if (exists($cell -> { _day_selected_double_click_callback }));
  });

  $calendar -> signal_connect(month_changed => sub {
    $cell -> { _month_changed } -> (@_)
      if (exists($cell -> { _month_changed }));
  });

  $hbox -> pack_start($today, 1, 1, 0);
  $hbox -> pack_start($none, 1, 1, 0);

  $vbox -> pack_start($calendar, 1, 1, 0);
  $vbox -> pack_start($hbox, 0, 0, 0);

  # Find out if the click happended outside of our window.  If so, hide it.
  # Largely copied from Planner (the former MrProject).

  # Implement via Gtk2::get_event_widget?
  $popup -> signal_connect(button_press_event => sub {
    my ($popup, $event) = @_;

    if ($event -> button() == 1) {
      my ($x, $y) = ($event -> x_root(), $event -> y_root());
      my ($xoffset, $yoffset) = $popup -> window() -> get_root_origin();

      my $allocation = $popup -> allocation();

      my $x1 = $xoffset + 2 * $allocation -> x();
      my $y1 = $yoffset + 2 * $allocation -> y();
      my $x2 = $x1 + $allocation -> width();
      my $y2 = $y1 + $allocation -> height();

      unless ($x > $x1 && $x < $x2 && $y > $y1 && $y < $y2) {
        $cell -> hide_popup();
        return 1;
      }
    }

    return 0;
  });

  $popup -> add($vbox);

  $cell -> { _popup } = $popup;
  $cell -> { _calendar } = $calendar;
}

sub START_EDITING {
  my ($cell, $event, $view, $path, $background_area, $cell_area, $flags)
= @_;

  my $popup = $cell -> { _popup };
  my $calendar = $cell -> { _calendar };

  # Specify the callbacks.  Will be called by the signal handlers set up in
  # INIT_INSTANCE.
  $cell -> { _today_clicked_callback } = sub {
    my ($button) = @_;
    my ($year, $month, $day) = $cell -> get_today();

    $cell -> signal_emit(edited => $path, join("-", $cell ->
add_padding($year, $month, $day)));
    $cell -> hide_popup();
  };

  $cell -> { _none_clicked_callback } = sub {
    my ($button) = @_;

    $cell -> signal_emit(edited => $path, "");
    $cell -> hide_popup();
  };

  $cell -> { _day_selected_double_click_callback } = sub {
    my ($calendar) = @_;
    my ($year, $month, $day) = $calendar -> get_date();

    $cell -> signal_emit(edited => $path, join("-", $cell ->
add_padding($year, ++$month, $day)));
    $cell -> hide_popup();
  };

  $cell -> { _month_changed } = sub {
    my ($calendar) = @_;

    my ($selected_year, $selected_month) = $calendar -> get_date();
    my ($current_year, $current_month, $current_day) = $cell -> get_today();

    if ($selected_year == $current_year &&
        ++$selected_month == $current_month) {
      $calendar -> mark_day($current_day);
    }
    else {
      $calendar -> unmark_day($current_day);
    }
  };

  my ($year, $month, $day) = $cell -> get_date();

  $calendar -> select_month($month - 1, $year);
  $calendar -> select_day($day);

  # Necessary to get the correct allocation of the popup.
  $popup -> move(-500, -500);
  $popup -> show_all();
 
  # Figure out where to put the popup - ie don't put it offscreen,
  # as it's not movable ( by the user )
  my $screen_height = $popup->get_screen->get_height;
  my $requisition = $popup->size_request();
  my $popup_width = $requisition->width;
  my $popup_height = $requisition->height;
  my ($x_origin, $y_origin) =  $view -> get_bin_window() -> get_origin();
  my ($popup_x, $popup_y);
 
  $popup_x = $x_origin + $cell_area->x() + $cell_area->width() -
$popup_width;
  $popup_x = 0 if $popup_x < 0;
 
  $popup_y = $y_origin + $cell_area -> y() + $cell_area -> height();
 
  if ( $popup_y + $popup_height > $screen_height ) {
    $popup_y = $y_origin + $cell_area -> y() - $popup_height;
  }
 
  $popup -> move( $popup_x, $popup_y );

  # Grab the focus and the pointer.
  Gtk2 -> grab_add($popup);
  $popup -> grab_focus();

  Gtk2::Gdk -> pointer_grab($popup -> window(),
                            1,
                            [qw(button-press-mask
                                button-release-mask
                                pointer-motion-mask)],
                            undef,
                            undef,
                            0);

  return;
}

sub get_date_string {
  my $cell = shift;
  return $cell->get ('date');
}

sub calc_size {
  my ($cell, $layout) = @_;
  my ($width, $height) = $layout -> get_pixel_size();

  return (0,
          0,
          $width + x_padding * 2 + arrow_width,
          $height + y_padding * 2);
}

sub GET_SIZE {
  my ($cell, $widget, $cell_area) = @_;

  my $layout = $cell -> get_layout($widget);
  $layout -> set_text( $cell -> get_date_string() || '' );

  return $cell -> calc_size($layout);
}

sub get_layout {
  my ($cell, $widget) = @_;

  return $widget -> create_pango_layout("");
}

sub RENDER {
  my ($cell, $window, $widget, $background_area, $cell_area,
$expose_area, $flags) = @_;
  my $state;

  if ($flags & 'selected') {
    $state = $widget -> has_focus()
      ? 'selected'
      : 'active';
  } else {
    $state = $widget -> state() eq 'insensitive'
      ? 'insensitive'
      : 'normal';
  }

  my $layout = $cell -> get_layout($widget);
  $layout -> set_text( $cell -> get_date_string() || '' );

  my ($x_offset, $y_offset, $width, $height) = $cell -> calc_size($layout);

  $widget -> get_style -> paint_layout($window,
                                       $state,
                                       1,
                                       $cell_area,
                                       $widget,
                                       "cellrenderertext",
                                       $cell_area -> x() + $x_offset +
x_padding,
                                       $cell_area -> y() + $y_offset +
y_padding,
                                       $layout);

  $widget -> get_style -> paint_arrow ($window,
                                       $widget->state,
                                       'none',
                                       $cell_area,
                                       $cell -> { _arrow },
                                       "",
                                       "down",
                                       1,
                                       $cell_area -> x + $cell_area ->
width - arrow_width,
                                       $cell_area -> y + $cell_area ->
height - arrow_height - 2,
                                       arrow_width - 3,
                                       arrow_height);
}

1;




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]