# 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
