|
楼主 |
发表于 2003-12-13 22:03:58
|
显示全部楼层
谢谢georgek从网上找到的脚本。
#!/usr/bin/perl -w
#
# Copyright (C) 2003 by the gtk2-perl team (see the file AUTHORS for the full
# list)
#
# This library is free software; you can redistribute it and/or modify it under
# the terms of the GNU Library General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Library General Public License for
# more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA.
#
# $Header: /cvsroot/gtk2-perl/gtk2-perl-xs/Gtk2/examples/histogramplot.pl,v 1.8 2003/10/19 04:42:44 muppetman Exp $
#
# originally written in C by muppet in 2001 or 2002, i can't remember.
# ported from C to gtk2-perl 2003 by muppet
package Histogram:lot;
use Gtk2;
use warnings;
use strict;
use Data:umper;
use constant FALSE => 0;
use constant TRUE => 1;
use constant MIN_CHART_WIDTH => 256;
use constant MIN_CHART_HEIGHT => 100;
my %drag_info;
use constant DRAG_PAD => 2;
sub screen_to_threshold {
my ($plot, $sx) = @_;
my $val = ($sx - $plot->{chartleft}) * 256 / $plot->{chartwidth};
return $val < 0 ? 0 : $val > 255 ? 255 : $val;
}
sub threshold_to_screen {
$_[1] / 256.0 * $_[0]->{chartwidth} + $_[0]->{chartleft}
}
#
# Glib::Objects are special; they're not normal perl objects (although
# the bindings go out of their way to make them act like it).
#
# if you just want to add a new function for yourself to a Gtk2:rawingArea,
# the stuff we're about to get into is not strictly necessary; you could just
# re-bless the object reference into the decendent class and add an @ISA for
# it, like normal perl.
#
# however, adding signals, properties, or virtual function overrides to a
# GObject-based class requires fiddling with a GObjectClass structure
# specific to that subclass. if you added a new property to a re-blessed
# Glib::Object, *all* instances of that reblessed object's GObject parent
# would have the new property! that's because you didn't create a new
# GObjectClass for that new subclass.
#
# in order to create a new type to which you can add signals and properties,
# and which will be indistinguishable from "normal" GObjects at the C level
# (which means you can pass it to other gtk functions), you need to
# register your subclass with the Glib::Type subsystem.
#
# here, we're registering the current package as a new subclass of
# Gtk2:rawingArea, and in the process adding a signal and a few
# object properties.
#
use Glib::Object::Subclass
'Gtk2:rawingArea',
signals => {
#
# create a new signal...
#
threshold_changed => {
method => 'do_threshold_changed',
flags => [qw/run-first/],
return_type => undef, # void return
param_types => [], # instance and data are automatic
},
#
# override some built-ins... note that for this to work
# there has to be a signal to go along with the virtual
# function you want to override...
#
# i chose do_size_request to keep from having the normal
# size_request method being called.
size_request => \&do_size_request,
# just to show it off... you can use names, but you have
# to use a qualified name, or it looks in the current package
# at runtime, not setup time.
expose_event => __PACKAGE__.'::expose_event',
configure_event => \&configure_event,
motion_notify_event => \&motion_notify_event,
button_press_event => \&button_press_event,
button_release_event => \&button_release_event,
},
properties => [
Glib:aramSpec->double ('threshold',
'Threshold',
'Diving line between above and below',
0.0, 255.0, 127.0,
[qw/readable writable/]),
Glib:aramSpec->boxed ('histogram',
'Histogram Data',
'Array reference containing histogram data',
'Glib::Scalar',
[qw/readable writable/]),
Glib:aramSpec->boolean ('continuous',
'Continuous updates',
'Emit the threshold_changed signal on every mouse event during drag, rather than just on release',
FALSE,
[qw/readable writable/]),
],
;
#
# at the lowest level, new Glib::Objects are created by Glib::Object::new.
# that function creates the instance and calls the instance initializers
# for all classes in the object's lineage, from the parent to the descendant.
# if there's any setup you would need to do in a constructor, it goes here.
#
sub INIT_INSTANCE {
my $plot = shift;
warn "INIT_INSTANCE $plot";
$plot->{threshold} = 0;
$plot->{histogram} = [ 0..255 ];
$plot->{pixmap} = undef;
$plot->{th_gc} = undef;
$plot->{dragging} = FALSE;
$plot->{continuous} = FALSE;
$plot->{origin_layout} = $plot->create_pango_layout ("0.0%");
$plot->{maxval_layout} = $plot->create_pango_layout ("100.0%");
$plot->{current_layout} = $plot->create_pango_layout ("0");
$plot->{maxscale_layout} = $plot->create_pango_layout ("255");
$plot->{minscale_layout} = $plot->create_pango_layout ("0");
$plot->{max} = 0;
$plot->{chartwidth} = 0;
$plot->{chartleft} = 0;
$plot->{bottom} = 0;
$plot->{height} = 0;
$plot->set_events ([qw/exposure-mask
leave-notify-mask
button-press-mask
button-release-mask
pointer-motion-mask
pointer-motion-hint-mask/]);
}
#
# whenever anybody tries to get the value of a gobject property belonging
# to this class, this function will be called. note that this call
# signature is different from the C version -- here we return the requested
# value.
#
sub GET_PROPERTY {
my ($plot, $pspec) = @_;
if ($pspec->get_name eq 'threshold') {
return $plot->{threshold};
} elsif ($pspec->get_name eq 'histogram') {
return $plot->{histogram};
} elsif ($pspec->get_name eq 'continuous') {
return $plot->{continuous};
}
}
#
# whenever anybody tries to set the value of a gobject property belonging
# to this class, this function will be called. the provided Glib::Object::Base
# method just stores the value in a hash key, but here we need to do other
# bits of work when a value is changed.
#
# note that this one also is changed from the C call signature; the order
# of the arguments has been swizzled to be more consistent with GET_PROPERTY.
#
sub SET_PROPERTY {
my ($plot, $pspec, $newval) = @_;
if ($pspec->get_name eq 'threshold') {
$plot->set_plot_data ($newval, ());
} elsif ($pspec->get_name eq 'histogram') {
$plot->set_plot_data (undef, @$newval);
} elsif ($pspec->get_name eq 'continuous') {
$plot->{continuous} = $newval;
}
}
sub calc_dims {
my $plot = shift;
my $context = $plot->{origin_layout}->get_context;
my $fontdesc = $context->get_font_description;
my $metrics = $context->get_metrics ($fontdesc, undef);
$plot->{textwidth} = 5 * $metrics->get_approximate_digit_width
/ Gtk2:ango->scale; #PANGO_SCALE;
$plot->{textheight} = ($metrics->get_descent + $metrics->get_ascent)
/ Gtk2:ango->scale; #PANGO_SCALE;
$plot->{chartleft} = $plot->{textwidth} + 2;
$plot->{chartwidth} = $plot->allocation->width - $plot->{chartleft};
$plot->{bottom} = $plot->allocation->height - $plot->{textheight} - 3;
$plot->{height} = $plot->{bottom};
}
# this gets called when the widget's parent container wants to know
# how much space we want. it's important to note that this sub will be
# called from deep within the gtk library, not from perl code, which is
# why it had to be implemented as a class closure override.
# we modify the requisition passed to us.
sub do_size_request {
my ($plot, $requisition) = @_;
warn "in class override for $_[0]\::do_size_request";
$requisition->width ($plot->{textwidth} + 2 + MIN_CHART_WIDTH);
$requisition->height ($plot->{textheight} + MIN_CHART_HEIGHT);
# chain up to the parent class.
shift->signal_chain_from_overridden (@_);
}
sub expose_event {
my ($plot, $event) = @_;
$plot->window->draw_drawable ($plot->style->fg_gc($plot->state),
$plot->{pixmap},
$event->area->x, $event->area->y,
$event->area->x, $event->area->y,
$event->area->width, $event->area->height);
return FALSE;
}
sub configure_event {
my ($plot, $event) = @_;
$plot->{pixmap} = Gtk2::Gdk:ixmap->new ($plot->window,
$plot->allocation->width,
$plot->allocation->height,
-1); # same depth as window
# update dims
$plot->calc_dims;
$plot->histogram_draw;
return TRUE;
}
sub draw_th_marker {
my ($plot, $w, $draw_text) = @_;
if (!$plot->{th_gc}) {
$plot->{th_gc} = Gtk2::Gdk::GC->new ($plot->{pixmap});
$plot->{th_gc}->copy ($plot->style->fg_gc ($plot->state));
$plot->{th_gc}->set_function ('invert');
}
$w->draw_line ($plot->{th_gc},
$plot->threshold_to_screen ($plot->{threshold}), 0,
$plot->threshold_to_screen ($plot->{threshold}), $plot->{bottom});
$plot->{current_layout}->set_text (sprintf '%d', $plot->{threshold});
my ($textwidth, $textheight) = $plot->{current_layout}->get_pixel_size;
$plot->{marker_textwidth} = $textwidth;
# erase text
$w->draw_rectangle ($plot->style->bg_gc($plot->state),
TRUE,
$plot->threshold_to_screen ($plot->{threshold})
- $plot->{marker_textwidth} - 1,
$plot->{bottom} + 1,
$plot->{marker_textwidth} + 1,
$textheight);
$w->draw_layout ($plot->{th_gc},
$plot->threshold_to_screen ($plot->{threshold})
- $plot->{marker_textwidth},
$plot->{bottom} + 1,
$plot->{current_layout})
if $draw_text;
}
#
# the user can click either very near the vertical line of the marker
# or on (actually in the bbox of) the marker text.
#
sub marker_hit {
my ($plot, $screen_x, $screen_y) = @_;
my $screen_th = $plot->threshold_to_screen ($plot->{threshold});
if ($screen_y > $plot->{bottom}) {
# check for hit on text
if ($screen_x > $screen_th - $plot->{marker_textwidth} &&
$screen_x <= $screen_th) {
return $screen_th;
}
} else {
# check for hit on line
if ($screen_x > $screen_th - DRAG_PAD &&
$screen_x < $screen_th + DRAG_PAD) {
return $screen_th;
}
}
return undef;
}
sub button_press_event {
my ($plot, $event) = @_;
return FALSE
if ($event->button != 1 || not defined $plot->{pixmap});
my $sx = $plot->marker_hit ($event->x, $event->y);
return FALSE
unless defined $sx;
# erase the previous threshold line from the pixmap...
$plot->{threshold_back} = $plot->{threshold};
$plot->draw_th_marker ($plot->{pixmap}, FALSE);
$plot->window->draw_drawable ($plot->style->fg_gc($plot->state),
$plot->{pixmap},
$plot->threshold_to_screen ($plot->{threshold}) - $plot->{marker_textwidth}, 0,
$plot->threshold_to_screen ($plot->{threshold}) - $plot->{marker_textwidth}, 0,
$plot->{marker_textwidth} + 1, $plot->allocation->height);
# and draw the new one on the window.
$plot->draw_th_marker ($plot->window, TRUE);
$plot->{dragging} = TRUE;
$drag_info{offset_x} =
$plot->threshold_to_screen ($plot->{threshold}) - $event->x;
return TRUE;
}
sub button_release_event {
my ($plot, $event) = @_;
return FALSE
if ($event->button != 1
|| !$plot->{dragging}
|| not defined $plot->{pixmap});
# erase the previous threshold line from the window...
$plot->draw_th_marker ($plot->window, FALSE);
$plot->{threshold} =
$plot->screen_to_threshold ($event->x + $drag_info{offset_x});
# and draw the new one on the pixmap.
$plot->draw_th_marker ($plot->{pixmap}, TRUE);
$plot->window->draw_drawable ($plot->style->fg_gc ($plot->state),
$plot->{pixmap},
0, 0, 0, 0,
$plot->allocation->width,
$plot->allocation->height);
$plot->{dragging} = FALSE;
# let any listeners know that if the threshold has changed
$plot->signal_emit ("threshold-changed")
if $plot->{threshold_back} != $plot->{threshold}
and not $plot->{continuous};
return TRUE;
}
my $sizer;
sub motion_notify_event {
my ($plot, $event) = @_;
my ($x, $y, $state);
if ($event->is_hint) {
(undef, $x, $y, $state) = $event->window->get_pointer;
} else {
$x = $event->x;
$y = $event->y;
$state = $event->state;
}
if ($plot->{dragging}) {
return FALSE
if (!(grep /button1-mask/, @$state) ||
not defined $plot->{pixmap});
$plot->draw_th_marker ($plot->window, FALSE);
$x += $drag_info{offset_x};
# confine to valid region
my $t = $plot->screen_to_threshold ($x);
$x = $plot->threshold_to_screen (0) if $t < 0;
$x = $plot->threshold_to_screen (255) if $t > 255;
$plot->{threshold} = $plot->screen_to_threshold ($x);
$plot->draw_th_marker ($plot->window, TRUE);
$plot->signal_emit ("threshold-changed")
if $plot->{continuous};
} else {
my $c = undef;
my $sx = $plot->marker_hit ($x, $y);
if (defined $sx) {
$sizer = Gtk2::Gdk::Cursor->new ('GDK_SB_H_DOUBLE_ARROW')
if not defined $sizer;
$c = $sizer;
}
$plot->window->set_cursor ($c);
}
return TRUE;
}
sub histogram_draw {
my $plot = shift;
my $gc = $plot->style->fg_gc ($plot->state);
# erase (the hard way)
$plot->{pixmap}->draw_rectangle ($plot->style->bg_gc ($plot->state),
TRUE, 0, 0,
$plot->allocation->width,
$plot->allocation->height);
if ($plot->{max} != 0 && scalar(@{$plot->{histogram}})) {
##GdkPoint points[256+2];
my @hist = @{ $plot->{histogram} };
my @points = ();
for (my $i = 0; $i < 256; $i++) {
push @points,
$i/256.0 * $plot->{chartwidth} + $plot->{chartleft},
$plot->{bottom} - $plot->{height} * $hist[$i] / $plot->{max};
}
$plot->{pixmap}->draw_polygon ($gc, TRUE, @points,
$plot->allocation->width, $plot->{bottom} + 1,
$plot->{chartleft}, $plot->{bottom} + 1);
}
# mark threshold
# should draw this after the scale...
draw_th_marker ($plot, $plot->{pixmap}, TRUE);
# the annotations
$plot->{pixmap}->draw_line ($gc, 0, 0, $plot->{chartleft}, 0);
$plot->{pixmap}->draw_line ($gc, 0, $plot->{bottom},
$plot->{chartleft}, $plot->{bottom});
$plot->{pixmap}->draw_line ($gc, $plot->{chartleft}, $plot->{bottom},
$plot->{chartleft},
$plot->{bottom} + $plot->{textheight} + 1);
$plot->{pixmap}->draw_line ($gc,
$plot->allocation->width - 1, $plot->{bottom},
$plot->allocation->width - 1, $plot->{bottom} + $plot->{textheight} + 1);
$plot->{pixmap}->draw_layout ($gc,
$plot->{chartleft} - (1 + $plot->{textwidth}),
1, $plot->{maxval_layout});
$plot->{pixmap}->draw_layout ($gc,
$plot->{chartleft} - (1 + $plot->{textwidth}),
$plot->{bottom} - 1 - $plot->{textheight},
$plot->{origin_layout});
$plot->{pixmap}->draw_layout ($gc,
$plot->{chartleft} + 2, $plot->{bottom} + 1,
$plot->{minscale_layout});
}
#
# change the data displayed in the window, with all the necessary
# work to get it properly updated.
#
# @threshold: new threshold. ignored if undef.
# @histogram: new histogram. if not empty, copy to the histwin's
# internal histogram cache. MUST be 256 items long.
#
sub set_plot_data {
my ($plot, $threshold, @hist) = @_;
$plot->{threshold} = $threshold if defined $threshold;
if (@hist) {
my $total = 0;
my $max = 0;
for (my $i = 0; $i < 256; $i++) {
$total += $hist[$i];
$max = $hist[$i]
if $hist[$i] > $max;
}
$plot->{max} = $max;
$plot->{histogram} = \@hist;
$plot->{maxval_layout}->set_text
( sprintf "%4.1f%%", (100.0 * $plot->{max}) / $total );
}
# update dims since text may have changed
$plot->calc_dims;
# if the pixmap doesn't exist, we haven't been put on screen yet.
# don't bother drawing anything.
if ($plot->{pixmap}) {
$plot->histogram_draw;
$plot->queue_draw;
}
}
sub do_threshold_changed {
warn "default threshold handler";
}
################
#
# public methods
#
# we inherit new from Glib::Object::Subclass, and all the stuff we'd need
# to get to is available as object properties, so, well, there's no work
# to do here. :-)
#
##########################################################################
# now let's take that code for a test drive...
#
package main;
use Gtk2 qw/-init -locale/;
use constant TRUE => 1;
use constant FALSE => 0;
my $window = Gtk2::Window->new;
$window->signal_connect (delete_event => sub { Gtk2->main_quit; FALSE });
my $vbox = Gtk2::VBox->new;
$window->add ($vbox);
$window->set_border_width (6);
#
# a nicely framed histogram plot with some cheesy data
#
my $plot = Histogram:lot->new (
threshold => 64,
histogram => [ map { sin $_/256*3.1415 } (0..255) ]
);
my $frame = Gtk2::Frame->new;
$vbox->pack_start ($frame, TRUE, TRUE, 0);
$frame->add ($plot);
$frame->set_shadow_type ('in');
#
# a way to manipulate one of the properties...
#
my $check = Gtk2::CheckButton->new ("Continuous");
$vbox->pack_start ($check, FALSE, FALSE, 0);
$check->set_active ($plot->get ('continuous'));
$check->signal_connect (toggled => sub {
$plot->set (continuous => $check->get_active);
1;
});
#
# do something fun when the threshold changes.
#
my $label = Gtk2:abel->new (sprintf "threshold: %.1f",
$plot->get ('threshold'));
$vbox->pack_start ($label, FALSE, FALSE, 0);
$plot->signal_connect (threshold_changed => sub {
$label->set_text (sprintf 'threshold: %d', $plot->get('threshold'));
});
#
# all systems go!
#
$window->show_all;
Gtk2->main;
# explicit clean up makes us see various messages on a debug build.
undef $plot;
undef $window; |
|