Index: channels/configwin.py
==================================================================
--- channels/configwin.py
+++ channels/configwin.py
@@ -3,11 +3,10 @@
 # description: Allows to configure players, options, and plugins
 # version: 2.5
 # type: feature
 # category: ui
 # config: -
-#    { name: arraysample, value: "1,2", type: array, rows: "xxx,yyy", description: table }
 # priority: core
 # 
 # Configuration dialog for audio applications,
 # general settings, and plugin activaiton and
 # their options.
@@ -68,12 +67,12 @@
                     w.set_value(int(val))
                 # list
                 elif isinstance(w, gtk.ListStore) and isinstance(val, dict):
                     w.clear()
                     for k,v in val.items():
-                        w.append([k, v, True, self.app_bin_check(v)])
-                    w.append(["", "", True, gtk.STOCK_NEW])
+                        w.append([k, v, uikit.app_bin_check(v)])
+                    w.append(["", "", gtk.STOCK_NEW])
             #log.CONF("config load", prefix+key, val, type(w))
 
     # Store gtk widget valus back into conf. dict
     def save_config(self, config, prefix="config_", save=0):
         for key,val in config.items():
@@ -97,31 +96,10 @@
                     for row in w:
                         if row[0] and row[1]:
                             config[key][row[0]] = row[1]
             log.CONF("config save", prefix+key, val)
     
-    
-    # Generic Gtk callback to update ListStore when entries get edited.
-    # (The main signal_connect() dict prepares individual lambda funcs
-    # for each ListStore column id.)
-    def list_edit(self, liststore, path, column, new_text):
-        liststore[path][column] = new_text
-        liststore[path][3] = self.app_bin_check(new_text)
-
-    # return OK or CANCEL depending on availability of app
-    def app_bin_check(self, v):
-        bin = re.findall(r"(?<![$(`%-])\b(\w+(?:-\w+)*)", v)
-        if bin:
-            bin = [find_executable(bin) for bin in bin]
-            if not None in bin:
-                return gtk.STOCK_MEDIA_PLAY
-            else:
-                return gtk.STOCK_CANCEL
-        else:
-            return gtk.STOCK_NEW
-        
-
 
     # iterate over channel and feature plugins
     def add_plugins(self):
         ls = all_plugin_meta()
         for name,meta in sorted(ls.items(), key=lambda e: e[1]["type"]+e[1]["title"].lower(), reverse=False):
@@ -177,13 +155,14 @@
                     cb.set_adjustment(adj)
                     cb.set_digits(0)
 
             # ListView
             elif opt["type"] in ("list", "table", "array", "dict"):
-                cb, ls = uikit.config_treeview(opt)
+                cb, ls = uikit.config_treeview(opt, opt.get("columns", "Key,Value").split(","))
                 add_("cfgui_tv", cb, "", None)
                 self.widgets["config_" + opt["name"]] = ls
+                add_({}, uikit.label("<small>%s</small>" % description, markup=True, size=455))
                 continue
 
             # text field
             else:
                 cb = gtk.Entry()

Index: uikit.py
==================================================================
--- uikit.py
+++ uikit.py
@@ -507,33 +507,64 @@
             w = a
         w.show_all()
         return w
     
     
-    # Config win table
+    # Config win table (editable dictionary, two columns w/ executable indicator pixbuf)
     @staticmethod
-    def config_treeview(opt):
-        w = gtk.TreeView()
+    def config_treeview(opt, columns=["Icon", "Command"]):
+        liststore = gtk.ListStore(str, str, str)
+        w = gtk.TreeView(liststore)
+        lno = len(columns)
+        # two text columns and renderers
+        for i in range(0, lno):
+            c = gtk.TreeViewColumn(columns[i])
+            c.set_resizable(True)
+            c.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+            c.set_fixed_width(150 + 75*i)
+            r = gtk.CellRendererText()
+            c.pack_end(r, expand=True)
+            r.set_property("editable", True)
+            r.connect("edited", uikit.liststore_edit, (liststore, i))
+            c.add_attribute(r, "text", i)
+            #c.add_attribute(r, "editable", 2)
+            w.append_column(c)
+        # add pixbuf holder to last column
+        if lno < 3:
+            r = gtk.CellRendererPixbuf()
+            c.pack_start(r, expand=False)
+            c.add_attribute(r, "stock_id", 2)
         w.set_property("width_request", 450)
-        w.set_property("height_request", 125)
-        # options
-        _k,_v = str(opt.get("rows", "x,y")).split(",")
-        # fill columns
-        liststore, rowmap, pix_entry = uikit.columns(
-            w,
-            [    # datamap
-                 [_k,125, [_k,str,"text",{"editable":2}] ],
-                 [_v,275, [_v,str,"text",{"editable":True}] ],
-                 [None,0, ['b',bool,None,{}] ],
-                 [None,0, ['i',str,None,{}] ]
-            ],
-            [{}]
-        )
-        for i,tvc in enumerate(w.get_children()):
-          for tvcr in tvc.get_children():
-            tvcr.connect("edited", lambda *x: log.EDIT(x))
+        w.set_property("height_request", 115)
         return w, liststore
+        
+    # Generic Gtk callback to update ListStore when entries get edited.
+    # where user_data = (liststore, column #id)
+    @staticmethod
+    def liststore_edit(cell, row, text, user_data):
+        #log.EDIT(cell, row, text, user_data)
+        liststore, column = user_data
+        liststore[row][column] = text
+        # update executable-indicator pixbuf
+        if column == 1 and len(liststore) == 3 and liststore[row][2].startswith("gtk."):
+            liststore[row][2] = uikit.app_bin_check(text)
+        # add new row if editing last
+        if row == len(liststore) -1:
+            liststore.append(*["" for c in liststore[column]])
+
+    # return OK or CANCEL depending on availability of app
+    @staticmethod
+    def app_bin_check(v):
+        bin = re.findall(r"(?<![$(`%-;/$])\b(\w+(?:-\w+)*)", v)
+        if bin:
+            bin = [find_executable(bin) for bin in bin]
+            if not None in bin:
+                return gtk.STOCK_MEDIA_PLAY
+            else:
+                return gtk.STOCK_CANCEL
+        else:
+            return gtk.STOCK_NEW
 
 
     # Attach textual menu entry and callback
     @staticmethod
     def add_menu(menuwidget, label, action, insert=None):