summaryrefslogtreecommitdiff
path: root/examples/pygtk-demo/demos/changedisplay.py
blob: e10fcee7c3680550f51ab9ba726a9c28524906ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
#!/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()