1 """pygtk GUI for qubx.settings.
2
3 Copyright 2007-2012 Research Foundation State University of New York
4 This file is part of QUB Express.
5
6 QUB Express is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 QUB Express is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License,
17 named LICENSE.txt, in the QUB Express program directory. If not, see
18 <http://www.gnu.org/licenses/>.
19
20 """
21
22 import pygtk
23 pygtk.require('2.0')
24 import gtk
25 from gtk import gdk
26 from gtk import keysyms
27 import gobject
28
29 import qubx.settings
30 from qubx.settings import *
31 from qubx.GTK import *
32 import qubx.treeGTK
33 import qubx.tree
34
37
39 """
40 Combo box for managing a category of presets/settings.
41
42 If you have a presets menu for a category, you must
43 - keep category.active up-to-date, in case the user saves a preset
44 - provide category.OnSet, in case the user chooses a preset
45 """
46
47 __explore_featured = ['_name', '_appName', '_parent', '_include', '_mask', '_additions']
48
51 """
52 @param name: the category name
53 @param appName: top-level application name, for dialog titles
54 @param parent: parent gtk.Window, for centering dialogs
55 @param includeF: filter function; we show only presets for which includeF(name) is True
56 @param mask: preferred file extension for import/export
57 @param caption: text on menu
58 @param additions: sequence of (caption, action()) to add to menu
59 """
60 MapSettings()
61 gtk.EventBox.__init__(self)
62 self._name = name
63 self._appName = appName
64 self._parent = parent
65 self._include = includeF
66 self._mask = mask
67 self._perma = 3
68 self._count = 0
69 self._box = gtk.combo_box_new_text()
70 self.add(self._box)
71 self._box.show()
72 self.connect('enter-notify-event', self.enter_notify_event)
73 self._box.connect('changed', self.combo_changed)
74 self._additions = additions
75 self._box.append_text(caption)
76 self._box.append_text('Save in this menu...')
77 self._box.append_text('Manage...')
78 self._box.set_active(0)
80 self._box.set_active(0)
81 for i in xrange(self._count):
82 self._box.remove_text(self._perma)
83 self._count = 0
84 self._addActions = {}
85 for lbl, action in self._additions:
86 self._box.append_text(lbl)
87 self._addActions[lbl] = action
88 self._count += 1
89 for name in sorted(SettingsMgr.listNames(self._name)):
90 if self._include(name):
91 self._box.append_text(name)
92 self._count += 1
114
116 """Manage Presets dialog"""
117 - def __init__(self, cat, parent, appName="", mask=".qprs"):
118 appPrefix = appName and (appName+" - ") or ""
119 gtk.Dialog.__init__(self, appPrefix+"Manage Presets", parent or get_active_window(), gtk.DIALOG_MODAL)
120 self.set_size_request(500, 350)
121 self.cat = cat
122 self.mask = mask
123 self.ref = Reffer()
124
125 btns = pack_item(gtk.HBox(False, 10), self.vbox)
126 self.btnImport = pack_button('Import...', btns, on_click=self._onClickImport)
127 self.btnExport = pack_button('Export...', btns, on_click=self._onClickExport)
128 self.btnRename = pack_button('Rename...', btns, on_click=self._onItemRename)
129 self.btnDelete = pack_button('Delete', btns, on_click=self._onItemDelete)
130
131 lr = pack_item(gtk.HPaned(), self.vbox, expand=True)
132 self.presets = SimpleList(sortable=False)
133 for name in SettingsMgr.listNames(cat):
134 self.presets.append(name)
135 self.presets.OnSelect += self.ref(self._onSelect)
136 scr = pack_scrolled(self.presets, None, size_request=(200, 70))
137 lr.pack1(scr, False, True)
138 self.treeView = qubx.treeGTK.TreeView()
139 scr = pack_scrolled(self.treeView, None)
140 lr.pack2(scr, True, True)
141
142 self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_REJECT)
143
144 popup = gtk.Menu()
145 build_menuitem('Rename', self.ref(self._onItemRename), menu=popup)
146 build_menuitem('Delete', self.ref(self._onItemDelete), menu=popup)
147 self.presets.popup = popup
149 if index == self._lastIndex:
150 return
151 if self.promptSave():
152 self._lastIndex = index
153 self._lastName = self.presets.active
154 self.treeView.root = SettingsMgr[self.cat].read(self.presets.active)
155 self.btnExport.set_sensitive(index >= 0)
156 self.btnRename.set_sensitive(index >= 0)
157 self.btnDelete.set_sensitive(index >= 0)
158 else:
159 gobject.idle_add(self._fixSel)
161 self.presets.index = self._lastIndex
172 if self._lastIndex < 0 or not self.treeView.changed:
173 return True
174 dlg = gtk.MessageDialog(self, buttons=gtk.BUTTONS_NONE,
175 flags=gtk.DIALOG_MODAL,
176 message_format='Save changes to %s?' % self._lastName)
177 dlg.add_buttons('Yes',gtk.RESPONSE_YES,
178 'No',gtk.RESPONSE_NO)
179 if canCancel:
180 dlg.add_buttons('Cancel',gtk.RESPONSE_CANCEL)
181 response = dlg.run()
182 dlg.destroy()
183 if response == gtk.RESPONSE_YES:
184 SettingsMgr.save(self.treeView.root, self.cat, self._lastName)
185 return True
186 elif response == gtk.RESPONSE_CANCEL:
187 return False
188 elif response == gtk.RESPONSE_NO:
189 return True
191 dlg = gtk.FileChooserDialog('Import presets...', self, gtk.FILE_CHOOSER_ACTION_OPEN,
192 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
193 dlg.set_default_response(gtk.RESPONSE_OK)
194 filter = gtk.FileFilter()
195 filter.set_name("Presets files")
196 filter.add_pattern("*"+self.mask)
197 dlg.add_filter(filter)
198 filter = gtk.FileFilter()
199 filter.set_name("All files")
200 filter.add_pattern("*")
201 dlg.add_filter(filter)
202 response = dlg.run()
203 if response == gtk.RESPONSE_OK:
204 try:
205 qprs = qubx.tree.Open(dlg.get_filename(), True)
206 qprs.close()
207 if str(qprs['Target'].data) != self.cat:
208 mdlg = gtk.MessageDialog(self, buttons=gtk.BUTTONS_NONE,
209 flags=gtk.DIALOG_MODAL,
210 message_format='This looks like a preset for a different category, "%s." Import it anyway?'%str(qprs['Target'].data))
211 mdlg.add_buttons('Yes', gtk.RESPONSE_YES,
212 'No', gtk.RESPONSE_NO)
213 if gtk.RESPONSE_NO == mdlg.run():
214 qprs = None
215 mdlg.destroy()
216 if qprs:
217 name = str(qprs['Label'].data) or "untitled"
218 try:
219 prev_ix = SettingsMgr.listNames(self.cat).index(name)
220 except:
221 prev_ix = -1
222 SettingsMgr.save(qprs['Presets'], self.cat, name)
223 if prev_ix >= 0:
224 self.presets.index = prev_ix
225 else:
226 self.presets.append(name)
227 self.presets.index = len(self.presets.model) - 1
228 except Exception, e:
229 traceback.print_exc()
230 dlg.destroy()
232 dlg = gtk.FileChooserDialog('Export presets as...', self, gtk.FILE_CHOOSER_ACTION_SAVE,
233 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK))
234 dlg.set_default_response(gtk.RESPONSE_OK)
235 filter = gtk.FileFilter()
236 filter.set_name("Presets files")
237 filter.add_pattern('*'+self.mask)
238 dlg.add_filter(filter)
239 filter = gtk.FileFilter()
240 filter.set_name("All files")
241 filter.add_pattern("*")
242 dlg.add_filter(filter)
243 response = dlg.run()
244 if response == gtk.RESPONSE_OK:
245 try:
246 qprs = qubx.tree.Node('Presets')
247 qprs['Target'].data = self.cat
248 qprs['Label'].data = self.presets.active
249 qprs.appendClone(self.treeView.root).name = 'Presets'
250 filename = dlg.get_filename()
251 if not os.path.splitext(filename)[1]:
252 filename = filename + self.mask
253 qprs.saveAs(filename, as_copy=True)
254 except Exception, e:
255 traceback.print_exc()
256 dlg.destroy()
258 if self.presets.index < 0:
259 return
260 dlog = InputDialog('Rename preset...', None, 'Preset name:', self.presets.active)
261 try:
262 if dlog.run() == gtk.RESPONSE_ACCEPT:
263 name = dlog.text
264 if name:
265 SettingsMgr.rename(self.cat, self.presets.active, name)
266 self.presets.edit(self.presets.index, name)
267 except Exception, e:
268 d = gtk.MessageDialog(self, buttons=gtk.BUTTONS_NONE, flags=gtk.DIALOG_MODAL,
269 message_format=str(e))
270 d.add_buttons('OK', gtk.RESPONSE_OK)
271 d.run()
272 d.destroy()
273 dlog.destroy()
275 if self.presets.index < 0:
276 return
277 dlg = gtk.MessageDialog(self, buttons=gtk.BUTTONS_NONE,
278 flags=gtk.DIALOG_MODAL,
279 message_format="Permanently delete %s?" % self.presets.active)
280 dlg.add_buttons('Yes',gtk.RESPONSE_YES,
281 'No', gtk.RESPONSE_NO)
282 response = dlg.run()
283 dlg.destroy()
284 if response == gtk.RESPONSE_NO:
285 return
286 try:
287 self.treeView.changed = False
288 self._lastIndex = -1
289 if SettingsMgr.delete(self.cat, self.presets.active):
290 self.presets.remove(self.presets.index)
291 except:
292 traceback.print_exc()
293
295 """Edits a settings category, with OK, Cancel, Presets."""
296 - def __init__(self, cat_name, properties, title="", parent=None):
297 """
298 @param properties: list of L{qubx.settings.Property}; if name is blank, doc is displayed as label; if not check/radio, properties need "accept" and "format".
299 """
300 gtk.Dialog.__init__(self, title, parent or get_active_window(), gtk.DIALOG_MODAL, buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
301 self.set_default_response(gtk.RESPONSE_ACCEPT)
302 props_only = [prop for prop in properties if prop.name]
303 self.propertied_specs = props_only
304 spec_dict = dict((spec.name, spec) for spec in props_only)
305 self.propertied_init(spec_dict)
306 self.propertied_connect_settings(cat_name)
307
308 for prop in properties:
309 if prop.doc and not prop.name:
310 pack_label(prop.doc, self.vbox)
311 elif prop.accept == acceptBool:
312 line = pack_item(gtk.HBox(), self.vbox)
313 chk = pack_check(prop.doc, line, expand=True)
314 self.propertied_connect_check(prop.name, chk)
315 elif prop.value_names:
316 line = pack_item(gtk.HBox(True), self.vbox)
317 pack_label(prop.doc, line)
318 radios = []
319 for val, val_name in prop.value_names.iteritems():
320 if radios:
321 line = pack_item(gtk.HBox(True), self.vbox)
322 pack_space(line, expand=True)
323 radios.append( (val, pack_radio(val_name, line, group=radios and radios[0][1] or None)) )
324 self.propertied_connect_radios(prop.name, radios)
325 else:
326 line = pack_item(gtk.HBox(True), self.vbox)
327 pack_label(prop.doc, line)
328 entry = pack_item(qubx.GTK.NumEntry(prop.default, prop.accept, prop.format), line)
329 self.propertied_connect_NumEntry(prop.name, entry)
330 pack_item(PresetsMenu(cat_name, parent=self), self.action_area)
332 """Returns {name : value}, or None if cancelled."""
333 response = gtk.Dialog.run(self)
334 if response == gtk.RESPONSE_ACCEPT:
335 return dict([(prop.name, self.propertied_items[prop.name].value) for prop in self.propertied_specs])
336 else:
337 return None
338
340 """
341 @param properties: list of L{qubx.settings.Property}; if name is blank, doc is displayed as label; if not check/radio, properties need "accept" and "format".
342 """
343 dlg = PropertiedDialog(cat_name, properties, title, parent)
344 try:
345 return dlg.run()
346 finally:
347 dlg.destroy()
348
349
355