From a41d447c6f897c82b94bc6a962d57dc2652a2058 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Sun, 25 Jul 2010 20:33:15 -0400 Subject: [PATCH] Major rework of gui.panel.playlist. I'm trying to get as much of the Playlist handling out into this submod as possible, which should make for a cleaner gui/__init__.py. Still, some ways to go, and the current situation is untested. --- hooke/ui/gui/__init__.py | 197 ++++++-------------- hooke/ui/gui/panel/playlist.py | 330 +++++++++++++++++++++++++++------ 2 files changed, 328 insertions(+), 199 deletions(-) diff --git a/hooke/ui/gui/__init__.py b/hooke/ui/gui/__init__.py index e4c966b..c70a0c7 100644 --- a/hooke/ui/gui/__init__.py +++ b/hooke/ui/gui/__init__.py @@ -226,7 +226,13 @@ class HookeFrame (wx.Frame): style=wx.DIRCTRL_SHOW_FILTERS, filter=self.gui.config['folders-filters'], defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'), #HACK: config should convert - ('playlists', panel.playlist.Playlist(self), 'left'), + ('playlists', panel.playlist.Playlist( + config=self.gui.config, + callbacks={}, + parent=self, + style=wx.WANTS_CHARS|wx.NO_BORDER, + # WANTS_CHARS so the panel doesn't eat the Return key. + size=(160, 200)), 'left'), ('note', panel.note.Note(self), 'left'), ('notebook', Notebook( parent=self, @@ -307,9 +313,8 @@ class HookeFrame (wx.Frame): treeCtrl = self._c['folders'].GetTreeCtrl() treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self._on_dir_ctrl_left_double_click) + # TODO: playlist callbacks return # TODO: cleanup - self._c['playlists'].PlaylistsTree.Bind(wx.EVT_LEFT_DOWN, self.OnPlaylistsLeftDown) - self._c['playlists'].PlaylistsTree.Bind(wx.EVT_LEFT_DCLICK, self.OnPlaylistsLeftDclick) #commands tree evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self._c['commands'].ExecuteButton) evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self._c['commands']._c['tree']) @@ -323,16 +328,16 @@ class HookeFrame (wx.Frame): def _GetActiveFileIndex(self): lib.playlist.Playlist = self.GetActivePlaylist() #get the selected item from the tree - selected_item = self._c['playlists'].PlaylistsTree.GetSelection() + selected_item = self._c['playlists']._c['tree'].GetSelection() #test if a playlist or a curve was double-clicked - if self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item): + if self._c['playlists']._c['tree'].ItemHasChildren(selected_item): return -1 else: count = 0 - selected_item = self._c['playlists'].PlaylistsTree.GetPrevSibling(selected_item) + selected_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item) while selected_item.IsOk(): count += 1 - selected_item = self._c['playlists'].PlaylistsTree.GetPrevSibling(selected_item) + selected_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item) return count def _GetPlaylistTab(self, name): @@ -341,14 +346,6 @@ class HookeFrame (wx.Frame): return index return -1 - def _GetUniquePlaylistName(self, name): - playlist_name = name - count = 1 - while playlist_name in self.playlists: - playlist_name = ''.join([name, str(count)]) - count += 1 - return playlist_name - def _restore_perspective(self, name): # TODO: cleanup self.gui.config['active perspective'] = name # TODO: push to engine's Hooke @@ -365,12 +362,6 @@ class HookeFrame (wx.Frame): perspectivesFile.write(perspective) perspectivesFile.close() - def AddPlaylist(self, playlist=None, name='Untitled'): - if playlist and playlist.count > 0: - playlist.name = self._GetUniquePlaylistName(name) - playlist.reset() - self.AddToPlaylists(playlist) - def AddPlaylistFromFiles(self, files=[], name='Untitled'): if files: playlist = lib.playlist.Playlist(self, self.drivers) @@ -381,34 +372,6 @@ class HookeFrame (wx.Frame): playlist.reset() self.AddTayliss(playlist) - def AddToPlaylists(self, playlist): - if playlist.count > 0: - #setup the playlist in the Playlist tree - tree_root = self._c['playlists'].PlaylistsTree.GetRootItem() - playlist_root = self._c['playlists'].PlaylistsTree.AppendItem(tree_root, playlist.name, 0) - #add all files to the Playlist tree -# files = {} - hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension') - for index, file_to_add in enumerate(playlist.files): - #optionally remove the extension from the name of the curve - if hide_curve_extension: - file_to_add.name = lh.remove_extension(file_to_add.name) - file_ID = self._c['playlists'].PlaylistsTree.AppendItem(playlist_root, file_to_add.name, 1) - if index == playlist.index: - self._c['playlists'].PlaylistsTree.SelectItem(file_ID) - playlist.reset() - #create the plot tab and add playlist to the dictionary - plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists)) - notebook_tab = self._c['notebook'].AddPage(plotPanel, playlist.name, True) - #tab_index = self._c['notebook'].GetSelection() - playlist.figure = plotPanel.get_figure() - self.playlists[playlist.name] = playlist - #self.playlists[playlist.name] = [playlist, figure] - self._c['playlists'].PlaylistsTree.Expand(playlist_root) - self.statusbar.SetStatusText(playlist.get_status_string(), 0) - self.UpdateNote() - self.UpdatePlot() - def AppendToOutput(self, text): self.panelOutput.AppendText(''.join([text, '\n'])) @@ -430,20 +393,6 @@ class HookeFrame (wx.Frame): manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file) return manipulated_plot - def DeleteFromPlaylists(self, name): - if name in self.playlists: - del self.playlists[name] - tree_root = self._c['playlists'].PlaylistsTree.GetRootItem() - item, cookie = self._c['playlists'].PlaylistsTree.GetFirstChild(tree_root) - while item.IsOk(): - playlist_name = self._c['playlists'].PlaylistsTree.GetItemText(item) - if playlist_name == name: - try: - self._c['playlists'].PlaylistsTree.Delete(item) - except: - pass - item = self._c['playlists'].PlaylistsTree.GetNextSibling(item) - def GetActiveFigure(self): playlist_name = self.GetActivePlaylistName() figure = self.playlists[playlist_name].figure @@ -457,24 +406,6 @@ class HookeFrame (wx.Frame): return playlist.get_active_file() return None - def GetActivePlaylist(self): - playlist_name = self.GetActivePlaylistName() - if playlist_name in self.playlists: - return self.playlists[playlist_name] - return None - - def GetActivePlaylistName(self): - #get the selected item from the tree - selected_item = self._c['playlists'].PlaylistsTree.GetSelection() - #test if a playlist or a curve was double-clicked - if self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item): - playlist_item = selected_item - else: - #get the name of the playlist - playlist_item = self._c['playlists'].PlaylistsTree.GetItemParent(selected_item) - #now we have a playlist - return self._c['playlists'].PlaylistsTree.GetItemText(playlist_item) - def GetActivePlot(self): playlist = self.GetActivePlaylist() if playlist is not None: @@ -707,20 +638,20 @@ class HookeFrame (wx.Frame): ----- Syntax: next, n ''' - selected_item = self._c['playlists'].PlaylistsTree.GetSelection() - if self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item): + selected_item = self._c['playlists']._c['tree'].GetSelection() + if self._c['playlists']._c['tree'].ItemHasChildren(selected_item): #GetFirstChild returns a tuple #we only need the first element - next_item = self._c['playlists'].PlaylistsTree.GetFirstChild(selected_item)[0] + next_item = self._c['playlists']._c['tree'].GetFirstChild(selected_item)[0] else: - next_item = self._c['playlists'].PlaylistsTree.GetNextSibling(selected_item) + next_item = self._c['playlists']._c['tree'].GetNextSibling(selected_item) if not next_item.IsOk(): - parent_item = self._c['playlists'].PlaylistsTree.GetItemParent(selected_item) + parent_item = self._c['playlists']._c['tree'].GetItemParent(selected_item) #GetFirstChild returns a tuple #we only need the first element - next_item = self._c['playlists'].PlaylistsTree.GetFirstChild(parent_item)[0] - self._c['playlists'].PlaylistsTree.SelectItem(next_item, True) - if not self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item): + next_item = self._c['playlists']._c['tree'].GetFirstChild(parent_item)[0] + self._c['playlists']._c['tree'].SelectItem(next_item, True) + if not self._c['playlists']._c['tree'].ItemHasChildren(selected_item): playlist = self.GetActivePlaylist() if playlist.count > 1: playlist.next() @@ -736,48 +667,6 @@ class HookeFrame (wx.Frame): def OnPaneClose(self, event): event.Skip() - def OnPlaylistsLeftDclick(self, event): - if self._c['playlists'].PlaylistsTree.Count > 0: - playlist_name = self.GetActivePlaylistName() - #if that playlist already exists - #we check if it is the active playlist (ie selected in panelPlaylists) - #and switch to it if necessary - if playlist_name in self.playlists: - index = self._c['notebook'].GetSelection() - current_playlist = self._c['notebook'].GetPageText(index) - if current_playlist != playlist_name: - index = self._GetPlaylistTab(playlist_name) - self._c['notebook'].SetSelection(index) - #if a curve was double-clicked - item = self._c['playlists'].PlaylistsTree.GetSelection() - if not self._c['playlists'].PlaylistsTree.ItemHasChildren(item): - index = self._GetActiveFileIndex() - else: - index = 0 - if index >= 0: - playlist = self.GetActivePlaylist() - playlist.index = index - self.statusbar.SetStatusText(playlist.get_status_string(), 0) - self.UpdateNote() - self.UpdatePlot() - #if you uncomment the following line, the tree will collapse/expand as well - #event.Skip() - - def OnPlaylistsLeftDown(self, event): - hit_item, hit_flags = self._c['playlists'].PlaylistsTree.HitTest(event.GetPosition()) - if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: - self._c['playlists'].PlaylistsTree.SelectItem(hit_item) - playlist_name = self.GetActivePlaylistName() - playlist = self.GetActivePlaylist() - #if a curve was clicked - item = self._c['playlists'].PlaylistsTree.GetSelection() - if not self._c['playlists'].PlaylistsTree.ItemHasChildren(item): - index = self._GetActiveFileIndex() - if index >= 0: - playlist.index = index - self.playlists[playlist_name] = playlist - event.Skip() - def _on_previous(self, event): ''' PREVIOUS @@ -789,15 +678,15 @@ class HookeFrame (wx.Frame): #playlist = self.playlists[self.GetActivePlaylistName()][0] #select the previous curve and tell the user if we wrapped around #self.AppendToOutput(playlist.previous()) - selected_item = self._c['playlists'].PlaylistsTree.GetSelection() - if self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item): - previous_item = self._c['playlists'].PlaylistsTree.GetLastChild(selected_item) + selected_item = self._c['playlists']._c['tree'].GetSelection() + if self._c['playlists']._c['tree'].ItemHasChildren(selected_item): + previous_item = self._c['playlists']._c['tree'].GetLastChild(selected_item) else: - previous_item = self._c['playlists'].PlaylistsTree.GetPrevSibling(selected_item) + previous_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item) if not previous_item.IsOk(): - parent_item = self._c['playlists'].PlaylistsTree.GetItemParent(selected_item) - previous_item = self._c['playlists'].PlaylistsTree.GetLastChild(parent_item) - self._c['playlists'].PlaylistsTree.SelectItem(previous_item, True) + parent_item = self._c['playlists']._c['tree'].GetItemParent(selected_item) + previous_item = self._c['playlists']._c['tree'].GetLastChild(parent_item) + self._c['playlists']._c['tree'].SelectItem(previous_item, True) playlist = self.GetActivePlaylist() if playlist.count > 1: playlist.previous() @@ -1123,7 +1012,36 @@ class HookeFrame (wx.Frame): #refresh the plot figure.canvas.draw() + def _on_curve_select(self, playlist, curve): + #create the plot tab and add playlist to the dictionary + plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists)) + notebook_tab = self._c['notebook'].AddPage(plotPanel, playlist.name, True) + #tab_index = self._c['notebook'].GetSelection() + playlist.figure = plotPanel.get_figure() + self.playlists[playlist.name] = playlist + #self.playlists[playlist.name] = [playlist, figure] + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdateNote() + self.UpdatePlot() + + def _on_playlist_left_doubleclick(self): + index = self._c['notebook'].GetSelection() + current_playlist = self._c['notebook'].GetPageText(index) + if current_playlist != playlist_name: + index = self._GetPlaylistTab(playlist_name) + self._c['notebook'].SetSelection(index) + self.statusbar.SetStatusText(playlist.get_status_string(), 0) + self.UpdateNote() + self.UpdatePlot() + + def _on_playlist_delete(self, playlist): + notebook = self.Parent.plotNotebook + index = self.Parent._GetPlaylistTab(playlist.name) + notebook.SetSelection(index) + notebook.DeletePage(notebook.GetSelection()) + self.Parent.DeleteFromPlaylists(playlist_name) + class HookeApp (wx.App): def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs): @@ -1216,6 +1134,9 @@ class GUI (UserInterface): Setting(section=self.setting_section, option='perspective path', value=os.path.join('resources', 'gui', 'perspective'), help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH. + Setting(section=self.setting_section, option='hide extensions', + value=False, + help='Hide file extensions when displaying names.'), Setting(section=self.setting_section, option='folders-workdir', value='.', help='This should probably go...'), diff --git a/hooke/ui/gui/panel/playlist.py b/hooke/ui/gui/panel/playlist.py index 022950d..2982744 100644 --- a/hooke/ui/gui/panel/playlist.py +++ b/hooke/ui/gui/panel/playlist.py @@ -1,78 +1,286 @@ # Copyright """Playlist panel for Hooke. + +Provides a nice GUI interface to the +:class:`~hooke.plugin.playlist.PlaylistPlugin`. """ +import types + import wx -class Playlist(wx.Panel): +from ....util.callback import callback, in_callback - def __init__(self, parent): - # Use the WANTS_CHARS style so the panel doesn't eat the Return key. - wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS|wx.NO_BORDER, size=(160, 200)) - self.PlaylistsTree = wx.TreeCtrl(self, -1, wx.Point(0, 0), wx.Size(160, 250), wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT) - imglist = wx.ImageList(16, 16, True, 2) - imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16))) - imglist.Add(wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16))) - self.PlaylistsTree.AssignImageList(imglist) - self.PlaylistsTree.AddRoot('Playlists', 0) - self.PlaylistsTree.Bind(wx.EVT_RIGHT_DOWN , self.OnContextMenu) +class Menu (wx.Menu): + """Popup menu for selecting playlist :class:`Tree` actions. + """ + def __init__(self, on_delete, *args, **kwargs): + super(Menu, self).__init__(*args, **kwargs) + self._c = { + 'delete': self.Append(id=wx.ID_ANY, text='Delete'), + } + self.Bind(wx.EVT_MENU, on_delete) - self.Playlists = {} - sizer = wx.BoxSizer(wx.VERTICAL) - sizer.Add(self.PlaylistsTree, 1, wx.EXPAND) - self.SetSizer(sizer) - sizer.Fit(self) +class Tree (wx.TreeCtrl): + """:class:`wx.TreeCtrl` subclass handling playlist and curve selection. + """ + def __init__(self, config, callbacks, *args, **kwargs): + super(Tree, self).__init__(*args, **kwargs) + imglist = wx.ImageList(width=16, height=16, mask=True, initialCount=2) + imglist.Add(wx.ArtProvider.GetBitmap( + wx.ART_FOLDER, wx.ART_OTHER, wx.Size(16, 16))) + imglist.Add(wx.ArtProvider.GetBitmap( + wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16))) + self.AssignImageList(imglist) + self.image = { + 'root': 0, + 'playlist': 0, + 'curve': 1, + } + self._c = { + 'menu': Menu(self._on_delete), + 'root': self.AddRoot('Playlists', self.image['root']) + } + self.Bind(wx.EVT_RIGHT_DOWN, self._on_context_menu) + self.Bind(wx.EVT_TREE_SEL_CHANGED, self._on_curve_select) + self.Bind(wx.EVT_LEFT_DOWN, self._on_left_down) + self.Bind(wx.EVT_LEFT_DCLICK, self._on_left_doubleclick) + + self.config = config + self._callbacks = callbacks + self._setup_playlists() + + def _setup_playlists(self): + self._playlists = {} # {name: hooke.playlist.Playlist()} + + # In both of the following dicts, curve names are + # (playlist.name, curve.name) to avoid cross-playlist + # collisions. See ._is_curve(). + self._id_for_name = {} # {name: id} + self._name_for_id = {} # {id: name} + + def _name(self, name): + """Cleanup names according to configured preferences. + """ + if self.config['hide extensions'] == 'True': # HACK: config should decode + name,ext = os.path.splitext(name) + return name + + def _is_curve(self, name): # name from ._id_for_name / ._name_for_id + """Return `True` if `name` corresponds to a :class:`hooke.curve.Curve`. + """ + # Playlist names are strings, Curve names are tuples. + # See ._setup_playlists(). + return not isinstance(name, types.StringTypes) - def OnContextMenu(self, event): - hit_item, hit_flags = self.PlaylistsTree.HitTest(event.GetPosition()) + def _on_curve_select(self, event): + """Act on playlist/curve selection. + + Currently just a hook for a potential callback. + """ + _id = self.GetSelection() + name = self._name_for_id(_id) + if self._is_curve(name): + playlist = self._playlists[name[0]] + curve = playlist.current() + in_callback(self, playlist, curve) + + def _on_left_down(self, event): + """Select the clicked-on curve/playlist. + """ # TODO: dup with _on_curve_select? + hit_id, hit_flags = self.HitTest(event.GetPosition()) + if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: + name = self._name_for_id[hit_id] + if self._is_curve(name): + self.set_selected_curve(name[0], name[1]) + else: + self.set_selected_playlist(name) + event.Skip() + + def _on_left_doubleclick(self, event): + playlist.index = index + event.Skip() + + def _on_context_menu(self, event): + """Launch a popup :class:`Menu` with per-playlist/curve activities. + """ + hit_id,hit_flags = self.HitTest(event.GetPosition()) if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: - self.PlaylistsTree.SelectItem(hit_item) - # only do this part the first time so the events are only bound once - # Yet another alternate way to do IDs. Some prefer them up top to - # avoid clutter, some prefer them close to the object of interest - # for clarity. - if not hasattr(self, 'ID_popupAdd'): - #self.ID_popupAdd = wx.NewId() - self.ID_popupDelete = wx.NewId() - #self.Bind(wx.EVT_MENU, self.OnPopupAdd, id=self.ID_popupAdd) - self.Bind(wx.EVT_MENU, self.OnPopupDelete, id=self.ID_popupDelete) - # make a menu - menu = wx.Menu() - #items = [['Add', self.ID_popupAdd] , ['Delete', self.ID_popupDelete]] - items = [['Delete', self.ID_popupDelete]] - for item in items: - menu.Append(item[1], item[0]) - # Popup the menu. If an item is selected then its handler - # will be called before PopupMenu returns. - self.PopupMenu(menu) + self._hit_id = hit_id # store for the callbacks + self.PopupMenu( + Menu(self._on_delete), event.GetPoint()) menu.Destroy() - def OnPopupAdd(self, event): - pass + def _on_delete(self, event): + """Handler for :class:`Menu`'s `Delete` button. - def OnPopupDelete(self, event): - item = self.PlaylistsTree.GetSelection() - playlist = self.Parent.GetActivePlaylist() - if self.PlaylistsTree.ItemHasChildren(item): - playlist_name = self.PlaylistsTree.GetItemText(item) - notebook = self.Parent.plotNotebook - index = self.Parent._GetPlaylistTab(playlist_name) - notebook.SetSelection(index) - notebook.DeletePage(notebook.GetSelection()) - self.Parent.DeleteFromPlaylists(playlist_name) + Determines the clicked item and calls the appropriate + `.delete_*()` method on it. + """ + if hasattr(self, '_hit_id'): # called via ._c['menu'] + _id = self._hit_id + del(self._hit_id) + name = self._name_for_id[_id] + if self._is_curve(name): + self.delete_curve(playlist_name=name[0], name=name[1]) else: - if playlist is not None: - if playlist.count == 1: - notebook = self.Parent.plotNotebook - index = self.Parent._GetPlaylistTab(playlist.name) - notebook.SetSelection(index) - notebook.DeletePage(notebook.GetSelection()) - self.Parent.DeleteFromPlaylists(playlist.name) - else: - file_name = self.PlaylistsTree.GetItemText(item) - playlist.delete_file(file_name) - self.PlaylistsTree.Delete(item) - self.Parent.UpdatePlaylistsTreeSelection() + self.delete_playlist(name) + + def add_playlist(self, playlist): + """Add a :class:`hooke.playlist.Playlist` to the tree. + """ + if playlist.name not in self._playlists: + pass + else: + raise ValueError('duplicate playlist: %s' % playlist.name) + self._playlists[playlist.name] = playlist + p_id = self.AppendItem( + parent=self._c['root'], + text=self._name(playlist.name), + image=self.image['playlist']) + self._id_for_name[playlist.name] = p_id + + # temporarily disable any add_curve callbacks + acc = self._callbacks.get('add_curve', None) + self._callbacks['add_curve'] = None + + for curve in playlist: + self.add_curve(playlist.name, curve) + + # restore the add_curve callback + self._callbacks['add_curve'] = acc + + in_callback(self, playlist) + + def add_curve(self, playlist_name, curve): + """Add a :class:`hooke.curve.Curve` to a curently loaded playlist. + """ + p = self._playlists[playlist_name] + p.append(curve) + c_id = AppendItem( + parent=self._id_for_name[playlist_name], + text=self._name(curve.name), + image=self.image['curve']) + self._id_for_name[(playlist.name, curve.name)] = c_id + in_callback(self, p, curve) + + def delete_playlist(self, name): + """Delete a :class:`hooke.playlist.Playlist` by name. + """ + _id = self._id_for_name.pop(name) + self.Delete(_id) + playlist = self._playlists.pop(name) + del(self._name_for_id[_id]) + for curve in playlist: + _id = self._id_for_name.pop((name, curve.name)) + del(self._name_for_id[_id]) + in_callback(self, playlist) + + def delete_curve(self, playlist_name, name): + """Delete a :class:`hooke.curve.Curve` by name. + """ + if playlist is not None: + if playlist.count == 1: + notebook = self.Parent.plotNotebook + index = self.Parent._GetPlaylistTab(playlist.name) + notebook.SetSelection(index) + notebook.DeletePage(notebook.GetSelection()) + self.Parent.DeleteFromPlaylists(playlist.name) + else: + file_name = self.GetItemText(item) + playlist.delete_file(file_name) + self.Delete(item) + self.Parent.UpdatePlaylistsTreeSelection() + in_callback(self, playlist, curve) + + def get_selected_playlist(self): + """Return the selected :class:`hooke.playlist.Playlist`. + """ + _id = self.GetSelection() + name = self._name_for_id(_id) + if self._is_curve(name): + name = name[0] + return self._playlists[name] + + def get_selected_curve(self): + """Return the selected :class:`hooke.curve.Curve`. + """ + _id = self.GetSelection() + name = self._name_for_id(_id) + if self._is_curve(name): + p_name,c_name = name + playlist = self._playlists[p_name] + index = [i for i,c in enumerate(playlist) if c.name == c_name] + playlist.jump(index) + else: + playlist = self._playlists[name] + return playlist.current() + + def set_selected_playlist(self, name): + """Set the selected :class:`hooke.playlist.Playlist` by name. + """ + playlist = self._playlists[name] + curve = playlist.current() + self.set_selected_curve(playlist.name, curve.name) + + def set_selected_curve(self, playlist_name, name): + """Set the selected :class:`hooke.curve.Curve` by name. + """ + playlist = self._playlists[playlist.name] + for i,curve in enumerate(playlist): + if curve.name == name: + playlist.jump(i) + break + curve = playlist.current() + _id = self._id_for_name[(playlist.name, curve.name)] + self.Expand(self._id_for_name[playlist.name]) + self.SelectItem(_id) + in_callback(self, playlist, curve) # TODO: dup callback with _on_curve_select + + @callback + def generate_new_playlist(self): + pass + + def _GetUniquePlaylistName(self, name): + playlist_name = name + count = 1 + while playlist_name in self.playlists: + playlist_name = ''.join([name, str(count)]) + count += 1 + return playlist_name + + +class Playlist (wx.Panel): + """:class:`wx.Panel` subclass wrapper for :class:`Tree`. + """ + def __init__(self, config, callbacks, *args, **kwargs): + # Use the WANTS_CHARS style so the panel doesn't eat the Return key. + super(Playlist, self).__init__(*args, **kwargs) + self.name = 'playlist panel' + + self._c = { + 'tree': Tree( + config=config, + callbacks=callbacks, + parent=self, + size=wx.Size(160, 250), + style=wx.TR_DEFAULT_STYLE | wx.NO_BORDER | wx.TR_HIDE_ROOT), + } + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self._c['tree'], 1, wx.EXPAND) + self.SetSizer(sizer) + sizer.Fit(self) + + # Expose all Tree's public curve/playlist methods directly. + for attribute_name in dir(self._c['tree']): + if (attribute_name.startswith('_') + or 'playlist' not in attribute_name + or 'curve' not in attribute_name): + continue # not an attribute we're interested in + attr = getattr(self._c['tree'], attribute_name) + if hasattr(attr, '__call__'): # attr is a function / method + setattr(self, attribute_name, attr) # expose it -- 2.26.2