easily accessible from anywhere in the code. As there is only one project at any given time this should not be a problem. The Gene Ontology browser needed this to be able to set the selection without passing the current project through several constructors. Added a context menu in the GO browser that allows the user to select all GO terms in the subgraph that starts on the current node.
345 lines
11 KiB
Python
345 lines
11 KiB
Python
#!/usr/bin/python
|
|
|
|
import os
|
|
import sys
|
|
|
|
import pygtk
|
|
pygtk.require('2.0')
|
|
import gobject
|
|
import gtk
|
|
import gtk.gdk
|
|
import gtk.glade
|
|
import gnome
|
|
import gnome.ui
|
|
import scipy
|
|
import pango
|
|
import project, workflow, dataset, logger, plots, navigator, dialogs, selections
|
|
|
|
|
|
PROGRAM_NAME = 'fluents'
|
|
VERSION = '0.1.0'
|
|
DATADIR = os.path.dirname(sys.modules['fluents'].__file__)
|
|
ICONDIR = os.path.join(DATADIR,"..","icons")
|
|
GLADEFILENAME = os.path.join(DATADIR, 'fluents.glade')
|
|
|
|
class IconFactory:
|
|
"""Factory for icons that ensures that each icon is only loaded once."""
|
|
|
|
def __init__(self, path):
|
|
self._path = path
|
|
self._icons = {}
|
|
|
|
def get(self, iconname):
|
|
"""Returns the gdk loaded PixBuf for the given icon.
|
|
Reads the icon from file if necessary."""
|
|
|
|
if self._icons.has_key(iconname):
|
|
return self._icons[iconname]
|
|
|
|
icon_fname = os.path.join(self._path, '%s.png' % iconname)
|
|
icon = gtk.gdk.pixbuf_new_from_file(icon_fname)
|
|
self._icons[iconname] = icon
|
|
return icon
|
|
|
|
icon_factory = IconFactory(ICONDIR)
|
|
|
|
class TableSizeSelection(gtk.Window):
|
|
|
|
def __init__(self):
|
|
gtk.Window.__init__(self, gtk.WINDOW_POPUP)
|
|
self._table = gtk.Table(3, 3, True)
|
|
self._items = []
|
|
|
|
## Create a 3x3 table of EventBox object, doubly stored because
|
|
## gtk.Table does not support indexed retrieval.
|
|
|
|
for y in range(3):
|
|
line = []
|
|
for x in range(3):
|
|
ebox = gtk.EventBox()
|
|
ebox.add(gtk.Frame())
|
|
ebox.set_size_request(20, 20)
|
|
ebox.set_visible_window(True)
|
|
self._table.attach(ebox, x, x+1, y, y+1, gtk.FILL, gtk.FILL)
|
|
line.append(ebox)
|
|
self._items.append(line)
|
|
|
|
self.set_border_width(5)
|
|
self.add(self._table)
|
|
self.connect_signals()
|
|
|
|
def _get_child_pos(self, child):
|
|
for x in range(3):
|
|
for y in range(3):
|
|
if self._items[y][x] == child:
|
|
return (x, y)
|
|
return None
|
|
|
|
def connect_signals(self):
|
|
for x in range(3):
|
|
for y in range(3):
|
|
self._items[y][x].add_events(gtk.gdk.ENTER_NOTIFY_MASK)
|
|
self._items[y][x].connect("enter-notify-event",
|
|
self._on_enter_notify)
|
|
self._items[y][x].connect("button-release-event",
|
|
self._on_button_release)
|
|
|
|
def _on_enter_notify(self, widget, event):
|
|
x, y = self._get_child_pos(widget)
|
|
for i in range(3):
|
|
for j in range(3):
|
|
if i <= x and j <= y:
|
|
self._items[j][i].set_state(gtk.STATE_SELECTED)
|
|
else:
|
|
self._items[j][i].set_state(gtk.STATE_NORMAL)
|
|
self.x = x
|
|
self.y = y
|
|
|
|
def _on_button_release(self, widget, event):
|
|
self.emit('table-size-set', self.x+1, self.y+1)
|
|
self.hide_all()
|
|
|
|
for x in range(3):
|
|
for y in range(3):
|
|
self._items[y][x].set_state(gtk.STATE_NORMAL)
|
|
|
|
|
|
class ViewFrameToolButton (gtk.ToolItem):
|
|
|
|
def __init__(self):
|
|
gtk.ToolItem.__init__(self)
|
|
|
|
fname = os.path.join(ICONDIR, "table_size.png")
|
|
image = gtk.Image()
|
|
image.set_from_file(fname)
|
|
|
|
self._button = gtk.Button()
|
|
self._button.set_image(image)
|
|
self._button.set_property("can-focus", False)
|
|
|
|
eb = gtk.EventBox()
|
|
eb.add(self._button)
|
|
self.add(eb)
|
|
self._item = TableSizeSelection()
|
|
self._button.connect("button-press-event", self._on_show_menu)
|
|
image.show()
|
|
self._image = image
|
|
|
|
self._item.connect("table-size-set", self._on_table_size_set)
|
|
self._button.set_relief(gtk.RELIEF_NONE)
|
|
self.show_all()
|
|
|
|
def _on_show_menu(self, widget, event):
|
|
x, y = self._image.window.get_origin()
|
|
x2, y2, w, h, b = self._image.window.get_geometry()
|
|
|
|
self._item.move(x, y+h)
|
|
self._item.show_all()
|
|
|
|
def _on_table_size_set(self, widget, width, height):
|
|
app['main_view'].resize_table(width, height)
|
|
|
|
|
|
class FluentApp:
|
|
|
|
def __init__(self, wf): # Application variables
|
|
self.project = None
|
|
self.current_data = None
|
|
self._last_view = None
|
|
self._plot_toolbar = None
|
|
self._toolbar_state = None
|
|
|
|
gtk.glade.set_custom_handler(self.custom_object_factory)
|
|
self.widget_tree = gtk.glade.XML(GLADEFILENAME, 'appwindow')
|
|
self.workflow = wf(self)
|
|
self.dimlistcontroller = selections.DimListController(self['dim_list'],
|
|
self['selection_tree'],
|
|
self['identifier_list'])
|
|
|
|
def init_gui(self):
|
|
self['appwindow'].set_size_request(800, 600)
|
|
|
|
# Set up workflow
|
|
self.wf_view = workflow.WorkflowView(self.workflow)
|
|
self.wf_view.show()
|
|
self['workflow_vbox'].pack_end(self.wf_view)
|
|
|
|
self._wf_menu = workflow.WorkflowMenu(self.workflow)
|
|
self._wf_menu.show()
|
|
wf_menuitem = gtk.MenuItem('Fu_nctions')
|
|
wf_menuitem.set_submenu(self._wf_menu)
|
|
wf_menuitem.show()
|
|
|
|
self['menubar1'].insert(wf_menuitem, 3)
|
|
|
|
# Connect signals
|
|
signals = {'on_quit1_activate' : (gtk.main_quit),
|
|
'on_appwindow_delete_event' : (gtk.main_quit),
|
|
'on_zoom_in_button_clicked' : (self.on_single_view),
|
|
'on_zoom_out_button_clicked' : (self.on_multiple_view),
|
|
'on_new1_activate' : (self.on_create_project),
|
|
'on_button_new_clicked' : (self.on_create_project),
|
|
'on_workflow_refresh_clicked' : (self.on_workflow_refresh_clicked),
|
|
'on_index1_activate' : (self.on_help_index),
|
|
'on_about1_activate' : (self.on_help_about),
|
|
'on_report_bug1_activate' : (self.on_help_report_bug),
|
|
'on_small_view1_activate' : (self.on_multiple_view),
|
|
'on_large_view1_activate' : (self.on_single_view),
|
|
}
|
|
self.widget_tree.signal_autoconnect(signals)
|
|
|
|
self['main_view'].connect('view-changed', self.on_view_changed)
|
|
|
|
# Log that we've set up the app now
|
|
logger.log('debug', 'Program started')
|
|
|
|
# Add ViewFrame table size to toolbar
|
|
tb = ViewFrameToolButton()
|
|
self['toolbar'].add(tb)
|
|
|
|
def set_project(self, proj):
|
|
logger.log('notice', 'Creating a new project')
|
|
self.project = proj
|
|
project.project = proj
|
|
self.workflow.add_project(proj)
|
|
self.navigator_view.add_project(proj)
|
|
self.dimlistcontroller.set_project(proj)
|
|
|
|
def set_workflow(self, workflow):
|
|
self.workflow = workflow
|
|
self.wf_view.set_workflow(self.workflow)
|
|
|
|
def show(self):
|
|
self.init_gui()
|
|
|
|
def change_plot(self, plot):
|
|
"""Sets the plot in the currently active ViewFrame. If the plot is
|
|
already shown in another ViewFrame it will be moved from there."""
|
|
# Set current selection in the plot before showing it.
|
|
plot.selection_changed(None, self.project.get_selection())
|
|
|
|
self['main_view'].insert_view(plot)
|
|
self._update_toolbar(plot)
|
|
|
|
def change_plots(self, plots):
|
|
"""Changes all plots."""
|
|
self['main_view'].set_all_plots(plots)
|
|
v = self.get_active_view_frame().get_view()
|
|
self._update_toolbar(v)
|
|
|
|
def get_active_view_frame(self):
|
|
return self['main_view'].get_active_view_frame()
|
|
|
|
def _update_toolbar(self, view):
|
|
"""Set the plot specific toolbar to the toolbar of the currently
|
|
active plot."""
|
|
|
|
# don't do anything on no change
|
|
if self._last_view == view:
|
|
return
|
|
self._last_view = view
|
|
|
|
logger.log("debug", "view changed to %s" % view)
|
|
|
|
window = self['plot_toolbar_dock']
|
|
if self._plot_toolbar:
|
|
toolbar_state = self._plot_toolbar.get_mode()
|
|
window.remove(self._plot_toolbar)
|
|
else:
|
|
toolbar_state = "default"
|
|
|
|
if view:
|
|
self._plot_toolbar = view.get_toolbar()
|
|
self._plot_toolbar.set_mode(toolbar_state)
|
|
else:
|
|
self._plot_toolbar = None
|
|
|
|
if self._plot_toolbar:
|
|
window.add(self._plot_toolbar)
|
|
|
|
# Methods to create GUI widgets from CustomWidgets in the glade file.
|
|
# The custom_object_factory calls other functions to generate specific
|
|
# widgets.
|
|
|
|
def custom_object_factory(self, glade, fun_name, widget_name, s1, s2, i1, i2):
|
|
"Called by the glade file reader to create custom GUI widgets."
|
|
handler = getattr(self, fun_name)
|
|
return handler(s1, s2, i1, i2)
|
|
|
|
def create_logview(self, str1, str2, int1, int2):
|
|
self.log_view = logger.LogView(logger.logger)
|
|
self.log_view.show()
|
|
return self.log_view
|
|
|
|
def create_main_view(self, str1, str2, int1, int2):
|
|
self.main_view = plots.MainView()
|
|
self.main_view.show()
|
|
return self.main_view
|
|
|
|
def create_navigator_view(self, str1, str2, int1, int2):
|
|
self.navigator_view = navigator.NavigatorView(None, self)
|
|
self.navigator_view.show()
|
|
return self.navigator_view
|
|
|
|
def create_dim_list(self, str1, str2, int1, int2):
|
|
self.dim_list = selections.DimList()
|
|
self.dim_list.show()
|
|
return self.dim_list
|
|
|
|
def create_selection_tree(self, str1, str2, int1, int2):
|
|
self.selection_tree = selections.SelectionTree()
|
|
self.selection_tree.show()
|
|
return self.selection_tree
|
|
|
|
def create_identifier_list(self, str1, str2, int1, int2):
|
|
self.identifier_list = selections.IdentifierList()
|
|
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.
|
|
|
|
def on_single_view(self, *ignored):
|
|
self['main_view'].goto_large()
|
|
|
|
def on_multiple_view(self, *ignored):
|
|
self['main_view'].goto_small()
|
|
|
|
def on_create_project(self, *rest):
|
|
d = dialogs.CreateProjectDruid(self)
|
|
d.run()
|
|
|
|
def on_help_about(self, *rest):
|
|
widget_tree = gtk.glade.XML(GLADEFILENAME, 'aboutdialog')
|
|
about = widget_tree.get_widget('aboutdialog')
|
|
about.run()
|
|
|
|
def on_help_index(self, *ignored):
|
|
gnome.help_display_uri('https://dev.pvv.org/projects/fluent/wiki/help')
|
|
|
|
def on_help_report_bug(self, *ignored):
|
|
gnome.help_display_uri('https://dev.pvv.org/projects/fluent/newticket')
|
|
|
|
def on_workflow_refresh_clicked(self, *ignored):
|
|
try:
|
|
reload(sys.modules[self.workflow.__class__.__module__])
|
|
except Exception, e:
|
|
logger.log('warning', 'Cannot reload workflow')
|
|
logger.log('warning', e)
|
|
else:
|
|
logger.log('notice', 'Successfully reloaded workflow')
|
|
|
|
def on_view_changed(self, widget, vf):
|
|
self._update_toolbar(vf.get_view())
|
|
|
|
|
|
gobject.signal_new('table-size-set', TableSizeSelection,
|
|
gobject.SIGNAL_RUN_LAST,
|
|
gobject.TYPE_NONE,
|
|
(gobject.TYPE_INT, gobject.TYPE_INT))
|
|
|