Annotation of previous/python-ui/uihelpers.py, revision 1.1.1.2

1.1       root        1: #!/usr/bin/env python
                      2: #
                      3: # Misc common helper classes and functions for the Hatari UI
                      4: #
1.1.1.2 ! root        5: # Copyright (C) 2008-2011 by Eero Tamminen <eerot at berlios>
1.1       root        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 = "v1.0"
                     38:     name = "Hatari UI"
                     39:     logo = "hatari.png"
                     40:     icon = "hatari-icon.png"
1.1.1.2 ! root       41:     copyright = "UI copyright (C) 2008-2011 by Eero Tamminen"
1.1       root       42: 
                     43:     # path to the directory where the called script resides
                     44:     path = os.path.dirname(sys.argv[0])
                     45:     
                     46:     def __init__(self, path = None):
                     47:         "UIinfo([path]), set suitable paths for resources from CWD and path"
                     48:         if path:
                     49:             self.path = path
                     50:         if not os.path.exists(UInfo.icon):
                     51:             UInfo.icon = self._get_path(UInfo.icon)
                     52:         if not os.path.exists(UInfo.logo):
                     53:             UInfo.logo = self._get_path(UInfo.logo)
                     54: 
                     55:     def _get_path(self, filename):
                     56:         sep = os.path.sep
                     57:         testpath = "%s%s%s" % (self.path, sep, filename)
                     58:         if os.path.exists(testpath):
                     59:             return testpath
                     60: 
                     61: 
                     62: # --------------------------------------------------------
                     63: # functions for showing HTML files
                     64: 
                     65: class UIHelp:
                     66:     def __init__(self):
                     67:         """determine HTML viewer and where docs are"""
                     68:         self._view = self.get_html_viewer()
                     69:         self._path = self.get_doc_path()
                     70: 
                     71:     def get_html_viewer(self):
                     72:         """return name of html viewer or None"""
                     73:         path = self.get_binary_path("xdg-open")
                     74:         if path:
                     75:             return path
                     76:         path = self.get_binary_path("firefox")
                     77:         if path:
                     78:             return path
                     79:         return None
                     80:         
                     81:     def get_binary_path(self, name):
                     82:         """return true if given binary is in path"""
                     83:         # could also try running the binary with "--version" arg
                     84:         # and check the exec return value
                     85:         if os.sys.platform == "win32":
1.1.1.2 ! root       86:             splitter = ';'
1.1       root       87:         else:
1.1.1.2 ! root       88:             splitter = ':'
        !            89:         for i in os.environ['PATH'].split(splitter):
1.1       root       90:                 fname = os.path.join(i, name)
                     91:                 if os.access(fname, os.X_OK) and not os.path.isdir(fname):
                     92:                     return fname
                     93:         return None
                     94: 
                     95:     def get_doc_path(self):
                     96:         """return path or URL to Hatari docs or None"""
                     97:         # first try whether there are local Hatari docs in standard place
                     98:         # for this Hatari/UI version
                     99:         sep = os.sep
                    100:         path = self.get_binary_path("hatari")
                    101:         path = sep.join(path.split(sep)[:-2]) # remove "bin/hatari"
                    102:         path = path + sep + "share" + sep + "doc" + sep + "hatari" + sep
                    103:         if os.path.exists(path + "manual.html"):
                    104:             return path
                    105:         # if not, point to latest Hatari HG version docs
1.1.1.2 ! root      106:         print("WARNING: Hatari manual not found at:", path + "manual.html")
1.1       root      107:         return "http://hg.berlios.de/repos/hatari/raw-file/tip/doc/"
                    108: 
                    109:     def set_mainwin(self, widget):
                    110:         self.mainwin = widget
                    111: 
                    112:     def view_url(self, url, name):
                    113:         """view given URL or file path, or error use 'name' as its name"""
                    114:         if self._view and "://" in url or os.path.exists(url):
