1 """A GTK dialog which lets the user add a new application to their desktop."""
3 # Copyright (C) 2009, Thomas Leonard
4 # See the README file for details, or visit http://0install.net.
6 from zeroinstall
import _
, gobject
10 from zeroinstall
import SafeException
11 from zeroinstall
.injector
import model
12 from zeroinstall
.injector
.namespaces
import XMLNS_IFACE
13 from zeroinstall
.injector
.iface_cache
import iface_cache
21 def N_(message
): return message
39 """A dialog box which prompts the user to choose the program to be added."""
40 def __init__(self
, interface_uri
= None):
41 builderfile
= os
.path
.join(os
.path
.dirname(__file__
), 'desktop.ui')
43 builder
= gtk
.Builder()
44 builder
.set_translation_domain('zero-install')
45 builder
.add_from_file(builderfile
)
46 self
.window
= builder
.get_object('main')
47 self
.set_keep_above(True)
51 self
.window
.set_response_sensitive(_RESPONSE_NEXT
, bool(text
))
53 uri
= builder
.get_object('interface_uri')
54 about
= builder
.get_object('about')
55 icon_widget
= builder
.get_object('icon')
56 category
= builder
.get_object('category')
57 dialog_next
= builder
.get_object('dialog_next')
58 dialog_ok
= builder
.get_object('dialog_ok')
61 uri
.set_text(interface_uri
)
63 uri
.connect('changed', set_uri_ok
)
67 category
.append_text(_(c
))
68 category
.set_active(11)
70 def uri_dropped(eb
, drag_context
, x
, y
, selection_data
, info
, timestamp
):
71 uris
= selection_data
.get_uris()
73 assert len(uris
) == 1, uris
78 data
= codecs
.getdecoder('utf16')(selection_data
.get_data())[0]
79 data
= data
.split('\n', 1)[0].strip()
81 data
= selection_data
.get_text().split('\n', 1)[0].strip()
82 if self
._sanity
_check
(data
):
84 drag_context
.finish(True, False, timestamp
)
85 self
.window
.response(_RESPONSE_NEXT
)
87 if sys
.version_info
[0] < 3:
88 def TargetEntry(*args
): return args
90 TargetEntry
= gtk
.TargetEntry
.new
91 self
.window
.drag_dest_set(gtk
.DEST_DEFAULT_MOTION | gtk
.DEST_DEFAULT_DROP | gtk
.DEST_DEFAULT_HIGHLIGHT
,
92 [TargetEntry('text/uri-list', 0, _URI_LIST
),
93 TargetEntry('text/x-moz-url', 0, _UTF_16
)],
95 self
.window
.drag_dest_add_uri_targets() # Needed for GTK 3
96 self
.window
.connect('drag-data-received', uri_dropped
)
98 nb
= builder
.get_object('notebook1')
100 def update_details_page():
101 iface
= iface_cache
.get_interface(model
.canonical_iface_uri(uri
.get_text()))
102 about
.set_text('%s - %s' % (iface
.get_name(), iface
.summary
))
103 icon_path
= iface_cache
.get_icon_path(iface
)
104 from zeroinstall
.gtkui
import icon
105 icon_pixbuf
= icon
.load_icon(icon_path
)
107 icon_widget
.set_from_pixbuf(icon_pixbuf
)
110 for meta
in iface
.get_metadata(XMLNS_IFACE
, 'category'):
111 feed_category
= meta
.content
115 for row
in categories
:
116 if row
.lower() == feed_category
.lower():
117 category
.set_active(i
)
120 self
.window
.set_response_sensitive(_RESPONSE_PREV
, True)
123 from . import xdgutils
124 iface
= iface_cache
.get_interface(model
.canonical_iface_uri(uri
.get_text()))
127 icon_path
= iface_cache
.get_icon_path(iface
)
128 xdgutils
.add_to_menu(iface
, icon_path
, categories
[category
.get_active()])
129 except SafeException
as ex
:
130 box
= gtk
.MessageDialog(self
.window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
, str(ex
))
134 self
.window
.destroy()
136 def response(box
, resp
):
137 if resp
== _RESPONSE_NEXT
:
138 iface
= uri
.get_text()
139 if not self
._sanity
_check
(iface
):
141 self
.window
.set_sensitive(False)
142 self
.set_keep_above(False)
144 child
= subprocess
.Popen(['0launch',
145 '--gui', '--download-only',
147 stdout
= subprocess
.PIPE
,
148 stderr
= subprocess
.STDOUT
)
150 def output_ready(src
, cond
):
151 got
= os
.read(src
.fileno(), 100)
155 status
= child
.wait()
156 self
.window
.set_sensitive(True)
157 self
.set_keep_above(True)
159 update_details_page()
161 dialog_next
.set_property('visible', False)
162 dialog_ok
.set_property('visible', True)
163 dialog_ok
.grab_focus()
165 box
= gtk
.MessageDialog(self
.window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
,
166 _('Failed to run 0launch.\n') + errors
[0])
171 gobject
.io_add_watch(child
.stdout
,
172 gobject
.IO_IN | gobject
.IO_HUP
,
174 elif resp
== gtk
.RESPONSE_OK
:
176 elif resp
== _RESPONSE_PREV
:
177 dialog_next
.set_property('visible', True)
178 dialog_ok
.set_property('visible', False)
179 dialog_next
.grab_focus()
181 self
.window
.set_response_sensitive(_RESPONSE_PREV
, False)
184 self
.window
.connect('response', response
)
186 if len(sys
.argv
) > 1:
187 self
.window
.response(_RESPONSE_NEXT
)
189 def set_keep_above(self
, above
):
190 if hasattr(self
.window
, 'set_keep_above'):
191 # This isn't very nice, but GNOME defaults to
192 # click-to-raise and in that mode drag-and-drop
193 # is useless without this...
194 self
.window
.set_keep_above(above
)
196 def _sanity_check(self
, uri
):
197 if uri
.endswith('.tar.bz2') or \
198 uri
.endswith('.tar.gz') or \
199 uri
.endswith('.exe') or \
200 uri
.endswith('.rpm') or \
201 uri
.endswith('.deb') or \
202 uri
.endswith('.tgz'):
203 box
= gtk
.MessageDialog(self
.window
, gtk
.DIALOG_MODAL
, gtk
.MESSAGE_ERROR
, gtk
.BUTTONS_OK
,
204 _("This URI (%s) looks like an archive, not a Zero Install feed. Make sure you're using the feed link!") % uri
)