def DataFactory(path, mimetype=None, **kwargs): path = Path(path) default_content = kwargs[ 'default_content'] if 'default_content' in kwargs else None dtype = kwargs.pop('dtype', None) if path.isfile(): if default_content is not None: raise ValueError( "got multiple values for content (parameter and '%s')" % path.name) else: path, mimetype = arrange_data_args(path, mimetype, dtype) klass = DataClass(mimetype) return klass(path=path, mimetype=mimetype) elif path.exists(): raise ValueError("'%s' exists but is not a file" % path) elif not path.exists(): if default_content is None: default_content = b'' try: f = path.open('wb') except IOError: content = default_content else: f.write(default_content) f.close() content = None path, mimetype = arrange_data_args(path, mimetype, dtype) klass = DataClass(mimetype) return klass(path=path, mimetype=mimetype, content=content)
def DataFactory(path, mimetype=None, **kwargs): path = Path(path) default_content = kwargs['default_content'] if 'default_content' in kwargs else None dtype = kwargs.pop('dtype', None) if path.isfile(): if default_content is not None: raise ValueError( "got multiple values for content (parameter and '%s')" % path.name) else: path, mimetype = arrange_data_args(path, mimetype, dtype) klass = DataClass(mimetype) return klass(path=path, mimetype=mimetype) elif path.exists(): raise ValueError("'%s' exists but is not a file" % path) elif not path.exists(): if default_content is None: default_content = b'' try: f = path.open('wb') except IOError: content = default_content else: f.write(default_content) f.close() content = None path, mimetype = arrange_data_args(path, mimetype, dtype) klass = DataClass(mimetype) return klass(path=path, mimetype=mimetype, content=content)
def load(self, name, projectdir=None, **kwargs): """ Load existing project :use: >>> project1 = project_manager.load('project1') >>> project2 = project_manager.load('project2', '/path/to/project') # doctest: +SKIP :param name: name of project to load. Must be a string. :param projectdir: path of project to load. Must be a path (see module path.py). By default, try to guess with name only. If there are various projects with the same name, return the first. :return: Project """ project = None if 'proj_path' in kwargs: projectdir = kwargs['proj_path'] elif 'path' in kwargs: projectdir = kwargs['path'] if not projectdir: try: project = self.item(name) except UnknownItemError: pass else: full_path = Path(projectdir) / name if full_path.exists(): project = Project(full_path) else: print 'Project %s in repository %s does not exist' % (name, projectdir) if project: self.cproject = project return self.cproject
def discover(self, config_name='oaproject.cfg'): """ Discover projects from your disk and put them in self.projects. Projects are not loaded, only metadata are. :use: >>> project_manager.discover() >>> list_of_projects = project_manager.projects To discover new projects, you can add path into *self.repositories* .. code-block:: python project_manager.repositories.append('path/to/search/projects') project_manager.discover() """ projects = {} for _path in self.repositories: _path = Path(_path) if not _path.exists(): continue for p in _path.walkfiles(config_name): project = Project(p.parent) projects[project.path] = project self.projects = projects.values()
def load(self, filename): """ :param filename: filename to convert into python object :return: a python object interpreted from string "text" """ filename = Path(filename) if filename.exists(): obj = open(filename, 'rU').read() return obj
def load(self, filename): """ :param filename: filename to convert into python object :return: a python object interpreted from string "text" """ filename = Path(filename) if filename.exists(): obj = open(filename, 'rU').read() return obj
def save(self, obj, path, protocol=None, **kwds): mode = kwds.pop('mode', 'all') path = Path(path) config_filename = kwds.get('config_filename', 'oaproject.cfg') config_path = path / config_filename if mode == 'all': if not path.exists(): path.makedirs() self._save_metadata(obj, config_path) lines = self._save_controls(obj) with open(path / 'control.py', 'w') as f: for line in lines: f.write(line) elif mode == 'metadata': if path.exists(): self._save_metadata(obj, config_path) else: raise NotImplementedError('mode=%s' % mode)
def save(self, obj, path, protocol=None, **kwds): mode = kwds.pop('mode', 'all') path = Path(path) config_filename = kwds.get('config_filename', 'oaproject.cfg') config_path = path / config_filename if mode == 'all': if not path.exists(): path.makedirs() self._save_metadata(obj, config_path) lines = self._save_controls(obj) with open(path / 'control.py', 'w') as f: for line in lines: f.write(line) elif mode == 'metadata': if path.exists(): self._save_metadata(obj, config_path) else: raise NotImplementedError('mode=%s' % mode)
def load(self, filename): """ :param filename: filename to convert into python object :return: a python object interpreted from string "text" """ filename = Path(filename) if filename.exists(): obj = open(filename, 'rU').read() try: return eval(obj) except SyntaxError: return obj except NameError: return obj
def load(self, filename): """ :param filename: filename to convert into python object :return: a python object interpreted from string "text" """ filename = Path(filename) if filename.exists(): obj = open(filename, 'rU').read() try: return eval(obj) except SyntaxError: return obj except NameError: return obj
def load(self, filename): """ :param filename: filename to convert into python object :return: a python object interpreted from string "text" """ filename = Path(filename) if filename.exists(): try: from openalea.plantgl.all import Scene sc = Scene() sc.clear() sc.read(str(filename), "BGEOM") return sc except ImportError: warnings.warn("You must install PlantGL if you want to load a BGEOM object.") except Exception, e: print e warnings.warn("Impossible to load the scene")
def load(self, filename): """ :param filename: filename to convert into python object :return: a python object interpreted from string "text" """ filename = Path(filename) if filename.exists(): try: from openalea.plantgl.all import Scene sc = Scene() sc.clear() sc.read(str(filename), "BGEOM") return sc except ImportError: warnings.warn( "You must install PlantGL if you want to load a BGEOM object." ) except Exception, e: print e warnings.warn("Impossible to load the scene")
def load(self, filename): """ :param filename: filename to convert into python object :return: a python object interpreted from filename """ filename = Path(filename) if filename.exists(): cpik = "False" try: import cPickle cpik = "True" except ImportError: warnings.warn("You must install cPickle.") if cpik: try: file_ = open(filename, "r") ret = cPickle.load(file_) file_.close() return ret except Exception, e: print "Can't load file " + filename + " with loader CPickleLoader. " print e
def load(self, filename): """ :param filename: filename to convert into python object :return: a python object interpreted from filename """ filename = Path(filename) if filename.exists(): cpik = "False" try: import cPickle cpik = "True" except ImportError: warnings.warn("You must install cPickle.") if cpik: try: file_ = open(filename, "r") ret = cPickle.load(file_) file_.close() return ret except Exception, e: print "Can't load file " + filename + " with loader CPickleLoader. " print e
def _add_item(self, category, obj=None, **kwargs): mode = kwargs.pop('mode', self.MODE_COPY) if obj and isinstance(obj, Data): # TODO: Check obj follow Data or Model interface ?? new_path = self.path / category / obj.path.name if obj.path != new_path and mode == self.MODE_COPY: # TODO: use Data.copy instead return self._add_item(category, path=obj.path, **kwargs) category_dict = getattr(self, category) if obj.filename not in category_dict: category_dict[str(obj.filename)] = obj else: raise ValueError("data '%s' already exists in project '%s'" % (obj.filename, self.alias)) elif obj: category_dict = getattr(self, category) if obj.name not in category_dict: category_dict[str(obj.name)] = obj else: raise ValueError("data '%s' already exists in project '%s'" % (obj.name, self.alias)) else: filename = Path( kwargs.pop('filename')) if 'filename' in kwargs else None content = kwargs.pop('content', None) dtype = kwargs.pop('dtype', None) mimetype = kwargs.pop('mimetype', None) path = Path(kwargs.pop('path')) if 'path' in kwargs else None # If project path exists, ie project exists on disk, # Create category dir if necessary category_path = self.path / category if self.path.exists() and not category_path.exists(): category_path.makedirs() if filename: new_path = self.path / category / filename.name elif path: if not path.exists(): raise ErrorInvalidItem("path '%s' doesn't exists" % path) filename = path.name new_path = self.path / category / filename else: raise ValueError("path or filename required") if path is None: path = new_path # If data was outside project, we try to fix it. # If mode is "prefer copy", we try to copy file inside project # If copy fails, we get original content and pass it to new data # If mode is "prefer link", we just keep original path (keep outside project) # TODO: Move to Data.copy data_obj = None if new_path.abspath() != path.abspath() and mode == self.MODE_COPY: try: path.copyfile(new_path) except IOError: data_obj = DataFactory(path, mimetype, dtype=dtype, default_content=content) content = data_obj.read() else: content = None elif new_path.abspath() != path.abspath( ) and mode == self.MODE_LINK: new_path = path else: pass # Nothing to do, data is yet in the right place data_obj = DataFactory(new_path, mimetype, dtype=dtype, default_content=content) obj = self._add_item(category, data_obj, **kwargs) obj.package = self return obj
def _add_item(self, category, obj=None, **kwargs): mode = kwargs.pop('mode', self.MODE_COPY) if obj and isinstance(obj, Data): # TODO: Check obj follow Data or Model interface ?? new_path = self.path / category / obj.path.name if obj.path != new_path and mode == self.MODE_COPY: # TODO: use Data.copy instead return self._add_item(category, path=obj.path, **kwargs) category_dict = getattr(self, category) if obj.filename not in category_dict: category_dict[str(obj.filename)] = obj else: raise ValueError("data '%s' already exists in project '%s'" % (obj.filename, self.alias)) elif obj: category_dict = getattr(self, category) if obj.name not in category_dict: category_dict[str(obj.name)] = obj else: raise ValueError("data '%s' already exists in project '%s'" % (obj.name, self.alias)) else: filename = Path(kwargs.pop('filename')) if 'filename' in kwargs else None content = kwargs.pop('content', None) dtype = kwargs.pop('dtype', None) mimetype = kwargs.pop('mimetype', None) path = Path(kwargs.pop('path')) if 'path' in kwargs else None # If project path exists, ie project exists on disk, # Create category dir if necessary category_path = self.path / category if self.path.exists() and not category_path.exists(): category_path.makedirs() if filename: new_path = self.path / category / filename.name elif path: if not path.exists(): raise ErrorInvalidItem("path '%s' doesn't exists" % path) filename = path.name new_path = self.path / category / filename else: raise ValueError("path or filename required") if path is None: path = new_path # If data was outside project, we try to fix it. # If mode is "prefer copy", we try to copy file inside project # If copy fails, we get original content and pass it to new data # If mode is "prefer link", we just keep original path (keep outside project) # TODO: Move to Data.copy data_obj = None if new_path.abspath() != path.abspath() and mode == self.MODE_COPY: try: path.copyfile(new_path) except IOError: data_obj = DataFactory(path, mimetype, dtype=dtype, default_content=content) content = data_obj.read() else: content = None elif new_path.abspath() != path.abspath() and mode == self.MODE_LINK: new_path = path else: pass # Nothing to do, data is yet in the right place data_obj = DataFactory(new_path, mimetype, dtype=dtype, default_content=content) obj = self._add_item(category, data_obj, **kwargs) obj.package = self return obj
class OALabMainWin(QtGui.QMainWindow): appletSet = QtCore.Signal(object, object) DEFAULT_MENU_NAMES = ('File', 'Edit', 'View', 'Help') DEFAULT_LAYOUT = dict( name='default', alias='Default Layout', children={}, parents={0: None}, properties={ 0: { 'widget': { 'properties': {'position': 0}, 'applets': [{'name': 'ShellWidget'}] } }} ) DEFAULT_LAYOUT_PATH = 'layout.oaui' LAB = None def __init__(self, layout=None, **kwds): QtGui.QMainWindow.__init__(self) self._lab = kwds.get('lab', None) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.DEFAULT_MENU_NAMES = list(self.DEFAULT_MENU_NAMES) if 'Edit' not in self.DEFAULT_MENU_NAMES: self.DEFAULT_MENU_NAMES.insert(0, 'Edit') if 'File' not in self.DEFAULT_MENU_NAMES: self.DEFAULT_MENU_NAMES.insert(0, 'File') if 'Help' not in self.DEFAULT_MENU_NAMES: self.DEFAULT_MENU_NAMES.append('Help') # Classic menu self._registered_applets = [] self._create_menus() self._create_actions() self._pre_fill_menus() self._splittable_list = [] self.layout_selector = LayoutSelector(parent=self) layout = self._load_layout(layout, **kwds) if isinstance(layout, (list, tuple)): layouts = layout else: layouts = [layout] for layout in layouts: title = layout.get('title', None) if 'children' not in layout: layout = None splittable = OALabSplittableUi(parent=self) splittable.setAttribute(QtCore.Qt.WA_DeleteOnClose) splittable.appletSet.connect(self.appletSet.emit) self.appletSet.connect(self._on_applet_set) if layout is None: container = AppletContainer() splittable.setContentAt(0, container) else: splittable.fromJSON(layout) self._splittable_list.append(splittable) self.layout_selector.add_widget(splittable, title=title) self.setCentralWidget(self.layout_selector) self._post_fill_menus() self.set_edit_mode(False) QtGui.QApplication.instance().focusChanged.connect(self._on_focus_changed) def emit_applet_set(self): self.splittable.emit_applet_set() @property def splittable(self): return self.layout_selector.widget() def _create_menus(self): self.menu_classic = {} menubar = QtGui.QMenuBar() self.setMenuBar(menubar) for menu_name in self.DEFAULT_MENU_NAMES: self.menu_classic[menu_name] = menubar.addMenu(menu_name) def _create_actions(self): self.action_edit = QtGui.QAction("Edit Layout", self.menu_classic['Edit']) self.action_edit.setCheckable(True) self.action_edit.toggled.connect(self.set_edit_mode) self.action_edit.setChecked(False) icon = QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_TitleBarCloseButton) self.action_quit = QtGui.QAction(icon, "Quit application", self.menu_classic['File']) self.action_quit.triggered.connect(self.close) self.action_quit.setChecked(False) def _pre_fill_menus(self): self.menu_classic['Edit'].addAction(self.action_edit) def _post_fill_menus(self): self.menu_classic['File'].addSeparator() self.menu_classic['File'].addAction(self.action_quit) def _load_layout(self, layout=None, **kwds): layout_file = kwds.pop('layout_file', self.DEFAULT_LAYOUT_PATH) default_layout = kwds.pop('default_layout', self.DEFAULT_LAYOUT) self.layout_filepath = Path(layout_file).abspath() if layout is None: if self.layout_filepath.exists(): with open(self.layout_filepath) as layout_file: content = layout_file.read() try: layout = json.loads(content) except ValueError: l = eval(content) layout = dict(children=l[0], parents=l[1], properties=l[2]) if layout is None: layout = default_layout return layout def _save_layout(self): with open(self.layout_filepath, 'w') as layout_file: json.dump(self.layout(), layout_file, sort_keys=True, indent=2) def closeEvent(self, event): close = True # If a lab is used, check if it can be close if hasattr(self._lab, 'readytoclose'): close = self._lab.readytoclose() # If lab is not ready, stop closing if close is False: event.ignore() return # If lab is ready to close, or no lab is used, close widget if self.splittable.close(): if hasattr(self._lab, 'finalize'): self._lab.finalize() self._save_layout() if hasattr(self._lab, 'stop'): self._lab.stop() event.accept() else: event.ignore() def set_edit_mode(self, mode=True): for widget in self.splittable.getAllContents(): if hasattr(widget, 'set_edit_mode'): widget.set_edit_mode(mode) if mode is True and self.LAB: print self.LAB.connections self.splittable.set_edit_mode(mode) def initialize(self): self.pm = PluginManager() for instance in plugin_instances('oalab.applet'): if hasattr(instance, 'initialize'): instance.initialize() def _widget_actions(self, obj): actions = None if hasattr(obj, 'toolbar_actions'): if isinstance(obj.toolbar_actions, list): actions = obj.toolbar_actions else: actions = obj.toolbar_actions() if actions is None: return [] else: return actions def _widget_name(self, obj): if hasattr(obj, 'name'): return obj.name def _on_focus_changed(self, old, new): self.clear_toolbar() if old is new: return # Generally focus is on "leaf" widget on widget hierarchy. # We try to browse all tree to get widget defining actions # For example, if an editor is defined as MyEditor -> Container -> Editor -> QTextEdit # Widget with focus is QTextEdit but widget that define actions is MyEditor # Search stops if widget has no more parents or if widget is AppletContainer parent = new actions = self._widget_actions(parent) name = self._widget_name(parent) while parent is not None: try: parent = parent.parent() except TypeError: break else: if isinstance(parent, AppletContainer): break name = name or self._widget_name(parent) actions += self._widget_actions(parent) if actions: self.fill_toolbar(name, actions) # toolbar creation/destruction set focus to toolbar so we reset it to widget if isinstance(new, QtGui.QWidget): new.setFocus(QtCore.Qt.OtherFocusReason) def fill_toolbar(self, name, actions): menus = plugin_instances('oalab.applet', 'ContextualMenu') for menu in menus: menu.set_actions(name, actions) def clear_toolbar(self): menus = plugin_instances('oalab.applet', 'ContextualMenu') for menu in menus: menu.clear() def _merge_menus(self, menus): parent = self default_menus = self.menu_classic menubar = self.menuBar() for _menu in menus: menu_name = _menu.title() if menu_name in default_menus: menu = default_menus[menu_name] else: menu = QtGui.QMenu(menu_name, parent) default_menus[menu_name] = menu menubar.addMenu(menu) for _menu in menus: menu_name = _menu.title() menu = default_menus[menu_name] for action in _menu.actions(): if isinstance(action, QtGui.QAction): menu.addAction(action) elif isinstance(action, QtGui.QMenu): menu.addMenu(action) elif action == '-': menu.addSeparator() def _on_applet_set(self, old, new): if new in self._registered_applets: return self._registered_applets.append(new) applet = plugin_instance('oalab.applet', new) # Add global menus if applet and hasattr(applet, 'menus'): menus = applet.menus() if menus is None: return self._merge_menus(menus) # Add global toolbars if applet and hasattr(applet, 'toolbars'): toolbars = applet.toolbars() if toolbars is None: return for toolbar in toolbars: self.addToolBar(QtCore.Qt.TopToolBarArea, toolbar) def layout(self): return self.splittable._repr_json_()
class Data(object): mimetype = None default_name = 'Data' default_file_name = "filename.ext" pattern = "*.ext" extension = "ext" icon = "Crystal_Clear_app_kcmdf.png" def __init__(self, **kwargs): """ Classical use : *path* exists. Nothing is loaded in memory. Use :meth:`~Data.read` to get content """ # TODO: document args self.path = Path(kwargs.pop('path')) if 'path' in kwargs else None self._filename = Path(kwargs.pop('filename')).name if 'filename' in kwargs else None if self._filename is None and self.path is None: raise ValueError('path or filename required') if self._filename and self.path and self.path.name != self._filename: raise ValueError("path '%s' and filename '%s' are not compatible" % (self.path, self._filename)) self.dtype = kwargs.pop('dtype', None) self._content = kwargs.pop('content', None) self.mimetype = kwargs.pop('mimetype', None) def get_documentation(self): return "No documentation for %s" % self.filename def is_same_data(self, other): if self.exists() and other.exists(): return self.path == other.path elif not self.exists() and not other.exists(): return self._content == other._content else: return False def save(self): if self.path is None: raise ValueError('You must specify a path to be able to save data') if self._content is not None: with open(self.path, 'wb') as f: f.write(self._content) self._content = None def read(self): if self.exists(): with open(self.path, 'rb') as f: return f.read() else: return self._content def rename(self, new): pnew = Path(new) if pnew.isabs() or pnew.name != new: raise ValueError('You must give filename only, not path') new_path = self.path.parent / new self.move(new_path) def move(self, new_path): new_path = Path(new_path) if self._filename is not None: self._filename = new_path.name if self.path.isfile(): self.path.move(new_path) self.path = new_path def exists(self): if self.path: return self.path.exists() else: return False @property def filename(self): if self._filename is None: return self.path.name else: return self._filename @property def name(self): return self.filename @filename.setter def filename(self, value): self._filename = value def _set_content(self, content): self._content = content def _get_content(self): if self._content is None: return self.read() else: return self._content content = property(fget=_get_content, fset=_set_content) code = property()
def generate_pyfile_from_uifile(name, src=None, dest=None, uibasename=None, force=None): """ Function searches ... if src is None, search in this order : - <moduledir>/designer/<modulename>.ui - <moduledir>/resources/<modulename>.ui - <moduledir>/<modulename>.ui else : - src File generated is if dest is None : - _<uifilebase>.py (Ex: mywdget.ui -> _mywidget.py) else : - dest .. warning :: To work, this function has to be called in an **imported** module. (__name__ must differ from __main__) else, nothing is done ! Do not edit generated file because all data written here are lost. :param name: :type name: str :return: Qt class (corresponding to filename), Qt type class (type of first value) :rtype: couple """ if force is None: force = FORCE_UI_GENERATION modulename = name if uibasename: name = uibasename else: name = name.split('.')[-1] if modulename == '__main__': return paths = [] if src: filepath = Path(src) paths.append(filepath) else: path = 'designer/%s.ui' % name filepath = Path(get_data(modulename, path)) paths.append(filepath) path = 'resources/%s.ui' % name filepath = Path(get_data(modulename, path)) paths.append(filepath) path = '%s.ui' % name filepath = Path(get_data(modulename, path)) paths.append(filepath) for path in paths: if path.isfile(): break # tmpdir = mkdtempu() if dest is None: pyfilename = path.parent / '_' + path.name.replace('.ui', '.py') else: pyfilename = Path(dest) if not pyfilename.exists(): generate = True else: mtime_py = mtime_datetime(pyfilename) mtime_ui = mtime_datetime(path) if mtime_py > mtime_ui: # If py file is more recent than ui, check user has not changed QT_API with open(pyfilename, 'r') as f: content = f.read() generate = QT_MODULE_NAME not in content else: generate = True if generate or force: module_dir = str(path.parent) if module_dir not in sys.path: sys.path.append(module_dir) if force: print('build %s from %s\n' % (pyfilename, path)) else: print('%s has changed, build %s\n' % (path, pyfilename)) pyfile = open(pyfilename, 'w') compileUi(path, pyfile, **compile_args) pyfile.close()
def generate_pyfile_from_uifile(name, src=None, dest=None, uibasename=None, force=None): """ Function searches ... if src is None, search in this order : - <moduledir>/designer/<modulename>.ui - <moduledir>/resources/<modulename>.ui - <moduledir>/<modulename>.ui else : - src File generated is if dest is None : - _<uifilebase>.py (Ex: mywdget.ui -> _mywidget.py) else : - dest .. warning :: To work, this function has to be called in an **imported** module. (__name__ must differ from __main__) else, nothing is done ! Do not edit generated file because all data written here are lost. :param name: :type name: str :return: Qt class (corresponding to filename), Qt type class (type of first value) :rtype: couple """ if force is None: force = FORCE_UI_GENERATION modulename = name if uibasename: name = uibasename else: name = name.split('.')[-1] if modulename == '__main__': return paths = [] if src: filepath = Path(src) paths.append(filepath) else: path = 'designer/%s.ui' % name filepath = Path(get_data(modulename, path)) paths.append(filepath) path = 'resources/%s.ui' % name filepath = Path(get_data(modulename, path)) paths.append(filepath) path = '%s.ui' % name filepath = Path(get_data(modulename, path)) paths.append(filepath) for path in paths: if path.isfile(): break # tmpdir = mkdtempu() if dest is None: pyfilename = path.parent / '_' + path.name.replace('.ui', '.py') else: pyfilename = Path(dest) if not pyfilename.exists(): generate = True else: mtime_py = mtime_datetime(pyfilename) mtime_ui = mtime_datetime(path) if mtime_py > mtime_ui: # If py file is more recent than ui, check user has not changed QT_API with open(pyfilename, 'r') as f: content = f.read() generate = QT_MODULE_NAME not in content else: generate = True if generate or force: module_dir = str(path.parent) if module_dir not in sys.path: sys.path.append(module_dir) if force: print 'build %s from %s\n' % (pyfilename, path) else: print '%s has changed, build %s\n' % (path, pyfilename) pyfile = open(pyfilename, 'w') compileUi(path, pyfile, **compile_args) pyfile.close()
class OALabMainWin(QtGui.QMainWindow): appletSet = QtCore.Signal(object, object) DEFAULT_MENU_NAMES = ('File', 'Edit', 'View', 'Help') DEFAULT_LAYOUT = dict( name='default', label='Default Layout', children={}, parents={0: None}, properties={ 0: { 'widget': { 'properties': {'position': 0}, 'applets': [{'name': 'ShellWidget'}] } }} ) DEFAULT_LAYOUT_PATH = 'layout.oaui' LAB = None def __init__(self, layout=None, **kwds): QtGui.QMainWindow.__init__(self) self.autosave = kwds.get('autosave', False) self._lab = kwds.get('lab', None) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.DEFAULT_MENU_NAMES = list(self.DEFAULT_MENU_NAMES) if 'Edit' not in self.DEFAULT_MENU_NAMES: self.DEFAULT_MENU_NAMES.insert(0, 'Edit') if 'File' not in self.DEFAULT_MENU_NAMES: self.DEFAULT_MENU_NAMES.insert(0, 'File') if 'Help' not in self.DEFAULT_MENU_NAMES: self.DEFAULT_MENU_NAMES.append('Help') # Classic menu self._registered_applets = [] self._create_menus() self._create_actions() self._pre_fill_menus() self._splittable_list = [] self.layout_selector = LayoutSelector(parent=self) layout = self._load_layout(layout, **kwds) if isinstance(layout, (list, tuple)): layouts = layout else: layouts = [layout] for layout in layouts: title = layout.get('title', None) if 'children' not in layout: layout = None splittable = OALabSplittableUi(parent=self) splittable.setAttribute(QtCore.Qt.WA_DeleteOnClose) splittable.appletSet.connect(self.appletSet.emit) self.appletSet.connect(self._on_applet_set) if layout is None: container = AppletContainer() splittable.setContentAt(0, container) else: splittable.fromJSON(layout) self._splittable_list.append(splittable) self.layout_selector.add_widget(splittable, title=title) self.setCentralWidget(self.layout_selector) self._post_fill_menus() self.set_edit_mode(False) QtGui.QApplication.instance().focusChanged.connect(self._on_focus_changed) def emit_applet_set(self): self.splittable.emit_applet_set() @property def splittable(self): return self.layout_selector.widget() def _create_menus(self): self.menu_classic = {} menubar = QtGui.QMenuBar() self.setMenuBar(menubar) for menu_name in self.DEFAULT_MENU_NAMES: self.menu_classic[menu_name] = menubar.addMenu(menu_name) def _create_actions(self): self.action_edit = QtGui.QAction("Edit Layout", self.menu_classic['Edit']) self.action_edit.setCheckable(True) self.action_edit.toggled.connect(self.set_edit_mode) self.action_edit.setChecked(False) self.action_about = QtGui.QAction("About", self.menu_classic['Help']) self.action_about.triggered.connect(self.show_about) self.action_plugins = QtGui.QAction("Plugins", self.menu_classic['Help']) self.action_plugins.triggered.connect(self.show_plugins) icon = QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_TitleBarCloseButton) self.action_quit = QtGui.QAction(icon, "Quit application", self.menu_classic['File']) self.action_quit.triggered.connect(self.close) self.action_quit.setChecked(False) def _pre_fill_menus(self): self.menu_classic['Edit'].addAction(self.action_edit) self.menu_classic['Help'].addAction(self.action_about) self.menu_classic['Help'].addAction(self.action_plugins) def _post_fill_menus(self): self.menu_classic['File'].addSeparator() self.menu_classic['File'].addAction(self.action_quit) def _load_layout(self, layout=None, **kwds): layout_file = kwds.pop('layout_file', self.DEFAULT_LAYOUT_PATH) default_layout = kwds.pop('default_layout', self.DEFAULT_LAYOUT) self.layout_filepath = Path(layout_file).abspath() if layout is None: if self.layout_filepath.exists(): with open(self.layout_filepath) as layout_file: content = layout_file.read() try: layout = json.loads(content) except ValueError: l = eval(content) layout = dict(children=l[0], parents=l[1], properties=l[2]) if layout is None: layout = default_layout return layout def _save_layout(self): if self.autosave: with open(self.layout_filepath, 'w') as layout_file: json.dump(self.layout(), layout_file, sort_keys=True, indent=2) def closeEvent(self, event): close = True # If a lab is used, check if it can be close if hasattr(self._lab, 'readytoclose'): close = self._lab.readytoclose() # If lab is not ready, stop closing if close is False: event.ignore() return # If lab is ready to close, or no lab is used, close widget if self.splittable.close(): if hasattr(self._lab, 'finalize'): self._lab.finalize() self._save_layout() if hasattr(self._lab, 'stop'): self._lab.stop() event.accept() else: event.ignore() def set_edit_mode(self, mode=True): for widget in self.splittable.getAllContents(): if hasattr(widget, 'set_edit_mode'): widget.set_edit_mode(mode) if mode is True and self.LAB: print self.LAB.connections self.splittable.set_edit_mode(mode) def initialize(self): for instance in plugin_instances('oalab.applet'): if hasattr(instance, 'initialize'): instance.initialize() def _widget_actions(self, obj, methodname='toolbar_actions'): actions = None if hasattr(obj, methodname): method_or_list = getattr(obj, methodname) if isinstance(method_or_list, list): actions = method_or_list else: actions = method_or_list() if actions is None: return [] else: return actions def _widget_name(self, obj): if hasattr(obj, 'name'): return obj.name def _on_focus_changed(self, old, new): self.clear_toolbar() if old is new: return # Generally focus is on "leaf" widget on widget hierarchy. # We try to browse all tree to get widget defining actions # For example, if an editor is defined as MyEditor -> Container -> Editor -> QTextEdit # Widget with focus is QTextEdit but widget that define actions is MyEditor # Search stops if widget has no more parents or if widget is AppletContainer parent = new actions = self._widget_actions(parent) name = self._widget_name(parent) while parent is not None: try: parent = parent.parent() except TypeError: break else: if isinstance(parent, AppletContainer): break name = name or self._widget_name(parent) actions += self._widget_actions(parent) if actions: self.fill_toolbar(name, actions) # toolbar creation/destruction set focus to toolbar so we reset it to widget if isinstance(new, QtGui.QWidget): new.setFocus(QtCore.Qt.OtherFocusReason) def fill_toolbar(self, name, actions): menus = plugin_instances('oalab.applet', 'ContextualMenu') for menu in menus: menu.set_actions(name, actions) def clear_toolbar(self): menus = plugin_instances('oalab.applet', 'ContextualMenu') for menu in menus: menu.clear() def _merge_menus(self, menus): parent = self default_menus = self.menu_classic menubar = self.menuBar() for _menu in menus: menu_name = _menu.title() if menu_name in default_menus: menu = default_menus[menu_name] else: menu = QtGui.QMenu(menu_name, parent) default_menus[menu_name] = menu menubar.addMenu(menu) for _menu in menus: menu_name = _menu.title() menu = default_menus[menu_name] for action in _menu.actions(): if isinstance(action, QtGui.QAction): menu.addAction(action) elif isinstance(action, QtGui.QMenu): menu.addMenu(action) elif action == '-': menu.addSeparator() def _on_applet_set(self, old, new): if new in self._registered_applets: return self._registered_applets.append(new) applet = plugin_instance('oalab.applet', new) # Add global menus if applet and hasattr(applet, 'menus'): menus = applet.menus() if menus is None: return self._merge_menus(menus) # Add global toolbars if applet and hasattr(applet, 'toolbars'): toolbars = applet.toolbars() if toolbars is None: return for toolbar in toolbars: self.addToolBar(QtCore.Qt.TopToolBarArea, toolbar) def layout(self): return self.splittable._repr_json_() def show_about(self): about = About() dialog = ModalDialog(about) dialog.resize(400, 600) dialog.setWindowTitle("About OpenAleaLab ...") dialog.exec_() def show_plugins(self): explorer = PluginExplorer() dialog = ModalDialog(explorer) dialog.resize(600, 600) dialog.setWindowTitle("OpenAleaLab plugin's ...") dialog.exec_()