|
|
1.1 root 1: #!/usr/bin/env python
2: #
3: # Misc common helper classes and functions for the Hatari UI
4: #
5: # Copyright (C) 2008 by Eero Tamminen <[email protected]>
6: #
7: # This program is free software; you can redistribute it and/or modify
8: # it under the terms of the GNU General Public License as published by
9: # the Free Software Foundation; either version 2 of the License, or
10: # (at your option) any later version.
11: #
12: # This program is distributed in the hope that it will be useful,
13: # but WITHOUT ANY WARRANTY; without even the implied warranty of
14: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15: # GNU General Public License for more details.
16:
17: import os
18: import sys
19: # use correct version of pygtk/gtk
20: import pygtk
21: pygtk.require('2.0')
22: import gtk
23: import gobject
24:
25:
26: # leak debugging
27: #import gc
28: #gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
29:
30:
31: # ---------------------
32: # Hatari UI information
33:
34: class UInfo:
35: """singleton constants for the UI windows,
36: one instance is needed to initialize these properly"""
37: version = "v0.9.2"
38: name = "Hatari UI"
39: logo = "hatari.png"
40: icon = "hatari-icon.png"
41:
42: # path to the directory where the called script resides
43: path = os.path.dirname(sys.argv[0])
44:
45: def __init__(self, path = None):
46: "UIinfo([path]), set suitable paths for resources from CWD and path"
47: if path:
48: self.path = path
49: if not os.path.exists(UInfo.icon):
50: UInfo.icon = self._get_path(UInfo.icon)
51: if not os.path.exists(UInfo.logo):
52: UInfo.logo = self._get_path(UInfo.logo)
53:
54: def _get_path(self, filename):
55: sep = os.path.sep
56: testpath = "%s%s%s" % (self.path, sep, filename)
57: if os.path.exists(testpath):
58: return testpath
59:
60:
61: # --------------------------------------------------------
62: # functions for showing HTML files
63:
64: class UIHelp:
65: def __init__(self):
66: """determine HTML viewer and where docs are"""
67: self._view = self.get_html_viewer()
68: self._path = self.get_doc_path()
69:
70: def get_html_viewer(self):
71: """return name of html viewer or None"""
72: path = self.get_binary_path("xdg-open")
73: if path:
74: return path
75: path = self.get_binary_path("firefox")
76: if path:
77: return path
78: return None
79:
80: def get_binary_path(self, name):
81: """return true if given binary is in path"""
82: # could also try running the binary with "--version" arg
83: # and check the exec return value
84: if os.sys.platform == "win32":
85: for i in os.environ['PATH'].split(';'):
86: fname = os.path.join(i, name)
87: if os.access(fname, os.X_OK) and not os.path.isdir(fname):
88: return fname
89: else:
90: for i in os.environ['PATH'].split(':'):
91: fname = os.path.join(i, name)
92: if os.access(fname, os.X_OK) and not os.path.isdir(fname):
93: return fname
94: return None
95:
96: def get_doc_path(self):
97: """return path or URL to Hatari docs or None"""
98: # first try whether there are local Hatari docs in standard place
99: # for this Hatari/UI version
100: sep = os.sep
101: path = self.get_binary_path("hatari")
102: path = sep.join(path.split(sep)[:-2]) # remove "bin/hatari"
103: path = path + sep + "share" + sep + "doc" + sep + "hatari" + sep
104: if os.path.exists(path + "manual.html"):
105: return path
106: # if not, point to latest Hatari HG version docs
107: print "WARNING: Hatari manual not found at:", path + "manual.html"
108: return "http://hg.berlios.de/repos/hatari/raw-file/tip/doc/"
109:
110: def set_mainwin(self, widget):
111: self.mainwin = widget
112:
113: def view_url(self, url, name):
114: """view given URL or file path, or error use 'name' as its name"""
115: if self._view and "://" in url or os.path.exists(url):
116: print "RUN: '%s' '%s'" % (self._view, url)
117: os.spawnlp(os.P_NOWAIT, self._view, self._view, url)
118: return
119: if not self._view:
120: msg = "Cannot view %s, HTML viewer missing" % name
121: else:
122: msg = "Cannot view %s,\n'%s' file is missing" % (name, url)
123: from dialogs import ErrorDialog
124: ErrorDialog(self.mainwin).run(msg)
125:
126: def view_hatari_manual(self, dummy=None):
127: self.view_url(self._path + "manual.html", "Hatari manual")
128:
129: def view_hatari_compatibility(self, dummy=None):
130: self.view_url(self._path + "compatibility.html", "Hatari compatibility list")
131:
132: def view_hatari_releasenotes(self, dummy=None):
133: self.view_url(self._path + "release-notes.txt", "Hatari release notes")
134:
135: def view_hatari_todo(self, dummy=None):
136: self.view_url(self._path + "todo.txt", "Hatari TODO items")
137:
138: def view_hatari_bugs(self, dummy=None):
139: self.view_url("http://developer.berlios.de/bugs/?group_id=10436", "Hatari bug tracker")
140:
141: def view_hatari_mails(self, dummy=None):
142: self.view_url("http://developer.berlios.de/mail/?group_id=10436", "Hatari mailing lists")
143:
144: def view_hatari_repository(self, dummy=None):
145: self.view_url("http://hg.berlios.de/repos/hatari", "latest Hatari changes")
146:
147: def view_hatari_authors(self, dummy=None):
148: self.view_url(self._path + "authors.txt", "Hatari authors")
149:
150: def view_hatari_page(self, dummy=None):
151: self.view_url("http://hatari.berlios.de/", "Hatari home page")
152:
153: def view_hatariui_page(self, dummy=None):
154: self.view_url("http://koti.mbnet.fi/tammat/hatari/hatari-ui.shtml", "Hatari UI home page")
155:
156:
157: # --------------------------------------------------------
158: # auxiliary class+callback to be used with the PasteDialog
159:
160: class HatariTextInsert:
161: def __init__(self, hatari, text):
162: self.index = 0
163: self.text = text
164: self.pressed = False
165: self.hatari = hatari
166: print "OUTPUT '%s'" % text
167: gobject.timeout_add(100, _text_insert_cb, self)
168:
169: # callback to insert text object to Hatari character at the time, at given interval
170: def _text_insert_cb(textobj):
171: char = textobj.text[textobj.index]
172: if textobj.pressed:
173: textobj.pressed = False
174: textobj.hatari.insert_event("keyrelease %c" % char)
175: textobj.index += 1
176: if textobj.index >= len(textobj.text):
177: del(textobj)
178: return False
179: else:
180: textobj.pressed = True
181: textobj.hatari.insert_event("keypress %c" % char)
182: return True
183:
184:
185: # ----------------------------
186: # helper functions for buttons
187:
188: def create_button(label, cb, data = None):
189: "create_button(label,cb[,data]) -> button widget"
190: button = gtk.Button(label)
191: if data == None:
192: button.connect("clicked", cb)
193: else:
194: button.connect("clicked", cb, data)
195: return button
196:
197: def create_toolbutton(stock_id, cb, data = None):
198: "create_toolbutton(stock_id,cb[,data]) -> toolbar button with stock icon+label"
199: button = gtk.ToolButton(stock_id)
200: if data == None:
201: button.connect("clicked", cb)
202: else:
203: button.connect("clicked", cb, data)
204: return button
205:
206: def create_toggle(label, cb, data = None):
207: "create_toggle(label,cb[,data]) -> toggle button widget"
208: button = gtk.ToggleButton(label)
209: if data == None:
210: button.connect("toggled", cb)
211: else:
212: button.connect("toggled", cb, data)
213: return button
214:
215:
216: # -----------------------------
217: # Table dialog helper functions
218:
219: def create_table_dialog(parent, title, rows, oktext = gtk.STOCK_APPLY):
220: "create_table_dialog(parent,title,rows) -> (table,dialog)"
221: dialog = gtk.Dialog(title, parent,
222: gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
223: (oktext, gtk.RESPONSE_APPLY,
224: gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
225:
226: table = gtk.Table(rows, 2) # rows, cols
227: table.set_col_spacings(8)
228: dialog.vbox.add(table)
229: return (table, dialog)
230:
231: def table_add_entry_row(table, row, label, size = None):
232: "table_add_entry_row(table,row,label,[entry size]) -> entry"
233: # adds given label to given row in given table
234: # returns entry for that line
235: label = gtk.Label(label)
236: align = gtk.Alignment(1) # right aligned
237: align.add(label)
238: table.attach(align, 0, 1, row, row+1, gtk.FILL)
239: if size:
240: entry = gtk.Entry(size)
241: entry.set_width_chars(size)
242: align = gtk.Alignment(0) # left aligned (default is centered)
243: align.add(entry)
244: table.attach(align, 1, 2, row, row+1)
245: else:
246: entry = gtk.Entry()
247: table.attach(entry, 1, 2, row, row+1)
248: return entry
249:
250: def table_add_widget_row(table, row, label, widget):
251: "table_add_widget_row(table,row,label,widget) -> widget"
252: # adds given label right aligned to given row in given table
253: # adds given widget to the right column and returns it
254: # returns entry for that line
255: if label:
256: label = gtk.Label(label)
257: align = gtk.Alignment(1)
258: align.add(label)
259: table.attach(align, 0, 1, row, row+1, gtk.FILL)
260: table.attach(widget, 1, 2, row, row+1)
261: return widget
262:
263: def table_add_separator(table, row):
264: "table_add_separator(table,row)"
265: widget = gtk.HSeparator()
266: table.attach(widget, 0, 2, row, row+1, gtk.FILL)
267:
268:
269: # -----------------------------
270: # File selection helpers
271:
272: def get_open_filename(title, parent, path = None):
273: buttons = (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
274: fsel = gtk.FileChooserDialog(title, parent, gtk.FILE_CHOOSER_ACTION_OPEN, buttons)
275: fsel.set_local_only(True)
276: if path:
277: fsel.set_filename(path)
278: if fsel.run() == gtk.RESPONSE_OK:
279: filename = fsel.get_filename()
280: else:
281: filename = None
282: fsel.destroy()
283: return filename
284:
285: def get_save_filename(title, parent, path = None):
286: buttons = (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
287: fsel = gtk.FileChooserDialog(title, parent, gtk.FILE_CHOOSER_ACTION_SAVE, buttons)
288: fsel.set_local_only(True)
289: fsel.set_do_overwrite_confirmation(True)
290: if path:
291: fsel.set_filename(path)
292: if not os.path.exists(path):
293: # above set only folder, this is needed to set
294: # the file name when the file doesn't exist
295: fsel.set_current_name(os.path.basename(path))
296: if fsel.run() == gtk.RESPONSE_OK:
297: filename = fsel.get_filename()
298: else:
299: filename = None
300: fsel.destroy()
301: return filename
302:
303: # Gtk is braindead, there's no way to set a default filename
304: # for file chooser button unless it already exists
305: # - set_filename() works only for files that already exist
306: # - set_current_name() works only for SAVE action,
307: # but file chooser button doesn't support that
308: # i.e. I had to do my own (less nice) container widget...
309: class FselEntry():
310: def __init__(self, parent, validate = None, data = None):
311: self._parent = parent
312: self._validate = validate
313: self._validate_data = data
314: entry = gtk.Entry()
315: entry.set_width_chars(12)
316: entry.set_editable(False)
317: hbox = gtk.HBox()
318: hbox.add(entry)
319: button = create_button("Select...", self._select_file_cb)
320: hbox.pack_start(button, False, False)
321: self._entry = entry
322: self._hbox = hbox
323:
324: def _select_file_cb(self, widget):
325: fname = self._entry.get_text()
326: while True:
327: fname = get_save_filename("Select file", self._parent, fname)
328: if not fname:
329: # assume cancel
330: return
331: if self._validate:
332: # filename needs validation and is valid?
333: if not self._validate(self._validate_data, fname):
334: continue
335: self._entry.set_text(fname)
336: return
337:
338: def set_filename(self, fname):
339: self._entry.set_text(fname)
340:
341: def get_filename(self):
342: return self._entry.get_text()
343:
344: def get_container(self):
345: return self._hbox
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.