1.1.1.2 ! root      115:             print("RUN: '%s' '%s'" % (self._view, url))
1.1       root      116:             os.spawnlp(os.P_NOWAIT, self._view, self._view, url)
                    117:             return
                    118:         if not self._view:
                    119:             msg = "Cannot view %s, HTML viewer missing" % name
                    120:         else:
                    121:             msg = "Cannot view %s,\n'%s' file is missing" % (name, url)
                    122:         from dialogs import ErrorDialog
                    123:         ErrorDialog(self.mainwin).run(msg)
                    124: 
                    125:     def view_hatari_manual(self, dummy=None):
                    126:         self.view_url(self._path + "manual.html", "Hatari manual")
                    127: 
                    128:     def view_hatari_compatibility(self, dummy=None):
                    129:         self.view_url(self._path + "compatibility.html", "Hatari compatibility list")
                    130: 
                    131:     def view_hatari_releasenotes(self, dummy=None):
                    132:         self.view_url(self._path + "release-notes.txt", "Hatari release notes")
                    133: 
                    134:     def view_hatari_todo(self, dummy=None):
                    135:         self.view_url(self._path + "todo.txt", "Hatari TODO items")
                    136: 
                    137:     def view_hatari_bugs(self, dummy=None):
                    138:         self.view_url("http://developer.berlios.de/bugs/?group_id=10436", "Hatari bug tracker")
                    139: 
                    140:     def view_hatari_mails(self, dummy=None):
                    141:         self.view_url("http://developer.berlios.de/mail/?group_id=10436", "Hatari mailing lists")
                    142: 
                    143:     def view_hatari_repository(self, dummy=None):
                    144:         self.view_url("http://hg.berlios.de/repos/hatari", "latest Hatari changes")
                    145: 
                    146:     def view_hatari_authors(self, dummy=None):
                    147:         self.view_url(self._path + "authors.txt", "Hatari authors")
                    148: 
                    149:     def view_hatari_page(self, dummy=None):
                    150:         self.view_url("http://hatari.berlios.de/", "Hatari home page")
                    151: 
                    152:     def view_hatariui_page(self, dummy=None):
                    153:         self.view_url("http://koti.mbnet.fi/tammat/hatari/hatari-ui.shtml", "Hatari UI home page")
                    154: 
                    155: 
                    156: # --------------------------------------------------------
                    157: # auxiliary class+callback to be used with the PasteDialog
                    158: 
                    159: class HatariTextInsert:
                    160:     def __init__(self, hatari, text):
                    161:         self.index = 0
                    162:         self.text = text
                    163:         self.pressed = False
                    164:         self.hatari = hatari
1.1.1.2 ! root      165:         print("OUTPUT '%s'" % text)
1.1       root      166:         gobject.timeout_add(100, _text_insert_cb, self)
                    167: 
1.1.1.2 ! root      168: # callback to insert text object to Hatari character at the time
        !           169: # (first key down, on next call up), at given interval
1.1       root      170: def _text_insert_cb(textobj):
                    171:     char = textobj.text[textobj.index]
1.1.1.2 ! root      172:     if char == ' ':
        !           173:         # white space gets stripped, use scancode instead
        !           174:         char = "57"
1.1       root      175:     if textobj.pressed:
                    176:         textobj.pressed = False
1.1.1.2 ! root      177:         textobj.hatari.insert_event("keyup %s" % char)
1.1       root      178:         textobj.index += 1
                    179:         if textobj.index >= len(textobj.text):
                    180:             del(textobj)
                    181:             return False
                    182:     else:
                    183:         textobj.pressed = True
1.1.1.2 ! root      184:         textobj.hatari.insert_event("keydown %s" % char)
        !           185:     # call again
