409 lines
11 KiB
Ruby
409 lines
11 KiB
Ruby
|
# Copyright (c) 2003-2005 Ruby-GNOME2 Project Team
|
||
|
# This program is licenced under the same licence as Ruby-GNOME2.
|
||
|
#
|
||
|
# $Id: changedisplay.rb,v 1.6 2005/07/30 11:22:15 mutoh Exp $
|
||
|
=begin
|
||
|
= 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 Gtk::ListStore and Gtk::TreeView
|
||
|
|
||
|
- Using Gtk::Dialog
|
||
|
=end
|
||
|
|
||
|
require 'common'
|
||
|
|
||
|
module Demo
|
||
|
class ChangeDisplay < Gtk::Dialog
|
||
|
# These enumerations provide symbolic names for the columns
|
||
|
# in the two GtkListStore models.
|
||
|
DISPLAY_COLUMN_NAME, DISPLAY_COLUMN_DISPLAY = 0, 1
|
||
|
SCREEN_COLUMN_NUMBER, SCREEN_COLUMN_SCREEN = 0, 1
|
||
|
|
||
|
# Main entry point. If the dialog for this demo doesn't yet exist, creates
|
||
|
# it. Otherwise, destroys it.
|
||
|
def initialize
|
||
|
@size_group = nil
|
||
|
|
||
|
@display_model = nil
|
||
|
@screen_model = nil
|
||
|
|
||
|
@screen_selection = nil
|
||
|
|
||
|
@current_display = nil
|
||
|
@current_screen = nil
|
||
|
|
||
|
super('Change Screen or display',
|
||
|
nil, # parent
|
||
|
Gtk::Dialog::NO_SEPARATOR,
|
||
|
[Gtk::Stock::CLOSE, Gtk::Dialog::RESPONSE_CLOSE],
|
||
|
['Change', Gtk::Dialog::RESPONSE_OK])
|
||
|
|
||
|
set_default_size(300, 400)
|
||
|
signal_connect('response') do |dialog, response_id|
|
||
|
if response_id == Gtk::Dialog::RESPONSE_OK
|
||
|
if Gtk.check_version?(2, 2, 0)
|
||
|
query_change_display
|
||
|
else
|
||
|
puts "This sample requires GTK+ 2.2.0 or later"
|
||
|
end
|
||
|
else
|
||
|
destroy # Gtk.main_quit?
|
||
|
end
|
||
|
end
|
||
|
signal_connect('destroy') do
|
||
|
|
||
|
end
|
||
|
|
||
|
unless Gtk.check_version?(2, 2, 0)
|
||
|
vbox.add(Gtk::Label.new("This sample requires GTK+ 2.2.0 or later"))
|
||
|
return
|
||
|
end
|
||
|
|
||
|
vbox = Gtk::VBox.new(false, 5)
|
||
|
vbox.set_border_width(8)
|
||
|
|
||
|
self.vbox.pack_start(vbox, true, true)
|
||
|
|
||
|
@size_group = Gtk::SizeGroup.new(Gtk::SizeGroup::HORIZONTAL)
|
||
|
|
||
|
frame = create_display_frame
|
||
|
vbox.pack_start(frame, true, true)
|
||
|
|
||
|
frame = create_screen_frame
|
||
|
vbox.pack_start(frame, true, true)
|
||
|
|
||
|
initialize_displays
|
||
|
end
|
||
|
|
||
|
# 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.
|
||
|
def initialize_displays
|
||
|
manager = Gdk::DisplayManager.get
|
||
|
|
||
|
manager.displays.each do |display|
|
||
|
add_display(display)
|
||
|
end
|
||
|
|
||
|
handler_id = manager.signal_connect('display_opened') do |display|
|
||
|
add_display(display)
|
||
|
end
|
||
|
signal_connect('destroy') do
|
||
|
manager.signal_handler_disconnect(handler_id)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Fills in the screen list based on the current display
|
||
|
def fill_screens
|
||
|
@screen_model.clear
|
||
|
|
||
|
if @current_display
|
||
|
n_screens = @current_display.n_screens
|
||
|
|
||
|
n_screens.times do |i|
|
||
|
iter = @screen_model.append
|
||
|
iter.set_value(SCREEN_COLUMN_NUMBER, i)
|
||
|
iter.set_value(SCREEN_COLUMN_SCREEN, @current_display.get_screen(i))
|
||
|
|
||
|
if i == 0
|
||
|
@screen_selection.select_iter(iter)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Creates the 'Display' frame in the main window.
|
||
|
def create_display_frame
|
||
|
frame, tree_view, button_vbox = create_frame('Display')
|
||
|
|
||
|
button = left_align_button_new('_Open...')
|
||
|
button.signal_connect('clicked') do
|
||
|
open_display_cb
|
||
|
end
|
||
|
button_vbox.pack_start(button, false, false, 0)
|
||
|
|
||
|
button = left_align_button_new('_Close')
|
||
|
button.signal_connect('clicked') do
|
||
|
if @current_display
|
||
|
@current_display.close
|
||
|
end
|
||
|
end
|
||
|
button_vbox.pack_start(button, false, false, 0)
|
||
|
|
||
|
@display_model = Gtk::ListStore.new(String, Gdk::Display)
|
||
|
tree_view.model = @display_model
|
||
|
|
||
|
column = Gtk::TreeViewColumn.new('Name',
|
||
|
Gtk::CellRendererText.new,
|
||
|
{'text' => DISPLAY_COLUMN_NAME})
|
||
|
tree_view.append_column(column)
|
||
|
|
||
|
selection = tree_view.selection
|
||
|
selection.signal_connect('changed') do
|
||
|
display_changed_cb(selection)
|
||
|
end
|
||
|
|
||
|
return frame
|
||
|
end
|
||
|
|
||
|
# Creates the 'Screen' frame in the main window.
|
||
|
def create_screen_frame
|
||
|
frame, tree_view, button_vbox = create_frame('Screen')
|
||
|
|
||
|
@screen_model = Gtk::ListStore.new(Integer, Gdk::Screen)
|
||
|
tree_view.model = @screen_model
|
||
|
|
||
|
column = Gtk::TreeViewColumn.new('Number',
|
||
|
Gtk::CellRendererText.new,
|
||
|
{'text' => SCREEN_COLUMN_NUMBER})
|
||
|
tree_view.append_column(column)
|
||
|
|
||
|
@screen_selection = tree_view.selection
|
||
|
@screen_selection.signal_connect('changed') do |selection|
|
||
|
@current_screen = if iter = selection.selected
|
||
|
iter.get_value(SCREEN_COLUMN_SCREEN)
|
||
|
else
|
||
|
nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return frame
|
||
|
end
|
||
|
|
||
|
# 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.
|
||
|
def create_frame(title)
|
||
|
frame = Gtk::Frame.new(title)
|
||
|
|
||
|
hbox = Gtk::HBox.new(false, 8)
|
||
|
hbox.set_border_width(8)
|
||
|
frame.add(hbox)
|
||
|
|
||
|
scrollwin = Gtk::ScrolledWindow.new
|
||
|
scrollwin.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC)
|
||
|
scrollwin.shadow_type = Gtk::SHADOW_IN
|
||
|
hbox.pack_start(scrollwin, true, true)
|
||
|
|
||
|
tree_view = Gtk::TreeView.new
|
||
|
tree_view.headers_visible = false
|
||
|
scrollwin.add(tree_view)
|
||
|
|
||
|
selection = tree_view.selection
|
||
|
selection.mode = Gtk::SELECTION_BROWSE
|
||
|
|
||
|
button_vbox = Gtk::VBox.new(false, 5)
|
||
|
hbox.pack_start(button_vbox, false, false)
|
||
|
|
||
|
@size_group.add_widget(button_vbox)
|
||
|
|
||
|
return frame, tree_view, button_vbox
|
||
|
end
|
||
|
|
||
|
# 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.
|
||
|
def left_align_button_new(label)
|
||
|
button = Gtk::Button.new(label, true)
|
||
|
button.child.set_alignment(0.0, 0.5)
|
||
|
|
||
|
return button
|
||
|
end
|
||
|
|
||
|
|
||
|
# Prompts the user for a toplevel window to move, and then moves
|
||
|
# that window to the currently selected display
|
||
|
def query_change_display
|
||
|
screen = self.window.screen
|
||
|
|
||
|
toplevel = query_for_toplevel(screen,
|
||
|
"Please select the toplevel\n"+
|
||
|
"to move to the new screen")
|
||
|
|
||
|
if toplevel
|
||
|
toplevel.screen = @current_screen
|
||
|
else
|
||
|
screen.display.beep
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# 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 nil, if there is none.
|
||
|
def query_for_toplevel(screen, prompt)
|
||
|
toplevel = nil
|
||
|
|
||
|
display = screen.display
|
||
|
|
||
|
popup = Gtk::Window.new(Gtk::Window::POPUP)
|
||
|
popup.screen = screen
|
||
|
popup.modal = true
|
||
|
popup.window_position = Gtk::Window::POS_CENTER
|
||
|
|
||
|
frame = Gtk::Frame.new
|
||
|
frame.set_shadow_type(Gtk::SHADOW_OUT)
|
||
|
popup.add(frame)
|
||
|
|
||
|
label = Gtk::Label.new(prompt)
|
||
|
label.set_padding(10, 10)
|
||
|
frame.add(label)
|
||
|
|
||
|
popup.show_all
|
||
|
|
||
|
# TODO: Gdk::Cursor.new(screen.display, Gdk::Cursor::CROSSHAIR)
|
||
|
cursor = Gdk::Cursor.new(Gdk::Cursor::CROSSHAIR)
|
||
|
|
||
|
if Gdk::pointer_grab(popup.window, false,
|
||
|
Gdk::Event::BUTTON_RELEASE_MASK,
|
||
|
nil,
|
||
|
cursor,
|
||
|
Gdk::Event::CURRENT_TIME) == Gdk::GRAB_SUCCESS
|
||
|
clicked = false
|
||
|
|
||
|
popup.signal_connect('button-release-event') do
|
||
|
clicked = true
|
||
|
end
|
||
|
|
||
|
# 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.
|
||
|
until clicked
|
||
|
Gtk.main_iteration # TODO: GLib::main_context_iteration(nil, true)
|
||
|
|
||
|
toplevel = find_toplevel_at_pointer(screen.display)
|
||
|
if toplevel == popup
|
||
|
toplevel = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
popup.destroy
|
||
|
Gdk.flush # Really release the grab
|
||
|
|
||
|
return toplevel
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Finds the toplevel window under the mouse pointer, if any.
|
||
|
def find_toplevel_at_pointer(display)
|
||
|
pointer_window, x, y = display.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
|
||
|
widget = pointer_window.user_data
|
||
|
end
|
||
|
|
||
|
return (if widget
|
||
|
widget.toplevel
|
||
|
else
|
||
|
nil
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
# Called when the user clicks on 'Open...' in the display
|
||
|
# frame. Prompts for a new display, and then opens a connection
|
||
|
# to that display.
|
||
|
def open_display_cb
|
||
|
dialog = Gtk::Dialog.new('Open Display',
|
||
|
self,
|
||
|
Gtk::Dialog::MODAL,
|
||
|
[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
|
||
|
[Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK])
|
||
|
|
||
|
dialog.default_response = Gtk::Dialog::RESPONSE_OK
|
||
|
display_entry = Gtk::Entry.new
|
||
|
display_entry.activates_default = true
|
||
|
dialog_label =
|
||
|
Gtk::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.vbox.show_all
|
||
|
|
||
|
result = nil
|
||
|
until result
|
||
|
response_id = dialog.run
|
||
|
break if response_id != Gtk::Dialog::RESPONSE_OK
|
||
|
|
||
|
new_screen_name = display_entry.text
|
||
|
|
||
|
unless new_screen_name.empty?
|
||
|
begin
|
||
|
result = Gdk::Dispaly.open(new_screen_name)
|
||
|
rescue
|
||
|
dialog_label.text = "Can't open display :\n\t%s\nplease try another one\n" % [new_screen_name]
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
dialog.destroy
|
||
|
end
|
||
|
|
||
|
# Called when the selected row in the display list changes.
|
||
|
# Updates info.current_display, then refills the list of
|
||
|
# screens.
|
||
|
def display_changed_cb(selection)
|
||
|
@current_display =
|
||
|
if iter = selection.selected
|
||
|
iter.get_value(DISPLAY_COLUMN_DISPLAY)
|
||
|
else
|
||
|
nil
|
||
|
end
|
||
|
fill_screens
|
||
|
end
|
||
|
|
||
|
# 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.
|
||
|
def add_display(display)
|
||
|
iter = @display_model.append
|
||
|
iter.set_value(DISPLAY_COLUMN_NAME, display.name)
|
||
|
iter.set_value(DISPLAY_COLUMN_DISPLAY, display)
|
||
|
|
||
|
handler_id = display.signal_connect('closed') do
|
||
|
display_closed_cb(display)
|
||
|
end
|
||
|
|
||
|
signal_connect('destroy') do
|
||
|
display.signal_handler_disconnect(handler_id)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Called when one of the currently open displays is closed.
|
||
|
# Remove it from our list of displays.
|
||
|
def display_closed_cb(display)
|
||
|
@display_model.each do |model, path, iter|
|
||
|
tmp_display = iter.get_value( DISPLAY_COLUMN_DISPLAY)
|
||
|
if tmp_display == display
|
||
|
@display_model.remove(iter)
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|