Check for available X DISPLAY when creating window
[dragbox.git] / Dragbox / window.py
blobda2906b9ffdb1e6c935401ae21e6a981988624b3
1 # Copyright (C) 2006, 2007 Ulrik Sverdrup
3 # dragbox -- Commandline drag-and-drop tool for GNOME
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
18 # USA
20 import pygtk
21 pygtk.require('2.0')
22 import gtk
23 import gobject
25 import preferences
26 from utils import print_error, print_debug
28 class ItemView(gtk.Bin):
29 """
30 A widget to display one drag item
32 Signals:
33 activate: parameters: item
34 clicked: clicked with other button, parameters: item, event
35 close: close button clicked, paramters: item
36 dragged: item was dragged successfully, parameters: item, success
37 """
38 __gtype_name__ = "ItemView"
40 def __init__(self, item):
41 """
42 Create widget for item
43 """
44 super(ItemView, self).__init__()
46 self.button = gtk.Button("Placeholder Button", use_underline=False)
47 self.button.set_relief(gtk.RELIEF_NONE)
48 self.label = self.button.get_children()[0] #this is a label
49 self.label.set_justify(gtk.JUSTIFY_LEFT)
51 # Create a hbox to contain the dragitem and
52 # a close button on the side
53 hbox = gtk.HBox(False,0)
54 hbox.pack_start(self.button, True, True, 0)
56 # close button
57 closebtn = gtk.Button("")
58 image = gtk.Image()
59 image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
60 image.set_padding(0,0)
61 image.set_alignment(0, 0)
62 closebtn.set_image(image)
63 closebtn.set_relief(gtk.RELIEF_NONE)
64 closebtn.connect("clicked", self._cb_close_button, hbox)
65 closebtn.set_property("can-focus", False)
66 closebtn.set_property("height-request", 22)
67 closebtn.set_property("width-request", 22)
69 alignment = gtk.Alignment()
70 alignment.add(closebtn)
71 alignment.set(1, 0, 0, 0)
72 hbox.pack_start(alignment, False,False,0)
74 event_box = gtk.EventBox()
75 event_box.add(hbox)
77 self.add(event_box)
78 self.__child = event_box
79 self.__child.show_all()
81 self.set_item(item)
82 self.setup_signals()
83 self.set_as_drag_source()
85 tip = self.item.get_description()
86 tools = gtk.Tooltips()
87 tools.set_tip(event_box, tip)
89 def setup_signals(self):
90 self.button.connect("clicked", self._cb_activate )
91 self.button.connect("button-press-event", self._cb_other_click)
93 def set_as_drag_source(self):
94 """
95 Make self a drag source
96 """
97 self.button.drag_source_set(gtk.gdk.BUTTON1_MASK,
98 self.item.get_targets(),
99 gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE | gtk.gdk.ACTION_LINK)
100 self.button.connect("drag-data-get", self._cb_drag_send)
101 self.button.connect("drag-end", self._cb_drag_end)
103 # common widget-children stuff
104 def do_size_request (self, requisition):
105 requisition.width, requisition.height = self.__child.size_request ()
107 def do_size_allocate (self, allocation):
108 self.__child.size_allocate (allocation)
110 def do_forall (self, include_internals, callback, user_data):
111 callback (self.__child, user_data)
113 def _cb_close_button(self, widget, event):
114 self.emit("close", self.item)
116 def _cb_activate(self, widget):
117 self.emit("activate", self.item)
119 def _cb_other_click(self, widget, event):
120 self.emit("clicked", self.item, event)
122 def _cb_drag_end(self, widget, context):
123 """
124 Called when a drag ends
125 """
126 success = False
127 if context.dest_window:
128 # Successful drag
129 success = True
130 self.emit("dragged", self.item, success)
132 def _cb_drag_send(self, widget, context, selection, target_type, event_time):
133 """
134 Write data for drop operation to active selection
135 """
136 self.item.write_to_selection(selection, target_type)
137 return True
139 def get_widget(self):
140 return self.button
142 def get_item(self):
143 return self.item
145 def set_item(self, obj):
147 Set the represented object
149 self.item = obj;
151 def ellipsize(s, width, mark=".."):
153 ellipsize (middle of) string s to width using mark
155 for example ellipsize("python for all", 7, "..")
156 produces "pyt..ll"
158 if len(s) < width:
159 return s
160 mark_len = len(mark)
161 tail = (width - mark_len)//2
162 return u"".join((s[:width - tail - mark_len], mark, s[-tail:]))
164 icon = self.item.get_icon()
165 name = self.item.get_name()
166 if icon:
167 icon_widget = gtk.Image()
168 icon_widget.set_from_pixbuf(icon)
169 self.button.set_image(icon_widget)
171 line_length = 35
172 # work around underline bug
173 name = name.replace("_", "__")
174 else:
175 line_length = 45
177 lines = name.split("\n")
178 name = u"\n".join(ellipsize(line, line_length) for line in lines)
179 self.button.set_label(name)
181 self.button.set_alignment(0, 0.5)
183 gobject.type_register(ItemView)
184 gobject.signal_new("activate", ItemView, gobject.SIGNAL_RUN_LAST,
185 gobject.TYPE_BOOLEAN, (gobject.TYPE_PYOBJECT, ))
186 gobject.signal_new("clicked", ItemView, gobject.SIGNAL_RUN_LAST,
187 gobject.TYPE_BOOLEAN, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT))
188 gobject.signal_new("close", ItemView, gobject.SIGNAL_RUN_LAST,
189 gobject.TYPE_BOOLEAN, (gobject.TYPE_PYOBJECT, ))
190 gobject.signal_new("dragged", ItemView, gobject.SIGNAL_RUN_LAST,
191 gobject.TYPE_BOOLEAN, (gobject.TYPE_PYOBJECT, gobject.TYPE_INT))
193 class WindowController(object):
195 Dragbox' window controller object
197 Listens to signals for added or removed items and
198 displays them in the main window. Handles the pop-up
199 menu.
201 def __init__(self, controller, window_title):
202 self.item_views = {}
203 self.item_control = controller
204 self.placeholder = None
205 self.create_window(window_title)
207 preferences.notify_add("stick", self.cb_stick_pref_changed)
208 controller.connect("item-added", self.added_item)
209 controller.connect("item-removed", self.removed_item)
211 def show_window(self):
213 Moves the window to front
215 self.window.deiconify()
216 self.window.present()
217 self.window.window.focus()
219 def ready_window(self):
221 Shows the controlled window
223 sets window properties
225 # show it all
226 self.window.show_all()
227 self.adjust_window_size()
228 # Check the sticky preference
229 stick = preferences.get_bool("stick", if_none=True)
230 if stick:
231 self.window.stick()
232 self.full_refresh()
234 def quit_all(self, ignored=None):
236 terminate the main loop on window destroy event
238 from utils import quit_dragbox
239 quit_dragbox()
241 def create_window(self, name=None):
243 create a new window
245 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
246 window_display = self.window.get_display()
247 print_debug("Window display is %s" % window_display)
248 if not window_display:
249 from utils import quit_dragbox
250 print_error("Could not get X Display")
251 quit_dragbox()
253 if name:
254 self.window.set_title(name)
256 self.window.connect("destroy", self.quit_all)
257 self.window.connect("button-press-event", self.cb_window_click)
259 self.vbox = gtk.VBox(False, 0)
261 # Add a scrollbox around the vbox
262 # scrolls the list when it's too long
263 scrollbox = gtk.ScrolledWindow()
264 scrollbox.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
265 scrollbox.set_border_width(0)
266 self.window.add(scrollbox)
267 scrollbox.add_with_viewport(self.vbox)
269 # Add placeholder for newbies
270 self.make_placeholder()
271 pixbuf = self.window.render_icon(gtk.STOCK_DND_MULTIPLE, gtk.ICON_SIZE_DIALOG)
272 self.window.set_icon(pixbuf)
274 # Setup window as a drag TARGET
275 from shelfitem import file_targets, text_targets
276 self.window.drag_dest_set(gtk.DEST_DEFAULT_ALL, file_targets+text_targets, gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY )
277 self.window.connect("drag-data-received", self.cb_drag_data_received)
279 def adjust_window_size(self, shrink=False):
281 Changes the window size to better fit the content
283 @param shrink: If the window is allowed to shrink horizontally
284 @type shrink: a bool object.
286 #Resize window horizontally
287 minwidth = 200
288 minheight = 200
289 maxheight = 500
291 win = self.window
292 # display new items
293 win.show_all()
294 scrollbox = self.window.get_child()
295 widget = scrollbox.get_child()
296 (oldw, oldh) = win.get_size()
297 (w, h) = widget.size_request()
299 if not self.item_views and not self.placeholder:
300 # Empty
301 self.make_placeholder()
302 return
304 w = max(minwidth, w)
305 h = max(minheight, h)
306 h = min(maxheight, h)
308 w = max(w, oldw)
310 if not shrink:
311 vadj = scrollbox.get_vadjustment()
312 if h > vadj.page_size:
313 vadj.value = vadj.upper - vadj.page_size
314 scrollbox.set_vadjustment(vadj)
315 widget.queue_draw()
317 #if not shrink:
318 if h < oldh: h = oldh
320 # keep old width
321 win.resize(w,h)
323 def make_placeholder(self):
324 self.placeholder = gtk.Label("Drag files and text here")
325 self.placeholder.set_sensitive(False)
326 self.placeholder.show()
327 self.vbox.add(self.placeholder)
329 def added_item(self, ctrl, item):
330 self.add_dragitem(item)
332 def removed_item(self, ctrl, item):
334 Handle dragitem removal
336 child = self.item_views.pop(item)
337 self._remove_child(child)
339 def _remove_child(self, child, resize=True):
341 Removes a child of the main container widget
343 child: the hbox child to be removed, a widget
345 self.vbox.remove(child)
346 if resize:
347 self.adjust_window_size(shrink=True)
349 def full_refresh(self):
351 Refresh widget list
353 for view in self.item_views.values():
354 self._remove_child(view, resize=False)
355 for item in self.item_control.get_items():
356 self.add_dragitem(item, lazily=True)
357 gobject.timeout_add(50, self.adjust_window_size)
359 def add_dragitem(self, item, lazily=False):
361 Adds a dragitem's widget to the window's main container
363 item: Item to be added
364 lazily: if True, don't refresh after adding
366 # Remove placeholder if there
367 if self.placeholder:
368 self.vbox.remove(self.placeholder)
369 self.placeholder = None
371 handle = ItemView(item)
372 # Add the dragitem & close package to main container
373 self.vbox.pack_start(handle, False, True, 0)
375 self.item_views[item] = handle
376 self._add_dragitem_callbacks(handle)
378 # postpone showing added widget until .adjust_window_size
379 if not lazily:
380 from gobject import timeout_add
381 timeout_add(50, self.adjust_window_size)
383 def _add_dragitem_callbacks(self, handle):
385 Adds callbacks for individual dragitems
387 handle.connect("activate", self.cb_dragitem_click)
388 handle.connect("clicked", self.cb_dragitem_other_click)
389 handle.connect("dragged", self.cb_dragitem_dragged)
390 handle.connect("close", self.cb_close_button)
392 def cb_drag_data_received(self, widget, drag_context, x, y, selection_data, info, timestamp):
394 Callback when something is dropped on the window
396 source_widget = drag_context.get_source_widget()
397 if source_widget:
398 # this is an internal drag, do not handle
399 return False
401 if not self.item_control.handle_drop_data(info, selection_data):
402 print_error("Drop data not handled")
403 return
404 drag_context.finish(True, False, timestamp) #Finished w succ
406 def cb_dragitem_dragged(self, widget, item, success):
408 Callback when drags end
410 if not success:
411 return
412 # Successful drag
413 # check if we should remove anyways
414 remove = preferences.get_bool("remove", False)
415 if remove:
416 self.item_control.remove_item(item)
417 return
419 # Check if the item (file) is still there after move
420 # We have to wait for the filesystem to react
421 # Make one quick and one slow to catch it all!
422 from gobject import timeout_add
424 check_valid = lambda wid, item: self.item_control.check_if_valid(item)
425 # Wait 0.1s and 3.0s
426 timeout_add(100, check_valid, widget, item)
427 timeout_add(3000, check_valid, widget, item)
429 def cb_close_button(self, widget, item):
431 Called when a drag item's close button is activated
433 self.item_control.remove_item(item)
435 def cb_dragitem_click(self, widget, item):
436 self.item_control.activate(item)
438 def cb_dragitem_other_click(self, widget, item, event):
440 callback when the mouse button is clicked on the window
441 handles mouse buttons 2 and 3
443 if event.button is 3:
444 return self._popup_menu(widget, event, item)
445 # Propagate event
446 return False
448 def cb_window_click(self, widget, event, user_info=None):
450 Callback from button click in window
451 handles popup menu
453 if event.button is 2:
454 return self._handle_move_drag(event)
455 if event.button is 3:
456 return self._popup_menu(widget, event)
457 return False
459 def _popup_menu(self, widget, event, item=None):
460 def prefs(item):
461 import preferences_controller
462 preferences_controller.shared.show_window()
463 def copy_item(item):
464 self.item_control.put_on_clipboard(item)
465 def remove_item(item):
466 self.item_control.remove_item(item)
467 def cut_item(item):
468 copy_item(item)
469 remove_item(item)
471 from aboutdialog import show_about_dialog
473 std = [
474 (gtk.STOCK_PREFERENCES, prefs),
475 (gtk.STOCK_ABOUT, show_about_dialog),
476 (gtk.STOCK_QUIT, self.quit_all)
478 edit_items = [
479 (gtk.STOCK_COPY, copy_item),
480 (gtk.STOCK_CUT, cut_item),
481 (gtk.STOCK_DELETE, remove_item),
482 (None, None) # Separator
484 if item:
485 menu_items = edit_items + std
486 else:
487 menu_items = std
488 menu = _make_menu(menu_items, item)
490 widget.grab_focus()
491 # Right click
492 menu.popup(None,None,None, event.button, event.time)
493 return True
495 def _handle_move_drag(self, event):
496 (x,y) = event.get_root_coords()
497 self.window.begin_move_drag(event.button, int(x), int(y), event.time)
498 return True
500 def cb_stick_pref_changed(client, connection, entry, user_info):
502 Callback from stick preference change
504 @param client: gconf_client
505 @type client: an object.
507 @param connection:
508 @type connection: an object.
510 @param entry: The gconf entry
511 @type entry: a string object.
513 from utils import print_debug
514 print_debug("In window sticky callback")
515 self.window.set_sticky(client.get_bool(entry))
517 def _make_menu(items, user_info):
518 cmenu = gtk.Menu()
519 # Create the menu items
520 for (name, callback) in items:
521 menu_item = gtk.ImageMenuItem(name)
522 cmenu.append(menu_item)
523 if callback:
524 menu_item.connect_object ("activate", callback, user_info)
526 cmenu.show_all()
527 return cmenu