|
|
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.