1 # -*- coding: utf-8 -*-
2 # Alacarte Menu Editor - Simple fd.o Compliant Menu Editor
3 # Copyright (C) 2013 Red Hat, Inc.
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Library General Public
7 # License as published by the Free Software Foundation; either
8 # version 2 of the License, or (at your option) any later version.
10 # This library 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 GNU
13 # Library General Public License for more details.
15 # You should have received a copy of the GNU Library General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 from gi
.repository
import GLib
, Gtk
22 from Alacarte
import config
, util
24 from gi
._glib
import GError
28 EXTENSIONS
= (".png", ".xpm", ".svg")
30 def try_icon_name(filename
):
31 # Detect if the user picked an icon, and make
32 # it into an icon name.
33 if not filename
.endswith(EXTENSIONS
):
36 filename
= filename
[:-4]
38 theme
= Gtk
.IconTheme
.get_default()
40 for path
in theme
.get_search_path():
41 if filename
.startswith(path
):
42 resolved_path
= filename
[len(path
):].lstrip(os
.sep
)
45 if resolved_path
is None:
48 parts
= resolved_path
.split(os
.sep
)
49 # icon-theme/size/category/icon
55 def get_icon_string(image
):
56 filename
= image
.props
.file
57 if filename
is not None:
58 return try_icon_name(filename
)
60 return image
.props
.icon_name
62 def strip_extensions(icon
):
63 if icon
.endswith(EXTENSIONS
):
68 def set_icon_string(image
, icon
):
69 if GLib
.path_is_absolute(icon
):
70 image
.props
.file = icon
72 image
.props
.icon_name
= strip_extensions(icon
)
74 DESKTOP_GROUP
= GLib
.KEY_FILE_DESKTOP_GROUP
76 # XXX - replace with a better UI eventually
77 class IconPicker(object):
78 def __init__(self
, dialog
, button
, image
):
81 self
.button
.connect('clicked', self
.pick_icon
)
84 def pick_icon(self
, button
):
85 chooser
= Gtk
.FileChooserDialog(title
=_("Choose an icon"),
87 buttons
=(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.REJECT
,
88 Gtk
.STOCK_OK
, Gtk
.ResponseType
.ACCEPT
))
89 response
= chooser
.run()
90 if response
== Gtk
.ResponseType
.ACCEPT
:
91 self
.image
.props
.file = chooser
.get_filename()
94 class ItemEditor(object):
95 def get_keyfile_edits(self
):
96 raise NotImplementedError()
98 def set_text(self
, ctl
, name
):
100 val
= self
.keyfile
.get_string(DESKTOP_GROUP
, name
)
104 self
.builder
.get_object(ctl
).set_text(val
)
106 def set_check(self
, ctl
, name
):
108 val
= self
.keyfile
.get_boolean(DESKTOP_GROUP
, name
)
112 self
.builder
.get_object(ctl
).set_active(val
)
114 def set_icon(self
, ctl
, name
):
116 val
= self
.keyfile
.get_string(DESKTOP_GROUP
, name
)
120 set_icon_string(self
.builder
.get_object(ctl
), val
)
123 self
.keyfile
= GLib
.KeyFile()
125 self
.keyfile
.load_from_file(self
.item_path
, util
.KEY_FILE_FLAGS
)
130 util
.fillKeyFile(self
.keyfile
, self
.get_keyfile_edits())
131 contents
, length
= self
.keyfile
.to_data()
132 with
open(self
.item_path
, 'w') as f
:
136 self
.dialog
.present()
138 def on_response(self
, dialog
, response
):
139 if response
== Gtk
.ResponseType
.OK
:
141 self
.dialog
.destroy()
143 class LauncherEditor(ItemEditor
):
144 def __init__(self
, parent
, item_path
):
145 self
.builder
= Gtk
.Builder()
146 self
.builder
.add_from_file(os
.path
.join(config
.pkgdatadir
, 'launcher-editor.ui'))
148 self
.dialog
= self
.builder
.get_object('editor')
149 self
.dialog
.set_transient_for(parent
)
150 self
.dialog
.connect('response', self
.on_response
)
152 self
.icon_picker
= IconPicker(self
.dialog
,
153 self
.builder
.get_object('icon-button'),
154 self
.builder
.get_object('icon-image'))
156 self
.builder
.get_object('exec-browse').connect('clicked', self
.pick_exec
)
158 self
.builder
.get_object('name-entry').connect('changed', self
.resync_validity
)
159 self
.builder
.get_object('exec-entry').connect('changed', self
.resync_validity
)
161 self
.item_path
= item_path
163 self
.resync_validity()
165 def resync_validity(self
, *args
):
166 name_text
= self
.builder
.get_object('name-entry').get_text()
167 exec_text
= self
.builder
.get_object('exec-entry').get_text()
168 valid
= (name_text
is not None and exec_text
is not None)
169 self
.builder
.get_object('ok').set_sensitive(valid
)
172 super(LauncherEditor
, self
).load()
173 self
.set_text('name-entry', "Name")
174 self
.set_text('exec-entry', "Exec")
175 self
.set_text('comment-entry', "Comment")
176 self
.set_check('terminal-check', "Terminal")
177 self
.set_icon('icon-image', "Icon")
179 def get_keyfile_edits(self
):
180 return dict(Name
=self
.builder
.get_object('name-entry').get_text(),
181 Exec
=self
.builder
.get_object('exec-entry').get_text(),
182 Comment
=self
.builder
.get_object('comment-entry').get_text(),
183 Terminal
=self
.builder
.get_object('terminal-check').get_active(),
184 Icon
=get_icon_string(self
.builder
.get_object('icon-image')),
187 def pick_exec(self
, button
):
188 chooser
= Gtk
.FileChooserDialog(title
=_("Choose a command"),
190 buttons
=(Gtk
.STOCK_CANCEL
, Gtk
.ResponseType
.REJECT
,
191 Gtk
.STOCK_OK
, Gtk
.ResponseType
.ACCEPT
))
192 response
= chooser
.run()
193 if response
== Gtk
.ResponseType
.ACCEPT
:
194 self
.builder
.get_object('exec-entry').set_text(chooser
.get_filename())
197 class DirectoryEditor(ItemEditor
):
198 def __init__(self
, parent
, item_path
):
199 self
.builder
= Gtk
.Builder()
200 self
.builder
.add_from_file(os
.path
.join(config
.pkgdatadir
, 'directory-editor.ui'))
202 self
.dialog
= self
.builder
.get_object('editor')
203 self
.dialog
.set_transient_for(parent
)
204 self
.dialog
.connect('response', self
.on_response
)
206 self
.icon_picker
= IconPicker(self
.dialog
,
207 self
.builder
.get_object('icon-button'),
208 self
.builder
.get_object('icon-image'))
210 self
.builder
.get_object('name-entry').connect('changed', self
.resync_validity
)
212 self
.item_path
= item_path
214 self
.resync_validity()
216 def resync_validity(self
, *args
):
217 name_text
= self
.builder
.get_object('name-entry').get_text()
218 valid
= (name_text
is not None)
219 self
.builder
.get_object('ok').set_sensitive(valid
)
222 super(DirectoryEditor
, self
).load()
223 self
.set_text('name-entry', "Name")
224 self
.set_text('comment-entry', "Comment")
225 self
.set_icon('icon-image', "Icon")
227 def get_keyfile_edits(self
):
228 return dict(Name
=self
.builder
.get_object('name-entry').get_text(),
229 Comment
=self
.builder
.get_object('comment-entry').get_text(),
230 Icon
=get_icon_string(self
.builder
.get_object('icon-image')),
233 def test_editor(path
):
234 if path
.endswith('.directory'):
235 return DirectoryEditor(path
)
236 elif path
.endswith('.desktop'):
237 return LauncherEditor(path
)
239 raise ValueError("Invalid filename, %r" % (path
,))
244 Gtk
.Window
.set_default_icon_name('alacarte')
245 editor
= test_editor(sys
.argv
[1])
246 editor
.dialog
.connect('destroy', Gtk
.main_quit
)
250 if __name__
== "__main__":