1.1       root      186:     return True
                    187: 
                    188: 
                    189: # ----------------------------
                    190: # helper functions for buttons
                    191: 
                    192: def create_button(label, cb, data = None):
                    193:     "create_button(label,cb[,data]) -> button widget"
                    194:     button = gtk.Button(label)
                    195:     if data == None:
                    196:         button.connect("clicked", cb)
                    197:     else:
                    198:         button.connect("clicked", cb, data)
                    199:     return button
                    200: 
                    201: def create_toolbutton(stock_id, cb, data = None):
                    202:     "create_toolbutton(stock_id,cb[,data]) -> toolbar button with stock icon+label"
                    203:     button = gtk.ToolButton(stock_id)
                    204:     if data == None:
                    205:         button.connect("clicked", cb)
                    206:     else:
                    207:         button.connect("clicked", cb, data)
                    208:     return button
                    209: 
                    210: def create_toggle(label, cb, data = None):
                    211:     "create_toggle(label,cb[,data]) -> toggle button widget"
                    212:     button = gtk.ToggleButton(label)
                    213:     if data == None:
                    214:         button.connect("toggled", cb)
                    215:     else:
                    216:         button.connect("toggled", cb, data)
                    217:     return button
                    218: 
                    219: 
                    220: # -----------------------------
                    221: # Table dialog helper functions
                    222: 
                    223: def create_table_dialog(parent, title, rows, cols, oktext = gtk.STOCK_APPLY):
                    224:     "create_table_dialog(parent,title,rows, cols, oktext) -> (table,dialog)"
                    225:     dialog = gtk.Dialog(title, parent,
                    226:         gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                    227:         (oktext,  gtk.RESPONSE_APPLY,
                    228:         gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
                    229: 
                    230:     table = gtk.Table(rows, cols)
                    231:     table.set_data("col_offset", 0)
                    232:     table.set_col_spacings(8)
                    233:     dialog.vbox.add(table)
                    234:     return (table, dialog)
                    235: 
                    236: def table_set_col_offset(table, offset):
                    237:     "set column offset for successive table_* ops on given table"
                    238:     table.set_data("col_offset", offset)
                    239: 
                    240: def table_add_entry_row(table, row, label, size = None):
                    241:     "table_add_entry_row(table,row,label,[entry size]) -> entry"
                    242:     # add given label to given row in given table
                    243:     # return entry for that line
                    244:     label = gtk.Label(label)
                    245:     align = gtk.Alignment(1) # right aligned
                    246:     align.add(label)
                    247:     col = table.get_data("col_offset")
                    248:     table.attach(align, col, col+1, row, row+1, gtk.FILL)
                    249:     col += 1
                    250:     if size:
                    251:         entry = gtk.Entry(size)
                    252:         entry.set_width_chars(size)
                    253:         align = gtk.Alignment(0) # left aligned (default is centered)
                    254:         align.add(entry)
                    255:         table.attach(align, col, col+1, row, row+1)
                    256:     else:
                    257:         entry = gtk.Entry()
                    258:         table.attach(entry, col, col+1, row, row+1)
                    259:     return entry
                    260: 
                    261: def table_add_widget_row(table, row, label, widget, fullspan = False):
                    262:     "table_add_widget_row(table,row,label,widget) -> widget"
                    263:     # add given label right aligned to given row in given table
                    264:     # add given widget to the right column and returns it
                    265:     # return entry for that line
                    266:     if fullspan:
                    267:         col = 0
                    268:     else:
                    269:         col = table.get_data("col_offset")
                    270:     if label:
                    271:         label = gtk.Label(label)
                    272:         align = gtk.Alignment(1)
                    273:         align.add(label)
                    274:         table.attach(align, col, col+1, row, row+1, gtk.FILL)
                    275:     if fullspan:
                    276:         col = table.get_data("col_offset")
                    277:         table.attach(widget, 1, col+2, row, row+1)
                    278:     else:
                    279:         table.attach(widget, col+1, col+2, row, row+1)
                    280:     return widget
                    281: 
                    282: def table_add_radio_rows(table, row, label, texts, cb = None):
                    283:     "table_add_radio_rows(table,row,label,texts[,cb]) -> [radios]"
                    284:     # - add given label right aligned to given row in given table
                    285:     # - create/add radio buttons with given texts to next row, set
                    286:     #   the one given as "active" as active and set 'cb' as their
                    287:     #   "toggled" callback handler
                    288:     # - return array or radiobuttons
                    289:     label = gtk.Label(label)
                    290:     align = gtk.Alignment(1)
                    291:     align.add(label)
                    292:     col = table.get_data("col_offset")
                    293:     table.attach(align, col, col+1, row, row+1, gtk.FILL)
                    294: 
                    295:     radios = []
                    296:     radio = None
                    297:     box = gtk.VBox()
                    298:     for text in texts:
                    299:         radio = gtk.RadioButton(radio, text)
                    300:         if cb:
                    301:             radio.connect("toggled", cb, text)
                    302:         radios.append(radio)
                    303:         box.add(radio)
                    304:     table.attach(box, col+1, col+2, row, row+1)
                    305:     return radios
                    306: 
                    307: def table_add_separator(table, row):
                    308:     "table_add_separator(table,row)"
                    309:     widget = gtk.HSeparator()
                    310:     endcol = table.get_data("n-columns")
                    311:     # separator for whole table width
                    312:     table.attach(widget, 0, endcol, row, row+1, gtk.FILL)
                    313: 
                    314: 
                    315: # -----------------------------
                    316: # File selection helpers
                    317: 
                    318: def get_open_filename(title, parent, path = None):
                    319:     buttons = (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
                    320:     fsel = gtk.FileChooserDialog(title, parent, gtk.FILE_CHOOSER_ACTION_OPEN, buttons)
                    321:     fsel.set_local_only(True)
                    322:     if path:
                    323:         fsel.set_filename(path)
                    324:     if fsel.run() == gtk.RESPONSE_OK:
                    325:         filename = fsel.get_filename()
                    326:     else:
                    327:         filename = None
                    328:     fsel.destroy()
                    329:     return filename
                    330: 
                    331: def get_save_filename(title, parent, path = None):
                    332:     buttons = (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
                    333:     fsel = gtk.FileChooserDialog(title, parent, gtk.FILE_CHOOSER_ACTION_SAVE, buttons)
                    334:     fsel.set_local_only(True)
                    335:     fsel.set_do_overwrite_confirmation(True)
                    336:     if path:
                    337:         fsel.set_filename(path)
                    338:         if not os.path.exists(path):
                    339:             # above set only folder, this is needed to set
                    340:             # the file name when the file doesn't exist
                    341:             fsel.set_current_name(os.path.basename(path))
                    342:     if fsel.run() == gtk.RESPONSE_OK:
                    343:         filename = fsel.get_filename()
                    344:     else:
                    345:         filename = None
                    346:     fsel.destroy()
                    347:     return filename
                    348: 
1.1.1.2 ! root      349: 
        !           350: # File selection button with eject button
        !           351: class FselAndEjectFactory:
        !           352:     def __init__(self):
        !           353:         pass
        !           354: 
        !           355:     def get(self, label, path, filename, action):
        !           356:         "returns file selection button and box having that + eject button"
        !           357:         fsel = gtk.FileChooserButton(label)
        !           358:         # Hatari cannot access URIs
        !           359:         fsel.set_local_only(True)
        !           360:         fsel.set_width_chars(12)
        !           361:         fsel.set_action(action)
        !           362:         if filename:
        !           363:             fsel.set_filename(filename)
        !           364:         elif path:
        !           365:             fsel.set_current_folder(path)
        !           366:         eject = create_button("Eject", self._eject, fsel)
        !           367: 
        !           368:         box = gtk.HBox()
        !           369:         box.pack_start(fsel)
        !           370:         box.pack_start(eject, False, False)
        !           371:         return (fsel, box)
        !           372: 
        !           373:     def _eject(self, widget, fsel):
        !           374:         fsel.unselect_all()
        !           375: 
        !           376: 
1.1       root      377: # Gtk is braindead, there's no way to set a default filename
                    378: # for file chooser button unless it already exists
                    379: # - set_filename() works only for files that already exist
                    380: # - set_current_name() works only for SAVE action,
                    381: #   but file chooser button doesn't support that
                    382: # i.e. I had to do my own (less nice) container widget...
1.1.1.2 ! root      383: class FselEntry:
1.1       root      384:     def __init__(self, parent, validate = None, data = None):
                    385:         self._parent = parent
                    386:         self._validate = validate
                    387:         self._validate_data = data
                    388:         entry = gtk.Entry()
                    389:         entry.set_width_chars(12)
                    390:         entry.set_editable(False)
                    391:         hbox = gtk.HBox()
                    392:         hbox.add(entry)
                    393:         button = create_button("Select...", self._select_file_cb)
                    394:         hbox.pack_start(button, False, False)
                    395:         self._entry = entry
                    396:         self._hbox = hbox
                    397:     
                    398:     def _select_file_cb(self, widget):
                    399:         fname = self._entry.get_text()
                    400:         while True:
                    401:             fname = get_save_filename("Select file", self._parent, fname)
                    402:             if not fname:
                    403:                 # assume cancel
                    404:                 return
                    405:             if self._validate:
                    406:                 # filename needs validation and is valid?
                    407:                 if not self._validate(self._validate_data, fname):
                    408:                     continue
                    409:             self._entry.set_text(fname)
                    410:             return
                    411:     
                    412:     def set_filename(self, fname):
                    413:         self._entry.set_text(fname)
                    414:         
                    415:     def get_filename(self):
                    416:         return self._entry.get_text()
                    417: 
                    418:     def get_container(self):
                    419:         return self._hbox

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.