class Tree (wx.TreeCtrl):\r
""":class:`wx.TreeCtrl` subclass handling playlist and curve selection.\r
"""\r
- def __init__(self, config, callbacks, *args, **kwargs):\r
+ def __init__(self, *args, **kwargs):\r
+ self._panel = kwargs['parent']\r
+ self._callbacks = self._panel._callbacks # TODO: CallbackClass.set_callback{,s}()\r
super(Tree, self).__init__(*args, **kwargs)\r
imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2)\r
imglist.Add(wx.ArtProvider.GetBitmap(\r
'root': self.AddRoot(text='Playlists', image=self.image['root'])\r
}\r
self.Bind(wx.EVT_RIGHT_DOWN, self._on_context_menu)\r
- self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_curve_select)\r
- self.Bind(wx.EVT_LEFT_DOWN, self._on_left_down)\r
- self.Bind(wx.EVT_LEFT_DCLICK, self._on_left_doubleclick)\r
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)\r
\r
- self.config = config\r
- self._callbacks = callbacks\r
self._setup_playlists()\r
\r
def _setup_playlists(self):\r
self._id_for_name = {} # {name: id}\r
self._name_for_id = {} # {id: name}\r
\r
- def _name(self, name):\r
- """Cleanup names according to configured preferences.\r
- """\r
- if self.config['hide extensions'] == 'True': # HACK: config should decode\r
- name,ext = os.path.splitext(name)\r
- return name\r
-\r
def _is_curve(self, name): # name from ._id_for_name / ._name_for_id\r
"""Return `True` if `name` corresponds to a :class:`hooke.curve.Curve`.\r
"""\r
return c_id\r
raise KeyError(_id)\r
\r
- def _on_curve_select(self, event):\r
- """Act on playlist/curve selection.\r
\r
- Currently just a hook for a potential callback.\r
- """\r
- _id = self.GetSelection()\r
- name = self._name_for_id[self._canonical_id(_id)]\r
- if self._is_curve(name):\r
- playlist = self._playlists[name[0]]\r
- curve = playlist.current()\r
- in_callback(self, playlist, curve)\r
-\r
- def _on_left_down(self, event):\r
- """Select the clicked-on curve/playlist.\r
- """ # TODO: dup with _on_curve_select?\r
- hit_id, hit_flags = self.HitTest(event.GetPosition())\r
- if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:\r
- name = self._name_for_id[self._canonical_id(hit_id)]\r
- if self._is_curve(name):\r
- self.set_selected_curve(name[0], name[1])\r
- else:\r
- self.set_selected_playlist(name)\r
- event.Skip()\r
-\r
- def _on_left_doubleclick(self, event):\r
- pass\r
- #playlist.index = index\r
- #event.Skip()\r
+ # Context menu\r
\r
def _on_context_menu(self, event):\r
"""Launch a popup :class:`Menu` with per-playlist/curve activities.\r
self.PopupMenu(menu, event.GetPosition())\r
menu.Destroy()\r
\r
- def _on_delete(self, event):\r
- """Handler for :class:`Menu`'s `Delete` button.\r
-\r
- Determines the clicked item and calls the appropriate\r
- `.delete_*()` method on it.\r
- """\r
- #if hasattr(self, '_hit_id'): # called via ._c['menu']\r
- _id = self._hit_id\r
- del(self._hit_id)\r
- name = self._name_for_id[_id]\r
- if self._is_curve(name):\r
- self.delete_curve(playlist_name=name[0], name=name[1])\r
- else:\r
- self.delete_playlist(name)\r
+ # Add\r
+ # add_* called directly by HookeFrame\r
+ # _add_* called on every addition\r
\r
def add_playlist(self, playlist):\r
"""Add a :class:`hooke.playlist.Playlist` to the tree.\r
+\r
+ Calls :meth:`_add_playlist` and triggers a callback.\r
+ """\r
+ self._add_playlist(playlist)\r
+ in_callback(self, playlist)\r
+\r
+ def _add_playlist(self, playlist):\r
+ """Add a class:`hooke.playlist.Playlist` to the tree.\r
+\r
+ No callback triggered.\r
"""\r
if playlist.name not in self._playlists:\r
pass\r
self._playlists[playlist.name] = playlist\r
p_id = self.AppendItem(\r
parent=self._c['root'],\r
- text=self._name(playlist.name),\r
+ text=self._panel._hooke_frame._file_name(playlist.name),\r
image=self.image['playlist'])\r
self._id_for_name[playlist.name] = p_id\r
self._name_for_id[p_id] = playlist.name\r
-\r
- # temporarily disable any add_curve callbacks\r
- acc = self._callbacks.get('add_curve', None)\r
- self._callbacks['add_curve'] = None\r
-\r
for curve in playlist:\r
- self.add_curve(playlist.name, curve)\r
-\r
- # restore the add_curve callback\r
- self._callbacks['add_curve'] = acc\r
-\r
- in_callback(self, playlist)\r
+ self._add_curve(playlist.name, curve)\r
\r
def add_curve(self, playlist_name, curve):\r
"""Add a :class:`hooke.curve.Curve` to a curently loaded playlist.\r
+\r
+ Calls :meth:`_add_curve` and triggers a callback.\r
+ """\r
+ self._add_curve(playlist_name, curve)\r
+ playlist = self._playlists[playlist_name]\r
+ in_callback(self, playlist, curve)\r
+\r
+ def _add_curve(self, playlist_name, curve):\r
+ """Add a class:`hooke.curve.Curve` to the tree.\r
+\r
+ No callback triggered.\r
"""\r
p = self._playlists[playlist_name]\r
if curve not in p:\r
p.append(curve)\r
c_id = self.AppendItem(\r
parent=self._id_for_name[playlist_name],\r
- text=self._name(curve.name),\r
+ text=self._panel._hooke_frame._file_name(curve.name),\r
image=self.image['curve'])\r
self._id_for_name[(p.name, curve.name)] = c_id\r
self._name_for_id[c_id] = (p.name, curve.name)\r
- in_callback(self, p, curve)\r
+\r
+ @callback\r
+ def generate_new_playlist(self):\r
+ pass # TODO\r
+\r
+ def _GetUniquePlaylistName(self, name): # TODO\r
+ playlist_name = name\r
+ count = 1\r
+ while playlist_name in self.playlists:\r
+ playlist_name = ''.join([name, str(count)])\r
+ count += 1\r
+ return playlist_name\r
+\r
+ # Delete\r
+ # delete_* called by _on_delete handler (user click) or HookeFrame\r
+ # _delete_* called on every deletion\r
+\r
+ def _on_delete(self, event):\r
+ """Handler for :class:`Menu`'s `Delete` button.\r
+\r
+ Determines the clicked item and calls the appropriate\r
+ `.delete_*()` method on it.\r
+ """\r
+ #if hasattr(self, '_hit_id'): # called via ._c['menu']\r
+ _id = self._hit_id\r
+ del(self._hit_id)\r
+ name = self._name_for_id[_id]\r
+ if self._is_curve(name):\r
+ self.delete_curve(playlist_name=name[0], name=name[1])\r
+ else:\r
+ self.delete_playlist(name)\r
\r
def delete_playlist(self, name):\r
"""Delete a :class:`hooke.playlist.Playlist` by name.\r
del(self._name_for_id[_id])\r
in_callback(self, playlist, curve)\r
\r
+ # Get selection\r
+\r
def get_selected_playlist(self):\r
"""Return the selected :class:`hooke.playlist.Playlist`.\r
"""\r
_id = self.GetSelection()\r
- name = self._name_for_id[self._canonical_id(_id)]\r
+ try:\r
+ _id = self._canonical_id(_id)\r
+ except KeyError: # no playlist selected\r
+ return None\r
+ name = self._name_for_id[_id]\r
if self._is_curve(name):\r
name = name[0]\r
return self._playlists[name]\r
if self._is_curve(name):\r
p_name,c_name = name\r
playlist = self._playlists[p_name]\r
- index = [i for i,c in enumerate(playlist) if c.name == c_name]\r
- playlist.jump(index)\r
+ c = playlist.current()\r
+ assert c.name == c_name, '%s != %s' % (c.name, c_name)\r
else:\r
playlist = self._playlists[name]\r
return playlist.current()\r
\r
- def set_selected_playlist(self, name):\r
- """Set the selected :class:`hooke.playlist.Playlist` by name.\r
- """\r
- playlist = self._playlists[name]\r
- curve = playlist.current()\r
- self.set_selected_curve(playlist.name, curve.name)\r
+ # Set selection (via user interaction with this panel)\r
+ #\r
+ # These are hooks for HookeFrame callbacks which will send\r
+ # the results back via 'get curve' calling 'set_selected_curve'.\r
\r
- def set_selected_curve(self, playlist_name, name):\r
- """Set the selected :class:`hooke.curve.Curve` by name.\r
+ def _on_select(self, event):\r
+ """Select the clicked-on curve/playlist.\r
"""\r
- playlist = self._playlists[playlist.name]\r
- for i,curve in enumerate(playlist):\r
- if curve.name == name:\r
- playlist.jump(i)\r
+ _id = self.GetSelection()\r
+ name = self._name_for_id[self._canonical_id(_id)]\r
+ if self._is_curve(name):\r
+ p_name,c_name = name\r
+ self._on_set_selected_curve(p_name, c_name)\r
+ else:\r
+ self._on_set_selected_playlist(name)\r
+\r
+ def _on_set_selected_playlist(self, name):\r
+ in_callback(self, self._playlists[name])\r
+\r
+ def _on_set_selected_curve(self, playlist_name, name):\r
+ playlist = self._playlists[playlist_name]\r
+ curve = None\r
+ for i,c in enumerate(playlist):\r
+ if c.name == name:\r
+ curve = c\r
break\r
- curve = playlist.current()\r
- _id = self._id_for_name[(playlist.name, curve.name)]\r
- self.Expand(self._id_for_name[playlist.name])\r
- self.SelectItem(_id)\r
- in_callback(self, playlist, curve) # TODO: dup callback with _on_curve_select\r
+ if curve == None:\r
+ raise ValueError(name)\r
+ in_callback(self, playlist, curve)\r
+ \r
+ # Set selection (from the HookeFrame)\r
\r
- @callback\r
- def generate_new_playlist(self):\r
- pass\r
+ def set_selected_curve(self, playlist, curve):\r
+ """Make the curve the playlist's current curve.\r
+ """\r
+ print 'expanding', playlist.name\r
+ self.Expand(self._id_for_name[playlist.name])\r
+ self.Unbind(wx.EVT_TREE_SEL_CHANGED)\r
+ print 'selecting', curve.name\r
+ self.SelectItem(self._id_for_name[(playlist.name, curve.name)])\r
+ self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_select)\r
\r
- def _GetUniquePlaylistName(self, name):\r
- playlist_name = name\r
- count = 1\r
- while playlist_name in self.playlists:\r
- playlist_name = ''.join([name, str(count)])\r
- count += 1\r
- return playlist_name\r
+ def update_playlist(self, playlist):\r
+ """Absorb changed `._index`, etc.\r
+ """\r
+ self._playlists[playlist.name] = playlist\r
\r
\r
class Playlist (Panel, wx.Panel):\r
""":class:`wx.Panel` subclass wrapper for :class:`Tree`.\r
"""\r
- def __init__(self, config, callbacks, *args, **kwargs):\r
+ def __init__(self, callbacks=None, **kwargs):\r
# Use the WANTS_CHARS style so the panel doesn't eat the Return key.\r
- super(Playlist, self).__init__(*args, **kwargs)\r
- self.name = 'playlist panel'\r
-\r
+ super(Playlist, self).__init__(\r
+ name='playlist', callbacks=callbacks, **kwargs)\r
self._c = {\r
'tree': Tree(\r
- config=config,\r
- callbacks=callbacks,\r
parent=self,\r
size=wx.Size(160, 250),\r
style=wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT),\r