On Dec 30, 2005, at 11:07 AM, Jörn Reder wrote:

I like to have a child window on my application window which has a fixed
position relative to the button which opens it (somewhat like a Popup
menu). It should look like this:

    +-------------------------------------------------+
    | Main App Window                                 |
    |                                                 |
    |              +----------------------------------+
    |              | Child Window                     |
    |              |                                  |
    |              +-------------------------+--------+
    |                                        | Button |
    +----------------------------------------+--------+

The child window should overlap other widgets in the main window and can be closed again on user action. If the main window is resized, the child
window should keep attached at the right side and above the button.

What's the easiest way to accomplish that?


The most obvious option is to create a GTK_WINDOW_POPUP window with GDK_GRAVITY_SOUTH_EAST, float it above the main window, and use gtk_window_move() to track its location in the main window's configure-event. However, this doesn't really work out that well. My window manager refused to honor set_transient_for() for the popup window, so i had to call $popup->window->raise every time i relocated it. This resulted in some unpleasant flashing and move lagging.

A slightly sneakier solution is to use an actual widget as a fake popup, by using manual absolute position layout. We just tell the widget that its parent is the toplevel window, and then update the widget's position from the toplevel window's size-allocate as though we were implementing a fixed-position layout container. The advantage here is that the "popup" tracks the toplevel's position perfectly during moves because the X server moves the GdkWindow for us, removing all of the lag and z-order problems of the separate window solution. Also, using size-allocate is a little easier. ;-)

In both solutions, the keyboard focus is an interesting issue. The code i implemented ignores the "popup"'s keyboard focus altogether -- it doesn't get focus when toggled visible, and doesn't get inserted into the toplevel's focus chain. You didn't mention whether you wanted to have actual interactive widgets in your popup, or whether it was just for information display. I leave that as an exercise for you. ;-)

Attached is a 100-line prototype that implements the popup in a quick and dirty procedural style.

#!/usr/bin/perl -w

use strict;
use Gtk2 -init;
use Glib qw(:constants);

{
# main window
my $window = Gtk2::Window->new;
$window->signal_connect (destroy => sub {Gtk2->main_quit});
$window->set_default_size (400, 300);

my $vbox = Gtk2::VBox->new;
my $hbox = Gtk2::HBox->new;
my $scroller = Gtk2::ScrolledWindow->new;
my $textview = Gtk2::TextView->new;
my $toggle = Gtk2::ToggleButton->new ('Poppy');

$window->add ($vbox);
$vbox->add ($scroller);
$scroller->add ($textview);
$vbox->pack_start ($hbox, FALSE, FALSE, 0);
$hbox->pack_end ($toggle, FALSE, FALSE, 0);

# Toggle the visibility of the "popup" whenever $toggle is toggled.
$toggle->signal_connect (toggled => \&toggle_popup);
# We need to keep a reference to this so we can use it as the relative
# base for the popup's location.
$window->{popup_toggle} = $toggle;
# Since we're managing the popup ourselves, we'll have to relocate
# it whenever the main window's size changes.  We don't have to
# worry about tracking movement of the main window, because we're
# going to make the "popup" be a child widget, rather than a proper
# popup window.
$window->signal_connect (size_allocate => \&allocate_main_window);

$window->show_all;
Gtk2->main;
}


sub toggle_popup {
	my $toggle = shift;
	my $window = $toggle->get_toplevel;
	my $popup = $window->{popup};
	if (!$popup) {
		# Create a little frame with some interesting stuff
		# inside, and place it in an event box that will
		# serve as the popup.
		$popup = Gtk2::EventBox->new;
		my $frame = Gtk2::Frame->new ("Cool shit");
		my $vbox = Gtk2::VBox->new;
		my $thing1 = Gtk2::CheckButton->new ("thing 1");
		my $thing2 = Gtk2::CheckButton->new ("thing 2");
		$vbox->add ($thing1);
		$vbox->add ($thing2);
		$frame->add ($vbox);
		$popup->add ($frame);
		$frame->show_all;
		# Tell the frame that it belongs to the window.
		# We'll manage its position in update_popup_position.
		$popup->set_parent ($window);
		$window->{popup} = $popup;  # for later.
	}
	if ($toggle->get_active) {
		update_popup_position ($window);
		$popup->show;
	} else {
		$popup->hide;
	}
}

sub update_popup_position {
	my ($window) = @_;
	my $popup = $window->{popup};
	return unless $popup;
	# Keep the popup's lower-right corner pinned to the upper-right
	# corner of the popup toggle.  We're acting as though we are
	# the popup's container, so use size_allocate() to tell the
	# widget where to live.  Note that we do this regardless of
	# whether the widget is visible.
	my $toggle_allocation = $window->{popup_toggle}->allocation;
	my $popup_width = $popup->size_request->width;
	my $popup_height = $popup->size_request->height;
	my $allocation = Gtk2::Gdk::Rectangle->new (
		$toggle_allocation->x + $toggle_allocation->width
		  - $popup_width,
		$toggle_allocation->y - $popup_height,
		$popup_width,
		$popup_height);
	$popup->size_allocate ($allocation);
}

sub allocate_main_window {
	my ($window) = @_;
	# The size of the main window has changed; position the
	# popup accordingly.
	update_popup_position ($window);
	return FALSE;
}


--
Our enemies are innovative and resourceful, and so are we. They never stop thinking about new ways to harm our country and our people, and neither do we.
  -- President George W. Bush

_______________________________________________
gtk-perl-list mailing list
[email protected]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list

Reply via email to