diff options
Diffstat (limited to 'examples/pygtk-demo/demos/changedisplay.py')
-rw-r--r-- | examples/pygtk-demo/demos/changedisplay.py | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/examples/pygtk-demo/demos/changedisplay.py b/examples/pygtk-demo/demos/changedisplay.py new file mode 100644 index 00000000..592a7e0f --- /dev/null +++ b/examples/pygtk-demo/demos/changedisplay.py @@ -0,0 +1,414 @@ +#!/usr/bin/env python +'''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 +''' +import gtk +import gobject + +# These enumerations provide symbolic names for the columns +# in the two GtkListStore models. +# +( + DISPLAY_COLUMN_NAME, + DISPLAY_COLUMN_DISPLAY, + DISPLAY_NUM_COLUMNS +) = range(3) + +( + SCREEN_COLUMN_NUMBER, + SCREEN_COLUMN_SCREEN, + SCREEN_NUM_COLUMNS +) = range(3) + +def find_toplevel_at_pointer(display): + ''' Finds the toplevel window under the mouse pointer, if any. + ''' + pointer_window = display.get_window_at_pointer()[0] + + # 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.get_user_data() + + return widget and widget.get_toplevel() or None + +class QueryForToplevel(gtk.Window): + ''' 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. + ''' + + def __init__(self, screen, prompt): + gtk.Window.__init__(self, gtk.WINDOW_POPUP) + self.set_screen(screen) + self.set_modal(True) + self.set_position(gtk.WIN_POS_CENTER) + + frame = gtk.Frame() + frame.set_shadow_type(gtk.SHADOW_OUT) + self.add(frame) + + label = gtk.Label(prompt) + label.set_padding(10, 10) + frame.add(label) + + self.show_all() + + def run(self): + display = self.get_screen().get_display() + cursor = gtk.gdk.Cursor(display, gtk.gdk.CROSSHAIR) + + main_context = gobject.main_context_default() + if (gtk.gdk.pointer_grab(self.window, False, + gtk.gdk.BUTTON_RELEASE_MASK, None, cursor) == gtk.gdk.GRAB_SUCCESS): + self.query_clicked = False + self.connect("button-release-event", self.button_release_event_cb) + + # 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 self.query_clicked is False: + main_context.iteration(True) + + toplevel = find_toplevel_at_pointer(display) + if (toplevel == self): + toplevel = None; + + self.destroy() + gtk.gdk.flush() # Really release the grab + + return toplevel + + def button_release_event_cb(self, winref, event): + self.query_clicked = True + return True + + +class LeftAlignButton(gtk.Button): + ''' If we have a stack of buttons, it often looks better if their contents + are left-aligned, rather than centered. This class creates a button + and left-aligns it contents. + ''' + def __init__(self, label): + gtk.Button.__init__(self, label) + child = self.get_children()[0] + child.set_alignment(0., 0.5) + + +# Main entry point. If the dialog for this demo doesn't yet exist, creates +# it. +# +class ChangeDisplayDemo(gtk.Dialog): + size_group = None + display_model = None + screen_model = None + screen_selection = None + current_display = None + current_screen = None + + def __init__(self, parent=None): + gtk.Dialog.__init__(self, "Change Screen or display", parent, + gtk.DIALOG_NO_SEPARATOR, + (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE, + "Change", gtk.RESPONSE_OK)) + self.set_default_size(300, 400) + + try: + self.set_screen(parent.get_screen()) + except AttributeError: + self.connect('destroy', lambda *w: gtk.main_quit()) + self.connect("response", self.response_cb) + self.connect("destroy", self.destroy_cb) + + vbox = gtk.VBox(False, 5) + vbox.set_border_width(8) + + self.vbox.pack_start(vbox, True, True, 0) + + frame = self.__create_display_frame() + vbox.pack_start(frame, True, True, 0) + + frame = self.__create_screen_frame() + vbox.pack_start(frame, True, True, 0) + + self.__initialize_displays() + + self.show_all() + + def __initialize_displays(self): + ''' 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. + ''' + manager = gtk.gdk.display_manager_get() + displays = manager.list_displays() + + for item in displays: + self.add_display(item) + id = manager.connect("display_opened", self.display_opened_cb) + manager.set_data('user-callback', id) + + def __create_frame(self, title): + ''' 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. + ''' + frame = gtk.Frame(title) + + hbox = gtk.HBox(False, 8) + hbox.set_border_width(8) + frame.add(hbox) + + scrollwin = gtk.ScrolledWindow(); + scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scrollwin.set_shadow_type (gtk.SHADOW_IN) + hbox.pack_start(scrollwin, True, True, 0) + + tree_view = gtk.TreeView() + tree_view.set_headers_visible(False) + scrollwin.add(tree_view) + + selection = tree_view.get_selection() + selection.set_mode(gtk.SELECTION_BROWSE) + + button_vbox = gtk.VBox(False, 5) + hbox.pack_start(button_vbox, False, False, 0) + + if self.size_group is None: + self.size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + self.size_group.add_widget(button_vbox) + + return (frame, tree_view, button_vbox) + + + def __create_display_frame(self): + ''' Creates the "Display" frame in the main window. + ''' + frame, tree_view, button_vbox = self.__create_frame("Display") + + button = LeftAlignButton("_Open...") + button.connect("clicked", self.open_display_cb) + button_vbox.pack_start(button, False, False, 0) + + button = LeftAlignButton("_Close") + button.connect ("clicked", self.close_display_cb) + button_vbox.pack_start(button, False, False, 0) + + self.display_model = gtk.ListStore(str, object); + tree_view.set_model(self.display_model) + + column = gtk.TreeViewColumn("Name", gtk.CellRendererText(), + text=DISPLAY_COLUMN_NAME) + tree_view.append_column(column) + + selection = tree_view.get_selection() + selection.connect("changed", self.display_changed_cb) + + return frame + + def __create_screen_frame(self): + ''' Creates the "Screen" frame in the main window. + ''' + frame, tree_view, button_vbox = self.__create_frame("Screen") + + self.screen_model = gtk.ListStore(int, object); + tree_view.set_model(self.screen_model) + + column = gtk.TreeViewColumn("Number", gtk.CellRendererText(), + text=SCREEN_COLUMN_NUMBER) + tree_view.append_column(column) + + self.screen_selection = tree_view.get_selection() + self.screen_selection.connect("changed", self.screen_changed_cb) + + return frame + + def query_change_display(self): + ''' Prompts the user for a toplevel window to move, and then moves + that window to the currently selected display + ''' + screen = self.window.get_screen() + + toplevel = QueryForToplevel(screen, + "Please select the toplevel\nto move to the new screen").run() + + if toplevel is not None: + toplevel.set_screen(self.current_screen) + else: + screen.get_display().beep() + + + def response_cb(self, dialog, response_id): + ''' 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. + ''' + if response_id == gtk.RESPONSE_OK: + self.query_change_display() + else: + dialog.destroy() + + def open_display_cb(self, button): + ''' Called when the user clicks on "Open..." in the display + frame. Prompts for a new display, and then opens a connection + to that display. + ''' + dialog = gtk.Dialog("Open Display", self, gtk.DIALOG_MODAL, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) + + dialog.set_default_response(gtk.RESPONSE_OK) + display_entry = gtk.Entry() + display_entry.set_activates_default(True) + dialog_label = gtk.Label("Please enter the name of\nthe new display\n") + + dialog.vbox.add(dialog_label) + dialog.vbox.add(display_entry) + + display_entry.grab_focus() + dialog.show_all() + + result = None + while result is None: + response_id = dialog.run() + if response_id != gtk.RESPONSE_OK: + break; + new_screen_name = display_entry.get_chars(0, -1) + print new_screen_name + if new_screen_name != "": + result = gtk.gdk.Display(new_screen_name) + if result is None: + error_msg = ( + "Can't open display :\n\t%s\nplease try another one\n" % + (new_screen_name,)) + dialog_label.set_text(error_msg) + + dialog.destroy() + + def close_display_cb(self, button): + ''' Called when the user clicks on the "Close" button in the + "Display" frame. Closes the selected display. + ''' + if self.current_display: + self.current_display.close() + + + def display_changed_cb(self, selection): + ''' Called when the selected row in the display list changes. + Updates info.current_display, then refills the list of + screens. + ''' + model, iter = selection.get_selected() + if iter is not None: + self.current_display = model.get_value(iter, DISPLAY_COLUMN_DISPLAY) + else: + self.current_display = None + self.fill_screens() + + def screen_changed_cb(self, selection): + ''' Called when the selected row in the sceen list changes. + Updates info->current_screen. + ''' + model, iter = selection.get_selected() + if iter: + self.current_screen = model.get(iter, SCREEN_COLUMN_SCREEN) + else: + self.current_screen = None; + + def destroy_cb(self, parent): + self.destroy_info() + if parent is None: + gtk.main_quit() + + + def fill_screens(self): + ''' Fills in the screen list based on the current display + ''' + self.screen_model.clear() + if self.current_display is not None: + n_screens = self.current_display.get_n_screens() + + for i in range(n_screens): + screen = self.current_display.get_screen(i); + iter = self.screen_model.append() + self.screen_model.set(iter, + SCREEN_COLUMN_NUMBER, i, SCREEN_COLUMN_SCREEN, screen) + if (i == 0): + self.screen_selection.select_iter(iter) + + def display_closed_cb(self, display, is_error, info): + ''' Called when one of the currently open displays is closed. + Remove it from our list of displays. + ''' + iter = self.display_model.get_iter_first() + while iter: + tmp_display = self.display_model.get_value(iter, DISPLAY_COLUMN_DISPLAY) + if (tmp_display == display): + info.display_model.remove(iter) + break; + iter = info.display_model.iter_next() + + def add_display(self, display): + ''' 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. + ''' + name = display.get_name() + + iter = self.display_model.append() + self.display_model.set(iter, + DISPLAY_COLUMN_NAME, name, DISPLAY_COLUMN_DISPLAY, display) + id = display.connect("closed", self.display_closed_cb) + display.set_data('user-callback', id) + + def display_opened_cb(self, manager, display): + ''' Called when a new display is opened + ''' + self.add_display(display) + + def destroy_info(self): + ''' Cleans up when the toplevel is destroyed; we remove the + connections we use to track currently open displays. + ''' + manager = gtk.gdk.display_manager_get() + displays = manager.list_displays() + + id = manager.get_data('user-callback') + manager.disconnect(id) + + for tmp_list in displays: + id = tmp_list.get_data('user-callback') + tmp_list.disconnect(id) + + +def main(): + ChangeDisplayDemo() + gtk.main() + +if __name__ == '__main__': + main() + |