diff --git a/system/dataset.py b/system/dataset.py index 7caa82f..7fbfa68 100644 --- a/system/dataset.py +++ b/system/dataset.py @@ -271,7 +271,7 @@ class GraphDataset(Dataset): return G Dataset._all_dims=set() - + class ReverseDict(dict): """ A dictionary which can lookup values by key, and keys by value. @@ -319,7 +319,25 @@ def from_file(filepath): return out_data -class Selection: +class Selection(dict): """Handles selected identifiers along each dimension of a dataset""" - def __init__(self): - self.current_selection={} + + def __init__(self, title='Unnamed Selecton'): + self.title = title + + def __getitem__(self, key): + if not self.has_key(key): + return None + return dict.__getitem__(self, key) + + def dims(self): + return self.keys() + + def axis_len(self, axis): + if self._selection.has_key(axis): + return len(self._selection[axis]) + return 0 + + def select(self, axis, labels): + self[axis] = labels + diff --git a/system/fluents.py b/system/fluents.py index 9bddfbd..6bd4e33 100755 --- a/system/fluents.py +++ b/system/fluents.py @@ -144,6 +144,9 @@ class FluentApp: self.identifier_list.show() return self.identifier_list + def __getitem__(self, key): + return self.widget_tree.get_widget(key) + # Event handlers. # These methods are called by the gtk framework in response to events and # should not be called directly. @@ -154,9 +157,6 @@ class FluentApp: def on_multiple_view(self, *ignored): self['main_view'].goto_small() - def __getitem__(self, key): - return self.widget_tree.get_widget(key) - def on_create_project(self, *rest): d = dialogs.CreateProjectDruid(self) d.run() diff --git a/system/project.py b/system/project.py index 30bac90..0a9b7c3 100644 --- a/system/project.py +++ b/system/project.py @@ -17,7 +17,8 @@ class Project: self._dataset_observers = [] self.current_data = [] self.datasets = [] - self.sel_obj = dataset.Selection() + self.sel_obj = dataset.Selection('Current Selection') + self.selections = [] def add_selection_observer(self, observer): self._selection_observers.append(observer) @@ -39,12 +40,12 @@ class Project: def set_selection(self, dim_name, selection): """Sets a current selection and notify observers""" - self.sel_obj.current_selection[dim_name] = set(selection) + self.sel_obj[dim_name] = set(selection) self.notify_selection_listeners(dim_name) def get_selection(self): """Returns the current selection object""" - return self.sel_obj.current_selection + return self.sel_obj def get_data_iter(self, obj): """Retuns an iterator to data.""" @@ -84,6 +85,8 @@ class Project: self.data_tree_insert(it, d.get_title(), d, "PaleGreen", "black") d.set_selection_listener(self.set_selection) self._selection_observers.append(d) + elif isinstance(d, dataset.Selection): + self.add_selection(d) def data_tree_insert(self, parent, text, data, bgcolour,fontcolour,selected = 0): tree = self.data_tree @@ -96,16 +99,20 @@ class Project: tree.set_value(it, 5, selected) return it - def add_dataset(self,dataset): + def add_dataset(self, dataset): """Appends a new Dataset to the project.""" logger.log('debug','Adding dataset: %s' %dataset.get_name()) self.datasets.append(dataset) for dim_name in dataset.get_all_dims(): if dim_name not in self.dim_names: self.dim_names.append(dim_name) - self.sel_obj.current_selection[dim_name] = set() + self.sel_obj[dim_name] = set() self.notify_dataset_listeners() + def add_selection(self, selection): + """Adds a new selection to the project.""" + self.selections.append(selection) + self.notify_dataset_listeners() def object_at(self, path): "Returns the object at a given path in the tree." diff --git a/system/selections.py b/system/selections.py index 5127112..cb51fdd 100644 --- a/system/selections.py +++ b/system/selections.py @@ -9,75 +9,145 @@ import gnome import gnome.ui import gobject +class SimpleMenu(gtk.Menu): + def __init__(self): + gtk.Menu.__init__(self) + + def add_simple_item(self, title, function): + item = gtk.MenuItem(title) + item.connect('activate', function) + self.append(item) + item.show() + class SelectionTree(gtk.TreeView): def __init__(self): - self.store = gtk.TreeStore(gobject.TYPE_STRING) + self.store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) gtk.TreeView.__init__(self, self.store) renderer = gtk.CellRendererText() sel_column = gtk.TreeViewColumn('Selection', renderer, text=0) self.insert_column(sel_column, 0) - self.connect('row-activated', self.on_row_activated) + self.connect('row-activated', self._on_row_activated) self._identifier_list = None self._dim_list = {} # A mapping of selection names to selection lines. - self._selections = {} + self._selections = [] self.set_headers_visible(True) self._current_dim = None + self._current_selection = None + + # Set up context menu + self._menu = SimpleMenu() + self._menu.add_simple_item('Select', self._on_set_selection) + + self.connect('popup_menu', self._on_popup_menu) + self.connect('button_press_event', self._on_button_press_event) def set_identifier_list(self, identifier_list): + """Dependency injection. Sets the widget that should be used to + display the selected identifiers.""" self._identifier_list = identifier_list def set_project(self, project): + """Dependency injection. Set the current project.""" self.project = project project.add_selection_observer(self) project.add_dataset_observer(self) self.update_dims(project.dim_names) + self.add_selection(project.get_selection()) def selection_changed(self, selection): + """Callback on selection update""" self.update_dims(selection.keys()) + self.add_selection(selection) self._update_current_dim() + def get_selection_iter(self, dimname, selection): + i = self._dim_list[dimname] + if not i: + return None + + children = self.store.iter_children(i) + if not children: + return None + + while children: + if self.store.get(children, 1)[0] == selection: + return self.store.get(children, 1)[0] + children = self.store.iter_next(children) + return None + def dataset_changed(self): - self.selection_changed(self.project.get_selection()) + """Callback when new datasets are created""" + self.selection_changed(self.project.get_selection()) + for sel in self.project.selections: + if not sel in self._selections: + self._selections.append(sel) + self.add_selection(sel) def update_dims(self, dim_list): + """Update the list of dimensions shown""" for dim in dim_list: if not self._dim_list.has_key(dim): - d = self.store.insert_after(None, None, (dim,)) + d = self.store.insert_after(None, None, (dim, None)) self._dim_list[dim] = d - self.store.insert_before(d, None, ('current selection',)) - def on_row_activated(self, treeview, path, column): + def add_selection(self, selection): + for dim in selection.dims(): + if not self.get_selection_iter(dim, selection): + d = self._dim_list[dim] + i = self.store.insert_after(d, None, (selection.title, selection)) + + def _update_current_dim(self): + if not self._current_dim or not self._current_selection: + return + id_list = self._current_selection[self._current_dim] + self._identifier_list.set_identifiers(id_list) + + # Callbacks + def _on_row_activated(self, treeview, path, column): i = self.store.get_iter(path) p = self.store.iter_parent(i) - self._current_dim = self.store.get_value(p, 0) + if p == None: + pass + else: + self._current_dim = self.store.get_value(p, 0) + self._current_selection = self.store.get_value(i, 1) self._update_current_dim() - def _update_current_dim(self): - if not self._current_dim: + def _on_set_selection(self, *rest): + if not self._current_selection: return - id_list = self.project.get_selection()[self._current_dim] - self._identifier_list.set_identifiers(id_list) + self.project.set_selection(self._current_dim, + self._current_selection[self._current_dim]) + + def _on_button_press_event(self, widget, event): + if event.button == 3: + self._menu.popup(None, None, None, event.button, event.time) + + def _on_popup_menu(self): + pass class IdentifierList(gtk.TreeView): def __init__(self): self.store = gtk.ListStore(gobject.TYPE_STRING) gtk.TreeView.__init__(self, self.store) + self.set_headers_visible(True) + # Set up identifier column to show active identifiers renderer = gtk.CellRendererText() ids_column = gtk.TreeViewColumn('Identifiers', renderer, text=0) self.insert_column(ids_column, 0) - self.set_headers_visible(True) - def set_identifiers(self, identifiers): + "Show the list of identifier strings." self.store.clear() for e in identifiers: self.store.append((e,)) + diff --git a/workflows/test_workflow.py b/workflows/test_workflow.py index 0a653d8..8204449 100644 --- a/workflows/test_workflow.py +++ b/workflows/test_workflow.py @@ -19,6 +19,7 @@ class TestWorkflow (workflow.Workflow): load.add_function(CelFileImportFunction()) load.add_function(TestDataFunction()) load.add_function(DatasetLoadFunction()) + load.add_function(SelectFunction()) self.add_stage(load) preproc = workflow.Stage('preprocess', 'Preprocessing') @@ -116,6 +117,17 @@ class TestDataFunction(workflow.Function): ds_scatter = plots.ScatterMarkerPlot(ds, ds, 'rows_0', 'rows_0', '0_1', '0_2') return [X, ds, plots.SinePlot(), p, ds_plot, ds_scatter,p2] +class SelectFunction(workflow.Function): + def __init__(self): + workflow.Function.__init__(self, 'select', 'Select') + + def run(self, data): + s = dataset.Selection('Arbitrary selection') + s.select('rows', ['0_1', '0_2']) + print s['rows'] + print s.dims() + return [s] + class DatasetLog(workflow.Function): def __init__(self): workflow.Function.__init__(self, 'log', 'Log')