def __init__(self, size = (800,600), position = (0,0), parent = None): ''' :Parameters: ``size``: (int,int) Absolute size in pixels. ``position``: Absolute position in pixels. ``parent``: Figure or Window Parent Figure or Window ''' EventDispatcher.__init__(self) self._size = size self._position = position self._parent = parent self._figures = [] if parent is None: w,h = size[0], size[1] self._root = Window(width=int(w), height=int(h), caption='Figure', visible=True) self._root.push_handlers(self) fig = Figure(size=(1.0,1.0), position=(0,0), parent = self) self._figures.append(fig)
class Growth(object): EVENTS = ["ON_MAXED"] def __init__(self, target, params): self._target = target self._params = params self.volume = 0.0 self.events = EventDispatcher(Growth.EVENTS, observer=target) def grow(self): params = self.current_params() if params.growth_volume == 0.0 or self.maxed_out(): return self._target.consume_material(params.consumption_for_growth) self.volume += params.growth_volume if self.maxed_out(): self.volume = params.max_volume self.events.trigger(self.events.ON_MAXED) def current_params(self): params = self._params.get(self._target.state(), None) if not params: params = self._params["default"] return params def maxed_out(self): return self.current_params().has_max_volume and self.volume >= self.current_params().max_volume
class Growth(object): EVENTS = ['ON_MAXED'] def __init__(self, target, params): self._target = target self._params = params self.volume = 0.0 self.events = EventDispatcher(Growth.EVENTS, observer=target) def grow(self): params = self.current_params() if params.growth_volume == 0.0 or self.maxed_out(): return self._target.consume_material(params.consumption_for_growth) self.volume += params.growth_volume if self.maxed_out(): self.volume = params.max_volume self.events.trigger(self.events.ON_MAXED) def current_params(self): params = self._params.get(self._target.state(), None) if not params: params = self._params['default'] return params def maxed_out(self): return self.current_params( ).has_max_volume and self.volume >= self.current_params().max_volume
def __init__(self, notifier, *args, **kwargs): super(LoginInputField, self).__init__(*args, **kwargs) self.addClass("vi-login-input") self.sinkEvent("onKeyPress") self.onKeyPressEvent = EventDispatcher("keyPress") self.onKeyPressEvent.register(notifier)
def __init__(self, size=(800, 600), position=(0, 0), parent=None): ''' :Parameters: ``size``: (int,int) Absolute size in pixels. ``position``: Absolute position in pixels. ``parent``: Figure or Window Parent Figure or Window ''' EventDispatcher.__init__(self) self._size = size self._position = position self._parent = parent self._figures = [] if parent is None: w, h = size[0], size[1] self._root = Window(width=int(w), height=int(h), caption='Figure', visible=True) self._root.push_handlers(self) fig = Figure(size=(1.0, 1.0), position=(0, 0), parent=self) self._figures.append(fig)
def event(self, *args): '''Function decorator for an event handler.''' if self.parent is None: return EventDispatcher.event(self.root,*args) else: # If the event is 'on_idle', we attache it directly to the window if args[0] == 'on_idle' or args[0].__name__ == 'on_idle': self.root.push_handlers(*args) else: return EventDispatcher.event(self,*args)
def event(self, *args): '''Function decorator for an event handler.''' if self.parent is None: return EventDispatcher.event(self.root, *args) else: # If the event is 'on_idle', we attache it directly to the window if args[0] == 'on_idle' or args[0].__name__ == 'on_idle': self.root.push_handlers(*args) else: return EventDispatcher.event(self, *args)
def __init__(self, module, rootNode=None, node=None, isSelector=False, *args, **kwargs): """ @param modul: Name of the modul we shall handle. Must be a list application! @type modul: string @param rootNode: The rootNode we shall display. If None, we try to select one. @type rootNode: String or None @param node: The node we shall display at start. Must be a child of rootNode @type node: String or None """ super(TreeWidget, self).__init__() self["class"].append("tree") self.module = module self.rootNode = rootNode self.node = node or rootNode self.actionBar = ActionBar(module, "tree") self.appendChild(self.actionBar) self.pathList = html5.Div() self.pathList["class"].append("breadcrumb") self.appendChild(self.pathList) self.entryFrame = SelectableDiv(self.nodeWidget, self.leafWidget) self.appendChild(self.entryFrame) self.entryFrame.selectionActivatedEvent.register(self) self._batchSize = 99 self._currentCursor = {"node": None, "leaf": None} self._currentRequests = [] self.rootNodeChangedEvent = EventDispatcher("rootNodeChanged") self.nodeChangedEvent = EventDispatcher("nodeChanged") self.isSelector = isSelector if self.rootNode: self.reloadData() else: NetworkService.request(self.module, "listRootNodes", successHandler=self.onSetDefaultRootNode) self.path = [] self.sinkEvent("onClick") #Proxy some events and functions of the original table for f in [ "selectionChangedEvent", "selectionActivatedEvent", "cursorMovedEvent", "getCurrentSelection", "selectionReturnEvent" ]: setattr(self, f, getattr(self.entryFrame, f)) self.actionBar.setActions(self.defaultActions + (["select", "close"] if isSelector else []))
def __init__(self, moduleName, boneName, readOnly, isPlainText, languages=None, descrHint=None, *args, **kwargs): super(TextEditBone, self).__init__(*args, **kwargs) self.moduleName = moduleName self.boneName = boneName self.readOnly = readOnly self.selectedLang = False self.isPlainText = isPlainText self.languages = languages self.descrHint = descrHint self.value = {} # multilangbone if self.languages: if "currentlanguage" in conf and conf[ "currentlanguage"] in self.languages: self.selectedLang = conf["currentlanguage"] elif len(self.languages) > 0: self.selectedLang = self.languages[0] self.langButContainer = html5.Div() self.langButContainer["class"].append("languagebuttons") for lang in self.languages: abut = html5.ext.Button(lang, self.changeLang) abut["value"] = lang self.langButContainer.appendChild(abut) self.appendChild(self.langButContainer) self._refreshBtnStates() if not readOnly and not self.isPlainText: self.input = HtmlEditor() self.input.boneName = self.boneName else: self.input = html5.Textarea() if readOnly: self.input["readonly"] = True self.appendChild(self.input) self.sinkEvent("onKeyUp") self.changeEvent = EventDispatcher("boneChange")
def __init__(self, checkboxes=False, indexes=False, *args, **kwargs): super(SelectTable, self).__init__(*args, **kwargs) #Events self.selectionChangedEvent = EventDispatcher("selectionChanged") self.selectionActivatedEvent = EventDispatcher("selectionActivated") self.cursorMovedEvent = EventDispatcher("cursorMoved") self.tableChangedEvent = EventDispatcher("tableChanged") self.sinkEvent("onDblClick", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp", "onMouseOut", "onChange") self["tabindex"] = 1 self._selectedRows = [] # List of row-indexes currently selected self._currentRow = None # Rowindex of the cursor row self._isMouseDown = False # Tracks status of the left mouse button self._isCtlPressed = False # Tracks status of the ctrl key self._isShiftPressed = False # Tracks status of the shift key self._ctlStartRow = None # Stores the row where a multi-selection (using the ctrl key) started self._selectionChangedListener = [ ] # All objects getting informed when the selection changes self._selectionActivatedListeners = [ ] # All objects getting informed when items are selected self._cursorMovedListeners = [ ] # All objects getting informed when the cursor moves self.indexes = indexes self.indexes_col = 0 if indexes else -1 self._checkboxes = { } # The checkbox items per row (better to use a dict!) self.checkboxes = checkboxes self.checkboxes_col = (self.indexes_col + 1) if checkboxes else -1
def __init__(self, extension, view, module, *args, **kwargs): super(RelationalSearch, self).__init__(*args, **kwargs) self.view = view self.extension = extension self.module = module self.currentSelection = None self.filterChangedEvent = EventDispatcher("filterChanged") self.appendChild(html5.TextNode("RELATIONAL SEARCH")) self.appendChild(html5.TextNode(extension["name"])) self.currentEntry = html5.Span() self.appendChild(self.currentEntry) btn = html5.ext.Button("Select", self.openSelector) self.appendChild(btn) btn = html5.ext.Button("Clear", self.clearSelection) self.appendChild(btn)
def __init__(self, *args, **kwargs): super(Search, self).__init__(*args, **kwargs) self.startSearchEvent = EventDispatcher("startSearch") self["class"].append("search") lblSearch = html5.H2() lblSearch.appendChild(html5.TextNode(translate("Fulltext search"))) self.appendChild(lblSearch) self.searchInput = html5.Input() self.searchInput["type"] = "text" self.appendChild(self.searchInput) self.btn = html5.ext.Button(translate("Search"), callback=self.doSearch) self.appendChild(self.btn) self.sinkEvent("onKeyDown") self.last_search = ""
def __init__(self, moduleName, boneName, readOnly, using, *args, **kwargs): super(RecordMultiBone, self).__init__(*args, **kwargs) self.addClass("recordbone", "recordbone-multi") self.moduleName = moduleName self.boneName = boneName self.readOnly = readOnly self.using = using self.changeEvent = EventDispatcher("boneChange") self.entries = [] self.extendedErrorInformation = {} self.currentDrag = None self.currentOver = None self.itemsDiv = html5.Div() self.itemsDiv.addClass("recordbone-entries") self.appendChild(self.itemsDiv) self.addBtn = html5.ext.Button("Add", self.onAddBtnClick) self.addBtn.addClass("icon", "add") self.appendChild(self.addBtn) if self.readOnly: self["disabled"] = True self.sinkEvent("onDragOver")
class ExtendedStringSearch(html5.Div): def __init__(self, extension, view, module, *args, **kwargs): super(ExtendedStringSearch, self).__init__(*args, **kwargs) self.view = view self.extension = extension self.module = module self.opMode = extension["mode"] self.filterChangedEvent = EventDispatcher("filterChanged") assert self.opMode in ["equals", "from", "to", "prefix", "range"] self.appendChild(html5.TextNode(extension["name"])) self.sinkEvent("onKeyDown") if self.opMode in ["equals", "from", "to", "prefix"]: self.input = html5.Input() self.input["type"] = "text" self.appendChild(self.input) elif self.opMode == "range": self.input1 = html5.Input() self.input1["type"] = "text" self.appendChild(self.input1) self.appendChild(html5.TextNode("to")) self.input2 = html5.Input() self.input2["type"] = "text" self.appendChild(self.input2) def onKeyDown(self, event): if html5.isReturn(event): self.filterChangedEvent.fire() def updateFilter(self, filter): if self.opMode == "equals": filter[self.extension["target"]] = self.input["value"] elif self.opMode == "from": filter[self.extension["target"] + "$gt"] = self.input["value"] elif self.opMode == "to": filter[self.extension["target"] + "$lt"] = self.input["value"] elif self.opMode == "prefix": filter[self.extension["target"] + "$lk"] = self.input["value"] elif self.opMode == "range": filter[self.extension["target"] + "$gt"] = self.input1["value"] filter[self.extension["target"] + "$lt"] = self.input2["value"] return (filter) @staticmethod def canHandleExtension(extension, view, module): return (isinstance(extension, dict) and "type" in extension.keys() and (extension["type"] == "string" or extension["type"].startswith("string.")))
class RelationalSearch(html5.Div): def __init__(self, extension, view, module, *args, **kwargs): super(RelationalSearch, self).__init__(*args, **kwargs) self.view = view self.extension = extension self.module = module self.currentSelection = None self.filterChangedEvent = EventDispatcher("filterChanged") self.appendChild(html5.TextNode("RELATIONAL SEARCH")) self.appendChild(html5.TextNode(extension["name"])) self.currentEntry = html5.Span() self.appendChild(self.currentEntry) btn = html5.ext.Button("Select", self.openSelector) self.appendChild(btn) btn = html5.ext.Button("Clear", self.clearSelection) self.appendChild(btn) def clearSelection(self, *args, **kwargs): self.currentSelection = None self.filterChangedEvent.fire() def openSelector(self, *args, **kwargs): currentSelector = ListWidget(self.extension["module"], selectMode="multi") currentSelector.selectionActivatedEvent.register(self) conf["mainWindow"].stackWidget(currentSelector) def onSelectionActivated(self, table, selection): self.currentSelection = selection self.filterChangedEvent.fire() def updateFilter(self, filter): if self.currentSelection: self.currentEntry.element.innerHTML = self.currentSelection[0][ "name"] newId = self.currentSelection[0]["key"] filter[self.extension["target"] + ".key"] = newId else: self.currentEntry.element.innerHTML = "" return (filter) @staticmethod def canHandleExtension(extension, view, module): return (isinstance(extension, dict) and "type" in extension.keys() and (extension["type"] == "relational" or extension["type"].startswith("relational.")))
def __init__(self, nodeWidget, leafWidget, selectionType="both", multiSelection=False, *args, **kwargs): super(SelectionContainer, self).__init__(*args, **kwargs) self["class"].append("selectioncontainer") self["tabindex"] = 1 self.selectionType = selectionType self.multiSelection = multiSelection self.nodeWidget = nodeWidget self.leafWidget = leafWidget self.selectionChangedEvent = EventDispatcher("selectionChanged") self.selectionActivatedEvent = EventDispatcher( "selectionActivated" ) # Double-Click on an currently selected item - ITEM CLICKED !!!! self.selectionReturnEvent = EventDispatcher( "selectionReturn" ) # The current selection should be returned to parent widget self.cursorMovedEvent = EventDispatcher("cursorMoved") self._selectedItems = [] # List of row-indexes currently selected self._currentItem = None # Rowindex of the cursor row self._isMouseDown = False # Tracks status of the left mouse button self._isCtlPressed = False # Tracks status of the ctrl key self._ctlStartRow = None # Stores the row where a multi-selection (using the ctrl key) started self.sinkEvent("onClick", "onDblClick", "onMouseMove", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp")
class Search(html5.Div): def __init__(self, *args, **kwargs): super(Search, self).__init__(*args, **kwargs) self.startSearchEvent = EventDispatcher("startSearch") self["class"].append("search") lblSearch = html5.H2() lblSearch.appendChild(html5.TextNode(translate("Fulltext search"))) self.appendChild(lblSearch) self.searchInput = html5.Input() self.searchInput["type"] = "text" self.appendChild(self.searchInput) self.btn = html5.ext.Button(translate("Search"), callback=self.doSearch) self.appendChild(self.btn) self.sinkEvent("onKeyDown") self.last_search = "" def doSearch(self, *args, **kwargs): if self.searchInput["value"] != self.last_search: if len(self.searchInput["value"]): self.startSearchEvent.fire(self.searchInput["value"]) else: self.resetSearch() self.last_search = self.searchInput["value"] def resetSearch(self): self.startSearchEvent.fire(None) def onKeyDown(self, event): if html5.isReturn(event): self.doSearch() event.preventDefault() event.stopPropagation() def resetLoadingState(self): if "is_loading" in self.btn["class"]: self.btn["class"].remove("is_loading") def reevaluate(self): self.doSearch() def focus(self): self.searchInput.focus()
def __init__(self, extension, view, modul, *args, **kwargs): super(ExtendedSelectMultiSearch, self).__init__(*args, **kwargs) self.view = view self.extension = extension self.module = modul self.filterChangedEvent = EventDispatcher("filterChanged") self.appendChild(html5.TextNode(extension["name"])) self.selectionCb = html5.Select() self.appendChild(self.selectionCb) o = html5.Option() o["value"] = "" o.appendChild(html5.TextNode(translate("Ignore"))) self.selectionCb.appendChild(o) for k, v in extension["values"].items(): o = html5.Option() o["value"] = k o.appendChild(html5.TextNode(v)) self.selectionCb.appendChild(o) self.sinkEvent("onChange")
class ExtendedSelectMultiSearch(html5.Div): def __init__(self, extension, view, modul, *args, **kwargs): super(ExtendedSelectMultiSearch, self).__init__(*args, **kwargs) self.view = view self.extension = extension self.module = modul self.filterChangedEvent = EventDispatcher("filterChanged") self.appendChild(html5.TextNode(extension["name"])) self.selectionCb = html5.Select() self.appendChild(self.selectionCb) o = html5.Option() o["value"] = "" o.appendChild(html5.TextNode(translate("Ignore"))) self.selectionCb.appendChild(o) for k, v in extension["values"].items(): o = html5.Option() o["value"] = k o.appendChild(html5.TextNode(v)) self.selectionCb.appendChild(o) self.sinkEvent("onChange") def onChange(self, event): event.stopPropagation() self.filterChangedEvent.fire() def updateFilter(self, filter): val = self.selectionCb["options"].item( self.selectionCb["selectedIndex"]).value if not val: if self.extension["target"] in filter.keys(): del filter[self.extension["target"]] else: filter[self.extension["target"]] = val return (filter) @staticmethod def canHandleExtension(extension, view, modul): return (isinstance(extension, dict) and "type" in extension.keys() and (((extension["type"] == "select" or extension["type"].startswith("select.")) and extension.get("multiple", False)) or (extension["type"] == "selectmulti" or extension["type"].startswith("selectmulti."))))
def __init__(self, file, node, context=None, *args, **kwargs): """ :param file: The file to upload :type file: A javascript "File" Object :param node: Key of the desired node of our parents tree application or None for an anonymous upload. :type node: str or None """ super(Uploader, self).__init__(*args, **kwargs) self.uploadSuccess = EventDispatcher("uploadSuccess") self.responseValue = None self.context = context # self.files = files r = NetworkService.request("file", "getUploadURL", successHandler=self.onUploadUrlAvailable, secure=True) r.file = file r.node = node conf["mainWindow"].log("progress", self) self.parent()["class"].append("is_uploading")
def __init__(self, extension, view, module, *args, **kwargs): super(DateRangeFilterPlugin, self).__init__(*args, **kwargs) self.view = view self.extension = extension self.module = module self.mutualExclusiveGroupTarget = "daterange-filter" self.mutualExclusiveGroupKey = extension["target"] self.filterChangedEvent = EventDispatcher("filterChanged") title = html5.H2() title.appendChild(html5.TextNode(extension["name"])) self.appendChild(title) # from daterange value self.beginDatepicker = html5.Input() self.beginDatepicker["type"] = "date" self.beginDatepicker["id"] = "begin-date-%s" % self.extension["target"] self.beginDatepicker["class"] = "js-extended-date-range-filter" beginLabel = html5.Label(translate("Begin date")) beginLabel["for"] = "begin-date-%s" % self.extension["target"] span = html5.Span() span.appendChild(beginLabel) span.appendChild(self.beginDatepicker) self.appendChild(span) # to daterange value self.toDatepicker = html5.Input() self.toDatepicker["type"] = "date" self.toDatepicker["id"] = "to-date-%s" % self.extension["target"] self.toDatepicker[ "class"] = "extended-date-range-filter %s" % self.extension[ "target"] toLabel = html5.Label(translate("End date")) toLabel["for"] = "to-date-%s" % self.extension["target"] span2 = html5.Span() span2.appendChild(toLabel) span2.appendChild(self.toDatepicker) self.appendChild(span2) self.clearButton = html5.ext.Button(translate("Clear filter"), self.clearFilter) self.appendChild(self.clearButton) self.sinkEvent("onChange")
def __init__(self, _loadOnDisplay=False, *args, **kwargs): super(DataTable, self).__init__() self.table = SelectTable(*args, **kwargs) self.appendChild(self.table) self._loadOnDisplay = _loadOnDisplay # Load all data content continuously when displaying self._model = [] # List of values we are displaying right now self._shownFields = [] # List of keys we display from the model self._modelIdx = 0 # Internal counter to distinguish between 2 rows with identical data self._isAjaxLoading = False # Determines if we already requested the next batch of rows self._dataProvider = None # Which object to call if we need more data self._cellRender = {} # Map of renders for a given field # We re-emit some events with custom parameters self.selectionChangedEvent = EventDispatcher("selectionChanged") self.selectionActivatedEvent = EventDispatcher("selectionActivated") self.tableChangedEvent = EventDispatcher("tableChanged") self.table.selectionChangedEvent.register(self) self.table.selectionActivatedEvent.register(self) self.table.tableChangedEvent.register(self) #Proxy some events and functions of the original table for f in ["cursorMovedEvent", "setHeader"]: setattr(self, f, getattr(self.table, f)) self.cursorMovedEvent.register(self) self["style"]["overflow"] = "scroll" self.recalcHeight() self.sinkEvent("onScroll")
def __init__(self, extension, view, module, *args, **kwargs): super(ExtendedStringSearch, self).__init__(*args, **kwargs) self.view = view self.extension = extension self.module = module self.opMode = extension["mode"] self.filterChangedEvent = EventDispatcher("filterChanged") assert self.opMode in ["equals", "from", "to", "prefix", "range"] self.appendChild(html5.TextNode(extension["name"])) self.sinkEvent("onKeyDown") if self.opMode in ["equals", "from", "to", "prefix"]: self.input = html5.Input() self.input["type"] = "text" self.appendChild(self.input) elif self.opMode == "range": self.input1 = html5.Input() self.input1["type"] = "text" self.appendChild(self.input1) self.appendChild(html5.TextNode("to")) self.input2 = html5.Input() self.input2["type"] = "text" self.appendChild(self.input2)
def __init__(self, extension, view, module, *args, **kwargs): super(ExtendedBooleanSearch, self).__init__(*args, **kwargs) self.view = view self.extension = extension self.module = module self.filterChangedEvent = EventDispatcher("filterChanged") self.appendChild(html5.TextNode(extension["name"])) self.selectionCb = html5.Select() self.appendChild(self.selectionCb) o = html5.Option() o["value"] = "" o.appendChild(html5.TextNode(translate("Ignore"))) self.selectionCb.appendChild(o) o = html5.Option() o["value"] = "0" o.appendChild(html5.TextNode(translate("No"))) self.selectionCb.appendChild(o) o = html5.Option() o["value"] = "1" o.appendChild(html5.TextNode(translate("Yes"))) self.selectionCb.appendChild(o) self.sinkEvent("onChange")
class System: _dispatcher = EventDispatcher() def __init__(self): self._entity = Entity.next() def on(self, event, handler): self._dispatcher.on(event, self.entity, handler) def emit(self, event_type, target, **kwargs): self._dispatcher.emit(event_type, target, self.entity, **kwargs) @property def entity(self): return self._entity
def __init__(self, moduleName, boneName, using, readOnly, required, *args, **kwargs): super(RecordSingleBone, self).__init__(*args, **kwargs) self.addClass("recordbone", "recordbone-single") self.moduleName = moduleName self.boneName = boneName self.readOnly = readOnly self.required = required self.using = using self.mask = None self.changeEvent = EventDispatcher("boneChange") if self.readOnly: self["disabled"] = True
def __init__(self, module, rootNode=None, isSelector=False, context=None, *args, **kwargs): """ @param module: Name of the modul we shall handle. Must be a hierarchy application! @type module: string @param rootNode: The repository we shall display. If none, we try to select one. @type rootNode: String or None """ super(HierarchyWidget, self).__init__() self.module = module self.rootNode = rootNode self.actionBar = ActionBar(module, "hierarchy") self.appendChild(self.actionBar) self.entryFrame = html5.Ol() self.entryFrame["class"].append("hierarchy") self.appendChild(self.entryFrame) self.selectionChangedEvent = EventDispatcher("selectionChanged") self.selectionActivatedEvent = EventDispatcher("selectionActivated") self.rootNodeChangedEvent = EventDispatcher("rootNodeChanged") self._currentCursor = None self._currentRequests = [] self.addClass("supports_drop") self.isSelector = isSelector self._expandedNodes = [] self.context = context if self.rootNode: self.reloadData() else: NetworkService.request(self.module, "listRootNodes", self.context or {}, successHandler=self.onSetDefaultRootNode, failureHandler=self.showErrorMsg) self.path = [] self.sinkEvent("onClick", "onDblClick") ##Proxy some events and functions of the original table #for f in ["selectionChangedEvent","selectionActivatedEvent","cursorMovedEvent","getCurrentSelection"]: # setattr( self, f, getattr(self.table,f)) self.actionBar.setActions( ["selectrootnode", "add", "edit", "clone", "delete"] + (["select", "close"] if isSelector else []) + ["reload"]) self.sinkEvent("onDrop", "onDragOver")
class Uploader(html5.Progress): """ Uploads a file to the server while providing visual feedback of the progress. """ def __init__(self, file, node, context=None, *args, **kwargs): """ :param file: The file to upload :type file: A javascript "File" Object :param node: Key of the desired node of our parents tree application or None for an anonymous upload. :type node: str or None """ super(Uploader, self).__init__(*args, **kwargs) self.uploadSuccess = EventDispatcher("uploadSuccess") self.responseValue = None self.context = context # self.files = files r = NetworkService.request("file", "getUploadURL", successHandler=self.onUploadUrlAvailable, secure=True) r.file = file r.node = node conf["mainWindow"].log("progress", self) self.parent()["class"].append("is_uploading") def onUploadUrlAvailable(self, req): """ Internal callback - the actual upload url (retrieved by calling /file/getUploadURL) is known. """ r = NetworkService.request("", "/admin/skey", successHandler=self.onSkeyAvailable) r.file = req.file r.node = req.node r.destUrl = req.result def onSkeyAvailable(self, req): """ Internal callback - the Security-Key is known. """ formData = eval("new FormData();") formData.append("file", req.file) if self.context: for k, v in self.context.items(): formData.append(k, v) if req.node and str(req.node) != "null": formData.append("node", req.node) formData.append("skey", NetworkService.decode(req)) self.xhr = eval("new XMLHttpRequest()") self.xhr.open("POST", req.destUrl) self.xhr.onload = self.onLoad self.xhr.upload.onprogress = self.onProgress self.xhr.send(formData) def onLoad(self, *args, **kwargs): """ Internal callback - The state of our upload changed. """ if self.xhr.status == 200: self.responseValue = json.loads(self.xhr.responseText) DeferredCall(self.onSuccess, _delay=1000) else: DeferredCall(self.onFailed, self.xhr.status, _delay=1000) def onProgress(self, event): """ Internal callback - further bytes have been transmitted """ if event.lengthComputable: complete = int(event.loaded / event.total * 100) self["value"] = complete self["max"] = 100 def onSuccess(self, *args, **kwargs): """ Internal callback - The upload succeeded. """ for v in self.responseValue["values"]: self.uploadSuccess.fire(self, v) NetworkService.notifyChange("file") self.replaceWithMessage("Upload complete", isSuccess=True) def onFailed(self, errorCode, *args, **kwargs): self.replaceWithMessage("Upload failed with status code %s" % errorCode, isSuccess=False) def replaceWithMessage(self, message, isSuccess): self.parent()["class"].remove("is_uploading") self.parent()["class"].remove("log_progress") if isSuccess: self.parent()["class"].append("log_success") else: self.parent()["class"].append("log_failed") msg = html5.Span() msg.appendChild(html5.TextNode(message)) self.parent().appendChild(msg) self.parent().removeChild(self)
class SelectTable(html5.Table): """ Provides an Html-Table which allows for row selections. Parent widgets can register for certain events: - selectionChanged: called if the current _multi_ selection changes. (Ie the user holds ctrl and clicks a row). The selection might contain no, one or multiple rows. Its also called if the cursor moves. Its called if the user simply double clicks a row. So its possible to receive a selectionActivated event without an selectionChanged Event. - selectionActivated: called if a selection is activated, ie. a row is double-clicked or Return is pressed. - cursorMoved: called when the currently active row changes. The user can select the current row with a single click or by moving the cursor up and down using the arrow keys. """ def __init__(self, checkboxes=False, indexes=False, *args, **kwargs): super(SelectTable, self).__init__(*args, **kwargs) #Events self.selectionChangedEvent = EventDispatcher("selectionChanged") self.selectionActivatedEvent = EventDispatcher("selectionActivated") self.cursorMovedEvent = EventDispatcher("cursorMoved") self.tableChangedEvent = EventDispatcher("tableChanged") self.sinkEvent("onDblClick", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp", "onMouseOut", "onChange") self["tabindex"] = 1 self._selectedRows = [] # List of row-indexes currently selected self._currentRow = None # Rowindex of the cursor row self._isMouseDown = False # Tracks status of the left mouse button self._isCtlPressed = False # Tracks status of the ctrl key self._isShiftPressed = False # Tracks status of the shift key self._ctlStartRow = None # Stores the row where a multi-selection (using the ctrl key) started self._selectionChangedListener = [ ] # All objects getting informed when the selection changes self._selectionActivatedListeners = [ ] # All objects getting informed when items are selected self._cursorMovedListeners = [ ] # All objects getting informed when the cursor moves self.indexes = indexes self.indexes_col = 0 if indexes else -1 self._checkboxes = { } # The checkbox items per row (better to use a dict!) self.checkboxes = checkboxes self.checkboxes_col = (self.indexes_col + 1) if checkboxes else -1 def onAttach(self): super(SelectTable, self).onAttach() self.focus() def setHeader(self, headers): """ Sets the table-headers to 'headers' :param headers: list of strings :type headers: list """ tr = html5.Tr() # Extra column for Index#s if self.indexes: th = html5.Th() th["class"] = "index" tr.appendChild(th) # Extra column for checkboxes if self.checkboxes: th = html5.Th() # fixme.. th["class"] = "check" tr.appendChild(th) # Now every title column for head in headers: th = html5.Th() th.appendChild(html5.TextNode(head)) tr.appendChild(th) self.head.removeAllChildren() self.head.appendChild(tr) def getTrByIndex(self, idx): """ Retrieves the TR element by the given row number :param idx: Rownumber to retrieve the tr of :type idx: int :returns: HTMLTableRowElement """ for c in self.body._children: if idx <= 0: return c idx -= c["rowspan"] return None def getIndexByTr(self, tr): """ Returns the rowNumber for the given tr element or None if the given tr element is invalid. :param tr: A HTMLTableRowElement of this table :type tr: HTMLTableRowElement :returns: int or None """ idx = 0 for c in self.body._children: if c.element == tr: return (idx) idx += c["rowspan"] return (idx) def _rowForEvent(self, event): """ Determines the row number for the given event """ try: # Chromium tc = event.srcElement except: # Firefox tc = event.target if tc is None: return (None) tr = tc.parentElement while (tr.parentElement is not None): if tr.parentElement == self.body.element: return (tr) tr = tr.parentElement return (None) def onChange(self, event): tr = self._rowForEvent(event) if tr is None: return row = self.getIndexByTr(tr) if self.checkboxes and html5.utils.doesEventHitWidgetOrChildren( event, self._checkboxes[row]): self._checkboxes[row]["checked"] = row in self._selectedRows def onMouseDown(self, event): tr = self._rowForEvent(event) if tr is None: return row = self.getIndexByTr(tr) if self._isCtlPressed: if row in self._selectedRows: for x in self._selectedRows: self.getTrByIndex(x).removeClass( "is_focused") # remove focus self.removeSelectedRow(row) else: self.addSelectedRow(row) self.setCursorRow(row, False) # set focus event.preventDefault() elif self._isShiftPressed: self.unSelectAll() for i in (range(self._ctlStartRow, row + 1) if self._ctlStartRow <= row else range( row, self._ctlStartRow + 1)): self.addSelectedRow(i) self.setCursorRow(row, False) # set focus event.preventDefault() elif self.checkboxes and html5.utils.doesEventHitWidgetOrChildren( event, self._checkboxes[row]): if row in self._selectedRows: self.removeSelectedRow(row) else: self.addSelectedRow(row) else: self._isMouseDown = True if self.checkboxes: if row in self._selectedRows: self.removeSelectedRow(row) else: self.addSelectedRow(row) self.setCursorRow(self.getIndexByTr(tr), removeExistingSelection=not self.checkboxes) self.focus() def onMouseOut(self, event): self._isMouseDown = False def onMouseUp(self, event): self._isMouseDown = False def onKeyDown(self, event): if html5.isArrowDown(event): # Arrow down if self._currentRow is None: self.setCursorRow(0) else: if self._isCtlPressed or self._isShiftPressed: if self._ctlStartRow > self._currentRow: self.removeSelectedRow(self._currentRow) else: self.addSelectedRow(self._currentRow) if self._currentRow + 1 < self.getRowCount(): self.addSelectedRow(self._currentRow + 1) if self._currentRow + 1 < self.getRowCount(): self.setCursorRow( self._currentRow + 1, removeExistingSelection=(not self._isShiftPressed and not self._isCtlPressed)) event.preventDefault() elif html5.isArrowUp(event): # Arrow up if self._currentRow is None: self.setCursorRow(0) else: if self._isCtlPressed or self._isShiftPressed: # Check if we extend a selection if self._ctlStartRow < self._currentRow: self.removeSelectedRow(self._currentRow) else: self.addSelectedRow(self._currentRow) if self._currentRow > 0: self.addSelectedRow(self._currentRow - 1) if self._currentRow > 0: # Move the cursor if possible self.setCursorRow( self._currentRow - 1, removeExistingSelection=(not self._isShiftPressed and not self._isCtlPressed)) event.preventDefault() elif html5.isReturn(event): # Return if len(self._selectedRows) > 0: self.selectionActivatedEvent.fire(self, self._selectedRows) event.preventDefault() return if self._currentRow is not None: self.selectionActivatedEvent.fire(self, [self._currentRow]) event.preventDefault() return elif html5.isControl(event): # Ctrl self._isCtlPressed = True self._ctlStartRow = self._currentRow or 0 if self._currentRow is not None: self.addSelectedRow( self._currentRow) # add already selected row to selection elif html5.isShift(event): # Shift self._isShiftPressed = True try: self._ctlStartRow = self._currentRow or self._selectedRows[ 0] or 0 except: self._ctlStartRow = 0 def onKeyUp(self, event): if html5.isControl(event): self._isCtlPressed = False self._ctlStartRow = None # leave selection mode if there is only one row selected and return to normal focus if len(self._selectedRows) == 1: for row in self.getCurrentSelection(): self.removeSelectedRow(row) elif html5.isShift(event): self._isShiftPressed = False self._ctlStartRow = None def onDblClick(self, event): if self._currentRow is not None: self.selectionActivatedEvent.fire(self, [self._currentRow]) event.preventDefault() def addSelectedRow(self, row): """ Marks a row as selected """ if row in self._selectedRows: return self._selectedRows.append(row) tr = self.getTrByIndex(row) tr["class"].append("is_selected") if self.checkboxes: self._checkboxes[row]["checked"] = True self.selectionChangedEvent.fire(self, self.getCurrentSelection()) def removeSelectedRow(self, row): """ Removes 'row' from the current selection (if any) :param row: Number of the row to unselect :type row: int """ if not row in self._selectedRows: return self._selectedRows.remove(row) tr = self.getTrByIndex(row) tr["class"].remove("is_selected") if self.checkboxes: self._checkboxes[row]["checked"] = False self.selectionChangedEvent.fire(self, self.getCurrentSelection()) def selectRow(self, newRow): """ Sets the current selection to 'row'. Any previous selection is removed. :param newRow: Number of the row to select :type newRow: int """ self.setCursorRow(newRow) for row in self._selectedRows[:]: if row != newRow: self.removeSelectedRow(row) if not newRow in self._selectedRows: self._selectedRows.append(newRow) tr = self.getTrByIndex(newRow) tr["class"].append("is_selected") self.selectionChangedEvent.fire(self, self.getCurrentSelection()) def setCursorRow(self, row, removeExistingSelection=True): """ Move the cursor to row 'row'. If removeExistingSelection is True, the current selection (if any) is invalidated. """ if self._currentRow is not None: self.getTrByIndex(self._currentRow)["class"].remove("is_focused") self._currentRow = row if self._currentRow is not None: self.getTrByIndex(self._currentRow)["class"].append("is_focused") self.cursorMovedEvent.fire(self, row) if removeExistingSelection: for row in self._selectedRows[:]: self.removeSelectedRow(row) self.selectionChangedEvent.fire(self, self.getCurrentSelection()) DeferredCall(self.focusRow, row) def focusRow(self, row): tr = self.getTrByIndex(row) # fixme: Re-implement maybe later? def getCurrentSelection(self): """ Returns a list of currently selected row-numbers :returns: list """ if self._selectedRows: return self._selectedRows[:] elif self._currentRow is not None: return [self._currentRow] return None def clear(self): """ Hook the clear() method so we can reset some internal states, too """ super(SelectTable, self).clear() self._currentRow = None self._selectedRows = [] self.selectionChangedEvent.fire(self, self.getCurrentSelection()) self.tableChangedEvent.fire(self, self.getRowCount()) def removeRow(self, row): """ Hook the removeRow method so we can reset some internal states, too """ if row in self._selectedRows: self._selectedRows.remove(row) self.selectionChangedEvent.fire(self) if self._currentRow == row: self._currentRow = None self.cursorMovedEvent.fire(self) self.tableChangedEvent.fire(self, self.getRowCount()) super(SelectTable, self).removeRow(row) def _extraCols(self): return int(self.checkboxes) + int(self.indexes) def prepareCol(self, row, col): """ Lets hook up the original removeRow function to optionally provide index and checkbox columns. """ super(SelectTable, self).prepareCol(row, col + self._extraCols() - 1) if self.checkboxes: checkbox = html5.Input() checkbox["type"] = "checkbox" checkbox["class"].append("check") checkbox["checked"] = False self["cell"][row][self.checkboxes_col] = checkbox self._checkboxes[row] = checkbox if self.indexes: lbl = html5.Label(str(row + 1)) lbl["class"].append("index") self["cell"][row][self.indexes_col] = lbl self.tableChangedEvent.fire(self, self.getRowCount()) def setCell(self, row, col, val): """ Interface for self["cell"] that directs to the correct cell if extra columns are configured for this SelectTable. """ self["cell"][row][col + self._extraCols()] = val def selectAll(self): """ Selects all entries of the table. """ self._selectedRows = range(0, self.getRowCount()) for row in self._selectedRows: tr = self.getTrByIndex(row) if not "is_selected" in tr["class"]: tr["class"].append("is_selected") if self.checkboxes: self._checkboxes[row]["checked"] = True self.selectionChangedEvent.fire(self, self.getCurrentSelection()) return len(self._selectedRows) def unSelectAll(self): """ Unselects all entries of the table. """ unsel = len(self._selectedRows) for row in self._selectedRows: tr = self.getTrByIndex(row) tr["class"].remove("is_selected") if self.checkboxes: self._checkboxes[row]["checked"] = False self._selectedRows = [] self.selectionChangedEvent.fire(self, self.getCurrentSelection()) return unsel def invertSelection(self): """ Inverts the current selection on the whole table currently displayed. """ current = self._selectedRows[:] self._selectedRows = [] for row in range(0, self.getRowCount()): tr = self.getTrByIndex(row) if row in current: tr["class"].remove("is_selected") else: self._selectedRows.append(row) tr["class"].append("is_selected") if self.checkboxes: self._checkboxes[row]["checked"] = row in self._selectedRows self.selectionChangedEvent.fire(self, self.getCurrentSelection()) return len(self._selectedRows), len(current)
class DataTable(html5.Div): """ Provides kind of MVC on top of SelectTable. """ def __init__(self, _loadOnDisplay=False, *args, **kwargs): super(DataTable, self).__init__() self.table = SelectTable(*args, **kwargs) self.appendChild(self.table) self._loadOnDisplay = _loadOnDisplay # Load all data content continuously when displaying self._model = [] # List of values we are displaying right now self._shownFields = [] # List of keys we display from the model self._modelIdx = 0 # Internal counter to distinguish between 2 rows with identical data self._isAjaxLoading = False # Determines if we already requested the next batch of rows self._dataProvider = None # Which object to call if we need more data self._cellRender = {} # Map of renders for a given field # We re-emit some events with custom parameters self.selectionChangedEvent = EventDispatcher("selectionChanged") self.selectionActivatedEvent = EventDispatcher("selectionActivated") self.tableChangedEvent = EventDispatcher("tableChanged") self.table.selectionChangedEvent.register(self) self.table.selectionActivatedEvent.register(self) self.table.tableChangedEvent.register(self) #Proxy some events and functions of the original table for f in ["cursorMovedEvent", "setHeader"]: setattr(self, f, getattr(self.table, f)) self.cursorMovedEvent.register(self) self["style"]["overflow"] = "scroll" self.recalcHeight() self.sinkEvent("onScroll") def recalcHeight(self, *args, **kwargs): self["style"]["max-height"] = "%spx" % ( int(eval("window.top.innerHeight")) - 280) def setDataProvider(self, obj): """ Register's 'obj' as the provider for this table. It must provide a onNextBatchNeeded function, which must fetch and feed new rows using add() or reset the dataProvider to None if no more rows are available. Notice: If the bottom of the table is reached, onNextBatchNeeded will only be called once. No further calls will be made until add() or setDataProvider() has been called afterwards. """ assert obj==None or "onNextBatchNeeded" in dir(obj),\ "The dataProvider must provide a 'onNextBatchNeeded' function" self._dataProvider = obj self._isAjaxLoading = False if "is_loading" in self.table["class"]: self.table["class"].remove("is_loading") def onCursorMoved(self, table, row): """ Ensure the table scrolls according to the position of its cursor """ tr = table.getTrByIndex(row) if tr is None: return return #FIXME if self.element.scrollTop > tr.offsetTop: self.element.scrollTop = tr.offsetTop elif self.element.scrollTop + self.element.clientHeight < tr.offsetTop: self.element.scrollTop = tr.offsetTop + tr.clientHeight - self.element.clientHeight def getRowCount(self): """ Returns the total amount of rows currently known. :returns: int """ return (len(self._model)) def add(self, obj): """ Adds an row to the model :param obj: Dictionary of values for this row :type obj: dict """ obj["_uniqeIndex"] = self._modelIdx self._modelIdx += 1 self._model.append(obj) self._renderObject(obj) self._isAjaxLoading = False if "is_loading" in self.table["class"]: self.table["class"].remove("is_loading") self.testIfNextBatchNeededImmediately() def extend(self, objList): """ Adds multiple rows at once. Much faster than calling add() multiple times. """ self.table.prepareGrid(len(objList), len(self._shownFields)) for obj in objList: obj["_uniqeIndex"] = self._modelIdx self._modelIdx += 1 self._model.append(obj) self._renderObject(obj, tableIsPrepared=True) self._isAjaxLoading = False if "is_loading" in self.table["class"]: self.table["class"].remove("is_loading") self.testIfNextBatchNeededImmediately() def testIfNextBatchNeededImmediately(self): """ Test if we display enough entries so that our contents are scrollable. Otherwise, we'll never request a second batch """ sumHeight = 0 for c in self.table._children: if "clientHeight" in dir(c.element): sumHeight += c.element.clientHeight if not sumHeight: # We'll get no height if not visible, so we'll append our self to the body for a moment parent = self.parent() parent.removeChild(self) html5.Body().appendChild(self) sumHeight = self.table.element.clientHeight html5.Body().removeChild(self) parent.appendChild(self) if (not self._isAjaxLoading and (self._loadOnDisplay or not sumHeight > int(self["style"]["max-height"][:-2]))): if self._dataProvider: self._isAjaxLoading = True if not "is_loading" in self.table["class"]: self.table["class"].append("is_loading") self._dataProvider.onNextBatchNeeded() def remove(self, objOrIndex): """ Removes 'obj' from the table. 'obj' may be an row-index or an object recieved by any eventListener. It _cannot_ be any original object passed to 'add' - it _must_ be recived by an eventListener! """ if isinstance(objOrIndex, dict): assert objOrIndex in self._model, "Cannot remove unknown object from Table" objOrIndex = self._model.index(objOrIndex) if isinstance(objOrIndex, int): assert objOrIndex > 0 and objOrIndex < len( self._model), "Modelindex out of range" self._model.remove(self._model[objOrIndex]) self.table.removeRow(objOrIndex) else: raise TypeError("Expected int or dict, got %s" % str(type(objOrIndex))) def clear(self, keepModel=False): """ Flushes the whole table. """ self.table.clear() if not keepModel: self._model = [] def _renderObject(self, obj, tableIsPrepared=False): """ Renders the object to into the table. Does nothing if the list of _shownFields is empty. :param obj: Dictionary of values for this row :type obj: dict """ if not self._shownFields: return rowIdx = self._model.index(obj) cellIdx = 0 if not tableIsPrepared: self.table.prepareCol(rowIdx, len(self._shownFields) - 1) for field in self._shownFields: if field in self._cellRender.keys(): lbl = self._cellRender[field].render(obj, field) elif field in obj.keys(): lbl = html5.Label(obj[field]) else: lbl = html5.Label("...") self.table.setCell(rowIdx, cellIdx, lbl) cellIdx += 1 def rebuildTable(self): """ Rebuilds the entire table. Useful if something fundamental changed (ie. the cell renderer or the list of visible fields) """ self.clear(keepModel=True) self.table.prepareGrid(len(self._model), len(self._shownFields)) for obj in self._model: self._renderObject(obj, tableIsPrepared=True) def setShownFields(self, fields): """ Sets the list of _shownFields. This causes the whole table to be rebuild. Be careful if calling this function often on a large table! :param fields: List of model-keys which will be displayed. :type fields: list """ self._shownFields = fields self.rebuildTable() def onScroll(self, event): """ Check if we got a scroll event and need to fetch another set of rows from our dataProvider """ if self._loadOnDisplay: return self.recalcHeight() if ((self.element.scrollTop + self.element.clientHeight) >= self.element.scrollHeight and not self._isAjaxLoading): if self._dataProvider: self._isAjaxLoading = True if not "is_loading" in self.table["class"]: self.table["class"].append("is_loading") self._dataProvider.onNextBatchNeeded() def onSelectionChanged(self, table, rows): """ Re-emit the event. Maps row-numbers to actual models. """ vals = [self._model[x] for x in (rows or [])] self.selectionChangedEvent.fire(self, vals) def onSelectionActivated(self, table, rows): """ Re-emit the event. Maps row-numbers to actual models. """ vals = [self._model[x] for x in rows] self.selectionActivatedEvent.fire(self, vals) def onTableChanged(self, table, rowCount): """ Re-emit the event. """ self.tableChangedEvent.fire(self, rowCount) def getCurrentSelection(self): """ Override the getCurrentSelection method to yield actual models, not row-numbers. """ rows = self.table.getCurrentSelection() if not self._model or not rows: return ([]) return ([self._model[x] for x in rows]) def setCellRender(self, field, render): """ Sets the render for cells of 'field' to render. A cell render receives the data for a given cell and returns the appropriate widget to display that data for the table. """ if render is None: if field in self._cellRender.keys(): del self._cellRender[field] else: assert "render" in dir( render), "The render must provide a 'render' method" self._cellRender[field] = render self.rebuildTable() def setCellRenders(self, renders): """ Like setCellRender, but sets multiple renders at one. Much faster than calling setCellRender repeatedly. """ assert isinstance(renders, dict) for field, render in renders.items(): if render is None: if field in self._cellRender.keys(): del self._cellRender[field] else: assert "render" in dir( render), "The render must provide a 'render' method" self._cellRender[field] = render self.rebuildTable() def activateCurrentSelection(self): """ Emits the selectionActivated event if there's currently a selection """ selection = self.getCurrentSelection() if len(selection) > 0: self.selectionActivatedEvent.fire(self, selection)
def __init__(self, target, params): self._target = target self._params = params self.volume = 0.0 self.events = EventDispatcher(Growth.EVENTS, observer=target)
class SelectionContainer(html5.Ul): """ Provides a Container, which allows selecting its contents. Designed to be used within a tree widget, as it distinguishes between two different types of content (nodes and leafs) and allows selections to be restricted to a certain kind. """ def __init__(self, nodeWidget, leafWidget, selectionType="both", multiSelection=False, *args, **kwargs): super(SelectionContainer, self).__init__(*args, **kwargs) self["class"].append("selectioncontainer") self["tabindex"] = 1 self.selectionType = selectionType self.multiSelection = multiSelection self.nodeWidget = nodeWidget self.leafWidget = leafWidget self.selectionChangedEvent = EventDispatcher("selectionChanged") self.selectionActivatedEvent = EventDispatcher( "selectionActivated" ) # Double-Click on an currently selected item - ITEM CLICKED !!!! self.selectionReturnEvent = EventDispatcher( "selectionReturn" ) # The current selection should be returned to parent widget self.cursorMovedEvent = EventDispatcher("cursorMoved") self._selectedItems = [] # List of row-indexes currently selected self._currentItem = None # Rowindex of the cursor row self._isMouseDown = False # Tracks status of the left mouse button self._isCtlPressed = False # Tracks status of the ctrl key self._ctlStartRow = None # Stores the row where a multi-selection (using the ctrl key) started self.sinkEvent("onClick", "onDblClick", "onMouseMove", "onMouseDown", "onMouseUp", "onKeyDown", "onKeyUp") def setCurrentItem(self, item): """ Sets the currently selected item (=the cursor) to 'item' If there was such an item before, its unselected afterwards. """ if self._currentItem: self._currentItem["class"].remove("cursor") self._currentItem = item if item: item["class"].append("cursor") def onClick(self, event): self.focus() for child in self._children: if html5.utils.doesEventHitWidgetOrChildren(event, child): self.setCurrentItem(child) if self._isCtlPressed: self.addSelectedItem(child) if not self._isCtlPressed: self.clearSelection() if self._selectedItems: self.selectionChangedEvent.fire(self, self._selectedItems[:]) elif self._currentItem: self.selectionChangedEvent.fire(self, [self._currentItem]) else: self.selectionChangedEvent.fire(self, []) def onDblClick(self, event): for child in self.children(): if html5.utils.doesEventHitWidgetOrChildren(event, child): if self.selectionType == "both" or self.selectionType == child.skelType: self.selectionActivatedEvent.fire(self, [child]) break def activateCurrentSelection(self): """ Emits the selectionActivated event if there's currently a selection """ if len(self._selectedItems) > 0: self.selectionActivatedEvent.fire(self, self._selectedItems) elif self._currentItem is not None: self.selectionActivatedEvent.fire(self, [self._currentItem]) def returnCurrentSelection(self): """ Emits the selectionReturn event if there's currently a selection """ selection = [] if len(self._selectedItems) > 0: selection = self._selectedItems # self.selectionReturnEvent.fire( self, self._selectedItems ) elif self._currentItem is not None: selection = [self._currentItem] if self.selectionType == "node": selection = [ x for x in selection if isinstance(x, self.nodeWidget) ] elif self.selectionType == "leaf": selection = [ x for x in selection if isinstance(x, self.leafWidget) ] self.selectionReturnEvent.fire(self, selection) def onKeyDown(self, event): if html5.isReturn(event): # Return self.activateCurrentSelection() event.preventDefault() return elif html5.isControl( event): # and "multi" in (self.selectMode or ""): #Ctrl self._isCtlPressed = True def onKeyUp(self, event): if html5.isControl(event): self._isCtlPressed = False def clearSelection(self): for child in self._children[:]: self.removeSelectedItem(child) def addSelectedItem(self, item): # if not self.multiSelection: # return if self.selectionType == "node" and isinstance(item, self.nodeWidget) or \ self.selectionType == "leaf" and isinstance(item, self.leafWidget) or \ self.selectionType == "both": if not item in self._selectedItems: self._selectedItems.append(item) item["class"].append("selected") def removeSelectedItem(self, item): if not item in self._selectedItems: return self._selectedItems.remove(item) item["class"].remove("selected") def clear(self): self.clearSelection() for child in self._children[:]: self.removeChild(child) self.selectionChangedEvent.fire(self, []) def getCurrentSelection(self): if len(self._selectedItems) > 0: return self._selectedItems[:] if self._currentItem: return [self._currentItem] return None
class TextEditBone(html5.Div): def __init__(self, moduleName, boneName, readOnly, isPlainText, languages=None, descrHint=None, *args, **kwargs): super(TextEditBone, self).__init__(*args, **kwargs) self.boneName = boneName self.readOnly = readOnly self.selectedLang = False self.isPlainText = isPlainText self.languages = languages self.descrHint = descrHint self.valuesdict = dict() # multilangbone if self.languages: self.value = {lng: "" for lng in self.languages} if "currentlanguage" in conf and conf["currentlanguage"] in self.languages: self.selectedLang = conf["currentlanguage"] elif len(self.languages) > 0: self.selectedLang = self.languages[0] self.langButContainer = html5.Div() self.langButContainer["class"].append("languagebuttons") for lang in self.languages: abut = html5.ext.Button(lang, self.changeLang) abut["value"] = lang self.langButContainer.appendChild(abut) self.appendChild(self.langButContainer) self.refreshLangButContainer() else: self.value = "" if not readOnly and not self.isPlainText: self.input = HtmlEditor() self.input.boneName = self.boneName else: self.input = html5.Textarea() if readOnly: self.input["readonly"] = True self.appendChild(self.input) self.sinkEvent("onKeyUp") self.changeEvent = EventDispatcher("boneChange") self._changeTimeout = None def _setDisabled(self, disable): """ Reset the is_active flag (if any) """ super(TextEditBone, self)._setDisabled(disable) if not disable and not self._disabledState and "is_active" in self.parent()["class"]: self.parent()["class"].remove("is_active") def changeLang(self, btn): self.valuesdict[self.selectedLang] = self.input["value"] self.selectedLang = btn["value"] if self.selectedLang in self.valuesdict.keys(): self.input["value"] = self.valuesdict[self.selectedLang] else: self.input["value"] = "" self.refreshLangButContainer() def refreshLangButContainer(self): for abut in self.langButContainer._children: if abut["value"] in self.valuesdict and self.valuesdict[abut["value"]]: if not "is_filled" in abut["class"]: abut["class"].append("is_filled") else: if not "is_unfilled" in abut["class"]: abut["class"].append("is_unfilled") if abut["value"] == self.selectedLang: if not "is_active" in abut["class"]: abut["class"].append("is_active") else: abut["class"].remove("is_active") @staticmethod def fromSkelStructure(moduleName, boneName, skelStructure, *args, **kwargs): readOnly = "readonly" in skelStructure[boneName].keys() and skelStructure[boneName]["readonly"] isPlainText = "validHtml" in skelStructure[boneName].keys() and not skelStructure[boneName]["validHtml"] langs = skelStructure[boneName]["languages"] if ( "languages" in skelStructure[boneName].keys() and skelStructure[boneName]["languages"]) else None descr = skelStructure[boneName]["descr"] if "descr" in skelStructure[boneName].keys() else None return TextEditBone(moduleName, boneName, readOnly, isPlainText, langs, descrHint=descr) def unserialize(self, data): self.valuesdict.clear() if self.boneName in data.keys(): if self.languages: for lang in self.languages: if self.boneName in data.keys() and isinstance(data[self.boneName], dict) and lang in data[ self.boneName].keys(): self.valuesdict[lang] = data[self.boneName][lang] else: self.valuesdict[lang] = "" self.input["value"] = self.valuesdict[self.selectedLang] else: self.input["value"] = data[self.boneName] if data[self.boneName] else "" def serializeForPost(self): if self.selectedLang: self.valuesdict[self.selectedLang] = self.input["value"] return {self.boneName: self.valuesdict} else: return {self.boneName: self.input["value"]} def serializeForDocument(self): return self.serializeForPost() def setExtendedErrorInformation(self, errorInfo): pass def onKeyUp(self, event): if not self.changeEvent.queue: return if self._changeTimeout: html5.window.clearTimeout(self._changeTimeout) self._changeTimeout = html5.window.setTimeout(lambda: self.changeEvent.fire(self), 2500) @staticmethod def checkForTextBone(moduleName, boneName, skelStucture, *args, **kwargs): return skelStucture[boneName]["type"] == "text"
class TreeWidget(html5.Div): nodeWidget = NodeWidget leafWidget = LeafWidget defaultActions = [ "add.node", "add.leaf", "selectrootnode", "edit", "delete", "reload" ] def __init__(self, module, rootNode=None, node=None, selectMode=None, *args, **kwargs): """ :param module: Name of the module we shall handle. Must be a list application! :type module: str :param rootNode: The rootNode we shall display. If None, we try to select one. :type rootNode: str or None :param node: The node we shall display at start. Must be a child of rootNode :type node: str or None """ super(TreeWidget, self).__init__() self.addClass("tree") self.module = module self.rootNode = rootNode self.node = node or rootNode self.actionBar = ActionBar(module, "tree") self.appendChild(self.actionBar) self.pathList = html5.Ul() self.pathList["class"].append("breadcrumb") self.appendChild(self.pathList) self.entryFrame = SelectionContainer(self.nodeWidget, self.leafWidget) self.appendChild(self.entryFrame) self.entryFrame.selectionActivatedEvent.register(self) self._batchSize = 99 self._currentCursor = {"node": None, "leaf": None} self._currentRequests = [] self.rootNodeChangedEvent = EventDispatcher("rootNodeChanged") self.nodeChangedEvent = EventDispatcher("nodeChanged") assert selectMode in [ None, "single", "multi", "single.leaf", "single.node", "multi.leaf", "multi.node" ] self.selectMode = selectMode if self.rootNode: self.reloadData() self.rebuildPath() else: NetworkService.request(self.module, "listRootNodes", successHandler=self.onSetDefaultRootNode) self.sinkEvent("onClick") # Proxy some events and functions of the original table for f in [ "selectionChangedEvent", "selectionActivatedEvent", "cursorMovedEvent", "getCurrentSelection", "selectionReturnEvent" ]: setattr(self, f, getattr(self.entryFrame, f)) self.actionBar.setActions(self.defaultActions + ( ["select", "close"] if self.selectMode else [])) def showErrorMsg(self, req=None, code=None): """ Removes all currently visible elements and displays an error message """ self.actionBar["style"]["display"] = "none" self.entryFrame["style"]["display"] = "none" errorDiv = html5.Div() errorDiv["class"].append("error_msg") if code and (code == 401 or code == 403): txt = "Access denied!" else: txt = "An unknown error occurred!" errorDiv["class"].append("error_code_%s" % (code or 0)) errorDiv.appendChild(html5.TextNode(txt)) self.appendChild(errorDiv) def onAttach(self): super(TreeWidget, self).onAttach() NetworkService.registerChangeListener(self) def onDetach(self): super(TreeWidget, self).onDetach() NetworkService.removeChangeListener(self) def onDataChanged(self, module, **kwargs): if module != self.module: isRootNode = False for k, v in conf["modules"].items(): if (k == module and v.get("handler") == "list" and v.get("rootNodeOf") == self.module): isRootNode = True break if not isRootNode: return if "selectrootnode" in self.actionBar.widgets.keys(): self.actionBar.widgets["selectrootnode"].update() self.reloadData() def onSelectionActivated(self, div, selection): if not selection: return item = selection[0] if item.skelType == "node": self.setNode(item.data["key"]) elif item.skelType == "leaf" and "leaf" in (self.selectMode or ""): self.returnCurrentSelection() def activateCurrentSelection(self): return self.entryFrame.activateCurrentSelection() def returnCurrentSelection(self): conf["mainWindow"].removeWidget(self) return self.entryFrame.returnCurrentSelection() def onClick(self, event): super(TreeWidget, self).onClick(event) for c in self.pathList._children: # Test if the user clicked inside the path-list if html5.utils.doesEventHitWidgetOrParents(event, c): self.setNode(c.data["key"]) return def onSetDefaultRootNode(self, req): data = NetworkService.decode(req) if len(data) > 0: self.setRootNode(data[0]["key"], self.node) def setRootNode(self, rootNode, node=None): self.rootNode = rootNode self.node = node or rootNode self.rootNodeChangedEvent.fire(rootNode) if node: self.nodeChangedEvent.fire(node) self.reloadData() self.rebuildPath() def setNode(self, node): self.node = node self.nodeChangedEvent.fire(node) self.reloadData() self.rebuildPath() def rebuildPath(self): """ Rebuild the displayed path-list. """ self.pathList.removeAllChildren() NetworkService.request(self.module, "view/node/%s" % self.node, successHandler=self.onPathRequestSucceded) def onPathRequestSucceded(self, req): """ Rebuild the displayed path-list according to request data """ answ = NetworkService.decode(req) skel = answ["values"] if skel["parentdir"] and skel["parentdir"] != skel["key"]: c = self.nodeWidget(self.module, skel, answ["structure"]) NetworkService.request(self.module, "view/node/%s" % skel["parentdir"], successHandler=self.onPathRequestSucceded) else: c = self.nodeWidget(self.module, { "key": self.rootNode, "name": "root" }, []) c.addClass("is_rootnode") self.pathList.prependChild(c) def reloadData(self, paramsOverride=None): assert self.node is not None, "reloadData called while self.node is None" self.entryFrame.clear() self._currentRequests = [] if paramsOverride: params = paramsOverride.copy() else: params = {"node": self.node} if "amount" not in params: params["amount"] = self._batchSize r = NetworkService.request(self.module, "list/node", params, successHandler=self.onRequestSucceded, failureHandler=self.showErrorMsg) r.reqType = "node" self._currentRequests.append(r) r = NetworkService.request(self.module, "list/leaf", params, successHandler=self.onRequestSucceded, failureHandler=self.showErrorMsg) r.reqType = "leaf" self._currentRequests.append(r) from handler.tree import TreeHandler # import must be here, otherwise it throws an importError if isinstance(conf["mainWindow"].currentPane, TreeHandler): conf["theApp"].setPath(self.module + "/list/" + self.node) def onRequestSucceded(self, req): if not req in self._currentRequests: return self._currentRequests.remove(req) data = NetworkService.decode(req) for skel in data["skellist"]: if req.reqType == "node": n = self.nodeWidget(self.module, skel, data["structure"]) else: n = self.leafWidget(self.module, skel, data["structure"]) self.entryFrame.appendChild(n) self.entryFrame.sortChildren(self.getChildKey) if "cursor" in data.keys() and len( data["skellist"]) == req.params["amount"]: self._currentCursor[req.reqType] = data["cursor"] req.params["cursor"] = data["cursor"] r = NetworkService.request(self.module, "list/%s" % req.reqType, req.params, successHandler=self.onRequestSucceded, failureHandler=self.showErrorMsg) r.reqType = req.reqType self._currentRequests.append(r) else: self._currentCursor[req.reqType] = None self.actionBar.resetLoadingState() def getChildKey(self, widget): """ Derives a string used to sort the entries in our entryframe """ name = (widget.data.get("name") or "").lower() if isinstance(widget, self.nodeWidget): return "0-%s" % name elif isinstance(widget, self.leafWidget): return "1-%s" % name else: return "2-" @staticmethod def canHandle(moduleName, moduleInfo): return moduleInfo["handler"].startswith("tree.") @staticmethod def render(moduleName, adminInfo, context): rootNode = context.get(conf["vi.context.prefix"] + "rootNode") if context else None return TreeWidget(module=moduleName, rootNode=rootNode, context=context)