DELETED mygtk.py Index: mygtk.py ================================================================== --- mygtk.py +++ mygtk.py @@ -1,546 +0,0 @@ -# -# encoding: UTF-8 -# api: python -# type: functions -# title: mygtk helper functions -# description: simplify usage of some gtk widgets -# version: 1.8 -# author: mario -# license: public domain -# -# -# Wrappers around gtk methods. The TreeView method .columns() allows -# to fill a treeview. It adds columns and data rows with a mapping -# dictionary (which specifies many options and data positions). -# -# The .tree() method is a trimmed-down variant of that, creates a -# single column, but has threaded entries. -# -# With the methodes .app_state() and .app_restore() named gtk widgets -# can be queried for attributes. The methods return a saveable dict, -# which contain current layout options for a few Widget types. Saving -# and restoring must be handled elsewhere. -# -# - - - - -# debug -from config import __print__, dbg - -# filesystem -import os.path -import copy -import sys - -from compat2and3 import unicode, xrange, PY3 - - -# gtk version (2=gtk2, 3=gtk3) -ver = 2 -# if running on Python3 or with commandline flag -if PY3 or "--gtk3" in sys.argv: - ver = 3 -# load gtk modules -if ver==3: - from gi import pygtkcompat as pygtk - pygtk.enable() - pygtk.enable_gtk(version='3.0') - from gi.repository import Gtk as gtk - from gi.repository import GObject as gobject - from gi.repository import GdkPixbuf - ui_file = "gtk3.xml" - empty_pixbuf = GdkPixbuf.Pixbuf.new_from_data(b"\0\0\0\0", GdkPixbuf.Colorspace.RGB, True, 8, 1, 1, 4, None, None) - __print__(dbg.PROC, gtk) - __print__(dbg.PROC, gobject) -else: - import pygtk - import gtk - import gobject - ui_file = "gtk2.xml" - empty_pixbuf = gtk.gdk.pixbuf_new_from_data(b"\0\0\0\0",gtk.gdk.COLORSPACE_RGB,True,8,1,1,4) - - - - -# simplified gtk constructors --------------------------------------------- -class mygtk: - - - - #-- fill a treeview - # - # Adds treeviewcolumns/cellrenderers and liststore from a data dictionary. - # Its datamap and the table contents can be supplied in one or two steps. - # When new data gets applied, the columns aren't recreated. - # - # The columns are created according to the datamap, which describes cell - # mapping and layout. Columns can have multiple cellrenderers, but usually - # there is a direct mapping to a data source key from entries. - # - # datamap = [ # title width dict-key type, renderer, attrs - # ["Name", 150, ["titlerow", str, "text", {} ] ], - # [False, 0, ["interndat", int, None, {} ] ], - # ["Desc", 200, ["descriptn", str, "text", {} ], ["icon",str,"pixbuf",{}] ], - # - # An according entries list then would contain a dictionary for each row: - # entries = [ {"titlerow":"first", "interndat":123}, {"titlerow":"..."}, ] - # Keys not mentioned in the datamap get ignored, and defaults are applied - # for missing cols. All values must already be in the correct type however. - # - @staticmethod - def columns(widget, datamap=[], entries=None, pix_entry=False, typecast=0): - - # create treeviewcolumns? - if (not widget.get_column(0)): - # loop through titles - datapos = 0 - for n_col,desc in enumerate(datamap): - - # check for title - if (type(desc[0]) != str): - datapos += 1 # if there is none, this is just an undisplayed data column - continue - # new tvcolumn - col = gtk.TreeViewColumn(desc[0]) # title - col.set_resizable(True) - # width - if (desc[1] > 0): - col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - col.set_fixed_width(desc[1]) - - # loop through cells - for var in xrange(2, len(desc)): - cell = desc[var] - # cell renderer - if (cell[2] == "pixbuf"): - rend = gtk.CellRendererPixbuf() # img cell - if (cell[1] == str): - cell[3]["stock_id"] = datapos # for stock icons - expand = False - else: - pix_entry = datapos - cell[3]["pixbuf"] = datapos - else: - rend = gtk.CellRendererText() # text cell - cell[3]["text"] = datapos - #col.set_sort_column_id(datapos) # only on textual cells - - # attach cell to column - col.pack_end(rend, expand=cell[3].get("expand",True)) - # apply attributes - for attr,val in list(cell[3].items()): - col.add_attribute(rend, attr, val) - # next - datapos += 1 - #__print__(dbg.INFO, cell, len(cell)) - - # add column to treeview - widget.append_column(col) - # finalize widget - widget.set_search_column(5) #?? - widget.set_search_column(4) #?? - widget.set_search_column(3) #?? - widget.set_search_column(2) #?? - widget.set_search_column(1) #?? - #widget.set_reorderable(True) - - # add data? - if (entries is not None): - #- expand datamap - vartypes = [] #(str, str, bool, str, int, int, gtk.gdk.Pixbuf, str, int) - rowmap = [] #["title", "desc", "bookmarked", "name", "count", "max", "img", ...] - if (not rowmap): - for desc in datamap: - for var in xrange(2, len(desc)): - vartypes.append(desc[var][1]) # content types - rowmap.append(desc[var][0]) # dict{} column keys in entries[] list - # create gtk array storage - ls = gtk.ListStore(*vartypes) # could be a TreeStore, too - #__print__(dbg.UI, vartypes, len(vartypes)) - #__print__(dbg.DATA, rowmap, len(rowmap)) - - # prepare for missing values, and special variable types - defaults = { - str: "", - unicode: "", - bool: False, - int: 0, - gtk.gdk.Pixbuf: empty_pixbuf - } - if gtk.gdk.Pixbuf in vartypes: - pix_entry = vartypes.index(gtk.gdk.Pixbuf) - - # sort data into gtk liststore array - for row in entries: - - # preset some values if absent - row.setdefault("deleted", False) - row.setdefault("search_col", "#ffffff") - row.setdefault("search_set", False) - - # generate ordered list from dictionary, using rowmap association - row = [ row.get( skey , defaults[vartypes[i]] ) for i,skey in enumerate(rowmap) ] - - # map Python2 unicode to str - row = [ str(value) if type(value) is unicode else value for value in row ] - - # autotransform string -> gtk image object - if (pix_entry and type(row[pix_entry]) == str): - row[pix_entry] = ( gtk.gdk.pixbuf_new_from_file(row[pix_entry]) if os.path.exists(row[pix_entry]) else defaults[gtk.gdk.Pixbuf] ) - - try: - # add - ls.append(row) # had to be adapted for real TreeStore (would require additional input for grouping/level/parents) - - except: - # brute-force typecast - ls.append( [va if ty==gtk.gdk.Pixbuf else ty(va) for va,ty in zip(row,vartypes)] ) - - #if entries: - #__print__("→", row, len(row)) - - # apply array to widget - widget.set_model(ls) - return ls - - pass - - - - - #-- treeview for categories - # - # simple two-level treeview display in one column - # with entries = [main,[sub,sub], title,[...],...] - # - @staticmethod - def tree(widget, entries, title="category", icon=gtk.STOCK_DIRECTORY): - - # list types - ls = gtk.TreeStore(str, str) - #__print__(dbg.DATA, ".tree", entries) - - # add entries - for entry in entries: - if isinstance(entry, (str,unicode)): - main = ls.append(None, [str(entry), icon]) - else: - for sub_title in entry: - ls.append(main, [str(sub_title), icon]) - - # just one column - tvcolumn = gtk.TreeViewColumn(title); - widget.append_column(tvcolumn) - - # inner display: icon & string - pix = gtk.CellRendererPixbuf() - txt = gtk.CellRendererText() - - # position - tvcolumn.pack_start(pix, expand=False) - tvcolumn.pack_end(txt, expand=True) - - # select array content source in treestore - tvcolumn.add_attribute(pix, "stock_id", 1) - tvcolumn.add_attribute(txt, "text", 0) - - # finalize - widget.set_model(ls) - tvcolumn.set_sort_column_id(0) - widget.set_search_column(0) - #tvcolumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) - #tvcolumn.set_fixed_width(125]) - #widget.expand_all() - #widget.expand_row("3", False) - #print(widget.row_expanded("3")) - - return ls - - - - - #-- save window size and widget properties - # - # needs a list of widgetnames - # e.g. pickle.dump(mygtk.app_state(...), open(os.environ["HOME"]+"/.config/app_winstate", "w")) - # - @staticmethod - def app_state(wTree, widgetnames=["window1", "treeview2", "vbox17"]): - r = {} # restore array - for wn in widgetnames: - r[wn] = {} - w = wTree.get_widget(wn) - t = type(w) -# print(wn, w, t) - # extract different information from individual widget types - if t == gtk.Window: - r[wn]["size"] = list(w.get_size()) - #print("WINDOW SIZE", list(w.get_size()), r[wn]) - if t == gtk.Widget: - r[wn]["name"] = w.get_name() - # gtk.TreeView - if t == gtk.TreeView: - r[wn]["columns:width"] = [] - for col in w.get_columns(): - r[wn]["columns:width"].append( col.get_width() ) - # - Rows - r[wn]["rows:expanded"] = [] - for i in xrange(0,50): - if w.row_expanded(str(i)): - r[wn]["rows:expanded"].append(i) - # - selected - (model, paths) = w.get_selection().get_selected_rows() - if paths: - r[wn]["row:selected"] = paths[0] - # gtk.Toolbar - if t == gtk.Toolbar: - r[wn]["icon_size"] = int(w.get_icon_size()) - r[wn]["style"] = int(w.get_style()) - # gtk.Notebook - if t == gtk.Notebook: - r[wn]["page"] = w.get_current_page() - r[wn]["tab_pos"] = int(w.get_tab_pos()) - #print(r) - return r - - gtk_position_type_enum = [gtk.POS_LEFT, gtk.POS_RIGHT, gtk.POS_TOP, gtk.POS_BOTTOM] - - - #-- restore window and widget properties - # - # requires only the previously saved widget state dict - # - @staticmethod - def app_restore(wTree, r=None): - for wn in r.keys(): # widgetnames - w = wTree.get_widget(wn) - if (not w): - continue - t = type(w) - for method,args in r[wn].items(): - # gtk.Window - if method == "size": - w.resize(args[0], args[1]) - # gtk.TreeView - if method == "columns:width": - for i,col in enumerate(w.get_columns()): - if (i < len(args)): - col.set_fixed_width(args[i]) - # - Rows - if method == "rows:expanded": - w.collapse_all() - for i in args: - w.expand_row(str(i), False) - # - selected - if method == "row:selected": - w.get_selection().select_path(tuple(args)) - # gtk.Toolbar - if method == "icon_size": - w.set_icon_size(args) - if method == "style": - w.set_style(args) - # gtk.Notebook - if method == "page": - w.set_current_page(args) - if method == "tab_pos": - w.set_tab_pos(r[wn]["tab_pos"]) - - pass - - - - #-- Save-As dialog - # - @staticmethod - def save_file(title="Save As", parent=None, fn="", formats=[("*","*")]): - c = gtk.FileChooserDialog(title, parent, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, 0, gtk.STOCK_SAVE, 1)) - # params - if fn: - c.set_current_name(fn) - fn = "" - for fname,ftype in formats: - f = gtk.FileFilter() - f.set_name(fname) - f.add_pattern(ftype) - c.add_filter(f) - # display - if c.run(): - fn = c.get_filename() # return filaname - c.destroy() - return fn - - - - # pass updates from another thread, ensures that it is called just once - @staticmethod - def do(lambda_func): - gobject.idle_add(lambda: lambda_func() and False) - - - # adds background color to widget, - # eventually wraps it into a gtk.Window, if it needs a container - @staticmethod - def bg(w, color="", where=["bg"]): - """ this method should be called after widget creation, and before .add()ing it to container """ - if color: - # wrap unstylable widgets into EventBox - if not isinstance(w, gtk.Window): - wrap = gtk.EventBox() - wrap.add(w) - wrap.set_property("visible", True) - w = wrap - # copy style object, modify settings - s = w.get_style().copy() - c = w.get_colormap().alloc_color(color) - for state in (gtk.STATE_NORMAL, gtk.STATE_SELECTED): - s.bg[state] = c - w.set_style(s) - # probably redundant, but better safe than sorry: - w.modify_bg(gtk.STATE_NORMAL, c) - # return modified or wrapped widget - return w - - - # Create GtkLabel - @staticmethod - def label(text, size=400, markup=0): - label = gtk.Label(text) - if markup: - label.set_markup(text) - label.set_property("visible", True) - label.set_line_wrap(True) - label.set_size_request(size, -1) - return label - - # Wrap two widgets in horizontal box - @staticmethod - def hbox(w1, w2): - b = gtk.HBox(homogeneous=False, spacing=10) - b.set_property("visible", True) - b.pack_start(w1, expand=False, fill=False) - b.pack_start(w2, expand=True, fill=True) - return b - - - # Attach textual menu entry and callback - @staticmethod - def add_menu(menuwidget, label, action): - m = gtk.MenuItem(label) - m.connect("activate", action) - m.show() - menuwidget.add(m) - - - # gtk.messagebox - @staticmethod - def msg(text, style=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE): - m = gtk.MessageDialog(None, 0, style, buttons, message_format=text) - m.show() - m.connect("response", lambda *w: m.destroy()) - - - -# Text-only dropdown list. -# -# Necessary because gtk.ComboBoxText binding is absent in debian packages -# https://bugzilla.gnome.org/show_bug.cgi?id=660659 -# -# This one implements a convenience method `.set_default()` to define the active -# selection by value, rather than by index. -# -# Can use a list[] of entries or a key->value dict{}, where the value becomes -# display text, and the key the internal value. -# -class ComboBoxText(gtk.ComboBox): - - ls = None - - def __init__(self, entries, no_scroll=1): - - # prepare widget - gtk.ComboBox.__init__(self) - self.set_property("visible", True) - cell = gtk.CellRendererText() - self.pack_start(cell, True) - self.add_attribute(cell, "text", 1) - if no_scroll: - self.connect("scroll_event", self.no_scroll) - - # collect entries - self.ls = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) - self.set_model(self.ls) - if type(entries[0]) is not tuple: - entries = zip(entries, entries) - for key,value in entries: - self.ls.append([key, value]) - - # activate dropdown of given value - def set_default(self, value): - for index,row in enumerate(self.ls): - if value in row: - return self.set_active(index) - # add as custom entry - self.ls.append([value, value]) - self.set_active(index + 1) - - # fetch currently selected text entry - def get_active_text(self): - index = self.get_active() - if index >= 0: - return self.ls[index][0] - - # Signal/Event callback to prevent hover scrolling of ComboBox widgets - def no_scroll(self, widget, event, data=None): - return True - - - # Expand A=a|B=b|C=c option list into (key,value) tuple list, or A|B|C just into a list. - @staticmethod - def parse_options(opts, sep="|", assoc="="): - if opts.find(assoc) >= 0: - return [ (k,v) for k,v in (x.split(assoc, 1) for x in opts.split(sep)) ] - else: - return opts.split(sep) #dict( (v,v) for v in opts.split(sep) ) - - - - - -#-- startup progress bar -progresswin, progressbar = None, None -def gui_startup(p=0/100.0, msg="streamtuner2 is starting"): - global progresswin, progressbar - - if not progresswin: - - # GtkWindow "progresswin" - progresswin = gtk.Window() - progresswin.set_property("title", "streamtuner2") - progresswin.set_property("default_width", 300) - progresswin.set_property("width_request", 300) - progresswin.set_property("default_height", 30) - progresswin.set_property("height_request", 30) - #progresswin.set_property("window_position", "center") - progresswin.set_property("decorated", False) - progresswin.set_property("visible", True) - - # GtkProgressBar "progressbar" - progressbar = gtk.ProgressBar() - progressbar.set_property("visible", True) - progressbar.set_property("show_text", True) - progressbar.set_property("text", msg) - progresswin.add(progressbar) - progresswin.show_all() - - try: - if p<1: - progressbar.set_fraction(p) - progressbar.set_property("text", msg) - while gtk.events_pending(): gtk.main_iteration(False) - else: - progresswin.hide() - except: return - - ADDED uikit.py Index: uikit.py ================================================================== --- uikit.py +++ uikit.py @@ -0,0 +1,585 @@ +# +# encoding: UTF-8 +# api: python +# type: functions +# title: uikit helper functions +# description: simplify usage of some gtk widgets +# version: 1.8 +# author: mario +# license: public domain +# +# +# Wrappers around gtk methods. The TreeView method .columns() allows +# to fill a treeview. It adds columns and data rows with a mapping +# dictionary (which specifies many options and data positions). +# +# The .tree() method is a trimmed-down variant of that, creates a +# single column, but has threaded entries. +# +# With the methodes .app_state() and .app_restore() named gtk widgets +# can be queried for attributes. The methods return a saveable dict, +# which contain current layout options for a few Widget types. Saving +# and restoring must be handled elsewhere. +# +# + + + + +# debug +from config import __print__, dbg, plugin_meta + +# filesystem +import os.path +import copy +import sys +import inspect +from compat2and3 import unicode, xrange, PY3 + + +# gtk version (2=gtk2, 3=gtk3) +ver = 2 +# if running on Python3 or with commandline flag +if PY3 or "--gtk3" in sys.argv: + ver = 3 +# load gtk modules +if ver==3: + from gi import pygtkcompat as pygtk + pygtk.enable() + pygtk.enable_gtk(version='3.0') + from gi.repository import Gtk as gtk + from gi.repository import GObject as gobject + from gi.repository import GdkPixbuf + ui_file = "gtk3.xml" + empty_pixbuf = GdkPixbuf.Pixbuf.new_from_data(b"\0\0\0\0", GdkPixbuf.Colorspace.RGB, True, 8, 1, 1, 4, None, None) + __print__(dbg.PROC, gtk) + __print__(dbg.PROC, gobject) +else: + import pygtk + import gtk + import gobject + ui_file = "gtk2.xml" + empty_pixbuf = gtk.gdk.pixbuf_new_from_data(b"\0\0\0\0",gtk.gdk.COLORSPACE_RGB,True,8,1,1,4) + + + + +# simplified gtk constructors --------------------------------------------- +class uikit: + + + + #-- fill a treeview + # + # Adds treeviewcolumns/cellrenderers and liststore from a data dictionary. + # Its datamap and the table contents can be supplied in one or two steps. + # When new data gets applied, the columns aren't recreated. + # + # The columns are created according to the datamap, which describes cell + # mapping and layout. Columns can have multiple cellrenderers, but usually + # there is a direct mapping to a data source key from entries. + # + # datamap = [ # title width dict-key type, renderer, attrs + # ["Name", 150, ["titlerow", str, "text", {} ] ], + # [False, 0, ["interndat", int, None, {} ] ], + # ["Desc", 200, ["descriptn", str, "text", {} ], ["icon",str,"pixbuf",{}] ], + # + # An according entries list then would contain a dictionary for each row: + # entries = [ {"titlerow":"first", "interndat":123}, {"titlerow":"..."}, ] + # Keys not mentioned in the datamap get ignored, and defaults are applied + # for missing cols. All values must already be in the correct type however. + # + @staticmethod + def columns(widget, datamap=[], entries=None, pix_entry=False, typecast=0): + + # create treeviewcolumns? + if (not widget.get_column(0)): + # loop through titles + datapos = 0 + for n_col,desc in enumerate(datamap): + + # check for title + if (type(desc[0]) != str): + datapos += 1 # if there is none, this is just an undisplayed data column + continue + # new tvcolumn + col = gtk.TreeViewColumn(desc[0]) # title + col.set_resizable(True) + # width + if (desc[1] > 0): + col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + col.set_fixed_width(desc[1]) + + # loop through cells + for var in xrange(2, len(desc)): + cell = desc[var] + # cell renderer + if (cell[2] == "pixbuf"): + rend = gtk.CellRendererPixbuf() # img cell + if (cell[1] == str): + cell[3]["stock_id"] = datapos # for stock icons + expand = False + else: + pix_entry = datapos + cell[3]["pixbuf"] = datapos + else: + rend = gtk.CellRendererText() # text cell + cell[3]["text"] = datapos + #col.set_sort_column_id(datapos) # only on textual cells + + # attach cell to column + col.pack_end(rend, expand=cell[3].get("expand",True)) + # apply attributes + for attr,val in list(cell[3].items()): + col.add_attribute(rend, attr, val) + # next + datapos += 1 + #__print__(dbg.INFO, cell, len(cell)) + + # add column to treeview + widget.append_column(col) + # finalize widget + widget.set_search_column(5) #?? + widget.set_search_column(4) #?? + widget.set_search_column(3) #?? + widget.set_search_column(2) #?? + widget.set_search_column(1) #?? + #widget.set_reorderable(True) + + # add data? + if (entries is not None): + #- expand datamap + vartypes = [] #(str, str, bool, str, int, int, gtk.gdk.Pixbuf, str, int) + rowmap = [] #["title", "desc", "bookmarked", "name", "count", "max", "img", ...] + if (not rowmap): + for desc in datamap: + for var in xrange(2, len(desc)): + vartypes.append(desc[var][1]) # content types + rowmap.append(desc[var][0]) # dict{} column keys in entries[] list + # create gtk array storage + ls = gtk.ListStore(*vartypes) # could be a TreeStore, too + #__print__(dbg.UI, vartypes, len(vartypes)) + #__print__(dbg.DATA, rowmap, len(rowmap)) + + # prepare for missing values, and special variable types + defaults = { + str: "", + unicode: "", + bool: False, + int: 0, + gtk.gdk.Pixbuf: empty_pixbuf + } + if gtk.gdk.Pixbuf in vartypes: + pix_entry = vartypes.index(gtk.gdk.Pixbuf) + + # sort data into gtk liststore array + for row in entries: + + # preset some values if absent + row.setdefault("deleted", False) + row.setdefault("search_col", "#ffffff") + row.setdefault("search_set", False) + + # generate ordered list from dictionary, using rowmap association + row = [ row.get( skey , defaults[vartypes[i]] ) for i,skey in enumerate(rowmap) ] + + # map Python2 unicode to str + row = [ str(value) if type(value) is unicode else value for value in row ] + + # autotransform string -> gtk image object + if (pix_entry and type(row[pix_entry]) == str): + row[pix_entry] = ( gtk.gdk.pixbuf_new_from_file(row[pix_entry]) if os.path.exists(row[pix_entry]) else defaults[gtk.gdk.Pixbuf] ) + + try: + # add + ls.append(row) # had to be adapted for real TreeStore (would require additional input for grouping/level/parents) + + except: + # brute-force typecast + ls.append( [va if ty==gtk.gdk.Pixbuf else ty(va) for va,ty in zip(row,vartypes)] ) + + #if entries: + #__print__("→", row, len(row)) + + # apply array to widget + widget.set_model(ls) + return ls + + pass + + + + + #-- treeview for categories + # + # simple two-level treeview display in one column + # with entries = [main,[sub,sub], title,[...],...] + # + @staticmethod + def tree(widget, entries, title="category", icon=gtk.STOCK_DIRECTORY): + + # list types + ls = gtk.TreeStore(str, str) + #__print__(dbg.DATA, ".tree", entries) + + # add entries + for entry in entries: + if isinstance(entry, (str,unicode)): + main = ls.append(None, [str(entry), icon]) + else: + for sub_title in entry: + ls.append(main, [str(sub_title), icon]) + + # just one column + tvcolumn = gtk.TreeViewColumn(title); + widget.append_column(tvcolumn) + + # inner display: icon & string + pix = gtk.CellRendererPixbuf() + txt = gtk.CellRendererText() + + # position + tvcolumn.pack_start(pix, expand=False) + tvcolumn.pack_end(txt, expand=True) + + # select array content source in treestore + tvcolumn.add_attribute(pix, "stock_id", 1) + tvcolumn.add_attribute(txt, "text", 0) + + # finalize + widget.set_model(ls) + tvcolumn.set_sort_column_id(0) + widget.set_search_column(0) + #tvcolumn.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + #tvcolumn.set_fixed_width(125]) + #widget.expand_all() + #widget.expand_row("3", False) + #print(widget.row_expanded("3")) + + return ls + + + + + #-- save window size and widget properties + # + # needs a list of widgetnames + # e.g. pickle.dump(uikit.app_state(...), open(os.environ["HOME"]+"/.config/app_winstate", "w")) + # + @staticmethod + def app_state(wTree, widgetnames=["window1", "treeview2", "vbox17"]): + r = {} # restore array + for wn in widgetnames: + r[wn] = {} + w = wTree.get_widget(wn) + t = type(w) +# print(wn, w, t) + # extract different information from individual widget types + if t == gtk.Window: + r[wn]["size"] = list(w.get_size()) + #print("WINDOW SIZE", list(w.get_size()), r[wn]) + if t == gtk.Widget: + r[wn]["name"] = w.get_name() + # gtk.TreeView + if t == gtk.TreeView: + r[wn]["columns:width"] = [] + for col in w.get_columns(): + r[wn]["columns:width"].append( col.get_width() ) + # - Rows + r[wn]["rows:expanded"] = [] + for i in xrange(0,50): + if w.row_expanded(str(i)): + r[wn]["rows:expanded"].append(i) + # - selected + (model, paths) = w.get_selection().get_selected_rows() + if paths: + r[wn]["row:selected"] = paths[0] + # gtk.Toolbar + if t == gtk.Toolbar: + r[wn]["icon_size"] = int(w.get_icon_size()) + r[wn]["style"] = int(w.get_style()) + # gtk.Notebook + if t == gtk.Notebook: + r[wn]["page"] = w.get_current_page() + r[wn]["tab_pos"] = int(w.get_tab_pos()) + #print(r) + return r + + gtk_position_type_enum = [gtk.POS_LEFT, gtk.POS_RIGHT, gtk.POS_TOP, gtk.POS_BOTTOM] + + + #-- restore window and widget properties + # + # requires only the previously saved widget state dict + # + @staticmethod + def app_restore(wTree, r=None): + for wn in r.keys(): # widgetnames + w = wTree.get_widget(wn) + if (not w): + continue + t = type(w) + for method,args in r[wn].items(): + # gtk.Window + if method == "size": + w.resize(args[0], args[1]) + # gtk.TreeView + if method == "columns:width": + for i,col in enumerate(w.get_columns()): + if (i < len(args)): + col.set_fixed_width(args[i]) + # - Rows + if method == "rows:expanded": + w.collapse_all() + for i in args: + w.expand_row(str(i), False) + # - selected + if method == "row:selected": + w.get_selection().select_path(tuple(args)) + # gtk.Toolbar + if method == "icon_size": + w.set_icon_size(args) + if method == "style": + w.set_style(args) + # gtk.Notebook + if method == "page": + w.set_current_page(args) + if method == "tab_pos": + w.set_tab_pos(r[wn]["tab_pos"]) + + pass + + + + #-- Save-As dialog + # + @staticmethod + def save_file(title="Save As", parent=None, fn="", formats=[("*","*")]): + c = gtk.FileChooserDialog(title, parent, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, 0, gtk.STOCK_SAVE, 1)) + # params + if fn: + c.set_current_name(fn) + fn = "" + for fname,ftype in formats: + f = gtk.FileFilter() + f.set_name(fname) + f.add_pattern(ftype) + c.add_filter(f) + # display + if c.run(): + fn = c.get_filename() # return filaname + c.destroy() + return fn + + + + # pass updates from another thread, ensures that it is called just once + @staticmethod + def do(lambda_func): + gobject.idle_add(lambda: lambda_func() and False) + + + # adds background color to widget, + # eventually wraps it into a gtk.Window, if it needs a container + @staticmethod + def bg(w, color="", where=["bg"]): + """ this method should be called after widget creation, and before .add()ing it to container """ + if color: + # wrap unstylable widgets into EventBox + if not isinstance(w, gtk.Window): + wrap = gtk.EventBox() + wrap.add(w) + wrap.set_property("visible", True) + w = wrap + # copy style object, modify settings + s = w.get_style().copy() + c = w.get_colormap().alloc_color(color) + for state in (gtk.STATE_NORMAL, gtk.STATE_SELECTED): + s.bg[state] = c + w.set_style(s) + # probably redundant, but better safe than sorry: + w.modify_bg(gtk.STATE_NORMAL, c) + # return modified or wrapped widget + return w + + + # Create GtkLabel + @staticmethod + def label(text, size=400, markup=0): + label = gtk.Label(text) + if markup: + label.set_markup(text) + label.set_property("visible", True) + label.set_line_wrap(True) + label.set_size_request(size, -1) + return label + + # Wrap two widgets in horizontal box + @staticmethod + def hbox(w1, w2): + b = gtk.HBox(homogeneous=False, spacing=10) + b.set_property("visible", True) + b.pack_start(w1, expand=False, fill=False) + b.pack_start(w2, expand=True, fill=True) + return b + + + # Attach textual menu entry and callback + @staticmethod + def add_menu(menuwidget, label, action): + m = gtk.MenuItem(label) + m.connect("activate", action) + m.show() + menuwidget.add(m) + + + # gtk.messagebox + @staticmethod + def msg(text, style=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE): + m = gtk.MessageDialog(None, 0, style, buttons, message_format=text) + m.show() + m.connect("response", lambda *w: m.destroy()) + + + +# Text-only dropdown list. +# +# Necessary because gtk.ComboBoxText binding is absent in debian packages +# https://bugzilla.gnome.org/show_bug.cgi?id=660659 +# +# This one implements a convenience method `.set_default()` to define the active +# selection by value, rather than by index. +# +# Can use a list[] of entries or a key->value dict{}, where the value becomes +# display text, and the key the internal value. +# +class ComboBoxText(gtk.ComboBox): + + ls = None + + def __init__(self, entries, no_scroll=1): + + # prepare widget + gtk.ComboBox.__init__(self) + self.set_property("visible", True) + cell = gtk.CellRendererText() + self.pack_start(cell, True) + self.add_attribute(cell, "text", 1) + if no_scroll: + self.connect("scroll_event", self.no_scroll) + + # collect entries + self.ls = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + self.set_model(self.ls) + if type(entries[0]) is not tuple: + entries = zip(entries, entries) + for key,value in entries: + self.ls.append([key, value]) + + # activate dropdown of given value + def set_default(self, value): + for index,row in enumerate(self.ls): + if value in row: + return self.set_active(index) + # add as custom entry + self.ls.append([value, value]) + self.set_active(index + 1) + + # fetch currently selected text entry + def get_active_text(self): + index = self.get_active() + if index >= 0: + return self.ls[index][0] + + # Signal/Event callback to prevent hover scrolling of ComboBox widgets + def no_scroll(self, widget, event, data=None): + return True + + + # Expand A=a|B=b|C=c option list into (key,value) tuple list, or A|B|C just into a list. + @staticmethod + def parse_options(opts, sep="|", assoc="="): + if opts.find(assoc) >= 0: + return [ (k,v) for k,v in (x.split(assoc, 1) for x in opts.split(sep)) ] + else: + return opts.split(sep) #dict( (v,v) for v in opts.split(sep) ) + + + + + +#-- startup progress bar +progresswin, progressbar = None, None +def gui_startup(p=0/100.0, msg="streamtuner2 is starting"): + global progresswin, progressbar + + if not progresswin: + + # GtkWindow "progresswin" + progresswin = gtk.Window() + progresswin.set_property("title", "streamtuner2") + progresswin.set_property("default_width", 300) + progresswin.set_property("width_request", 300) + progresswin.set_property("default_height", 30) + progresswin.set_property("height_request", 30) + #progresswin.set_property("window_position", "center") + progresswin.set_property("decorated", False) + progresswin.set_property("visible", True) + + # GtkProgressBar "progressbar" + progressbar = gtk.ProgressBar() + progressbar.set_property("visible", True) + progressbar.set_property("show_text", True) + progressbar.set_property("text", msg) + progresswin.add(progressbar) + progresswin.show_all() + + try: + if p<1: + progressbar.set_fraction(p) + progressbar.set_property("text", msg) + while gtk.events_pending(): gtk.main_iteration(False) + else: + progresswin.hide() + except: return + + + + +# Encapsulates references to gtk objects AND properties in main window, +# which allows to use self. and main. almost interchangably. +# +# This is a kludge to keep gtkBuilder widgets accessible; so just one +# instance has to be built. Also ties main.channels{} or .features{} +# dicts together for feature windows. Used by search, config, streamedit. +# +class AuxiliaryWindow(object): + + def __init__(self, parent): + self.main = parent + self.meta = plugin_meta(None, inspect.getcomments(inspect.getmodule(self))) + + def __getattr__(self, name): + if name in self.main.__dict__: + return self.main.__dict__[name] + elif name in self.main.__class__.__dict__: + return self.main.__class__.__dict__[name] + else: + return self.main.get_widget(name) + + + + +# Auxiliary window: about dialog +# +class AboutStreamtuner2(AuxiliaryWindow): + def __init__(self, parent): + a = gtk.AboutDialog() + a.set_version(parent.__version__) + a.set_name("streamtuner2") + a.set_license("Public Domain\n\nNo Strings Attached.\nUnrestricted distribution,\nmodification, use.") + a.set_authors(["Mario Salzer \n\nConcept based on streamtuner 0."+"99."+"99 from\nJean-Yves Lefort, of which some code remains\nin the Google stations plugin.\n\n\nMyOggRadio plugin based on cooperation\nwith Christian Ehm. "]) + a.set_website("http://milki.include-once.org/streamtuner2/") + a.connect("response", lambda a, ok: ( a.hide(), a.destroy() ) ) + a.show() +