This development deserves a new version number!
[pylooper.git] / loopertreeview.py
blob5ee6b170df76fd53386d9ce8509c7d3e515abcdd
1 from __future__ import with_statement
3 try:
4 import gtk
5 import gtk.glade
6 except:
7 raise ImportError('You need gtk and glade')
8 try:
9 import gobject
10 except:
11 raise ImportError('You need gobject')
12 try:
13 from xml.dom.minidom import parseString, parse
14 except:
15 raise ImportError('You need python-xml')
17 import os.path
19 class XMLLoop(object):
20 """An abstraction layer for all the XML crap necessary for these loops."""
22 def __init__(self, file=None):
23 if file and os.path.exists(file):
24 self.doc = parse(file)
25 else:
26 self.doc = parseString(u'<!DOCTYPE loopobj SYSTEM "loopobj.dtd"><loopobj />'.encode('UTF-8'))
28 self.toxml = self.doc.toxml
29 self.toprettyxml = self.doc.toprettyxml
31 def _get_filepath(self):
32 return getCBTNText(self.doc, u'filepath')
34 def _set_filepath(self, filepath):
35 if len(self.doc.getElementsByTagName(u'filepath')) > 0:
36 getChildByTagName(self.doc, u'filepath').firstChild.data = filepath
37 else:
38 filepath_node = self.doc.createElement(u'filepath')
39 filepath_node.appendChild(self.doc.createTextNode(filepath))
40 self.doc.documentElement.appendChild(filepath_node)
42 filepath = property(_get_filepath, _set_filepath)
44 def get_intervals(self):
45 """Returns a list of intervals, where each interval is a list of four elements:
46 - name
47 - start point
48 - end point
49 - whether this is the selected interval or not
51 For example, get_intervals might return the following:
52 [u'loop 1', 0, 150, False]"""
54 return [[getCBTNText(interval, tag)
55 for tag in [u'name', u'start', u'end']
57 for interval in self.doc.getElementsByTagName(u'interval')]
59 def add_interval(self, interval):
60 """Adds the requested interval, which must be of the same form get_intervals returns"""
62 interval_node = self.doc.createElement(u'interval')
64 name = self.doc.createElement(u'name')
65 name.appendChild(self.doc.createTextNode(interval[0]))
66 interval_node.appendChild(name)
68 start = self.doc.createElement(u'start')
69 start.appendChild(self.doc.createTextNode(str(interval[1])))
70 interval_node.appendChild(start)
72 end = self.doc.createElement(u'end')
73 end.appendChild(self.doc.createTextNode(str(interval[2])))
74 interval_node.appendChild(end)
76 loopobj = self.doc.documentElement
77 loopobj.appendChild(interval_node)
79 # XXX These are convenience methods that do not make sure you aren't stupid.
80 # Perform this check yourself before using them.
81 def getChildByTagName(node, tag):
82 return node.getElementsByTagName(tag)[0]
83 def getText(node):
84 return node.firstChild.data
85 def getCBTNText(node, tag):
86 return getText(getChildByTagName(node, tag))
88 class LooperTreeView(object):
89 def __init__(self, wTree, pylooper=None):
90 self.pylooper = pylooper
92 self.treeview = wTree.get_widget('treeview')
93 self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT64, gobject.TYPE_INT64)
94 self.treeview.set_model(self.liststore)
96 cell0 = gtk.CellRendererText()
97 cell1 = gtk.CellRendererText()
98 cell2 = gtk.CellRendererText()
99 self.treeview.append_column(gtk.TreeViewColumn('Name', cell0, text=0, editable=True))
100 self.treeview.append_column(gtk.TreeViewColumn('Begin', cell1))
101 self.treeview.append_column(gtk.TreeViewColumn('End', cell2))
103 cell0.connect('edited', self.name_edited)
105 self.treeview.get_column(1).set_cell_data_func(cell1, nsecs2str, 1)
106 self.treeview.get_column(2).set_cell_data_func(cell2, nsecs2str, 2)
108 self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
109 self.treeview.get_selection().connect('changed', self.changed)
111 def add(self, begin, end, name='new_interval'):
112 iter = self.liststore.append([name, long(begin), long(end)])
113 if iter:
114 self.treeview.get_selection().select_iter(iter)
116 def changed(self, treeselection):
117 model, iter = treeselection.get_selected()
118 if iter:
119 self.pylooper.update_interval(model.get_value(iter, 1), model.get_value(iter, 2))
120 return True
121 else:
122 return False
124 def name_edited(self, cellrenderertext, path, new_text):
125 try:
126 self.liststore.set(self.liststore.get_iter_from_string(path), 0, new_text)
127 return True
128 except:
129 return False
131 def update_interval(self, begin, end):
132 if self.treeview.get_selection().get_selected()[1]:
133 self.liststore.set(self.treeview.get_selection().get_selected()[1], 1, begin, 2, end)
134 return True
135 else:
136 return False
138 def clear(self):
139 self.liststore.clear()
141 def save(self, filepath):
142 xlp = XMLLoop()
143 xlp.filepath = self.pylooper.player.status.filepath
145 def callback_add(model, path, iter):
146 xlp.add_interval([model.get_value(iter, 0), model.get_value(iter, 1), model.get_value(iter, 2)])
147 return False
149 self.liststore.foreach(callback_add)
151 with open(filepath, 'w') as file:
152 file.write(xlp.toxml())
153 return True
155 def open(self, filepath):
156 try:
157 xlp = XMLLoop(filepath)
158 except:
159 return False
161 oldpath = self.pylooper.player.status.filepath
162 self.pylooper.player.status.filepath = xlp.filepath
164 # We need to do this, or when we call self.add, the changed event will
165 # fail with duration = 0
166 if not self.pylooper.player.load():
167 self.pylooper.player.status.filepath = oldpath
168 return False
170 self.clear()
172 for interval in xlp.get_intervals():
173 self.add(interval[1], interval[2], interval[0])
175 def delete(self, widget):
176 treeselection = self.treeview.get_selection()
177 model, iter = treeselection.get_selected()
178 if iter and model.remove(iter):
179 treeselection.select_iter(iter)
181 def nsecs2str(treeviewcolumn, cellrenderer, model, iter, column):
182 nsecs = model.get_value(iter, column)
183 cellrenderer.set_property('text', "%(m)02d:%(s)02d.%(ms)03d" % {
184 'm': nsecs / 60000000000,
185 's': (nsecs / 1000000000) % 60,
186 'ms': (nsecs / 1000000) % 1000})