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
26 from utils
import print_error
, print_debug
28 class ItemView(gtk
.Bin
):
30 A widget to display one drag item
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
38 __gtype_name__
= "ItemView"
40 def __init__(self
, item
):
42 Create widget for item
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)
57 closebtn
= gtk
.Button("")
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()
78 self
.__child
= event_box
79 self
.__child
.show_all()
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
):
95 Make self a drag source
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
):
124 Called when a drag ends
127 if context
.dest_window
:
130 self
.emit("dragged", self
.item
, success
)
132 def _cb_drag_send(self
, widget
, context
, selection
, target_type
, event_time
):
134 Write data for drop operation to active selection
136 self
.item
.write_to_selection(selection
, target_type
)
139 def get_widget(self
):
145 def set_item(self
, obj
):
147 Set the represented object
151 def ellipsize(s
, width
, mark
=".."):
153 ellipsize (middle of) string s to width using mark
155 for example ellipsize("python for all", 7, "..")
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()
167 icon_widget
= gtk
.Image()
168 icon_widget
.set_from_pixbuf(icon
)
169 self
.button
.set_image(icon_widget
)
172 # work around underline bug
173 name
= name
.replace("_", "__")
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
201 def __init__(self
, controller
, window_title
):
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
226 self
.window
.show_all()
227 self
.adjust_window_size()
228 # Check the sticky preference
229 stick
= preferences
.get_bool("stick", if_none
=True)
234 def quit_all(self
, ignored
=None):
236 terminate the main loop on window destroy event
238 from utils
import quit_dragbox
241 def create_window(self
, name
=None):
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")
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
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
:
301 self
.make_placeholder()
305 h
= max(minheight
, h
)
306 h
= min(maxheight
, h
)
311 vadj
= scrollbox
.get_vadjustment()
312 if h
> vadj
.page_size
:
313 vadj
.value
= vadj
.upper
- vadj
.page_size
314 scrollbox
.set_vadjustment(vadj
)
318 if h
< oldh
: h
= oldh
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
)
347 self
.adjust_window_size(shrink
=True)
349 def full_refresh(self
):
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
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
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()
398 # this is an internal drag, do not handle
401 if not self
.item_control
.handle_drop_data(info
, selection_data
):
402 print_error("Drop data not handled")
404 drag_context
.finish(True, False, timestamp
) #Finished w succ
406 def cb_dragitem_dragged(self
, widget
, item
, success
):
408 Callback when drags end
413 # check if we should remove anyways
414 remove
= preferences
.get_bool("remove", False)
416 self
.item_control
.remove_item(item
)
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
)
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
)
448 def cb_window_click(self
, widget
, event
, user_info
=None):
450 Callback from button click in window
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
)
459 def _popup_menu(self
, widget
, event
, item
=None):
461 import preferences_controller
462 preferences_controller
.shared
.show_window()
464 self
.item_control
.put_on_clipboard(item
)
465 def remove_item(item
):
466 self
.item_control
.remove_item(item
)
471 from aboutdialog
import show_about_dialog
474 (gtk
.STOCK_PREFERENCES
, prefs
),
475 (gtk
.STOCK_ABOUT
, show_about_dialog
),
476 (gtk
.STOCK_QUIT
, self
.quit_all
)
479 (gtk
.STOCK_COPY
, copy_item
),
480 (gtk
.STOCK_CUT
, cut_item
),
481 (gtk
.STOCK_DELETE
, remove_item
),
482 (None, None) # Separator
485 menu_items
= edit_items
+ std
488 menu
= _make_menu(menu_items
, item
)
492 menu
.popup(None,None,None, event
.button
, event
.time
)
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
)
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.
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
):
519 # Create the menu items
520 for (name
, callback
) in items
:
521 menu_item
= gtk
.ImageMenuItem(name
)
522 cmenu
.append(menu_item
)
524 menu_item
.connect_object ("activate", callback
, user_info
)