# # Change Display # # Demonstrates migrating a window between different displays and # screens. A display is a mouse and keyboard with some number of # associated monitors. A screen is a set of monitors grouped # into a single physical work area. The neat thing about having # multiple displays is that they can be on a completely separate # computers, as long as there is a network connection to the # computer where the application is running. # # Only some of the windowing systems where GTK+ runs have the # concept of multiple displays and screens. (The X Window System # is the main example.) Other windowing systems can only # handle one keyboard and mouse, and combine all monitors into # a single screen. # # This is a moderately complex example, and demonstrates: # # - Tracking the currently open displays and screens # # - Changing the screen for a window # # - Letting the user choose a window by clicking on it # # - Using GtkListStore and GtkTreeView # # - Using GtkDialog # package changedisplay; use Glib qw(TRUE FALSE); use Gtk2; #include <string.h> #include <gtk/gtk.h> #include "demo-common.h" ## ## The ChangeDisplayInfo structure corresponds to a toplevel window and ## holds pointers to widgets inside the toplevel window along with other ## information about the contents of the window. ## This is a common organizational structure in real applications. ## #typedef struct _ChangeDisplayInfo ChangeDisplayInfo; # #struct _ChangeDisplayInfo #{ # GtkWidget *window; # GtkSizeGroup *size_group; # # GtkTreeModel *display_model; # GtkTreeModel *screen_model; # GtkTreeSelection *screen_selection; # # GdkDisplay *current_display; # GdkScreen *current_screen; #}; # # These enumerations provide symbolic names for the columns # in the two GtkListStore models. # use constant DISPLAY_COLUMN_NAME => 0; use constant DISPLAY_COLUMN_DISPLAY => 1; use constant DISPLAY_NUM_COLUMNS => 2; use constant SCREEN_COLUMN_NUMBER => 0; use constant SCREEN_COLUMN_SCREEN => 1; use constant SCREEN_NUM_COLUMNS => 2; # # Finds the toplevel window under the mouse pointer, if any. # sub find_toplevel_at_pointer { my $display = shift; # my $pointer_window = $display->get_window_at_pointer; use Data::Dumper; my ($pointer_window, undef, undef) = $display->get_window_at_pointer; # # The user data field of a GdkWindow is used to store a pointer # to the widget that created it. # if ($pointer_window) { my $ptr = $pointer_window->get_user_data; if ($ptr) { my $widget = Glib::Object->new_from_pointer ($ptr); return $widget ? $widget->get_toplevel : undef; } else { return undef; } } else { return undef; } } sub button_release_event_cb { # my ($widget, $event, $clicked_ref) = @_; my (undef, undef, $clicked_ref) = @_; $$clicked_ref = TRUE; return TRUE; } # # Asks the user to click on a window, then waits for them click # the mouse. When the mouse is released, returns the toplevel # window under the pointer, or NULL, if there is none. # sub query_for_toplevel { my ($screen, $prompt) = @_; my $display = $screen->get_display; my $toplevel = undef; my $popup = Gtk2::Window->new ('popup'); $popup->set_screen ($screen); $popup->set_modal (TRUE); $popup->set_position ('center'); my $frame = Gtk2::Frame->new; $frame->set_shadow_type ('out'); $popup->add ($frame); my $label = Gtk2::Label->new ($prompt); $label->set_padding (10, 10); $frame->add ($label); $popup->show_all; my $cursor = Gtk2::Gdk::Cursor->new_for_display ($display, 'crosshair'); if (Gtk2::Gdk->pointer_grab ($popup->window, FALSE, 'button-release-mask', undef, $cursor, 0) eq 'success') #'GDK_GRAB_SUCCESS') { my $clicked = FALSE; $popup->signal_connect (button_release_event => \&button_release_event_cb, \$clicked); # # Process events until clicked is set by button_release_event_cb. # We pass in may_block=TRUE since we want to wait if there # are no events currently. # while (!$clicked) { Glib::MainContext->default->iteration (TRUE); } $toplevel = find_toplevel_at_pointer ($screen->get_display); # don't move yourself $toplevel = undef if defined $toplevel and $toplevel == $popup; } $popup->destroy; Gtk2::Gdk->flush; # Really release the grab return $toplevel; } # Prompts the user for a toplevel window to move, and then moves # that window to the currently selected display # sub query_change_display { my $info = shift; my $screen = $info->{window}->get_screen; my $toplevel = query_for_toplevel ($screen, "Please select the toplevel\n" . "to move to the new screen"); if ($toplevel) { $toplevel->set_screen ($info->{current_screen}); } else { $screen->get_display->beep; } } # # Fills in the screen list based on the current display # sub fill_screens { my $info = shift; $info->{screen_model}->clear; if ($info->{current_display}) { my $n_screens = $info->{current_display}->get_n_screens; for (my $i = 0; $i < $n_screens; $i++) { my $screen = $info->{current_display}->get_screen ($i); my $iter = $info->{screen_model}->append; $info->{screen_model}->set ($iter, SCREEN_COLUMN_NUMBER, $i, SCREEN_COLUMN_SCREEN, $screen); $info->{screen_selection}->select_iter ($iter) if $i == 0; } } } # # Called when the user clicks on a button in our dialog or # closes the dialog through the window manager. Unless the # "Change" button was clicked, we destroy the dialog. # sub response_cb { my ($dialog, $response_id, $info) = @_; if ($response_id eq 'ok') { query_change_display ($info); } else { $dialog->destroy; } } # # Called when the user clicks on "Open..." in the display # frame. Prompts for a new display, and then opens a connection # to that display. # sub open_display_cb { my ($button, $info) = @_; my $result = undef; my $dialog = Gtk2::Dialog->new ("Open Display", $info->{window}, 'modal', 'gtk-cancel', 'cancel', 'gtk-ok', 'ok'); $dialog->set_default_response ('ok'); my $display_entry = Gtk2::Entry->new; $display_entry->set_activates_default (TRUE); my $dialog_label = Gtk2::Label->new ("Please enter the name of\nthe new display\n"); $dialog->vbox->add ($dialog_label); $dialog->vbox->add ($display_entry); $display_entry->grab_focus; $dialog->child->show_all; while (!$result) { my $response_id = $dialog->run; last unless $response_id eq 'ok'; my $new_screen_name = $display_entry->get_chars (0, -1); if (length $new_screen_name) { $result = Gtk2::Gdk::Display->open ($new_screen_name); if (!$result) { $dialog_label->set_text ("Can't open display :\n\t$new_screen_name\nplease try another one\n"); } } } $dialog->destroy; } # # Called when the user clicks on the "Close" button in the # "Display" frame. Closes the selected display. # sub close_display_cb { my ($button, $info) = @_; $info->{current_display}->close if $info->{current_display}; } # # Called when the selected row in the display list changes. # Updates info->current_display, then refills the list of # screens. # sub display_changed_cb { my ($selection, $info) = @_; my ($model, $iter) = $selection->get_selected; if ($iter) { my ($d) = $model->get ($iter, DISPLAY_COLUMN_DISPLAY); $info->{current_display} = $d; } else { delete $info->{current_display}; } fill_screens ($info); } # # Called when the selected row in the sceen list changes. # Updates info->current_screen. # sub screen_changed_cb { my ($selection, $info) = @_; my ($model, $iter) = $selection->get_selected; if ($iter) { my ($s) = $model->get ($iter, SCREEN_COLUMN_SCREEN); $info->{current_screen} = $s; } else { $info->{current_screen} = undef; } } # # This function is used both for creating the "Display" and # "Screen" frames, since they have a similar structure. The # caller hooks up the right context for the value returned # in tree_view, and packs any relevant buttons into button_vbox. # sub create_frame { my ($info, $title) = @_; my $frame = Gtk2::Frame->new ($title); my $hbox = Gtk2::HBox->new (FALSE, 8); $hbox->set_border_width (8); $frame->add ($hbox); my $scrollwin = Gtk2::ScrolledWindow->new; $scrollwin->set_policy ('never', 'automatic'); $scrollwin->set_shadow_type ('in'); $hbox->pack_start ($scrollwin, TRUE, TRUE, 0); my $tree_view = Gtk2::TreeView->new; $tree_view->set_headers_visible (FALSE); $scrollwin->add ($tree_view); my $selection = $tree_view->get_selection; $selection->set_mode ('browse'); my $button_vbox = Gtk2::VBox->new (FALSE, 5); $hbox->pack_start ($button_vbox, FALSE, FALSE, 0); if (!$info->{size_group}) { $info->{size_group} = Gtk2::SizeGroup->new ('horizontal'); } $info->{size_group}->add_widget ($button_vbox); return ($frame, $tree_view, $button_vbox); } # # If we have a stack of buttons, it often looks better if their contents # are left-aligned, rather than centered. This function creates a button # and left-aligns it contents. # sub left_align_button_new { my $label = shift; my $button = Gtk2::Button->new_with_mnemonic ($label); $button->get_child->set_alignment (0.0, 0.5); return $button; } # # Creates the "Display" frame in the main window. # sub create_display_frame { my $info = shift; my ($frame, $tree_view, $button_vbox) = create_frame ($info, "Display"); my $button = left_align_button_new ("_Open..."); $button->signal_connect (clicked => \&open_display_cb, $info); $button_vbox->pack_start ($button, FALSE, FALSE, 0); $button = left_align_button_new ("_Close"); $button->signal_connect (clicked => \&close_display_cb, $info); $button_vbox->pack_start ($button, FALSE, FALSE, 0); $info->{display_model} = Gtk2::ListStore->new ("Glib::String", "Gtk2::Gdk::Display"); $tree_view->set_model ($info->{display_model}); my $column = Gtk2::TreeViewColumn->new_with_attributes ("Name", Gtk2::CellRendererText->new, text => DISPLAY_COLUMN_NAME); $tree_view->append_column ($column); my $selection = $tree_view->get_selection; $selection->signal_connect (changed => \&display_changed_cb, $info); return $frame; } # # Creates the "Screen" frame in the main window. # sub create_screen_frame { my $info = shift; my ($frame, $tree_view, $button_vbox) = create_frame ($info, "Screen"); $info->{screen_model} = Gtk2::ListStore->new ("Glib::Int", "Gtk2::Gdk::Screen"); $tree_view->set_model ($info->{screen_model}); my $column = Gtk2::TreeViewColumn->new_with_attributes ("Number", Gtk2::CellRendererText->new, text => SCREEN_COLUMN_NUMBER); $tree_view->append_column ($column); $info->{screen_selection} = $tree_view->get_selection; $info->{screen_selection}->signal_connect (changed => \&screen_changed_cb, $info); return $frame; } # # Called when one of the currently open displays is closed. # Remove it from our list of displays. # sub display_closed_cb { my ($display, $is_error, $info) = @_; my $iter = $info->{display_model}->get_iter_first; while ($iter) { my ($tmp_display) = $info->{display_model}->get ($iter, DISPLAY_COLUMN_DISPLAY); if ($tmp_display == $display) { $info->{display_model}->remove ($iter); last; } $iter = $info->{display_model}->iter_next ($iter); } } # # Adds a new display to our list of displays, and connects # to the "closed" signal so that we can remove it from the # list of displays again. # sub add_display { my ($info, $display) = @_; my $name = $display->get_name; my $iter = $info->{display_model}->append; $info->{display_model}->set ($iter, DISPLAY_COLUMN_NAME, $name, DISPLAY_COLUMN_DISPLAY, $display); $display->signal_connect (closed => \&display_closed_cb, $info); } # # Called when a new display is opened # sub display_opened_cb { my ($manager, $display, $info) = @_; add_display ($info, $display); } # # Adds all currently open displays to our list of displays, # and set up a signal connection so that we'll be notified # when displays are opened in the future as well. # sub initialize_displays { my $info = shift; my $manager = Gtk2::Gdk::DisplayManager->get; foreach my $display ($manager->list_displays) { add_display ($info, $display); } $manager->signal_connect (display_opened => \&display_opened_cb, $info); } # # Cleans up when the toplevel is destroyed; we remove the # connections we use to track currently open displays, then # free the ChangeDisplayInfo structure. # sub destroy_info { my $info = shift; my $manager = Gtk2::Gdk::DisplayManager->get; my @displays = $manager->list_displays; $manager->signal_handlers_disconnect_by_func (\&display_opened_cb, $info); foreach my $display ($manager->list_displays) { $display->signal_handlers_disconnect_by_func (\&display_closed_cb, $info); } $info = undef; } sub destroy_cb { my ($object, $inforef) = @_; destroy_info ($$inforef); $$inforef = undef; # just to be sure } my $info = undef; # # Main entry point. If the dialog for this demo doesn't yet exist, creates # it. Otherwise, destroys it. # sub do { if (!$info) { $info = {}; if ($ver = Gtk2->check_version (2, 2, 0)) { my $dialog = Gtk2::MessageDialog->new (undef, 'destroy-with-parent', 'info', 'ok', $ver."\n\nThis sample requires" ." at least Gtk+ version 2.2.0"); $dialog->show; $dialog->signal_connect (destroy => sub {$info = undef}); $dialog->signal_connect (response => sub {$dialog->destroy; 1}); $info->{window} = $dialog; } else { $info->{window} = Gtk2::Dialog->new ("Change Screen or display", undef, # parent 'no-separator', 'gtk-close' => 'close', 'Change' => 'ok'); $info->{window}->set_default_size (300, 400); $info->{window}->signal_connect (response => \&response_cb, $info); $info->{window}->signal_connect (destroy => \&destroy_cb, \$info); my $vbox = Gtk2::VBox->new (FALSE, 5); $vbox->set_border_width (8); $info->{window}->vbox->pack_start ($vbox, TRUE, TRUE, 0); my $frame = create_display_frame ($info); $vbox->pack_start ($frame, TRUE, TRUE, 0); $frame = create_screen_frame ($info); $vbox->pack_start ($frame, TRUE, TRUE, 0); initialize_displays ($info); $info->{window}->show_all; return $info->{window}; } } else { $info->{window}->destroy; return undef; } } 1; __END__ 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.